You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by al...@apache.org on 2018/09/24 11:30:24 UTC

[2/7] brooklyn-server git commit: add {forbidden, required}{If, Unless} constraints

add {forbidden,required}{If,Unless} constraints


Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo
Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/0d6f5721
Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/0d6f5721
Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/0d6f5721

Branch: refs/heads/master
Commit: 0d6f57219bd7ce36c912fddd26f37a46c6f8dbc2
Parents: bd34655
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Fri Sep 21 11:49:47 2018 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Fri Sep 21 11:49:47 2018 +0100

----------------------------------------------------------------------
 .../brooklyn/core/config/ConfigConstraints.java |  69 ++++++++++
 .../core/objs/ConstraintSerialization.java      |  13 +-
 .../brooklyn/util/core/ResourcePredicates.java  |   6 +-
 .../core/config/ConfigKeyConstraintTest.java    | 133 ++++++++++++++-----
 .../java/org/apache/brooklyn/test/Asserts.java  |  16 ++-
 5 files changed, 195 insertions(+), 42 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/0d6f5721/core/src/main/java/org/apache/brooklyn/core/config/ConfigConstraints.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/config/ConfigConstraints.java b/core/src/main/java/org/apache/brooklyn/core/config/ConfigConstraints.java
index 35f672e..4ee584d 100644
--- a/core/src/main/java/org/apache/brooklyn/core/config/ConfigConstraints.java
+++ b/core/src/main/java/org/apache/brooklyn/core/config/ConfigConstraints.java
@@ -30,6 +30,7 @@ import org.apache.brooklyn.api.objs.BrooklynObject;
 import org.apache.brooklyn.api.objs.EntityAdjunct;
 import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.core.entity.EntityInternal;
+import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
 import org.apache.brooklyn.core.objs.AbstractEntityAdjunct;
 import org.apache.brooklyn.core.objs.BrooklynObjectInternal;
 import org.apache.brooklyn.core.objs.BrooklynObjectPredicate;
@@ -37,6 +38,7 @@ import org.apache.brooklyn.core.objs.ConstraintSerialization;
 import org.apache.brooklyn.util.core.task.Tasks;
 import org.apache.brooklyn.util.exceptions.ReferenceWithError;
 import org.apache.brooklyn.util.guava.Maybe;
+import org.apache.brooklyn.util.text.StringEscapes.JavaStringEscapes;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -243,4 +245,71 @@ public abstract class ConfigConstraints<T extends BrooklynObject> {
             return "required()";
         }
     }
+    
+    private static abstract class OtherKeyPredicate implements BrooklynObjectPredicate<Object> {
+        private String otherKeyName;
+
+        public OtherKeyPredicate(String otherKeyName) {
+            this.otherKeyName = otherKeyName;
+        }
+
+        public abstract String predicateName();
+        
+        @Override
+        public String toString() {
+            return predicateName()+"("+JavaStringEscapes.wrapJavaString(otherKeyName)+")";
+        }
+        
+        @Override
+        public boolean apply(Object input) {
+            return apply(input, BrooklynTaskTags.getContextEntity(Tasks.current()));
+        }
+
+        @Override
+        public boolean apply(Object input, BrooklynObject context) {
+            if (context==null) return true;
+            // would be nice to offer an explanation, but that will need a richer API or a thread local
+            return test(input, context.config().get(ConfigKeys.newConfigKey(Object.class, otherKeyName)));
+        }
+        
+        public abstract boolean test(Object thisValue, Object otherValue);
+        
+    }
+    
+    public static Predicate<Object> forbiddenIf(String otherKeyName) { return new ForbiddenIfPredicate(otherKeyName); }
+    public static class ForbiddenIfPredicate extends OtherKeyPredicate {
+        public ForbiddenIfPredicate(String otherKeyName) { super(otherKeyName); }
+        @Override public String predicateName() { return "forbiddenIf"; }
+        @Override public boolean test(Object thisValue, Object otherValue) { 
+            return (thisValue==null) || (otherValue==null);
+        } 
+    }
+    
+    public static Predicate<Object> forbiddenUnless(String otherKeyName) { return new ForbiddenUnlessPredicate(otherKeyName); }
+    public static class ForbiddenUnlessPredicate extends OtherKeyPredicate {
+        public ForbiddenUnlessPredicate(String otherKeyName) { super(otherKeyName); }
+        @Override public String predicateName() { return "forbiddenUnless"; }
+        @Override public boolean test(Object thisValue, Object otherValue) { 
+            return (thisValue==null) || (otherValue!=null);
+        } 
+    }
+    
+    public static Predicate<Object> requiredIf(String otherKeyName) { return new RequiredIfPredicate(otherKeyName); }
+    public static class RequiredIfPredicate extends OtherKeyPredicate {
+        public RequiredIfPredicate(String otherKeyName) { super(otherKeyName); }
+        @Override public String predicateName() { return "requiredIf"; }
+        @Override public boolean test(Object thisValue, Object otherValue) { 
+            return (thisValue!=null) || (otherValue==null);
+        } 
+    }
+    
+    public static Predicate<Object> requiredUnless(String otherKeyName) { return new RequiredUnlessPredicate(otherKeyName); }
+    public static class RequiredUnlessPredicate extends OtherKeyPredicate {
+        public RequiredUnlessPredicate(String otherKeyName) { super(otherKeyName); }
+        @Override public String predicateName() { return "requiredUnless"; }
+        @Override public boolean test(Object thisValue, Object otherValue) { 
+            return (thisValue!=null) || (otherValue!=null);
+        } 
+    }
+    
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/0d6f5721/core/src/main/java/org/apache/brooklyn/core/objs/ConstraintSerialization.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/objs/ConstraintSerialization.java b/core/src/main/java/org/apache/brooklyn/core/objs/ConstraintSerialization.java
index a6b7039..57258bc 100644
--- a/core/src/main/java/org/apache/brooklyn/core/objs/ConstraintSerialization.java
+++ b/core/src/main/java/org/apache/brooklyn/core/objs/ConstraintSerialization.java
@@ -34,6 +34,7 @@ import org.apache.brooklyn.core.config.ConfigConstraints;
 import org.apache.brooklyn.util.collections.MutableList;
 import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.collections.MutableSet;
+import org.apache.brooklyn.util.core.ResourcePredicates;
 import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.text.StringEscapes.JavaStringEscapes;
 import org.apache.brooklyn.util.text.StringPredicates;
@@ -168,11 +169,19 @@ public class ConstraintSerialization {
 
         PredicateSerializationRuleAdder.predicateListConstructor((o) -> Predicates.or((Iterable)o)).preferredName("any").equivalentNames("or").add(this);
         PredicateSerializationRuleAdder.predicateListConstructor((o) -> /* and predicate is default when given list */ toPredicateFromJson(o)).preferredName("all").sample(Predicates.and(Collections.emptyList())).equivalentNames("and").add(this);
-        PredicateSerializationRuleAdder.noArgConstructor(() -> Predicates.alwaysFalse()).add(this);
-        PredicateSerializationRuleAdder.noArgConstructor(() -> Predicates.alwaysTrue()).add(this);
+        PredicateSerializationRuleAdder.noArgConstructor(Predicates::alwaysFalse).add(this);
+        PredicateSerializationRuleAdder.noArgConstructor(Predicates::alwaysTrue).add(this);
+        
+        PredicateSerializationRuleAdder.noArgConstructor(ResourcePredicates::urlExists).preferredName("urlExists").add(this);
+        PredicateSerializationRuleAdder.noArgConstructor(StringPredicates::isBlank).add(this);
         
         PredicateSerializationRuleAdder.stringConstructor(StringPredicates::matchesRegex).preferredName("regex").add(this);
         PredicateSerializationRuleAdder.stringConstructor(StringPredicates::matchesGlob).preferredName("glob").add(this);
+        
+        PredicateSerializationRuleAdder.stringConstructor(ConfigConstraints::forbiddenIf).add(this);
+        PredicateSerializationRuleAdder.stringConstructor(ConfigConstraints::forbiddenUnless).add(this);
+        PredicateSerializationRuleAdder.stringConstructor(ConfigConstraints::requiredIf).add(this);
+        PredicateSerializationRuleAdder.stringConstructor(ConfigConstraints::requiredUnless).add(this);
     }
     
     public static ConstraintSerialization INSTANCE = new ConstraintSerialization();

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/0d6f5721/core/src/main/java/org/apache/brooklyn/util/core/ResourcePredicates.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/util/core/ResourcePredicates.java b/core/src/main/java/org/apache/brooklyn/util/core/ResourcePredicates.java
index 439aaae..240ab52 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/ResourcePredicates.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/ResourcePredicates.java
@@ -22,7 +22,9 @@ package org.apache.brooklyn.util.core;
 import javax.annotation.Nullable;
 
 import org.apache.brooklyn.api.objs.BrooklynObject;
+import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
 import org.apache.brooklyn.core.objs.BrooklynObjectPredicate;
+import org.apache.brooklyn.util.core.task.Tasks;
 import org.apache.brooklyn.util.text.StringPredicates;
 import org.apache.brooklyn.util.text.Strings;
 
@@ -54,7 +56,7 @@ public class ResourcePredicates {
 
         @Override
         public boolean apply(@Nullable String resource) {
-            return apply(resource, null);
+            return apply(resource, BrooklynTaskTags.getContextEntity(Tasks.current()));
         }
 
         @Override
@@ -64,7 +66,7 @@ public class ResourcePredicates {
 
         @Override
         public String toString() {
-            return "ResourcePredicates.exists()";
+            return "ResourcePredicates.urlExists()";
         }
 
     }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/0d6f5721/core/src/test/java/org/apache/brooklyn/core/config/ConfigKeyConstraintTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/config/ConfigKeyConstraintTest.java b/core/src/test/java/org/apache/brooklyn/core/config/ConfigKeyConstraintTest.java
index 46710fb..a251cbc 100644
--- a/core/src/test/java/org/apache/brooklyn/core/config/ConfigKeyConstraintTest.java
+++ b/core/src/test/java/org/apache/brooklyn/core/config/ConfigKeyConstraintTest.java
@@ -20,11 +20,10 @@
 package org.apache.brooklyn.core.config;
 
 import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.fail;
 
 import java.util.concurrent.Callable;
 
+import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.entity.EntitySpec;
 import org.apache.brooklyn.api.entity.ImplementedBy;
 import org.apache.brooklyn.api.location.Location;
@@ -45,7 +44,6 @@ import org.apache.brooklyn.core.test.entity.TestEntityImpl;
 import org.apache.brooklyn.core.test.policy.TestPolicy;
 import org.apache.brooklyn.test.Asserts;
 import org.apache.brooklyn.util.core.task.Tasks;
-import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.javalang.JavaClassNames;
 import org.apache.brooklyn.util.net.Networking;
 import org.apache.brooklyn.util.time.Duration;
@@ -154,10 +152,9 @@ public class ConfigKeyConstraintTest extends BrooklynAppUnitTestSupport {
     public void testExceptionWhenEntityHasNullConfig() {
         try {
             app.createAndManageChild(EntitySpec.create(EntityWithNonNullConstraint.class));
-            fail("Expected exception when managing entity with missing config");
+            Asserts.shouldHaveFailedPreviously("Expected exception when managing entity with missing config");
         } catch (Exception e) {
-            Throwable t = Exceptions.getFirstThrowableOfType(e, ConstraintViolationException.class);
-            assertNotNull(t);
+            Asserts.expectedFailureOfType(e, ConstraintViolationException.class);
         }
     }
 
@@ -176,10 +173,9 @@ public class ConfigKeyConstraintTest extends BrooklynAppUnitTestSupport {
     public void testExceptionWhenSubclassSetsInvalidDefaultValue() {
         try {
             app.createAndManageChild(EntitySpec.create(EntityProvidingDefaultValueForConfigKeyInRange.class));
-            fail("Expected exception when managing entity setting invalid default value");
+            Asserts.shouldHaveFailedPreviously("Expected exception when managing entity setting invalid default value");
         } catch (Exception e) {
-            Throwable t = Exceptions.getFirstThrowableOfType(e, ConstraintViolationException.class);
-            assertNotNull(t, "Original exception was: " + Exceptions.collapseText(e));
+            Asserts.expectedFailureOfType(e, ConstraintViolationException.class);
         }
     }
 
@@ -188,10 +184,9 @@ public class ConfigKeyConstraintTest extends BrooklynAppUnitTestSupport {
         try {
             app.createAndManageChild(EntitySpec.create(EntityWithNonNullConstraintWithNonNullDefault.class)
                     .configure(EntityWithNonNullConstraintWithNonNullDefault.NON_NULL_WITH_DEFAULT, (Object) null));
-            fail("Expected exception when config key set to null");
+            Asserts.shouldHaveFailedPreviously("Expected exception when config key set to null");
         } catch (Exception e) {
-            Throwable t = Exceptions.getFirstThrowableOfType(e, ConstraintViolationException.class);
-            assertNotNull(t, "Original exception was: " + Exceptions.collapseText(e));
+            Asserts.expectedFailureOfType(e, ConstraintViolationException.class);
         }
     }
 
@@ -200,10 +195,9 @@ public class ConfigKeyConstraintTest extends BrooklynAppUnitTestSupport {
         try {
             app.createAndManageChild(EntitySpec.create(EntityRequiringConfigKeyInRange.class)
                     .configure(ImmutableMap.of("test.conf.range", -1)));
-            fail("Expected exception when managing entity with invalid config");
+            Asserts.shouldHaveFailedPreviously("Expected exception when managing entity with invalid config");
         } catch (Exception e) {
-            Throwable t = Exceptions.getFirstThrowableOfType(e, ConstraintViolationException.class);
-            assertNotNull(t, "Original exception was: " + Exceptions.collapseText(e));
+            Asserts.expectedFailureOfType(e, ConstraintViolationException.class);
         }
     }
 
@@ -214,10 +208,9 @@ public class ConfigKeyConstraintTest extends BrooklynAppUnitTestSupport {
         try {
             testEntity.addChild(EntitySpec.create(EntityRequiringConfigKeyInRange.class)
                 .configure(EntityRequiringConfigKeyInRange.RANGE, -1));
-            fail("Expected exception when managing child with invalid config");
+            Asserts.shouldHaveFailedPreviously("Expected exception when managing child with invalid config");
         } catch (Exception e) {
-            Throwable t = Exceptions.getFirstThrowableOfType(e, ConstraintViolationException.class);
-            assertNotNull(t, "Original exception was: " + Exceptions.collapseText(e));
+            Asserts.expectedFailureOfType(e, ConstraintViolationException.class);
         }
     }
 
@@ -228,10 +221,9 @@ public class ConfigKeyConstraintTest extends BrooklynAppUnitTestSupport {
                 .configure(EntityWithNonNullConstraint.NON_NULL_CONFIG, (Object) null));
         try {
             ConfigConstraints.assertValid(p);
-            fail("Expected exception when validating policy with missing config");
+            Asserts.shouldHaveFailedPreviously("Expected exception when validating policy with missing config");
         } catch (Exception e) {
-            Throwable t = Exceptions.getFirstThrowableOfType(e, ConstraintViolationException.class);
-            assertNotNull(t, "Original exception was: " + Exceptions.collapseText(e));
+            Asserts.expectedFailureOfType(e, ConstraintViolationException.class);
         }
     }
 
@@ -240,10 +232,9 @@ public class ConfigKeyConstraintTest extends BrooklynAppUnitTestSupport {
         try {
             mgmt.getEntityManager().createPolicy(PolicySpec.create(PolicyWithConfigConstraint.class)
                     .configure(PolicyWithConfigConstraint.NON_NULL_CONFIG, (Object) null));
-            fail("Expected exception when creating policy with missing config");
+            Asserts.shouldHaveFailedPreviously("Expected exception when creating policy with missing config");
         } catch (Exception e) {
-            Throwable t = Exceptions.getFirstThrowableOfType(e, ConstraintViolationException.class);
-            assertNotNull(t, "Original exception was: " + Exceptions.collapseText(e));
+            Asserts.expectedFailureOfType(e, ConstraintViolationException.class);
         }
     }
 
@@ -252,10 +243,9 @@ public class ConfigKeyConstraintTest extends BrooklynAppUnitTestSupport {
         try {
             mgmt.getEntityManager().createEnricher(EnricherSpec.create(EnricherWithConfigConstraint.class)
                     .configure(EnricherWithConfigConstraint.PATTERN, "123.123.256.10"));
-            fail("Expected exception when creating enricher with invalid config");
+            Asserts.shouldHaveFailedPreviously("Expected exception when config key set to null");
         } catch (Exception e) {
-            Throwable t = Exceptions.getFirstThrowableOfType(e, ConstraintViolationException.class);
-            assertNotNull(t, "Original exception was: " + Exceptions.collapseText(e));
+            Asserts.expectedFailureOfType(e, ConstraintViolationException.class);
         }
     }
 
@@ -289,7 +279,7 @@ public class ConfigKeyConstraintTest extends BrooklynAppUnitTestSupport {
             app.createAndManageChild(EntitySpec.create(EntityWithContextAwareConstraint.class)
                     .displayName("Mr. Big")
                     .configure("must-be-display-name", "Mr. Bag"));
-            fail("Expected exception when managing entity with incorrect config");
+            Asserts.shouldHaveFailedPreviously("Expected exception when managing entity with incorrect config");
         } catch (Exception e) {
             Asserts.expectedFailureOfType(e, ConstraintViolationException.class);
         }
@@ -306,7 +296,7 @@ public class ConfigKeyConstraintTest extends BrooklynAppUnitTestSupport {
             // NB the call above does not currently/necessarily apply validation
             log.debug(JavaClassNames.niceClassAndMethod()+" got "+value+" for "+EntityRequiringConfigKeyInRange.RANGE+", now explicitly validating");
             ConfigConstraints.assertValid(child);
-            fail("Expected exception when managing entity with incorrect config; instead passed assertion and got: "+value);
+            Asserts.shouldHaveFailedPreviously("Expected exception when managing entity with incorrect config; instead passed assertion and got: "+value);
         } catch (Exception e) {
             Asserts.expectedFailureOfType(e, ConstraintViolationException.class);
         }
@@ -349,11 +339,90 @@ public class ConfigKeyConstraintTest extends BrooklynAppUnitTestSupport {
     public void testCannotUpdateConfigToInvalidValue(BrooklynObject object) {
         try {
             object.config().set(EntityRequiringConfigKeyInRange.RANGE, -1);
-            fail("Expected exception when calling config().set with invalid value on " + object);
+            Asserts.shouldHaveFailedPreviously("Expected exception when calling config().set with invalid value on " + object);
         } catch (Exception e) {
-            Throwable t = Exceptions.getFirstThrowableOfType(e, ConstraintViolationException.class);
-            assertNotNull(t, "Original exception was: " + Exceptions.collapseText(e));
+            Asserts.expectedFailureOfType(e, ConstraintViolationException.class);
         }
     }
 
+    public static interface EntityForForbiddenAndRequiredConditionalConstraints extends TestEntity {
+        ConfigKey<Object> X = ConfigKeys.builder(Object.class).name("x")
+                .build();
+    }
+    @ImplementedBy(EntityForForbiddenAndRequiredConditionalConstraintsForbiddenIfImpl.class)
+    public static interface EntityForForbiddenAndRequiredConditionalConstraintsForbiddenIf extends EntityForForbiddenAndRequiredConditionalConstraints {
+        static ConfigKey<Object> FI = ConfigKeys.builder(Object.class).name("forbiddenIfX")
+            .constraint(ConfigConstraints.forbiddenIf("x")).build();
+    }
+    public static class EntityForForbiddenAndRequiredConditionalConstraintsForbiddenIfImpl extends TestEntityImpl implements EntityForForbiddenAndRequiredConditionalConstraintsForbiddenIf {}
+    
+    @ImplementedBy(EntityForForbiddenAndRequiredConditionalConstraintsForbiddenUnlessImpl.class)
+    public static interface EntityForForbiddenAndRequiredConditionalConstraintsForbiddenUnless extends EntityForForbiddenAndRequiredConditionalConstraints {
+        static ConfigKey<Object> FU = ConfigKeys.builder(Object.class).name("forbiddenUnlessX")
+            .constraint(ConfigConstraints.forbiddenUnless("x")).build();
+    }
+    public static class EntityForForbiddenAndRequiredConditionalConstraintsForbiddenUnlessImpl extends TestEntityImpl implements EntityForForbiddenAndRequiredConditionalConstraintsForbiddenUnless {}
+    
+    @ImplementedBy(EntityForForbiddenAndRequiredConditionalConstraintsRequiredIfImpl.class)
+    public static interface EntityForForbiddenAndRequiredConditionalConstraintsRequiredIf extends EntityForForbiddenAndRequiredConditionalConstraints {
+        static ConfigKey<Object> RI = ConfigKeys.builder(Object.class).name("requiredIfX")
+            .constraint(ConfigConstraints.requiredIf("x")).build();
+    }
+    public static class EntityForForbiddenAndRequiredConditionalConstraintsRequiredIfImpl extends TestEntityImpl implements EntityForForbiddenAndRequiredConditionalConstraintsRequiredIf {}
+    
+    @ImplementedBy(EntityForForbiddenAndRequiredConditionalConstraintsRequiredUnlessImpl.class)
+    public static interface EntityForForbiddenAndRequiredConditionalConstraintsRequiredUnless extends EntityForForbiddenAndRequiredConditionalConstraints {
+        static ConfigKey<Object> RU = ConfigKeys.builder(Object.class).name("requiredUnlessX")
+            .constraint(ConfigConstraints.requiredUnless("x")).build();
+    }
+    public static class EntityForForbiddenAndRequiredConditionalConstraintsRequiredUnlessImpl extends TestEntityImpl implements EntityForForbiddenAndRequiredConditionalConstraintsRequiredUnless {}
+
+    @Test
+    public void testForbiddenAndRequiredConditionalConstraintsForbiddenIf() {
+        assertKeyBehaviour(EntityForForbiddenAndRequiredConditionalConstraintsForbiddenIf.class, EntityForForbiddenAndRequiredConditionalConstraintsForbiddenIf.FI,
+            false, true, true, true);
+    }
+
+    @Test
+    public void testForbiddenAndRequiredConditionalConstraintsForbiddenUnless() {
+        assertKeyBehaviour(EntityForForbiddenAndRequiredConditionalConstraintsForbiddenUnless.class, EntityForForbiddenAndRequiredConditionalConstraintsForbiddenUnless.FU,
+            true, true, false, true);
+    }
+
+    @Test
+    public void testForbiddenAndRequiredConditionalConstraintsRequiredIf() {
+        assertKeyBehaviour(EntityForForbiddenAndRequiredConditionalConstraintsRequiredIf.class, EntityForForbiddenAndRequiredConditionalConstraintsRequiredIf.RI,
+            true, false, true, true);
+    }
+
+    @Test
+    public void testForbiddenAndRequiredConditionalConstraintsRequiredUnlelss() {
+        assertKeyBehaviour(EntityForForbiddenAndRequiredConditionalConstraintsRequiredUnless.class, EntityForForbiddenAndRequiredConditionalConstraintsRequiredUnless.RU,
+            true, true, true, false);
+    }
+
+    private void assertKeyBehaviour(Class<? extends Entity> clazz, ConfigKey<Object> key, boolean ifBoth, boolean ifJustX, boolean ifJustThis, boolean ifNone) {
+        assertKeyBehaviour("both set", clazz, true, key, true, ifBoth);
+        assertKeyBehaviour("only other key set", clazz, true, key, false, ifJustX);
+        assertKeyBehaviour("only this key set", clazz, false, key, true, ifJustThis);
+        assertKeyBehaviour("neither key set", clazz, false, key, false, ifNone);
+    }
+    
+    private void assertKeyBehaviour(String description, Class<? extends Entity> clazz, boolean isXSet, ConfigKey<Object> key, boolean isKeySet, boolean shouldSucceed) {
+        try {
+            EntitySpec<?> spec = EntitySpec.create(clazz);
+            if (isXSet) spec.configure(EntityForForbiddenAndRequiredConditionalConstraints.X, "set");
+            if (isKeySet) spec.configure(key, "set");
+            app.createAndManageChild(spec);
+            if (!shouldSucceed) {
+                Asserts.shouldHaveFailedPreviously("Expected failure when testing "+key.getName()+" - "+description);
+            }
+        } catch (Exception e) {
+            if (!shouldSucceed) {
+                Asserts.expectedFailureOfType("Expected ConstraintViolationException when testing "+key.getName()+" - "+description, e, ConstraintViolationException.class);
+            } else {
+                throw new AssertionError("Expected success when testing "+key.getName()+" - "+description+"; instead got "+e, e);
+            }
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/0d6f5721/utils/common/src/main/java/org/apache/brooklyn/test/Asserts.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/test/Asserts.java b/utils/common/src/main/java/org/apache/brooklyn/test/Asserts.java
index 6975139..eb9275a 100644
--- a/utils/common/src/main/java/org/apache/brooklyn/test/Asserts.java
+++ b/utils/common/src/main/java/org/apache/brooklyn/test/Asserts.java
@@ -1261,17 +1261,21 @@ public class Asserts {
      * or more usually the test failure of this method is thrown, 
      * with detail of the original {@link Throwable} logged and included in the caused-by.
      */
-    @SuppressWarnings("unchecked")
     public static void expectedFailureOfType(Throwable e, Class<?> permittedSupertype, Class<?> ...permittedSupertypes) {
+        expectedFailureOfType(null, e, permittedSupertype, permittedSupertypes);
+    }
+    @SuppressWarnings("unchecked")
+    public static void expectedFailureOfType(String message, Throwable e, Class<?> permittedSupertype, Class<?> ...permittedSupertypeExtras) {
+        @SuppressWarnings("rawtypes")
+        List<Class<?>> permittedSupertypes = MutableList.of(permittedSupertype).appendAll((List)Arrays.asList(permittedSupertypeExtras));
         if (e instanceof ShouldHaveFailedPreviouslyAssertionError) throw (Error) e;
-        Throwable match = Exceptions.getFirstThrowableOfType(e, (Class<? extends Throwable>) permittedSupertype);
-        if (match != null) return;
         for (Class<?> clazz: permittedSupertypes) {
-            match = Exceptions.getFirstThrowableOfType(e, (Class<? extends Throwable>)clazz);
-            if (match != null) return;
+            if (Exceptions.getFirstThrowableOfType(e, (Class<? extends Throwable>)clazz) != null) {
+                return;
+            }
         }
         rethrowPreferredException(e, 
-            new AssertionError("Error "+JavaClassNames.simpleClassName(e)+" is not any of the expected types: " + Arrays.asList(permittedSupertypes), e));
+            new AssertionError((message!=null ? message+": " : "") + "Error "+JavaClassNames.simpleClassName(e)+" is not any of the expected types: " + permittedSupertypes, e));
     }
     
     /** Tests {@link #expectedFailure(Throwable)} and that the <code>toString</code>