Repository: brooklyn-server Updated Branches: refs/heads/master e27cc7b6d -> a3d0ea06e
proper serialization and deserialization for 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/bd34655c Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/bd34655c Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/bd34655c Branch: refs/heads/master Commit: bd34655c81400f8228c329ecec31ce6e378f78b0 Parents: e27cc7b Author: Alex Heneveld <alex.henev...@cloudsoftcorp.com> Authored: Thu Sep 20 22:34:25 2018 +0100 Committer: Alex Heneveld <alex.henev...@cloudsoftcorp.com> Committed: Thu Sep 20 22:34:25 2018 +0100 ---------------------------------------------------------------------- .../brooklyn/core/config/ConfigConstraints.java | 25 ++ .../core/objs/ConstraintSerialization.java | 354 +++++++++++++++++++ .../core/objs/ConstraintSerializationTest.java | 84 +++++ .../brooklyn/rest/domain/ConfigSummary.java | 17 +- .../brooklyn/util/text/StringPredicates.java | 11 +- .../org/apache/brooklyn/util/text/Strings.java | 22 +- .../org/apache/brooklyn/test/AssertsTest.java | 2 +- 7 files changed, 497 insertions(+), 18 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/bd34655c/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 17b7c9d..35f672e 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 @@ -33,6 +33,7 @@ import org.apache.brooklyn.core.entity.EntityInternal; import org.apache.brooklyn.core.objs.AbstractEntityAdjunct; import org.apache.brooklyn.core.objs.BrooklynObjectInternal; import org.apache.brooklyn.core.objs.BrooklynObjectPredicate; +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; @@ -185,6 +186,13 @@ public abstract class ConfigConstraints<T extends BrooklynObject> { return brooklynObject; } + /** + * Convenience method to get the serialization routines. + */ + public static ConstraintSerialization serialization() { + return ConstraintSerialization.INSTANCE; + } + private static class EntityConfigConstraints extends ConfigConstraints<Entity> { public EntityConfigConstraints(Entity brooklynObject) { super(brooklynObject); @@ -218,4 +226,21 @@ public abstract class ConfigConstraints<T extends BrooklynObject> { } } + public static <T> Predicate<T> required() { + return new RequiredPredicate<T>(); + } + + /** Predicate indicating a field is required: it must not be null and if a string it must not be empty */ + public static class RequiredPredicate<T> implements Predicate<T> { + @Override + public boolean apply(T input) { + if (input==null) return false; + if (input instanceof CharSequence && ((CharSequence)input).length()==0) return false; + return true; + } + @Override + public String toString() { + return "required()"; + } + } } http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/bd34655c/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 new file mode 100644 index 0000000..a6b7039 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/objs/ConstraintSerialization.java @@ -0,0 +1,354 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.core.objs; + +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +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.exceptions.Exceptions; +import org.apache.brooklyn.util.text.StringEscapes.JavaStringEscapes; +import org.apache.brooklyn.util.text.StringPredicates; +import org.apache.brooklyn.util.text.Strings; + +import com.google.common.base.Preconditions; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; + +public class ConstraintSerialization { + + private final Map<String, String> predicateToStringToPreferredName = MutableMap.of(); + private final Map<String, Function<List<?>,Predicate<?>>> predicatePreferredNameToConstructor = MutableMap.of(); + + public static class PredicateSerializationRuleAdder<T> { + private String preferredName; + private Function<List<?>, T> constructorArgsFromList; + private Function<T, Predicate<?>> constructor; + private Predicate<?> predicateSample; + private T constructorSampleInput; + private Set<String> equivalentNames = MutableSet.of(); + private Set<Predicate<?>> equivalentPredicateSamples = MutableSet.of(); + + ConstraintSerialization serialization; + + public PredicateSerializationRuleAdder(Function<T, Predicate<?>> constructor, Function<List<?>, T> constructorArgsFromList, T constructorSampleInput) { + this.constructorArgsFromList = constructorArgsFromList; + this.constructor = constructor; + this.constructorSampleInput = constructorSampleInput; + } + + public static PredicateSerializationRuleAdder<List<Predicate<?>>> predicateListConstructor(Function<List<Predicate<?>>,Predicate<?>> constructor) { + PredicateSerializationRuleAdder<List<Predicate<?>>> result = new PredicateSerializationRuleAdder<List<Predicate<?>>>(constructor, + null, MutableList.of()); + result.constructorArgsFromList = o -> result.serialization.toPredicateListFromJsonList(o); + return result; + } + + public static PredicateSerializationRuleAdder<String> stringConstructor(Function<String,Predicate<?>> constructor) { + return new PredicateSerializationRuleAdder<String>(constructor, + o -> Strings.toString(Iterables.getOnlyElement(o)), ""); + } + + public static PredicateSerializationRuleAdder<Void> noArgConstructor(Supplier<Predicate<?>> constructor) { + return new PredicateSerializationRuleAdder<Void>( + (o) -> constructor.get(), o -> null, null); + } + + /** Preferred name for predicate when serializing. Defaults to the predicate name in the output of the {@link #sample(Predicate)}. */ + public PredicateSerializationRuleAdder<T> preferredName(String preferredName) { + this.preferredName = preferredName; + return this; + } + + /** Other predicates which are different to the type indicated by {@link #sample(Predicate)} but equivalent, + * and after serialization will be represented by {@link #preferredName} and after deserialization + * will result in the {@link Predicate} produced by {@link #constructor}. */ + public PredicateSerializationRuleAdder<T> equivalentNames(String ...equivs) { + for (String equiv: equivs) equivalentNames.add(equiv); + return this; + } + + /** Sample of what the {@link #constructor} will produce, used to recognise this rule when parsing. + * Can be omitted if {@link #sampleArg(Object)} supplied or its default is accepted. */ + public PredicateSerializationRuleAdder<T> sample(Predicate<?> samplePreferredPredicate) { + predicateSample = samplePreferredPredicate; + return this; + } + + /** This should supply args accepted by {@link #constructor} to generate a {@link #sample(Predicate)}. + * At most one of this or {@link #sample(Predicate)} should be supplied. + * If the constructor accepts a default empty list/string/null then these can be omitted. */ + public PredicateSerializationRuleAdder<T> sampleArg(T arg) { + constructorSampleInput = arg; + return this; + } + + /** Other predicates which are different to the type indicated by {@link #sample(Predicate)} but equivalent, + * and after serialization will be represented by {@link #preferredName} and after deserialization + * will result in the {@link Predicate} produced by {@link #constructor}. */ + public PredicateSerializationRuleAdder<T> equivalentPredicates(Predicate<?> ...equivs) { + for (Predicate<?> equiv: equivs) equivalentPredicateSamples.add(equiv); + return this; + } + + public void add(ConstraintSerialization constraintSerialization) { + this.serialization = constraintSerialization; + if (predicateSample==null) predicateSample = constructor.apply(constructorSampleInput); + String toStringName = Strings.removeAfter(Preconditions.checkNotNull(predicateSample, "sample or sampleArg must be supplied").toString(), "(", false); + if (preferredName==null) { + preferredName = toStringName; + } else { + constraintSerialization.predicateToStringToPreferredName.put(preferredName, preferredName); + } + constraintSerialization.predicateToStringToPreferredName.put(toStringName, preferredName); + + for (String equiv: equivalentNames) { + constraintSerialization.predicateToStringToPreferredName.put(equiv, preferredName); + } + + constraintSerialization.predicatePreferredNameToConstructor.put(preferredName, constructor.compose(constructorArgsFromList)); + + for (Predicate<?> equiv: equivalentPredicateSamples) { + String equivToStringName = Strings.removeAfter(equiv.toString(), "(", false); + constraintSerialization.predicateToStringToPreferredName.put(equivToStringName, preferredName); + } + } + } + + private static String GROUP(String in) { return "("+in+")"; } + private static String NOT_CHARSET(String ...in) { return "[^"+Strings.join(in, "")+"]"; } + private static String OR_GROUP(String ...in) { return GROUP(Strings.join(in, "|")); } + private static String ZERO_OR_MORE(String in) { return in + "*"; } + + private static String DOUBLE_QUOTED_STRING = "\""+GROUP(ZERO_OR_MORE(OR_GROUP(NOT_CHARSET("\\", "\""), "\\.")))+"\""; + private static String SINGLE_QUOTED_STRING = "\'"+GROUP(ZERO_OR_MORE(OR_GROUP(NOT_CHARSET("\\", "\'"), "\\.")))+"\'"; + + private static String PREDICATE = "[A-Za-z0-9_\\-\\.]+"; + + private static Pattern PATTERN_START_WITH_QUOTED_STRING = Pattern.compile("^"+OR_GROUP(DOUBLE_QUOTED_STRING, SINGLE_QUOTED_STRING)); + private static Pattern PATTERN_START_WITH_PREDICATE = Pattern.compile("^"+GROUP(PREDICATE)); + + { + init(); + } + @SuppressWarnings({ "unchecked", "rawtypes" }) + private void init() { + PredicateSerializationRuleAdder.predicateListConstructor((o) -> ConfigConstraints.required()). + equivalentPredicates(Predicates.notNull(), StringPredicates.isNonBlank()).add(this); + + 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.stringConstructor(StringPredicates::matchesRegex).preferredName("regex").add(this); + PredicateSerializationRuleAdder.stringConstructor(StringPredicates::matchesGlob).preferredName("glob").add(this); + } + + public static ConstraintSerialization INSTANCE = new ConstraintSerialization(); + + private ConstraintSerialization() {} + + public List<Object> toJsonList(ConfigKey<?> config) { + return toJsonList(config.getConstraint()); + } + + public List<Object> toJsonList(Predicate<?> constraint) { + // map twice to clean it (flatten "and" lists, etc) + // but if not possible go with progressively simpler items + try { + return toExactJsonList(toPredicateFromJson(toExactJsonList(constraint))); + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + try { + return toExactJsonList(constraint); + } catch (Exception e2) { + Exceptions.propagateIfFatal(e); + return Collections.singletonList(constraint.toString()); + } + } + } + @SuppressWarnings({ "unchecked", "rawtypes" }) + public List<Object> toExactJsonList(Predicate<?> constraint) { + StringConstraintParser parser = StringConstraintParser.forConstraint(this, Strings.toString(constraint)); + if (!parser.parse()) throw new IllegalStateException("cannot match: "+constraint); + if (parser.result instanceof Map && ((Map)parser.result).size()==1 && ((Map)parser.result).containsKey("all")) { + return (List<Object>) ((Map)parser.result).get("all"); + } + return ImmutableList.of(parser.result); + } + + private static class StringConstraintParser { + ConstraintSerialization serialization; + String remaining; + Object result; + List<Object> resultList = MutableList.of(); + boolean list = false; + + static StringConstraintParser forConstraint(ConstraintSerialization serialization, String in) { + StringConstraintParser result = new StringConstraintParser(); + result.serialization = serialization; + result.remaining = in; + return result; + } + + static StringConstraintParser forArgsInternal(ConstraintSerialization serialization, String in) { + StringConstraintParser result = forConstraint(serialization, in); + result.list = true; + return result; + } + + boolean parse() { + remaining = remaining.trim(); + Matcher m = PATTERN_START_WITH_PREDICATE.matcher(remaining); + if (!m.find()) { + if (!list) return false; + // when looking at args, + // allow empty list + if (remaining.startsWith(")")) { + result = resultList; + return true; + } + // and allow strings + m = PATTERN_START_WITH_QUOTED_STRING.matcher(remaining); + if (!m.find()) { + return false; + } + result = JavaStringEscapes.unwrapJavaString(m.group()); + remaining = remaining.substring(m.end()); + } else { + String p1 = m.group(1); + String p2 = serialization.predicateToStringToPreferredName.get(p1); + if (p2==null) p2 = p1; + remaining = remaining.substring(m.end()).trim(); + + if (!remaining.startsWith("(")) { + result = p2; + } else { + remaining = remaining.substring(1).trim(); + StringConstraintParser args = forArgsInternal(serialization, remaining); + if (!args.parse()) return false; + if (args.resultList.isEmpty()) { + result = p2; + } else if (args.resultList.size()==1) { + result = MutableMap.of(p2, Iterables.getOnlyElement(args.resultList)); + } else { + result = MutableMap.of(p2, args.result); + } + remaining = args.remaining; + if (!remaining.startsWith(")")) return false; + remaining = remaining.substring(1).trim(); + } + if (!list) return remaining.isEmpty(); + } + resultList.add(result); + if (remaining.isEmpty() || remaining.startsWith(")")) { + result = resultList; + return true; + } + if (!remaining.startsWith(",")) return false; + remaining = remaining.substring(1); + return parse(); + } + } + + private void collectPredicateListFromJson(Object o, List<Predicate<?>> result) { + if (o instanceof Collection) { + ((Collection<?>)o).stream().forEach(i -> collectPredicateListFromJson(i, result)); + return; + } + Predicate<?> p = toPredicateFromJson(o); + if (Predicates.alwaysTrue().equals(p)) { + // no point in keeping this one + return; + } + result.add(p); + } + public Predicate<?> toPredicateFromJson(Object o) { + if (o instanceof Collection) { + @SuppressWarnings({ "rawtypes", "unchecked" }) + Predicate<?> result2 = and((List)toPredicateListFromJsonList((Collection<?>)o)); + return result2; + } + + String key; + List<Object> args; + if (o instanceof String) { + key = (String)o; + args = MutableList.of(); + } else if (o instanceof Map) { + if (((Map<?,?>)o).size()!=1) { + throw new IllegalArgumentException("Unsupported constraint; map input should have a single key: "+o); + } + // we only support single-key maps with string as key and value as list (of args) or other type as single arg, as in predicateName(args) + key = (String) Iterables.getOnlyElement( ((Map<?,?>)o).keySet() ); + Object v = Iterables.getOnlyElement( ((Map<?,?>)o).values() ); + if (v instanceof Iterable) { + args = MutableList.copyOf((Iterable<?>)v); + } else { + args = Collections.singletonList(v); + } + } else if (o instanceof Predicate) { + return (Predicate<?>)o; + } else { + throw new IllegalArgumentException("Unsupported constraint; constraint should be string, list, or single-key map: "+o); + } + Function<List<?>, Predicate<?>> constructor = predicatePreferredNameToConstructor.get(key); + if (constructor==null) { + String preferredName = predicateToStringToPreferredName.get(key); + if (preferredName!=null) { + constructor = predicatePreferredNameToConstructor.get(preferredName); + if (constructor==null) { + throw new IllegalArgumentException("Incomplete constraint: "+key+", maps to "+preferredName+", but no constructor known"); + } + } else { + throw new IllegalArgumentException("Unsupported constraint: "+key); + } + } + return constructor.apply(args); + } + + private <T> Predicate<?> and(Iterable<Predicate<? super T>> preds) { + Iterator<Predicate<? super T>> pi = preds.iterator(); + if (!pi.hasNext()) return Predicates.alwaysTrue(); + Predicate<?> first = pi.next(); + if (!pi.hasNext()) return first; + return Predicates.and(preds); + } + public List<Predicate<?>> toPredicateListFromJsonList(Collection<?> o) { + List<Predicate<?>> result = MutableList.of(); + collectPredicateListFromJson(o, result); + return result; + } + +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/bd34655c/core/src/test/java/org/apache/brooklyn/core/objs/ConstraintSerializationTest.java ---------------------------------------------------------------------- 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 new file mode 100644 index 0000000..2065044 --- /dev/null +++ b/core/src/test/java/org/apache/brooklyn/core/objs/ConstraintSerializationTest.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.core.objs; + +import java.util.List; + +import org.apache.brooklyn.core.config.ConfigConstraints; +import org.apache.brooklyn.core.test.BrooklynMgmtUnitTestSupport; +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.text.StringPredicates; +import org.testng.Assert; +import org.testng.annotations.Test; + +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; + +public class ConstraintSerializationTest extends BrooklynMgmtUnitTestSupport { + + @Test + public void testSimple() { + assertPredJsonBidi(ConfigConstraints.required(), MutableList.of("required")); + } + + @Test + public void testInteresting() { + assertPredJsonBidi(Predicates.and(ConfigConstraints.required(), StringPredicates.matchesRegex(".*")), + MutableList.of("required", MutableMap.of("regex", ".*"))); + } + + @SuppressWarnings("unchecked") + @Test + public void testNestedAnd() { + Predicate<String> p = Predicates.<String>and( + ConfigConstraints.required(), + Predicates.and(Predicates.alwaysTrue()), + Predicates.<String>and(StringPredicates.matchesRegex(".*"))); + Assert.assertEquals(ConstraintSerialization.INSTANCE.toJsonList(p), + MutableList.of("required", MutableMap.of("regex", ".*"))); + } + + @Test + public void testAltName() { + Predicate<String> p = StringPredicates.matchesGlob("???*"); + Assert.assertEquals(ConstraintSerialization.INSTANCE.toPredicateFromJson( + MutableList.of(MutableMap.of("matchesGlob", "???*"))).toString(), p.toString()); + Assert.assertEquals(ConstraintSerialization.INSTANCE.toPredicateFromJson( + MutableList.of(MutableMap.of("glob", "???*"))).toString(), p.toString()); + Assert.assertEquals(ConstraintSerialization.INSTANCE.toJsonList(p), + MutableList.of(MutableMap.of("glob", "???*"))); + } + + @Test + public void testAltPred() { + Predicate<?> p = Predicates.notNull(); + Assert.assertEquals(ConstraintSerialization.INSTANCE.toJsonList(p), + MutableList.of("required")); + Assert.assertEquals(ConstraintSerialization.INSTANCE.toPredicateFromJson("required").toString(), + ConfigConstraints.required().toString()); + } + + private void assertPredJsonBidi(Predicate<?> pred, List<?> json) { + Assert.assertEquals(ConstraintSerialization.INSTANCE.toJsonList(pred), json); + // some predicates don't support equals, but all (the ones we use) must support toString + Assert.assertEquals(ConstraintSerialization.INSTANCE.toPredicateFromJson(json).toString(), pred.toString()); + } + +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/bd34655c/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/ConfigSummary.java ---------------------------------------------------------------------- diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/ConfigSummary.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/ConfigSummary.java index 49aa2fe..c6f1ec6 100644 --- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/ConfigSummary.java +++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/ConfigSummary.java @@ -28,17 +28,14 @@ import java.util.Objects; import javax.annotation.Nullable; import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.objs.ConstraintSerialization; import org.apache.brooklyn.util.collections.Jsonya; -import org.apache.brooklyn.util.core.flags.TypeCoercions; -import org.apache.brooklyn.util.javalang.coerce.TypeCoercer; -import org.apache.brooklyn.util.text.StringPredicates; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.google.common.base.Function; -import com.google.common.base.Predicates; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -64,7 +61,7 @@ public class ConfigSummary implements HasName, Serializable { @JsonInclude(Include.NON_NULL) private final Boolean pinned; @JsonInclude(Include.NON_NULL) - private final List<String> constraints; + private final List<Object> constraints; @JsonInclude(Include.NON_NULL) private final Map<String, URI> links; @@ -84,7 +81,7 @@ public class ConfigSummary implements HasName, Serializable { @JsonProperty("priority") Double priority, @JsonProperty("possibleValues") List<Map<String, String>> possibleValues, @JsonProperty("pinned") Boolean pinned, - @JsonProperty("constraints") List<String> constraints, + @JsonProperty("constraints") List<?> constraints, @JsonProperty("links") Map<String, URI> links) { this.name = name; this.type = type; @@ -95,7 +92,7 @@ public class ConfigSummary implements HasName, Serializable { this.priority = priority; this.possibleValues = possibleValues; this.pinned = pinned; - this.constraints = (constraints == null) ? ImmutableList.<String>of() : ImmutableList.copyOf(constraints); + this.constraints = (constraints == null) ? ImmutableList.<Object>of() : ImmutableList.copyOf(constraints); this.links = (links == null) ? ImmutableMap.<String, URI>of() : ImmutableMap.copyOf(links); } @@ -111,9 +108,7 @@ public class ConfigSummary implements HasName, Serializable { this.label = label; this.priority = priority; this.pinned = pinned; - this.constraints = !config.getConstraint().equals(Predicates.alwaysTrue()) - ? ImmutableList.of((config.getConstraint().getClass().equals(StringPredicates.isNonBlank().getClass()) ? "required" : config.getConstraint().toString())) - : ImmutableList.<String>of(); + this.constraints = ConstraintSerialization.INSTANCE.toJsonList(config); if (config.getType().isEnum()) { this.type = Enum.class.getName(); this.defaultValue = config.getDefaultValue() instanceof Enum? ((Enum<?>) config.getDefaultValue()).name() : @@ -175,7 +170,7 @@ public class ConfigSummary implements HasName, Serializable { return pinned; } - public List<String> getConstraints() { + public List<Object> getConstraints() { return constraints; } http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/bd34655c/utils/common/src/main/java/org/apache/brooklyn/util/text/StringPredicates.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/text/StringPredicates.java b/utils/common/src/main/java/org/apache/brooklyn/util/text/StringPredicates.java index 2006e65..b57b1db 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/text/StringPredicates.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/text/StringPredicates.java @@ -26,6 +26,7 @@ import java.util.Set; import javax.annotation.Nullable; import org.apache.brooklyn.util.collections.MutableSet; +import org.apache.brooklyn.util.text.StringEscapes.JavaStringEscapes; import com.google.common.base.Function; import com.google.common.base.Predicate; @@ -109,7 +110,7 @@ public class StringPredicates { @Override public String toString() { - return "containsLiteralCaseInsensitive("+fragment+")"; + return "containsLiteralCaseInsensitive("+JavaStringEscapes.wrapJavaString(fragment)+")"; } } @@ -131,7 +132,7 @@ public class StringPredicates { @Override public String toString() { - return "containsLiteral("+fragment+")"; + return "containsLiteral("+JavaStringEscapes.wrapJavaString(fragment)+")"; } } @@ -210,7 +211,7 @@ public class StringPredicates { } @Override public String toString() { - return "startsWith("+prefix+")"; + return "startsWith("+JavaStringEscapes.wrapJavaString(prefix)+")"; } } @@ -284,7 +285,7 @@ public class StringPredicates { } @Override public String toString() { - return "matchesRegex("+regex+")"; + return "matchesRegex("+JavaStringEscapes.wrapJavaString(regex)+")"; } } @@ -303,7 +304,7 @@ public class StringPredicates { } @Override public String toString() { - return "matchesGlob("+glob+")"; + return "matchesGlob("+JavaStringEscapes.wrapJavaString(glob)+")"; } } http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/bd34655c/utils/common/src/main/java/org/apache/brooklyn/util/text/Strings.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/text/Strings.java b/utils/common/src/main/java/org/apache/brooklyn/util/text/Strings.java index d7ffd4b..1eb1435 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/text/Strings.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/text/Strings.java @@ -193,7 +193,27 @@ public class Strings { } return string.substring(index); } - + + /** + * Removes everything after the marker, optionally also removing the marker. + * If marker not found the string is unchanged. + */ + public static String removeAfter(String string, String marker, boolean includeMarker) { + int i = string.indexOf(marker); + if (i==-1) return string; + return string.substring(0, i + (includeMarker ? marker.length() : 0)); + } + + /** + * Removes everything before the marker, optionally also removing the marker. + * If marker not found the string is unchanged. + */ + public static String removeBefore(String string, String marker, boolean includeMarker) { + int i = string.indexOf(marker); + if (i==-1) return string; + return string.substring(i + (includeMarker ? 0 : marker.length())); + } + /** convenience for {@link com.google.common.base.Joiner} */ public static String join(Iterable<? extends Object> list, String separator) { if (list==null) return null; http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/bd34655c/utils/common/src/test/java/org/apache/brooklyn/test/AssertsTest.java ---------------------------------------------------------------------- diff --git a/utils/common/src/test/java/org/apache/brooklyn/test/AssertsTest.java b/utils/common/src/test/java/org/apache/brooklyn/test/AssertsTest.java index 1105acd..96e31fd 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/test/AssertsTest.java +++ b/utils/common/src/test/java/org/apache/brooklyn/test/AssertsTest.java @@ -128,7 +128,7 @@ public class AssertsTest { try { Asserts.assertStringMatchesRegex("hello", "hello", "he"); Asserts.shouldHaveFailedPreviously(); - } catch (AssertionError e) { Asserts.expectedFailureContains(e, "hello", "matchesRegex(he)"); } + } catch (AssertionError e) { Asserts.expectedFailureContains(e, "hello", "matchesRegex(\"he\")"); } } @Test