Skip to content

Commit

Permalink
Closes apache#1008
Browse files Browse the repository at this point in the history
Adds constraint (required|forbidden)unlessAnyOf
  • Loading branch information
geomacy committed Oct 23, 2018
2 parents ad8574f + 554b9f7 commit 04c3283
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -255,18 +259,34 @@ public int hashCode() {
}
}

private static abstract class OtherKeyPredicate implements BrooklynObjectPredicate<Object> {
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<Object> otherValues) {
return test(thisValue, Iterables.getOnlyElement(otherValues));
}

public abstract boolean test(Object thisValue, Object otherValue);
}

private static abstract class OtherKeysPredicate implements BrooklynObjectPredicate<Object> {
private final List<String> otherKeyNames;

public OtherKeysPredicate(List<String> 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
Expand All @@ -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<Object> 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<Object> otherValues);
}

public static Predicate<Object> forbiddenIf(String otherKeyName) { return new ForbiddenIfPredicate(otherKeyName); }
Expand Down Expand Up @@ -321,4 +344,21 @@ protected static class RequiredUnlessPredicate extends OtherKeyPredicate {
}
}

public static Predicate<Object> forbiddenUnlessAnyOf(List<String> otherKeyNames) { return new ForbiddenUnlessAnyOfPredicate(otherKeyNames); }
protected static class ForbiddenUnlessAnyOfPredicate extends OtherKeysPredicate {
public ForbiddenUnlessAnyOfPredicate(List<String> otherKeyNames) { super(otherKeyNames); }
@Override public String predicateName() { return "forbiddenUnlessAnyOf"; }
@Override public boolean test(Object thisValue, List<Object> otherValue) {
return (thisValue==null) || (otherValue!=null && Iterables.tryFind(otherValue, Predicates.notNull()).isPresent());
}
}

public static Predicate<Object> requiredUnlessAnyOf(List<String> otherKeyNames) { return new RequiredUnlessAnyOfPredicate(otherKeyNames); }
protected static class RequiredUnlessAnyOfPredicate extends OtherKeysPredicate {
public RequiredUnlessAnyOfPredicate(List<String> otherKeyNames) { super(otherKeyNames); }
@Override public String predicateName() { return "requiredUnlessAnyOf"; }
@Override public boolean test(Object thisValue, List<Object> otherValue) {
return (thisValue!=null) || (otherValue!=null && Iterables.tryFind(otherValue, Predicates.notNull()).isPresent());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@
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;
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.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;
Expand Down Expand Up @@ -65,8 +67,8 @@ public static class PredicateSerializationRuleAdder<T> {
ConstraintSerialization serialization;

public PredicateSerializationRuleAdder(Function<T, Predicate<?>> constructor, Function<List<?>, T> constructorArgsFromList, T constructorSampleInput) {
this.constructorArgsFromList = constructorArgsFromList;
this.constructor = constructor;
this.constructorArgsFromList = constructorArgsFromList;
this.constructorSampleInput = constructorSampleInput;
}

Expand All @@ -82,6 +84,12 @@ public static PredicateSerializationRuleAdder<String> stringConstructor(Function
o -> Strings.toString(Iterables.getOnlyElement(o)), "");
}

public static PredicateSerializationRuleAdder<List<String>> listConstructor(Function<List<String>,Predicate<?>> constructor) {
Function<Object, String> cooercer = (o) -> TypeCoercions.coerce(o, String.class);
Function<List<?>, List<String>> constructorArgsFromList = (o) -> o.stream().map(cooercer).collect(Collectors.toList());
return new PredicateSerializationRuleAdder<List<String>>(constructor, constructorArgsFromList, ImmutableList.of());
}

public static PredicateSerializationRuleAdder<Void> noArgConstructor(Supplier<Predicate<?>> constructor) {
return new PredicateSerializationRuleAdder<Void>(
(o) -> constructor.get(), o -> null, null);
Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,8 @@ public void testCannotUpdateConfigToInvalidValue(BrooklynObject object) {
public static interface EntityForForbiddenAndRequiredConditionalConstraints extends TestEntity {
ConfigKey<Object> X = ConfigKeys.builder(Object.class).name("x")
.build();
ConfigKey<Object> Y = ConfigKeys.builder(Object.class).name("y")
.build();
}
@ImplementedBy(EntityForForbiddenAndRequiredConditionalConstraintsForbiddenIfImpl.class)
public static interface EntityForForbiddenAndRequiredConditionalConstraintsForbiddenIf extends EntityForForbiddenAndRequiredConditionalConstraints {
Expand Down Expand Up @@ -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<Object> 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<Object> 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,
Expand All @@ -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<EntityForForbiddenAndRequiredConditionalConstraintsRequiredUnlessAnyOf> clazz = EntityForForbiddenAndRequiredConditionalConstraintsRequiredUnlessAnyOf.class;
ConfigKey<Object> 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<EntityForForbiddenAndRequiredConditionalConstraintsForbiddenUnlessAnyOf> clazz = EntityForForbiddenAndRequiredConditionalConstraintsForbiddenUnlessAnyOf.class;
ConfigKey<Object> 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<? extends Entity> clazz, ConfigKey<Object> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand Down

0 comments on commit 04c3283

Please sign in to comment.