diff --git a/src/main/java/org/ultramine/permission/GroupPermission.java b/src/main/java/org/ultramine/permission/GroupPermission.java index 988e4a6..6606578 100644 --- a/src/main/java/org/ultramine/permission/GroupPermission.java +++ b/src/main/java/org/ultramine/permission/GroupPermission.java @@ -1,5 +1,8 @@ package org.ultramine.permission; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + import java.util.*; /** @@ -7,13 +10,15 @@ */ public class GroupPermission extends MetaHolder implements IChangeablePermission, IDirtyListener { + private static Logger logger = LogManager.getLogger(); + private String key; private boolean dirty; - private PermissionResolver resolver = new PermissionResolver(); + private PermissionResolver permissionResolver = new PermissionResolver(); private List listeners = new ArrayList(); - private Set permissions = new HashSet(); - private Map effectiveMeta = new HashMap(); + private Map permissions = new HashMap(); + private MetaResolver metaResolver = new MetaResolver(); public GroupPermission(String key) { @@ -54,32 +59,49 @@ } @Override - public PermissionResolver getResolver() + public PermissionResolver getPermissions() { if (isDirty()) calculate(); - return resolver; + return permissionResolver; } @Override - public Map getEffectiveMeta() + public MetaResolver getMeta() { if (isDirty()) calculate(); - return effectiveMeta; + return metaResolver; } public void addPermission(IPermission permission) { - permissions.add(permission); + if (permissions.containsKey(permission.getKey())) + return; + + permissions.put(permission.getKey(), permission); + if (permission instanceof IChangeablePermission) + ((IChangeablePermission)permission).subscribe(this); + makeDirty(); } public void removePermission(IPermission permission) { - permissions.remove(permission); + removePermission(permission.getKey()); + } + + public void removePermission(String key) + { + if (!permissions.containsKey(key)) + return; + + IPermission perm = permissions.remove(key); + if (perm instanceof IChangeablePermission) + ((IChangeablePermission)perm).unsubscribe(this); + makeDirty(); } @@ -113,47 +135,50 @@ } @Override - public void setValue(String key, Object value) + public void setMeta(String key, Object value) { - super.setValue(key, value); + super.setMeta(key, value); makeDirty(); } @Override - public void removeValue(String key) + public void removeMeta(String key) { - super.removeValue(key); + super.removeMeta(key); makeDirty(); } - public void calculate() + public void calculate() throws RecursiveCalculationException { if (!isDirty()) return; - resolver.clear(); - for (IPermission permission : permissions) - resolver.merge(permission.getResolver(), permission.getPriority()); + permissionResolver.clear(); + metaResolver.clear(); - effectiveMeta.clear(); - Map priorities = new HashMap(); - - for (IPermission permission : permissions) + try { - Map meta = permission.getEffectiveMeta(); - - for (String key : meta.keySet()) - if (!priorities.containsKey(key) || permission.getPriority() > priorities.get(key)) - { - effectiveMeta.put(key, meta.get(key)); - priorities.put(key, permission.getPriority()); - } + for (IPermission permission : permissions.values()) + { + permissionResolver.merge(permission.getPermissions(), permission.getPriority()); + metaResolver.merge(permission.getMeta(), permission.getPriority()); + } } + catch (StackOverflowError ignored) + { + throw new RecursiveCalculationException(this); + } + finally + { + metaResolver.merge(innerMeta, Integer.MAX_VALUE); + dirty = false; + } + } - for (String key : innerMeta.keySet()) - effectiveMeta.put(key, innerMeta.get(key)); - this.dirty = false; + private void mergeResolvers() throws StackOverflowError + { + } @Override diff --git a/src/main/java/org/ultramine/permission/IPermission.java b/src/main/java/org/ultramine/permission/IPermission.java index 7b3e197..a1e551b 100644 --- a/src/main/java/org/ultramine/permission/IPermission.java +++ b/src/main/java/org/ultramine/permission/IPermission.java @@ -1,7 +1,5 @@ package org.ultramine.permission; -import java.util.Map; - /** * Created by Евгений on 02.05.2014. */ @@ -12,6 +10,6 @@ public String getDescription(); public int getPriority(); - public PermissionResolver getResolver(); - public Map getEffectiveMeta(); + public PermissionResolver getPermissions(); + public MetaResolver getMeta(); } \ No newline at end of file diff --git a/src/main/java/org/ultramine/permission/MetaHolder.java b/src/main/java/org/ultramine/permission/MetaHolder.java index 93bfb80..7e92981 100644 --- a/src/main/java/org/ultramine/permission/MetaHolder.java +++ b/src/main/java/org/ultramine/permission/MetaHolder.java @@ -20,38 +20,20 @@ innerMeta = meta; } - public void setValue(String key, Object value) + public void setMeta(String key, Object value) { innerMeta.put(key, value); } - public void removeValue(String key) + public void removeMeta(String key) { innerMeta.remove(key); } - public String getString(String key) - { - Map meta = getEffectiveMeta(); - if (meta.containsKey(key)) - return (String)meta.get(key); - else - return ""; - } - - public int getInt(String key) - { - Map meta = getEffectiveMeta(); - if (meta.containsKey(key)) - return (Integer)meta.get(key); - else - return 0; - } - public int getPriority() { - return getInt("priority"); + return getMeta().getInt("priority"); } - public abstract Map getEffectiveMeta(); + public abstract MetaResolver getMeta(); } diff --git a/src/main/java/org/ultramine/permission/MetaResolver.java b/src/main/java/org/ultramine/permission/MetaResolver.java new file mode 100644 index 0000000..1388c97 --- /dev/null +++ b/src/main/java/org/ultramine/permission/MetaResolver.java @@ -0,0 +1,27 @@ +package org.ultramine.permission; + +/** + * Created by uguuseha on 08.05.14. + */ +public class MetaResolver extends Resolver +{ + public static final MetaResolver BLANK_RESOLVER = new MetaResolver(); + + public String getString(String key) + { + if (values.containsKey(key)) + return (String)values.get(key); + else + return ""; + } + + public int getInt(String key) + { + if (values.containsKey(key)) + return (Integer)values.get(key); + else + return 0; + } + + +} diff --git a/src/main/java/org/ultramine/permission/NegativePermission.java b/src/main/java/org/ultramine/permission/NegativePermission.java index 24871fd..0b72ec9 100644 --- a/src/main/java/org/ultramine/permission/NegativePermission.java +++ b/src/main/java/org/ultramine/permission/NegativePermission.java @@ -1,7 +1,5 @@ package org.ultramine.permission; -import java.util.Map; - /** * Created by Евгений on 07.05.2014. */ @@ -14,14 +12,14 @@ public NegativePermission(IPermission permission) { this.permission = permission; - this.resolver = PermissionResolver.createInverted(permission.getResolver()); + this.resolver = PermissionResolver.createInverted(permission.getPermissions()); this.isChangeable = false; } public NegativePermission(IChangeablePermission permission) { this.permission = permission; - this.resolver = PermissionResolver.createInverted(permission.getResolver()); + this.resolver = PermissionResolver.createInverted(permission.getPermissions()); this.isChangeable = true; } @@ -53,17 +51,17 @@ } @Override - public PermissionResolver getResolver() + public PermissionResolver getPermissions() { if (isDirty()) - resolver = PermissionResolver.createInverted(permission.getResolver()); + resolver = PermissionResolver.createInverted(permission.getPermissions()); return resolver; } @Override - public Map getEffectiveMeta() + public MetaResolver getMeta() { - return permission.getEffectiveMeta(); + return permission.getMeta(); } @Override diff --git a/src/main/java/org/ultramine/permission/Permission.java b/src/main/java/org/ultramine/permission/Permission.java index f1b6b99..98d1142 100644 --- a/src/main/java/org/ultramine/permission/Permission.java +++ b/src/main/java/org/ultramine/permission/Permission.java @@ -1,8 +1,5 @@ package org.ultramine.permission; -import java.util.Collections; -import java.util.Map; - /** * Created by Евгений on 07.05.2014. */ @@ -51,15 +48,15 @@ } @Override - public PermissionResolver getResolver() + public PermissionResolver getPermissions() { return resolver; } @Override - public Map getEffectiveMeta() + public MetaResolver getMeta() { - return Collections.emptyMap(); + return MetaResolver.BLANK_RESOLVER; } @Override diff --git a/src/main/java/org/ultramine/permission/PermissionRepository.java b/src/main/java/org/ultramine/permission/PermissionRepository.java index 23021ba..3989c31 100644 --- a/src/main/java/org/ultramine/permission/PermissionRepository.java +++ b/src/main/java/org/ultramine/permission/PermissionRepository.java @@ -81,15 +81,15 @@ } @Override - public PermissionResolver getResolver() + public PermissionResolver getPermissions() { - return wrappedPermission.getResolver(); + return wrappedPermission.getPermissions(); } @Override - public Map getEffectiveMeta() + public MetaResolver getMeta() { - return wrappedPermission.getEffectiveMeta(); + return wrappedPermission.getMeta(); } @Override diff --git a/src/main/java/org/ultramine/permission/PermissionResolver.java b/src/main/java/org/ultramine/permission/PermissionResolver.java index a24d583..8e3ea52 100644 --- a/src/main/java/org/ultramine/permission/PermissionResolver.java +++ b/src/main/java/org/ultramine/permission/PermissionResolver.java @@ -1,23 +1,18 @@ package org.ultramine.permission; -import java.util.HashMap; -import java.util.Iterator; import java.util.Map; /** * Created by Евгений on 07.05.2014. */ -public class PermissionResolver +public class PermissionResolver extends Resolver { - private Map permissions; - private Map priorities; - public static PermissionResolver createInverted(PermissionResolver anotherResolver) { PermissionResolver resolver = new PermissionResolver(); - for (Map.Entry entry : anotherResolver.permissions.entrySet()) + for (Map.Entry entry : anotherResolver.values.entrySet()) { - resolver.permissions.put(entry.getKey(), !entry.getValue()); + resolver.values.put(entry.getKey(), !entry.getValue()); resolver.priorities.put(entry.getKey(), anotherResolver.priorities.get(entry.getKey())); } return resolver; @@ -26,54 +21,33 @@ public static PermissionResolver createForKey(String key, int priority) { PermissionResolver resolver = new PermissionResolver(); - resolver.permissions.put(key, true); + resolver.values.put(key, true); resolver.priorities.put(key, priority); return resolver; } - public PermissionResolver() - { - permissions = new HashMap(); - priorities = new HashMap(); - } - public boolean has(String key) { if (key == null) return false; key = key.toLowerCase(); - if (permissions.containsKey(key)) - return permissions.get(key); + if (values.containsKey(key)) + return values.get(key); int index = key.lastIndexOf('.'); while (index >= 0) { key = key.substring(0, index); String wildcard = key + ".*"; - if (permissions.containsKey(wildcard)) - return permissions.get(wildcard); + if (values.containsKey(wildcard)) + return values.get(wildcard); index = key.lastIndexOf('.'); } - if (permissions.containsKey("*")) - return permissions.get("*"); + if (values.containsKey("*")) + return values.get("*"); return false; } - - public void clear() - { - permissions.clear(); - } - - public void merge(PermissionResolver anotherResolver, int priority) - { - for (String key : anotherResolver.permissions.keySet()) - if (!priorities.containsKey(key) || priorities.get(key) < priority) - { - permissions.put(key, anotherResolver.permissions.get(key)); - priorities.put(key, priority); - } - } } diff --git a/src/main/java/org/ultramine/permission/RecursiveCalculationException.java b/src/main/java/org/ultramine/permission/RecursiveCalculationException.java new file mode 100644 index 0000000..a218b9c --- /dev/null +++ b/src/main/java/org/ultramine/permission/RecursiveCalculationException.java @@ -0,0 +1,17 @@ +package org.ultramine.permission; + +/** + * Created by uguuseha on 08.05.14. + */ +public class RecursiveCalculationException extends RuntimeException +{ + public RecursiveCalculationException(String location) + { + super("Recursive calculation detected in " + location); + } + + public RecursiveCalculationException(GroupPermission groupPermission) + { + this(groupPermission.getKey()); + } +} diff --git a/src/main/java/org/ultramine/permission/Resolver.java b/src/main/java/org/ultramine/permission/Resolver.java new file mode 100644 index 0000000..fe1e834 --- /dev/null +++ b/src/main/java/org/ultramine/permission/Resolver.java @@ -0,0 +1,35 @@ +package org.ultramine.permission; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +/** + * Created by uguuseha on 08.05.14. + */ +public class Resolver +{ + protected Map values = new HashMap(); + protected Map priorities = new HashMap(); + + public void clear() + { + values.clear(); + priorities.clear(); + } + + public void merge(Resolver anotherResolver, int priority) + { + merge(anotherResolver.values, priority); + } + + public void merge(Map newValues, int priority) + { + for (String key : newValues.keySet()) + if (!priorities.containsKey(key) || priorities.get(key) < priority) + { + values.put(key, newValues.get(key)); + priorities.put(key, priority); + } + } +} diff --git a/src/test/groovy/org/ultramine/permission/GroupPermissionTest.groovy b/src/test/groovy/org/ultramine/permission/GroupPermissionTest.groovy index fe948ed..6ca47d0 100644 --- a/src/test/groovy/org/ultramine/permission/GroupPermissionTest.groovy +++ b/src/test/groovy/org/ultramine/permission/GroupPermissionTest.groovy @@ -1,5 +1,7 @@ package org.ultramine.permission +import org.apache.logging.log4j.LogManager +import org.apache.logging.log4j.Logger import spock.lang.Specification /** @@ -7,24 +9,31 @@ */ class GroupPermissionTest extends Specification { + MetaResolver createMetaResolver(Map meta) + { + def resolver = new MetaResolver() + resolver.merge(meta, 0) + return resolver + } + def "Test calculation"() { setup: def resolver = Mock(PermissionResolver) def perm1 = Mock(IPermission) { getKey() >> "p.1" - getResolver() >> resolver - getEffectiveMeta() >> [test1: "1", test2: "1", test3: "1"] + getPermissions() >> resolver + getMeta() >> createMetaResolver([test1: "1", test2: "1", test3: "1"]) getPriority() >> 1 } def perm2 = Mock(IPermission) { getKey() >> "p.2" - getResolver() >> resolver - getEffectiveMeta() >> [test2: "2"] + getPermissions() >> resolver + getMeta() >> createMetaResolver([test2: "2"]) getPriority() >> 2 } def group = new GroupPermission("group.test", [test1: "0"]) - group.resolver = resolver + group.permissionResolver = resolver group.addPermission(perm1) group.addPermission(perm2) @@ -32,22 +41,156 @@ group.calculate() then: "Permissions are calculated" + !group.isDirty() 1 * resolver.clear() 1 * resolver.merge(resolver, 1) 1 * resolver.merge(resolver, 2) 0 * resolver._ and: "Meta is correct" - def meta = group.getEffectiveMeta() - meta.test1 == "0" - meta.test2 == "2" - meta.test3 == "1" + group.getMeta().getString("test1") == "0" + group.getMeta().getString("test2") == "2" + group.getMeta().getString("test3") == "1" when: "Calculate one more time" group.calculate() then: "Nothing happens" + !group.isDirty() 0 * resolver._ + } + def "Test recursive calculation"() { + setup: "Prepare recursive groups" + def logger = Mock(Logger) + def group1 = new GroupPermission("g1", [m1: "a"]) + def group2 = new GroupPermission("g2", [m2: "b"]) + group1.addPermission(group2) + group2.addPermission(group1) + + when: "Calculate permissions" + group1.calculate() + + then: "Recursive calculation is logged" + thrown(RecursiveCalculationException) + + and: "Both groups are not dirty" + !group1.isDirty() + !group2.isDirty() + + and: "Both groups are calculated" + group1.getMeta().getString("m1") + group1.getMeta().getString("m2") + + group2.getMeta().getString("m1") + group2.getMeta().getString("m2") + + } + + def "Test dirty notification"() { + setup: + def listener = Mock(IDirtyListener) + def group = new GroupPermission("group.test") + group.subscribe(listener) + + when: "Group becomes dirty several times" + group.makeDirty() + group.makeDirty() + group.makeDirty() + + then: "Listener is notified only once" + 1 * listener.makeDirty() + } + + def "Test subscribing to permission changes"() { + setup: + def sPerm = Mock(IPermission) { getKey() >> "p" } + def cPerm = Mock(IChangeablePermission) { getKey() >> "c" } + def group = new GroupPermission("group.test") + + when: "Add permissions to group" + group.addPermission(sPerm) + group.addPermission(cPerm) + + then: "Group subscribes to IChangeablePermission" + 1 * cPerm.subscribe(group) + 0 * sPerm.subscribe(_) + + when: "Remove permissions from group" + group.removePermission(sPerm) + group.removePermission(cPerm) + + then: "Group unsubscribes to IChangeablePermission" + 1 * cPerm.unsubscribe(group) + 0 * sPerm.unsubscribe(_) + } + + def "Test dirty methods"() { + setup: + def listener = Mock(IDirtyListener) + def group = new GroupPermission("group") + group.subscribe(listener) + + when: "Call setMeta method" + group.calculate() + group.setMeta("test", 21) + + then: "Group becomes dirty" + group.isDirty() + 1 * listener.makeDirty() + + when: "Call removeMeta method" + group.calculate() + group.removeMeta("test") + + then: "Group becomes dirty" + group.isDirty() + 1 * listener.makeDirty() + + when: "Call addPermission method" + group.calculate() + group.addPermission(new Permission("test")) + + then: "Group becomes dirty" + group.isDirty() + 1 * listener.makeDirty() + + when: "Call removePermission method" + group.calculate() + group.removePermission("test") + + then: "Group becomes dirty" + group.isDirty() + 1 * listener.makeDirty() + } + + def "Test meta parsing"() { + setup: + def group = new GroupPermission("group.test", [ + name: "Test1", + description: "Test2", + priority: 200, + perfix: "Test3" + ]) + + expect: + group.getKey() == "group.test" + group.getName() == "Test1" + group.getDescription() == "Test2" + group.getPriority() == 200 + group.getMeta().getString("perfix") == "Test3" + group.getMeta().getString("asd") == "" + group.getMeta().getInt("dsa") == 0 + } + + def "Test blank group"() { + setup: + def group = new GroupPermission("group.test", [:]) + + expect: + group.getKey() == "group.test" + group.getName() == "group.test" + group.getDescription() == "" + group.getPriority() == 0 } } diff --git a/src/test/groovy/org/ultramine/permission/NegativePermissionTest.groovy b/src/test/groovy/org/ultramine/permission/NegativePermissionTest.groovy index 2648528..4b04d70 100644 --- a/src/test/groovy/org/ultramine/permission/NegativePermissionTest.groovy +++ b/src/test/groovy/org/ultramine/permission/NegativePermissionTest.groovy @@ -14,8 +14,8 @@ getName() >> "Test Name" getDescription() >> "Test Description" getPriority() >> 100 - getResolver() >> PermissionResolver.createForKey("test.key", 1) - getEffectiveMeta() >> [test: "data"] + getPermissions() >> PermissionResolver.createForKey("test.key", 1) + getMeta() >> Mock(MetaResolver) { getString(_) >> "mock" } } when: "Create new NegativePermission" @@ -26,14 +26,14 @@ perm.getName() == "NOT: Test Name" perm.getDescription() == "NOT: Test Description" perm.getPriority() == 100 - perm.getEffectiveMeta() == [test: "data"] - !perm.getResolver().has("test.key") + perm.getMeta().getString("1") == "mock" + !perm.getPermissions().has("test.key") } def "Test isDirty IPermission"() { setup: IPermission permission = Mock(IPermission) { - getResolver() >> PermissionResolver.createForKey("test.key", 1) + getPermissions() >> PermissionResolver.createForKey("test.key", 1) isDirty() >> true } @@ -48,7 +48,7 @@ def "Test subscribe/unsubscribe IPermission"() { setup: IPermission permission = Mock(IPermission) { - getResolver() >> PermissionResolver.createForKey("test.key", 1) + getPermissions() >> PermissionResolver.createForKey("test.key", 1) } def listener = Mock(IDirtyListener) @@ -70,8 +70,8 @@ getName() >> "Test Name" getDescription() >> "Test Description" getPriority() >> 100 - getResolver() >> PermissionResolver.createForKey("test.key", 1) - getEffectiveMeta() >> [test: "data"] + getPermissions() >> PermissionResolver.createForKey("test.key", 1) + getMeta() >> Mock(MetaResolver) { getString(_) >> "mock" } } when: "Create new NegativePermission" @@ -82,14 +82,14 @@ perm.getName() == "NOT: Test Name" perm.getDescription() == "NOT: Test Description" perm.getPriority() == 100 - perm.getEffectiveMeta() == [test: "data"] - !perm.getResolver().has("test.key") + perm.getMeta().getString("1") == "mock" + !perm.getPermissions().has("test.key") } def "Test isDirty IChangeablePermission"() { setup: IPermission permission = Mock(IChangeablePermission) { - getResolver() >> PermissionResolver.createForKey("test.key", 1) + getPermissions() >> PermissionResolver.createForKey("test.key", 1) } def perm = new NegativePermission(permission) @@ -104,7 +104,7 @@ def "Test subscribe/unsubscribe IChangeablePermission"() { setup: IPermission permission = Mock(IChangeablePermission) { - getResolver() >> PermissionResolver.createForKey("test.key", 1) + getPermissions() >> PermissionResolver.createForKey("test.key", 1) } def listener = Mock(IDirtyListener) @@ -123,7 +123,7 @@ def "Test blank description"() { setup: "Permission with blank description" IPermission permission = Mock(IChangeablePermission) { - getResolver() >> PermissionResolver.createForKey("test.key", 1) + getPermissions() >> PermissionResolver.createForKey("test.key", 1) getDescription() >> "" } diff --git a/src/test/groovy/org/ultramine/permission/PermissionResolverTest.groovy b/src/test/groovy/org/ultramine/permission/PermissionResolverTest.groovy index c08765a..5486561 100644 --- a/src/test/groovy/org/ultramine/permission/PermissionResolverTest.groovy +++ b/src/test/groovy/org/ultramine/permission/PermissionResolverTest.groovy @@ -10,8 +10,9 @@ def setupSpec() { PermissionResolver.metaClass.addEntry = { String key, Boolean value, Integer prio -> - delegate.permissions.put(key, value) + delegate.values.put(key, value) delegate.priorities.put(key, prio) + return delegate } } @@ -138,6 +139,18 @@ !resolver2.has("test.perm.2") resolver2.has("test.perm.3") !resolver2.has("group.admin") + } + def "Test clear -> merge lower priority"() { + setup: "resolver(^test, 100)" + def resolver = new PermissionResolver().addEntry("test", false, 100) + def inverted = new PermissionResolver().addEntry("test", true, 50) + + when: + resolver.clear() + resolver.merge(inverted, 50) + + then: + resolver.has("test") } }