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 e22c7325a39bfbb8d1ca7872dbfb7915cdaad913
Author: Alex Heneveld <[email protected]>
AuthorDate: Mon May 1 12:54:17 2023 +0100

    add support for members and looking up identifiable things by id in 
freemarker
---
 .../brooklyn/util/core/text/TemplateProcessor.java | 70 ++++++++++++++++++----
 .../util/core/text/TemplateProcessorTest.java      | 39 +++++++++---
 2 files changed, 88 insertions(+), 21 deletions(-)

diff --git 
a/core/src/main/java/org/apache/brooklyn/util/core/text/TemplateProcessor.java 
b/core/src/main/java/org/apache/brooklyn/util/core/text/TemplateProcessor.java
index 066489c3d1..0892ff789d 100644
--- 
a/core/src/main/java/org/apache/brooklyn/util/core/text/TemplateProcessor.java
+++ 
b/core/src/main/java/org/apache/brooklyn/util/core/text/TemplateProcessor.java
@@ -29,13 +29,17 @@ import freemarker.core.TemplateElement;
 import freemarker.core._CoreAPI;
 import freemarker.template.*;
 import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.Group;
 import org.apache.brooklyn.api.entity.drivers.EntityDriver;
 import org.apache.brooklyn.api.location.Location;
 import org.apache.brooklyn.api.mgmt.ManagementContext;
 import org.apache.brooklyn.api.objs.BrooklynObject;
+import org.apache.brooklyn.api.objs.Identifiable;
 import org.apache.brooklyn.api.sensor.AttributeSensor;
 import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
 import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.entity.EntityAsserts;
 import org.apache.brooklyn.core.entity.EntityInternal;
 import org.apache.brooklyn.core.location.internal.LocationInternal;
 import org.apache.brooklyn.core.sensor.DependentConfiguration;
@@ -55,10 +59,7 @@ import org.slf4j.LoggerFactory;
 import java.io.*;
 import java.lang.reflect.Method;
 import java.time.Instant;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Date;
-import java.util.Map;
+import java.util.*;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
@@ -145,22 +146,36 @@ public class TemplateProcessor {
         }
 
         @Override
-        public TemplateModel wrap(Object o) throws TemplateModelException {
-            if (o instanceof TemplateModel) {
-                return (TemplateModel) o;
+        public TemplateModel wrap(Object obj) throws TemplateModelException {
+            if (obj == null) {
+                return super.wrap(null);
+            }
+
+            if (obj instanceof TemplateModel) {
+                return (TemplateModel) obj;
             }
 
-            if (o instanceof Map) {
+            if (obj instanceof Map) {
                 // use our map recursively, so a map with `a.b` as a single 
key can be referenced as` ${a.b}` in the freemarker template
-                return new DotSplittingTemplateModel((Map<?,?>)o);
+                return new DotSplittingTemplateModel((Map<?,?>)obj);
             }
 
-            if (o instanceof Instant) {
+            if (obj instanceof Instant) {
                 // Freemarker doesn't support Instant, so we add
-                return super.wrap(Date.from( (Instant)o ));
+                return super.wrap(Date.from( (Instant)obj ));
+            }
+
+            Object objOrig = obj;
+            if (obj.getClass().isArray()) {
+                obj = convertArray(obj);
+            }
+            if (obj instanceof Collection) {
+                if (!((Collection<?>) obj).isEmpty() && ((Collection<?>) 
obj).stream().allMatch(x -> x instanceof Identifiable)) {
+                }
+                return new SimpleSequenceWithLookup((Collection<?>) obj, this);
             }
 
-            return super.wrap(o);
+            return super.wrap(objOrig);
         }
 
         @Override
@@ -196,6 +211,35 @@ public class TemplateProcessor {
 
     }
 
+    static class SimpleSequenceWithLookup extends SimpleSequence implements 
TemplateHashModel {
+        SimpleSequenceWithLookup(Collection<?> collection, ObjectWrapper 
wrapper) {
+            super(collection, wrapper);
+        }
+
+        protected Object findKey(String key) {
+            for (Object l: list) {
+                if (l instanceof Entity) {
+                    String planId = ((Entity) 
l).config().get(BrooklynConfigKeys.PLAN_ID);
+                    if (Strings.isNonEmpty(planId) && Objects.equals(planId, 
key)) return l;
+                }
+                if (l instanceof Identifiable && 
Objects.equals(((Identifiable)l).getId(), key)) return l;
+            }
+            return null;
+        }
+
+        @Override
+        public TemplateModel get(String key) throws TemplateModelException {
+            Object match = findKey(key);
+            if (match==null) return null;
+            return getObjectWrapper().wrap(match);
+        }
+
+        @Override
+        public boolean isEmpty() throws TemplateModelException {
+            return false;
+        }
+    }
+
     public static TemplateModel wrapAsTemplateModel(Object o) throws 
TemplateModelException {
         return BROOKLYN_WRAPPER.wrap(o);
     }
@@ -701,6 +745,8 @@ public class TemplateProcessor {
             }
             if ("children".equals(key) && entity!=null)
                 return wrapAsTemplateModel( entity.getChildren() );
+            if ("members".equals(key) && entity!=null)
+                return wrapAsTemplateModel( entity instanceof Group ? 
((Group)entity).getMembers() : MutableList.of() );
             if ("sensor".equals(key)) return new 
EntityAttributeTemplateModel(entity, 
EntityAttributeTemplateModel.SensorResolutionMode.ATTRIBUTE_VALUE);
             if ("attribute".equals(key)) return new 
EntityAttributeTemplateModel(entity, 
EntityAttributeTemplateModel.SensorResolutionMode.ATTRIBUTE_VALUE);
             if ("attributeWhenReady".equals(key)) return new 
EntityAttributeTemplateModel(entity, 
EntityAttributeTemplateModel.SensorResolutionMode.ATTRIBUTE_WHEN_READY);
diff --git 
a/core/src/test/java/org/apache/brooklyn/util/core/text/TemplateProcessorTest.java
 
b/core/src/test/java/org/apache/brooklyn/util/core/text/TemplateProcessorTest.java
index 369e086888..a7dc0c7a12 100644
--- 
a/core/src/test/java/org/apache/brooklyn/util/core/text/TemplateProcessorTest.java
+++ 
b/core/src/test/java/org/apache/brooklyn/util/core/text/TemplateProcessorTest.java
@@ -18,13 +18,16 @@
  */
 package org.apache.brooklyn.util.core.text;
 
-import static org.testng.Assert.assertEquals;
-
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
+import freemarker.core.InvalidReferenceException;
 import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.entity.Group;
 import org.apache.brooklyn.api.location.LocationSpec;
 import org.apache.brooklyn.api.sensor.AttributeSensor;
 import org.apache.brooklyn.core.entity.Attributes;
+import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
 import org.apache.brooklyn.core.entity.EntityInternal;
 import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
 import org.apache.brooklyn.core.sensor.DependentConfiguration;
@@ -32,16 +35,18 @@ import org.apache.brooklyn.core.sensor.Sensors;
 import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
 import org.apache.brooklyn.core.test.entity.TestApplication;
 import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.apache.brooklyn.entity.group.BasicGroup;
 import org.apache.brooklyn.entity.group.DynamicCluster;
 import 
org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation;
+import org.apache.brooklyn.test.Asserts;
 import org.apache.brooklyn.test.FixedLocaleTest;
+import org.apache.brooklyn.util.collections.MutableList;
 import org.testng.Assert;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
-import com.google.common.base.Joiner;
-import com.google.common.collect.ImmutableMap;
+import static org.testng.Assert.assertEquals;
 
 public class TemplateProcessorTest extends BrooklynAppUnitTestSupport {
     private FixedLocaleTest localeFix = new FixedLocaleTest();
@@ -187,11 +192,27 @@ public class TemplateProcessorTest extends 
BrooklynAppUnitTestSupport {
     }
 
     @Test
-    public void testEntityChildren() {
-        String templateContents = "${entity.children[0].id}";
-        app.createAndManageChild( EntitySpec.create(TestApplication.class));
-        String result = 
TemplateProcessor.processTemplateContents(templateContents, app, 
ImmutableMap.<String,Object>of());
-        assertEquals(result, 
Iterables.getOnlyElement(app.getChildren()).getId());
+    public void testEntityChildrenAndMembers() {
+        TestEntity child = 
app.createAndManageChild(EntitySpec.create(TestEntity.class).configure(BrooklynConfigKeys.PLAN_ID,
 "my_child"));
+
+        
assertEquals(TemplateProcessor.processTemplateContents("${entity.children[0].id}",
 app, null), child.getId());
+        
assertEquals(TemplateProcessor.processTemplateContents("${entity.children[\"my_child\"].id}",
 app, null), child.getId());
+
+        Asserts.assertFailsWith(() -> 
TemplateProcessor.processTemplateContents("${entity.children[\"no_such_child\"].id}",
 app, null),
+            error -> Asserts.expectedFailureContains(error, 
"children[\"no_such_child\"] ", 
InvalidReferenceException.class.getSimpleName()));
+
+        assertEquals(TemplateProcessor.processTemplateContents("test", 
"${entity.children}", 
TemplateProcessor.EntityAndMapTemplateModel.forEntity(app, null), true, false),
+                MutableList.of(child));
+        assertEquals(TemplateProcessor.processTemplateContents("test", 
"${entity.children[0]}", 
TemplateProcessor.EntityAndMapTemplateModel.forEntity(app, null), true, false),
+                child);
+        assertEquals(TemplateProcessor.processTemplateContents("test", 
"${entity.children[\"my_child\"]}", 
TemplateProcessor.EntityAndMapTemplateModel.forEntity(app, null), true, false),
+                child);
+
+        Group group = 
app.createAndManageChild(EntitySpec.create(BasicGroup.class));
+        group.addMember(child);
+
+        
assertEquals(TemplateProcessor.processTemplateContents("${entity.members[0].id}",
 (EntityInternal) group, null), child.getId());
+        
assertEquals(TemplateProcessor.processTemplateContents("${entity.members[\"my_child\"].id}",
 (EntityInternal) group, null), child.getId());
     }
 
     // Test takes 2.5s.

Reply via email to