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 67d8aef320..b0ed3f8271 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 @@ -19,10 +19,12 @@ package org.apache.brooklyn.core.config; +import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Objects; +import java.util.stream.Collectors; import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.location.Location; @@ -44,6 +46,8 @@ import org.slf4j.LoggerFactory; import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; @@ -255,18 +259,34 @@ public int hashCode() { } } - private static abstract class OtherKeyPredicate implements BrooklynObjectPredicate { - private final String otherKeyName; - + private static abstract class OtherKeyPredicate extends OtherKeysPredicate { public OtherKeyPredicate(String otherKeyName) { - this.otherKeyName = otherKeyName; + super(ImmutableList.of(otherKeyName)); + } + + @Override + public boolean test(Object thisValue, List otherValues) { + return test(thisValue, Iterables.getOnlyElement(otherValues)); + } + + public abstract boolean test(Object thisValue, Object otherValue); + } + + private static abstract class OtherKeysPredicate implements BrooklynObjectPredicate { + private final List otherKeyNames; + + public OtherKeysPredicate(List otherKeyNames) { + this.otherKeyNames = otherKeyNames; } public abstract String predicateName(); @Override public String toString() { - return predicateName()+"("+JavaStringEscapes.wrapJavaString(otherKeyName)+")"; + String params = otherKeyNames.stream() + .map(k -> JavaStringEscapes.wrapJavaString(k)) + .collect(Collectors.joining(", ")); + return predicateName()+"("+params+")"; } @Override @@ -278,11 +298,14 @@ public boolean apply(Object input) { 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))); + List vals = new ArrayList<>(); + for (String otherKeyName : otherKeyNames) { + vals.add(context.config().get(ConfigKeys.newConfigKey(Object.class, otherKeyName))); + } + return test(input, vals); } - public abstract boolean test(Object thisValue, Object otherValue); - + public abstract boolean test(Object thisValue, List otherValues); } public static Predicate forbiddenIf(String otherKeyName) { return new ForbiddenIfPredicate(otherKeyName); } @@ -321,4 +344,21 @@ protected static class RequiredUnlessPredicate extends OtherKeyPredicate { } } + public static Predicate forbiddenUnlessAnyOf(List otherKeyNames) { return new ForbiddenUnlessAnyOfPredicate(otherKeyNames); } + protected static class ForbiddenUnlessAnyOfPredicate extends OtherKeysPredicate { + public ForbiddenUnlessAnyOfPredicate(List otherKeyNames) { super(otherKeyNames); } + @Override public String predicateName() { return "forbiddenUnlessAnyOf"; } + @Override public boolean test(Object thisValue, List otherValue) { + return (thisValue==null) || (otherValue!=null && Iterables.tryFind(otherValue, Predicates.notNull()).isPresent()); + } + } + + public static Predicate requiredUnlessAnyOf(List otherKeyNames) { return new RequiredUnlessAnyOfPredicate(otherKeyNames); } + protected static class RequiredUnlessAnyOfPredicate extends OtherKeysPredicate { + public RequiredUnlessAnyOfPredicate(List otherKeyNames) { super(otherKeyNames); } + @Override public String predicateName() { return "requiredUnlessAnyOf"; } + @Override public boolean test(Object thisValue, List otherValue) { + return (thisValue!=null) || (otherValue!=null && Iterables.tryFind(otherValue, Predicates.notNull()).isPresent()); + } + } } 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 32b71f0461..2cb8ea0298 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 @@ -28,6 +28,7 @@ import java.util.function.Supplier; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.core.config.ConfigConstraints; @@ -35,6 +36,7 @@ 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.core.flags.TypeCoercions; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.text.StringEscapes.JavaStringEscapes; import org.apache.brooklyn.util.text.StringPredicates; @@ -65,8 +67,8 @@ public static class PredicateSerializationRuleAdder { ConstraintSerialization serialization; public PredicateSerializationRuleAdder(Function> constructor, Function, T> constructorArgsFromList, T constructorSampleInput) { - this.constructorArgsFromList = constructorArgsFromList; this.constructor = constructor; + this.constructorArgsFromList = constructorArgsFromList; this.constructorSampleInput = constructorSampleInput; } @@ -82,6 +84,12 @@ public static PredicateSerializationRuleAdder stringConstructor(Function o -> Strings.toString(Iterables.getOnlyElement(o)), ""); } + public static PredicateSerializationRuleAdder> listConstructor(Function,Predicate> constructor) { + Function cooercer = (o) -> TypeCoercions.coerce(o, String.class); + Function, List> constructorArgsFromList = (o) -> o.stream().map(cooercer).collect(Collectors.toList()); + return new PredicateSerializationRuleAdder>(constructor, constructorArgsFromList, ImmutableList.of()); + } + public static PredicateSerializationRuleAdder noArgConstructor(Supplier> constructor) { return new PredicateSerializationRuleAdder( (o) -> constructor.get(), o -> null, null); @@ -185,6 +193,8 @@ private void init() { PredicateSerializationRuleAdder.stringConstructor(ConfigConstraints::forbiddenUnless).add(this); PredicateSerializationRuleAdder.stringConstructor(ConfigConstraints::requiredIf).add(this); PredicateSerializationRuleAdder.stringConstructor(ConfigConstraints::requiredUnless).add(this); + PredicateSerializationRuleAdder.listConstructor(ConfigConstraints::requiredUnlessAnyOf).add(this); + PredicateSerializationRuleAdder.listConstructor(ConfigConstraints::forbiddenUnlessAnyOf).add(this); } public final static ConstraintSerialization INSTANCE = new ConstraintSerialization(); 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 16f2f77044..656ca3e48b 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 @@ -350,6 +350,8 @@ public void testCannotUpdateConfigToInvalidValue(BrooklynObject object) { public static interface EntityForForbiddenAndRequiredConditionalConstraints extends TestEntity { ConfigKey X = ConfigKeys.builder(Object.class).name("x") .build(); + ConfigKey Y = ConfigKeys.builder(Object.class).name("y") + .build(); } @ImplementedBy(EntityForForbiddenAndRequiredConditionalConstraintsForbiddenIfImpl.class) public static interface EntityForForbiddenAndRequiredConditionalConstraintsForbiddenIf extends EntityForForbiddenAndRequiredConditionalConstraints { @@ -379,6 +381,20 @@ public static interface EntityForForbiddenAndRequiredConditionalConstraintsRequi } public static class EntityForForbiddenAndRequiredConditionalConstraintsRequiredUnlessImpl extends TestEntityImpl implements EntityForForbiddenAndRequiredConditionalConstraintsRequiredUnless {} + @ImplementedBy(EntityForForbiddenAndRequiredConditionalConstraintsRequiredUnlessAnyOfImpl.class) + public static interface EntityForForbiddenAndRequiredConditionalConstraintsRequiredUnlessAnyOf extends EntityForForbiddenAndRequiredConditionalConstraints { + static ConfigKey RUAO = ConfigKeys.builder(Object.class).name("requiredUnlessAnyOfXY") + .constraint(ConfigConstraints.requiredUnlessAnyOf(ImmutableList.of("x", "y"))).build(); + } + public static class EntityForForbiddenAndRequiredConditionalConstraintsRequiredUnlessAnyOfImpl extends TestEntityImpl implements EntityForForbiddenAndRequiredConditionalConstraintsRequiredUnlessAnyOf {} + + @ImplementedBy(EntityForForbiddenAndRequiredConditionalConstraintsForbiddenUnlessAnyOfImpl.class) + public static interface EntityForForbiddenAndRequiredConditionalConstraintsForbiddenUnlessAnyOf extends EntityForForbiddenAndRequiredConditionalConstraints { + static ConfigKey FUAO = ConfigKeys.builder(Object.class).name("forbiddenUnlessAnyOfXY") + .constraint(ConfigConstraints.forbiddenUnlessAnyOf(ImmutableList.of("x", "y"))).build(); + } + public static class EntityForForbiddenAndRequiredConditionalConstraintsForbiddenUnlessAnyOfImpl extends TestEntityImpl implements EntityForForbiddenAndRequiredConditionalConstraintsForbiddenUnlessAnyOf {} + @Test public void testForbiddenAndRequiredConditionalConstraintsForbiddenIf() { assertKeyBehaviour(EntityForForbiddenAndRequiredConditionalConstraintsForbiddenIf.class, EntityForForbiddenAndRequiredConditionalConstraintsForbiddenIf.FI, @@ -398,11 +414,33 @@ public void testForbiddenAndRequiredConditionalConstraintsRequiredIf() { } @Test - public void testForbiddenAndRequiredConditionalConstraintsRequiredUnlelss() { + public void testForbiddenAndRequiredConditionalConstraintsRequiredUnless() { assertKeyBehaviour(EntityForForbiddenAndRequiredConditionalConstraintsRequiredUnless.class, EntityForForbiddenAndRequiredConditionalConstraintsRequiredUnless.RU, true, true, true, false); } + @Test + public void testForbiddenAndRequiredConditionalConstraintsRequiredUnlessAnyOf() { + Class clazz = EntityForForbiddenAndRequiredConditionalConstraintsRequiredUnlessAnyOf.class; + ConfigKey key = EntityForForbiddenAndRequiredConditionalConstraintsRequiredUnlessAnyOf.RUAO; + assertKeyBehaviour(clazz, key, + true, true, true, false); + assertKeyBehaviour("only other key set", clazz, MutableMap.of("y", "myval"), true); + assertKeyBehaviour("both set", clazz, MutableMap.of("y", "myval", key, "myval"), true); + assertKeyBehaviour("both set", clazz, MutableMap.of("x", "myval", "y", "myval", key, "myval"), true); + } + + @Test + public void testForbiddenAndRequiredConditionalConstraintsForbiddenUnlessAnyOf() { + Class clazz = EntityForForbiddenAndRequiredConditionalConstraintsForbiddenUnlessAnyOf.class; + ConfigKey key = EntityForForbiddenAndRequiredConditionalConstraintsForbiddenUnlessAnyOf.FUAO; + assertKeyBehaviour(clazz, key, + true, true, false, true); + assertKeyBehaviour("only other key set", clazz, MutableMap.of("y", "myval"), true); + assertKeyBehaviour("both set", clazz, MutableMap.of("y", "myval", key, "myval"), true); + assertKeyBehaviour("both set", clazz, MutableMap.of("x", "myval", "y", "myval", key, "myval"), true); + } + private void assertKeyBehaviour(Class clazz, ConfigKey key, boolean ifBoth, boolean ifJustX, boolean ifJustThis, boolean ifNone) { assertKeyBehaviour("both set", clazz, MutableMap.of("x", "myval", key, "myval"), ifBoth); assertKeyBehaviour("only other key set", clazz, MutableMap.of("x", "myval"), ifJustX); diff --git a/core/src/test/java/org/apache/brooklyn/core/objs/ConstraintSerializationTest.java b/core/src/test/java/org/apache/brooklyn/core/objs/ConstraintSerializationTest.java index 088232588e..66747ac5b8 100644 --- a/core/src/test/java/org/apache/brooklyn/core/objs/ConstraintSerializationTest.java +++ b/core/src/test/java/org/apache/brooklyn/core/objs/ConstraintSerializationTest.java @@ -31,6 +31,7 @@ import com.google.common.base.Predicate; import com.google.common.base.Predicates; +import com.google.common.collect.ImmutableList; import com.google.gson.Gson; public class ConstraintSerializationTest extends BrooklynMgmtUnitTestSupport { @@ -48,6 +49,8 @@ public void testSimple() { assertPredJsonBidi(ConfigConstraints.forbiddenUnless("myother"), MutableList.of(MutableMap.of("forbiddenUnless", "myother"))); assertPredJsonBidi(ConfigConstraints.requiredIf("myother"), MutableList.of(MutableMap.of("requiredIf", "myother"))); assertPredJsonBidi(ConfigConstraints.requiredUnless("myother"), MutableList.of(MutableMap.of("requiredUnless", "myother"))); + assertPredJsonBidi(ConfigConstraints.requiredUnlessAnyOf(ImmutableList.of("myother1", "myother2")), MutableList.of(MutableMap.of("requiredUnlessAnyOf", ImmutableList.of("myother1", ("myother2"))))); + assertPredJsonBidi(ConfigConstraints.forbiddenUnlessAnyOf(ImmutableList.of("myother1", "myother2")), MutableList.of(MutableMap.of("forbiddenUnlessAnyOf", ImmutableList.of("myother1", ("myother2"))))); } @Test