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.
