This is an automated email from the ASF dual-hosted git repository.

heneveld pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/brooklyn-server.git

commit 7461003c72eea27bcb63f9766e7cabaad59a592f
Author: Alex Heneveld <[email protected]>
AuthorDate: Fri Aug 19 15:42:24 2022 +0100

    add jsonpath for predicates, and list filter, size
---
 .../util/core/predicates/DslPredicates.java        | 73 ++++++++++++++++++++--
 .../util/core/predicates/DslPredicateTest.java     | 24 +++++++
 2 files changed, 92 insertions(+), 5 deletions(-)

diff --git 
a/core/src/main/java/org/apache/brooklyn/util/core/predicates/DslPredicates.java
 
b/core/src/main/java/org/apache/brooklyn/util/core/predicates/DslPredicates.java
index 5d4ff03a39..d027292fd9 100644
--- 
a/core/src/main/java/org/apache/brooklyn/util/core/predicates/DslPredicates.java
+++ 
b/core/src/main/java/org/apache/brooklyn/util/core/predicates/DslPredicates.java
@@ -21,6 +21,7 @@ package org.apache.brooklyn.util.core.predicates;
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.*;
 import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
 import com.fasterxml.jackson.databind.util.TokenBuffer;
@@ -29,6 +30,7 @@ import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.reflect.TypeToken;
+import com.jayway.jsonpath.JsonPath;
 import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.objs.BrooklynObject;
 import org.apache.brooklyn.api.objs.Configurable;
@@ -37,6 +39,7 @@ import org.apache.brooklyn.core.config.ConfigKeys;
 import org.apache.brooklyn.core.entity.EntityInternal;
 import org.apache.brooklyn.core.location.Locations;
 import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
+import org.apache.brooklyn.core.resolve.jackson.BeanWithTypeUtils;
 import 
org.apache.brooklyn.core.resolve.jackson.BrooklynJacksonSerializationUtils;
 import 
org.apache.brooklyn.core.resolve.jackson.JsonSymbolDependentDeserializer;
 import org.apache.brooklyn.core.resolve.jackson.WrappedValue;
@@ -46,6 +49,7 @@ import org.apache.brooklyn.util.collections.MutableList;
 import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.core.flags.BrooklynTypeNameResolution;
 import org.apache.brooklyn.util.core.flags.TypeCoercions;
+import org.apache.brooklyn.util.core.json.BrooklynObjectsJsonMapper;
 import org.apache.brooklyn.util.core.task.DeferredSupplier;
 import org.apache.brooklyn.util.core.task.Tasks;
 import org.apache.brooklyn.util.core.task.ValueResolver;
@@ -194,6 +198,8 @@ public class DslPredicates {
         public List<DslPredicate> all;
 
         public @JsonProperty("has-element") DslPredicate hasElement;
+        public DslPredicate size;
+        public DslPredicate filter;
         public Object key;
         public Integer index;
         public String jsonpath;
@@ -268,6 +274,7 @@ public class DslPredicates {
 
             if (index != null) resolvers.put("index", (value) -> {
                 Integer i = index;
+                if (value instanceof Map) value = ((Map)value).entrySet();
                 if (value instanceof Iterable) {
                     int size = Iterables.size((Iterable) value);
                     if (i<0) {
@@ -279,6 +286,48 @@ public class DslPredicates {
                     return Maybe.absent("Cannot evaluate index on non-list 
target");
                 }
             });
+
+            if (filter != null) resolvers.put("filter", (value) -> {
+                if (value instanceof Map) value = ((Map)value).entrySet();
+                if (value instanceof Iterable) {
+                    return Maybe.of(Iterables.filter((Iterable) value, 
filter));
+                } else {
+                    return Maybe.absent("Cannot evaluate filter on non-list 
target");
+                }
+            });
+
+            if (jsonpath!=null) resolvers.put("jsonpath", (value) -> {
+                Entity entity = 
BrooklynTaskTags.getContextEntity(Tasks.current());
+                String json;
+                try {
+                    json = BeanWithTypeUtils.newMapper(entity!=null ? 
((EntityInternal)entity).getManagementContext() : null, false, null, 
false).writeValueAsString(value);
+                } catch (Exception e) {
+                    Exceptions.propagateIfFatal(e);
+                    if (LOG.isTraceEnabled()) LOG.trace("Unable to consider 
jsonpath for non-serializable '"+value+"' due to: "+e, e);
+                    return Maybe.absent("Cannot serialize object as JSON");
+                }
+
+                String jsonpathTidied = jsonpath;
+                if (jsonpathTidied!=null && !jsonpathTidied.startsWith("$")) {
+                    if (jsonpathTidied.startsWith("@") || 
jsonpathTidied.startsWith(".") || jsonpathTidied.startsWith("[")) {
+                        jsonpathTidied = "$" + jsonpathTidied;
+                    } else {
+                        jsonpathTidied = "$." + jsonpathTidied;
+                    }
+                }
+                Object result;
+                try {
+                    result = JsonPath.read(json, jsonpathTidied);
+                } catch (Exception e) {
+                    Exceptions.propagateIfFatal(e);
+                    if (LOG.isTraceEnabled()) LOG.trace("Unable to evaluate 
jsonpath '"+jsonpathTidied+"' for '"+value+"' due to: "+e, e);
+                    return Maybe.absent("Cannot evaluate jsonpath");
+                }
+
+                // above will throw if jsonpath doesn't match anything
+                // this will return single object possibly null, or a list 
possibly empty
+                return Maybe.ofAllowingNull(result);
+            });
         }
 
         protected Maybe<Object> resolveTargetAgainstInput(Object input) {
@@ -297,7 +346,9 @@ public class DslPredicates {
         public boolean applyToResolved(Maybe<Object> result) {
             CheckCounts counts = new CheckCounts();
             applyToResolved(result, counts);
-            if (counts.checksDefined==0) throw new 
IllegalStateException("Predicate does not define any checks; if always true or 
always false is desired, use 'when'");
+            if (counts.checksDefined==0) {
+                throw new IllegalStateException("Predicate does not define any 
checks; if always true or always false is desired, use 'when'");
+            }
             return counts.allPassed(true);
         }
 
@@ -332,8 +383,17 @@ public class DslPredicates {
                 }
                 return false;
             });
+            checker.check(size, result, (test,value) -> {
+                Integer computedSize = null;
+                if (value instanceof CharSequence) computedSize = 
((CharSequence)value).length();
+                else if (value instanceof Map) computedSize = 
((Map)value).size();
+                else if (value instanceof Iterable) computedSize = 
Iterables.size((Iterable)value);
+                else return nestedPredicateCheck(test, Maybe.absent("size not 
applicable"));
+
+                return nestedPredicateCheck(test, Maybe.of(computedSize));
+            });
 
-            checker.checkTest(check, test -> nestedPredicateCheck(check, 
result));
+            checker.checkTest(check, test -> nestedPredicateCheck(test, 
result));
             checker.checkTest(any, test -> test.stream().anyMatch(p -> 
nestedPredicateCheck(p, result)));
             checker.checkTest(all, test -> test.stream().allMatch(p -> 
nestedPredicateCheck(p, result)));
 
@@ -394,7 +454,10 @@ public class DslPredicates {
     @Beta
     public static class DslPredicateDefault<T2> extends DslPredicateBase<T2> 
implements DslPredicate<T2> {
         public DslPredicateDefault() {}
+
+        // allow a string or int to be an implicit equality target
         public DslPredicateDefault(String implicitEquals) { 
this.implicitEquals = implicitEquals; }
+        public DslPredicateDefault(Integer implicitEquals) { 
this.implicitEquals = implicitEquals; }
 
         public Object target;
 
@@ -447,7 +510,10 @@ public class DslPredicates {
 
         protected Maybe<Object> resolveTargetStringAgainstInput(String target, 
Object input) {
             if ("location".equals(target) && input instanceof Entity) return 
Maybe.of( Locations.getLocationsCheckingAncestors(null, (Entity)input) );
+            if ("locations".equals(target) && input instanceof Entity) return 
Maybe.of( Locations.getLocationsCheckingAncestors(null, (Entity)input) );
             if ("children".equals(target) && input instanceof Entity) return 
Maybe.of( ((Entity)input).getChildren() );
+            if ("tags".equals(target) && input instanceof BrooklynObject) 
return Maybe.of( ((BrooklynObject)input).tags().getTags() );
+
             return Maybe.absent("Unsupported target '"+target+"' on input 
"+input);
         }
 
@@ -468,9 +534,6 @@ public class DslPredicates {
         public boolean checkTag(DslPredicate tagCheck, Object value) {
             if (value instanceof BrooklynObject) return ((BrooklynObject) 
value).tags().getTags().stream().anyMatch(tag);
 
-            // not needed, caller iterates
-            //if (value instanceof Iterable) return MutableList.of((Iterable) 
value).stream().anyMatch(vv -> checkTag(tagCheck, vv));
-
             return false;
         }
 
diff --git 
a/core/src/test/java/org/apache/brooklyn/util/core/predicates/DslPredicateTest.java
 
b/core/src/test/java/org/apache/brooklyn/util/core/predicates/DslPredicateTest.java
index 779970b50a..957c3e23cf 100644
--- 
a/core/src/test/java/org/apache/brooklyn/util/core/predicates/DslPredicateTest.java
+++ 
b/core/src/test/java/org/apache/brooklyn/util/core/predicates/DslPredicateTest.java
@@ -272,6 +272,30 @@ public class DslPredicateTest extends 
BrooklynMgmtUnitTestSupport {
         Asserts.assertFalse(p.test(MutableList.of("Astrid", 
MutableMap.of("name", "Carver"))));
     }
 
+    @Test
+    public void testJsonpath() {
+        DslPredicates.DslPredicate p = 
TypeCoercions.coerce(MutableMap.of("jsonpath", "name", "regex", "[Bb].*"), 
DslPredicates.DslPredicate.class);
+        Asserts.assertTrue(p.test(MutableMap.of("id", 123, "name", "Bob")));
+        Asserts.assertFalse(p.test(MutableMap.of("id", 124, "name", 
"Astrid")));
+        Asserts.assertFalse(p.test(MutableMap.of("id", 0)));
+        Asserts.assertFalse(p.test(MutableList.of("id", 0)));
+        Asserts.assertFalse(p.test("not json"));
+
+        p = TypeCoercions.coerce(MutableMap.of("jsonpath", "$.[*].name", 
"has-element", MutableMap.of("regex", "[Bb].*")), 
DslPredicates.DslPredicate.class);
+        Asserts.assertTrue(p.test(MutableList.of(MutableMap.of("id", 123, 
"name", "Bob"), MutableMap.of("id", 124, "name", "Astrid"))));
+        Asserts.assertFalse(p.test(MutableList.of(MutableMap.of("id", 125), 
MutableMap.of("id", 124, "name", "Astrid"))));
+        Asserts.assertFalse(p.test(MutableList.of(MutableMap.of("id", 125))));
+
+        p = TypeCoercions.coerce(MutableMap.of("jsonpath", "[*].name",
+                "check",
+                    MutableMap.of("filter", MutableMap.of("regex", "[Bb].*"),
+                        "size", 1))
+                , DslPredicates.DslPredicate.class);
+        Asserts.assertTrue(p.test(MutableList.of(MutableMap.of("id", 123, 
"name", "Bob"), MutableMap.of("id", 124, "name", "Astrid"))));
+        Asserts.assertFalse(p.test(MutableList.of(MutableMap.of("id", 125), 
MutableMap.of("id", 124, "name", "Astrid"))));
+        Asserts.assertFalse(p.test(MutableList.of(MutableMap.of("id", 125))));
+    }
+
     @Test
     public void testLocationTagImplicitEquals() {
         DslPredicates.DslPredicate p = TypeCoercions.coerce(MutableMap.of(

Reply via email to