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

Reply via email to