BROOKLYN-381: nested DSL support in $brooklyn:entity() For example, $brooklyn:entity(attributeWhenReady(âtargetIdâ))
Also tests this in the context of a TestHttpCall, in TestHttpCallTest Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/62a295cc Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/62a295cc Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/62a295cc Branch: refs/heads/master Commit: 62a295cc6967ed507f041365f91bd1fbfd377d8e Parents: d7b4459 Author: Aled Sage <aled.s...@gmail.com> Authored: Tue Nov 8 15:59:39 2016 +0000 Committer: Aled Sage <aled.s...@gmail.com> Committed: Fri Nov 11 09:30:22 2016 +0000 ---------------------------------------------------------------------- .../spi/dsl/methods/BrooklynDslCommon.java | 31 ++-- .../brooklyn/spi/dsl/methods/DslComponent.java | 175 ++++++++++++++++--- .../camp/brooklyn/DslAndRebindYamlTest.java | 74 ++++++++ .../framework/yaml/TestHttpCallYamlTest.java | 21 +++ 4 files changed, 258 insertions(+), 43 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/62a295cc/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/BrooklynDslCommon.java ---------------------------------------------------------------------- diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/BrooklynDslCommon.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/BrooklynDslCommon.java index 8fb48cf..1bf93d7 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/BrooklynDslCommon.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/BrooklynDslCommon.java @@ -39,7 +39,6 @@ import org.apache.brooklyn.camp.brooklyn.spi.creation.BrooklynYamlTypeInstantiat import org.apache.brooklyn.camp.brooklyn.spi.creation.EntitySpecConfiguration; import org.apache.brooklyn.camp.brooklyn.spi.dsl.BrooklynDslDeferredSupplier; import org.apache.brooklyn.camp.brooklyn.spi.dsl.methods.DslComponent.Scope; -import org.apache.brooklyn.core.config.ConfigKeys; import org.apache.brooklyn.core.config.external.ExternalConfigSupplier; import org.apache.brooklyn.core.entity.EntityDynamicType; import org.apache.brooklyn.core.entity.EntityInternal; @@ -53,8 +52,8 @@ import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.core.ClassLoaderUtils; import org.apache.brooklyn.util.core.config.ConfigBag; import org.apache.brooklyn.util.core.flags.FlagUtils; +import org.apache.brooklyn.util.core.task.DeferredSupplier; import org.apache.brooklyn.util.core.task.Tasks; -import org.apache.brooklyn.util.core.task.ValueResolver; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.javalang.Reflections; @@ -78,31 +77,31 @@ public class BrooklynDslCommon { // Access specific entities public static DslComponent self() { - return new DslComponent(Scope.THIS, null); + return new DslComponent(Scope.THIS); } - public static DslComponent entity(String id) { - return new DslComponent(Scope.GLOBAL, id); + public static DslComponent entity(Object id) { + return DslComponent.newInstance(Scope.GLOBAL, id); } public static DslComponent parent() { - return new DslComponent(Scope.PARENT, null); + return new DslComponent(Scope.PARENT); } - public static DslComponent child(String id) { - return new DslComponent(Scope.CHILD, id); + public static DslComponent child(Object id) { + return DslComponent.newInstance(Scope.CHILD, id); } - public static DslComponent sibling(String id) { - return new DslComponent(Scope.SIBLING, id); + public static DslComponent sibling(Object id) { + return DslComponent.newInstance(Scope.SIBLING, id); } - public static DslComponent descendant(String id) { - return new DslComponent(Scope.DESCENDANT, id); + public static DslComponent descendant(Object id) { + return DslComponent.newInstance(Scope.DESCENDANT, id); } - public static DslComponent ancestor(String id) { - return new DslComponent(Scope.ANCESTOR, id); + public static DslComponent ancestor(Object id) { + return DslComponent.newInstance(Scope.ANCESTOR, id); } public static DslComponent root() { - return new DslComponent(Scope.ROOT, null); + return new DslComponent(Scope.ROOT); } public static DslComponent scopeRoot() { - return new DslComponent(Scope.SCOPE_ROOT, null); + return new DslComponent(Scope.SCOPE_ROOT); } // prefer the syntax above to the below now, but not deprecating the below public static DslComponent component(String id) { http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/62a295cc/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/DslComponent.java ---------------------------------------------------------------------- diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/DslComponent.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/DslComponent.java index b2f20d3..1bd20eb 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/DslComponent.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/DslComponent.java @@ -18,6 +18,8 @@ */ package org.apache.brooklyn.camp.brooklyn.spi.dsl.methods; +import static org.apache.brooklyn.camp.brooklyn.spi.dsl.DslUtils.resolved; + import java.util.NoSuchElementException; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; @@ -38,6 +40,9 @@ import org.apache.brooklyn.core.mgmt.BrooklynTaskTags; import org.apache.brooklyn.core.mgmt.internal.EntityManagerInternal; import org.apache.brooklyn.core.sensor.DependentConfiguration; import org.apache.brooklyn.core.sensor.Sensors; +import org.apache.brooklyn.util.core.flags.TypeCoercions; +import org.apache.brooklyn.util.core.task.BasicExecutionContext; +import org.apache.brooklyn.util.core.task.DeferredSupplier; import org.apache.brooklyn.util.core.task.ImmediateSupplier; import org.apache.brooklyn.util.core.task.TaskBuilder; import org.apache.brooklyn.util.core.task.Tasks; @@ -45,6 +50,7 @@ import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.groovy.GroovyJavaMethods; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.text.StringEscapes.JavaStringEscapes; +import org.apache.brooklyn.util.text.Strings; import com.google.common.base.CaseFormat; import com.google.common.base.Converter; @@ -61,10 +67,39 @@ public class DslComponent extends BrooklynDslDeferredSupplier<Entity> { private static final long serialVersionUID = -7715984495268724954L; private final String componentId; + private final DeferredSupplier<?> componentIdSupplier; private final DslComponent scopeComponent; private final Scope scope; /** + * Checks the type of {@code componentId} to create the right kind of {@link DslComponent} + * (based on whether the componentId is already resolved. Accepts either a {@link String} or a + * {@link DeferredSupplier}. + */ + public static DslComponent newInstance(DslComponent scopeComponent, Scope scope, Object componentId) { + if (resolved(componentId)) { + // if all args are resolved, apply the componentId now + return new DslComponent(scopeComponent, scope, (String) componentId); + } else { + return new DslComponent(scopeComponent, scope, (DeferredSupplier<?>)componentId); + } + } + + /** + * Checks the type of {@code componentId} to create the right kind of {@link DslComponent} + * (based on whether the componentId is already resolved. Accepts either a {@link String} or a + * {@link DeferredSupplier}. + */ + public static DslComponent newInstance(Scope scope, Object componentId) { + if (resolved(componentId)) { + // if all args are resolved, apply the componentId now + return new DslComponent(scope, (String) componentId); + } else { + return new DslComponent(scope, (DeferredSupplier<?>)componentId); + } + } + + /** * Resolve componentId in the {@link Scope#GLOBAL} scope. * * @deprecated since 0.10.0; pass the {@link Scope} explicitly. @@ -75,6 +110,25 @@ public class DslComponent extends BrooklynDslDeferredSupplier<Entity> { } /** + * Resolve in scope relative to the current + * {@link BrooklynTaskTags#getTargetOrContextEntity) target or context} entity + * (where the scope defines an unambiguous relationship that will resolve to a single + * component - e.g. "parent"). + */ + public DslComponent(Scope scope) { + this(null, scope); + } + + /** + * Resolve in scope relative to {@code scopeComponent} entity + * (where the scope defines an unambiguous relationship that will resolve to a single + * component - e.g. "parent"). + */ + public DslComponent(DslComponent scopeComponent, Scope scope) { + this(scopeComponent, scope, (String)null); + } + + /** * Resolve componentId in scope relative to the current * {@link BrooklynTaskTags#getTargetOrContextEntity) target or context} entity. */ @@ -82,6 +136,10 @@ public class DslComponent extends BrooklynDslDeferredSupplier<Entity> { this(null, scope, componentId); } + public DslComponent(Scope scope, DeferredSupplier<?> componentIdSupplier) { + this(null, scope, componentIdSupplier); + } + /** * Resolve componentId in scope relative to scopeComponent. */ @@ -89,6 +147,18 @@ public class DslComponent extends BrooklynDslDeferredSupplier<Entity> { Preconditions.checkNotNull(scope, "scope"); this.scopeComponent = scopeComponent; this.componentId = componentId; + this.componentIdSupplier = null; + this.scope = scope; + } + + /** + * Resolve componentId in scope relative to scopeComponent. + */ + public DslComponent(DslComponent scopeComponent, Scope scope, DeferredSupplier<?> componentIdSupplier) { + Preconditions.checkNotNull(scope, "scope"); + this.scopeComponent = scopeComponent; + this.componentId = null; + this.componentIdSupplier = componentIdSupplier; this.scope = scope; } @@ -96,7 +166,7 @@ public class DslComponent extends BrooklynDslDeferredSupplier<Entity> { @Override public final Maybe<Entity> getImmediately() { - return new EntityInScopeFinder(scopeComponent, scope, componentId).getImmediately(); + return new EntityInScopeFinder(scopeComponent, scope, componentId, componentIdSupplier).getImmediately(); } @Override @@ -104,7 +174,7 @@ public class DslComponent extends BrooklynDslDeferredSupplier<Entity> { return TaskBuilder.<Entity>builder() .displayName(toString()) .tag(BrooklynTaskTags.TRANSIENT_TASK_TAG) - .body(new EntityInScopeFinder(scopeComponent, scope, componentId)) + .body(new EntityInScopeFinder(scopeComponent, scope, componentId, componentIdSupplier)) .build(); } @@ -112,11 +182,13 @@ public class DslComponent extends BrooklynDslDeferredSupplier<Entity> { protected final DslComponent scopeComponent; protected final Scope scope; protected final String componentId; - - public EntityInScopeFinder(DslComponent scopeComponent, Scope scope, String componentId) { + protected final DeferredSupplier<?> componentIdSupplier; + + public EntityInScopeFinder(DslComponent scopeComponent, Scope scope, String componentId, DeferredSupplier<?> componentIdSupplier) { this.scopeComponent = scopeComponent; this.scope = scope; this.componentId = componentId; + this.componentIdSupplier = componentIdSupplier; } @Override @@ -185,63 +257,112 @@ public class DslComponent extends BrooklynDslDeferredSupplier<Entity> { throw new IllegalStateException("Unexpected scope "+scope); } - Optional<Entity> result = Iterables.tryFind(entitiesToSearch, EntityPredicates.configEqualTo(BrooklynCampConstants.PLAN_ID, componentId)); + String desiredComponentId; + if (componentId == null) { + if (componentIdSupplier == null) { + throw new IllegalArgumentException("No component-id or component-id supplier, when resolving entity in scope '" + scope + "' wrt " + entity); + } + + Maybe<Object> maybeComponentId = Tasks.resolving(componentIdSupplier) + .as(Object.class) + .context(getExecutionContext()) + .immediately(immediate) + .description("Resolving component-id from " + componentIdSupplier) + .getMaybe(); + + if (immediate) { + if (maybeComponentId.isAbsent()) { + return Maybe.absent(Maybe.getException(maybeComponentId)); + } + } + + // Support being passes an explicit entity via the DSL + if (maybeComponentId.get() instanceof Entity) { + if (Iterables.contains(entitiesToSearch, maybeComponentId.get())) { + return Maybe.of((Entity)maybeComponentId.get()); + } else { + throw new IllegalStateException("Resolved component " + maybeComponentId.get() + " is not in scope '" + scope + "' wrt " + entity); + } + } + + desiredComponentId = TypeCoercions.coerce(maybeComponentId.get(), String.class); + + if (Strings.isBlank(desiredComponentId)) { + throw new IllegalStateException("component-id blank, from " + componentIdSupplier); + } + + } else { + desiredComponentId = componentId; + } + + Optional<Entity> result = Iterables.tryFind(entitiesToSearch, EntityPredicates.configEqualTo(BrooklynCampConstants.PLAN_ID, desiredComponentId)); if (result.isPresent()) { return Maybe.of(result.get()); } // TODO may want to block and repeat on new entities joining? - throw new NoSuchElementException("No entity matching id " + componentId+ + throw new NoSuchElementException("No entity matching id " + desiredComponentId+ (scope==Scope.GLOBAL ? "" : ", in scope "+scope+" wrt "+entity+ (scopeComponent!=null ? " ("+scopeComponent+" from "+entity()+")" : ""))); - } + } + + private ExecutionContext getExecutionContext() { + EntityInternal contextEntity = (EntityInternal) BrooklynTaskTags.getTargetOrContextEntity(Tasks.current()); + ExecutionContext execContext = + (contextEntity != null) ? contextEntity.getExecutionContext() + : BasicExecutionContext.getCurrentExecutionContext(); + if (execContext == null) { + throw new IllegalStateException("No execution context available to resolve " + toString()); + } + return execContext; + } } // ------------------------------- // DSL words which move to a new component - public DslComponent entity(String scopeOrId) { - return new DslComponent(this, Scope.GLOBAL, scopeOrId); + public DslComponent entity(Object id) { + return DslComponent.newInstance(this, Scope.GLOBAL, id); } - public DslComponent child(String scopeOrId) { - return new DslComponent(this, Scope.CHILD, scopeOrId); + public DslComponent child(Object id) { + return DslComponent.newInstance(this, Scope.CHILD, id); } - public DslComponent sibling(String scopeOrId) { - return new DslComponent(this, Scope.SIBLING, scopeOrId); + public DslComponent sibling(Object id) { + return DslComponent.newInstance(this, Scope.SIBLING, id); } - public DslComponent descendant(String scopeOrId) { - return new DslComponent(this, Scope.DESCENDANT, scopeOrId); + public DslComponent descendant(Object id) { + return DslComponent.newInstance(this, Scope.DESCENDANT, id); } - public DslComponent ancestor(String scopeOrId) { - return new DslComponent(this, Scope.ANCESTOR, scopeOrId); + public DslComponent ancestor(Object id) { + return DslComponent.newInstance(this, Scope.ANCESTOR, id); } public DslComponent root() { - return new DslComponent(this, Scope.ROOT, ""); + return new DslComponent(this, Scope.ROOT); } public DslComponent scopeRoot() { - return new DslComponent(this, Scope.SCOPE_ROOT, ""); + return new DslComponent(this, Scope.SCOPE_ROOT); } @Deprecated /** @deprecated since 0.7.0 */ - public DslComponent component(String scopeOrId) { - return new DslComponent(this, Scope.GLOBAL, scopeOrId); + public DslComponent component(Object id) { + return DslComponent.newInstance(this, Scope.GLOBAL, id); } public DslComponent self() { - return new DslComponent(this, Scope.THIS, null); + return new DslComponent(this, Scope.THIS); } public DslComponent parent() { - return new DslComponent(this, Scope.PARENT, ""); + return new DslComponent(this, Scope.PARENT); } - public DslComponent component(String scope, String id) { + public DslComponent component(String scope, Object id) { if (!DslComponent.Scope.isValid(scope)) { - throw new IllegalArgumentException(scope + " is not a vlaid scope"); + throw new IllegalArgumentException(scope + " is not a valid scope"); } - return new DslComponent(this, DslComponent.Scope.fromString(scope), id); + return DslComponent.newInstance(this, DslComponent.Scope.fromString(scope), id); } // DSL words which return things @@ -564,7 +685,7 @@ public class DslComponent extends BrooklynDslDeferredSupplier<Entity> { return "$brooklyn:entity("+ (scopeComponent==null ? "" : JavaStringEscapes.wrapJavaString(scopeComponent.toString())+", ")+ (scope==Scope.GLOBAL ? "" : JavaStringEscapes.wrapJavaString(scope.toString())+", ")+ - JavaStringEscapes.wrapJavaString(componentId)+ + (componentId != null ? JavaStringEscapes.wrapJavaString(componentId) : componentIdSupplier)+ ")"; } http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/62a295cc/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/DslAndRebindYamlTest.java ---------------------------------------------------------------------- diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/DslAndRebindYamlTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/DslAndRebindYamlTest.java index 16ff411..91da883 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/DslAndRebindYamlTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/DslAndRebindYamlTest.java @@ -45,7 +45,9 @@ import org.apache.brooklyn.core.mgmt.persist.BrooklynPersistenceUtils; import org.apache.brooklyn.core.sensor.Sensors; import org.apache.brooklyn.core.test.entity.TestEntity; import org.apache.brooklyn.entity.group.DynamicCluster; +import org.apache.brooklyn.test.Asserts; import org.apache.brooklyn.util.core.task.Tasks; +import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.guava.Maybe; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -432,6 +434,78 @@ public class DslAndRebindYamlTest extends AbstractYamlRebindTest { Assert.assertEquals(getConfigInTask(e2, TestEntity.CONF_NAME), "hello world"); } + @Test + public void testDslEntityById() throws Exception { + Entity testEntity = setupAndCheckTestEntityInBasicYamlWith( + " id: x", + " brooklyn.config:", + " test.confObject: $brooklyn:entity(\"x\")"); + Assert.assertEquals(getConfigInTask(testEntity, TestEntity.CONF_OBJECT), testEntity); + + Entity e2 = rebind(testEntity); + Assert.assertEquals(getConfigInTask(e2, TestEntity.CONF_OBJECT), e2); + } + + @Test + public void testDslEntityWhereIdRetrievedFromAttributeWhenReadyDsl() throws Exception { + Entity testEntity = setupAndCheckTestEntityInBasicYamlWith( + " id: x", + " brooklyn.config:", + " test.confObject: $brooklyn:entity(attributeWhenReady(\"mySensor\"))"); + testEntity.sensors().set(Sensors.newStringSensor("mySensor"), "x"); + Assert.assertEquals(getConfigInTask(testEntity, TestEntity.CONF_OBJECT), testEntity); + + Entity e2 = rebind(testEntity); + Assert.assertEquals(getConfigInTask(e2, TestEntity.CONF_OBJECT), e2); + } + + @Test + public void testDslEntityWhereAttributeWhenReadyDslReturnsEntity() throws Exception { + Entity testEntity = setupAndCheckTestEntityInBasicYamlWith( + " id: x", + " brooklyn.config:", + " test.confObject: $brooklyn:entity(attributeWhenReady(\"mySensor\"))"); + testEntity.sensors().set(Sensors.newSensor(Entity.class, "mySensor"), testEntity); + Assert.assertEquals(getConfigInTask(testEntity, TestEntity.CONF_OBJECT), testEntity); + + Entity e2 = rebind(testEntity); + Assert.assertEquals(getConfigInTask(e2, TestEntity.CONF_OBJECT), e2); + } + + @Test + public void testDslChildWhereIdRetrievedFromAttributeWhenReadyDsl() throws Exception { + Entity testEntity = setupAndCheckTestEntityInBasicYamlWith( + " id: x", + " brooklyn.config:", + " test.confObject: $brooklyn:child(attributeWhenReady(\"mySensor\"))", + " brooklyn.children:", + " - type: " + TestEntity.class.getName(), + " id: x"); + Entity childEntity = Iterables.getOnlyElement(testEntity.getChildren()); + testEntity.sensors().set(Sensors.newStringSensor("mySensor"), "x"); + Assert.assertEquals(getConfigInTask(testEntity, TestEntity.CONF_OBJECT), childEntity); + + Entity e2 = rebind(testEntity); + Entity child2 = Iterables.getOnlyElement(e2.getChildren()); + Assert.assertEquals(getConfigInTask(e2, TestEntity.CONF_OBJECT), child2); + } + + @Test + public void testDslChildWhereAttributeWhenReadyDslReturnsEntityOutOfScopeFails() throws Exception { + Entity testEntity = setupAndCheckTestEntityInBasicYamlWith( + " id: x", + " brooklyn.config:", + " test.confObject: $brooklyn:child(attributeWhenReady(\"mySensor\"))"); + testEntity.sensors().set(Sensors.newSensor(Entity.class, "mySensor"), testEntity); + try { + Object val = getConfigInTask(testEntity, TestEntity.CONF_OBJECT); + Asserts.shouldHaveFailedPreviously("actual="+val); + } catch (Exception e) { + IllegalStateException ise = Exceptions.getFirstThrowableOfType(e, IllegalStateException.class); + if (ise == null || !ise.toString().contains("is not in scope 'child'")) throw e; + } + } + /* - type: org.apache.brooklyn.enricher.stock.Transformer brooklyn.config: http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/62a295cc/test-framework/src/test/java/org/apache/brooklyn/test/framework/yaml/TestHttpCallYamlTest.java ---------------------------------------------------------------------- diff --git a/test-framework/src/test/java/org/apache/brooklyn/test/framework/yaml/TestHttpCallYamlTest.java b/test-framework/src/test/java/org/apache/brooklyn/test/framework/yaml/TestHttpCallYamlTest.java index 38a6328..671b2c4 100644 --- a/test-framework/src/test/java/org/apache/brooklyn/test/framework/yaml/TestHttpCallYamlTest.java +++ b/test-framework/src/test/java/org/apache/brooklyn/test/framework/yaml/TestHttpCallYamlTest.java @@ -90,4 +90,25 @@ public class TestHttpCallYamlTest extends AbstractYamlRebindTest { " equals: 200" ); } + + @Test + public void testUrlConstructedFromTargetEntity() throws Exception { + origApp = (BasicApplication) createStartWaitAndLogApplication( + "services:", + "- type: " + TestEntity.class.getName(), + " id: target-app", + " brooklyn.config:", + " main.uri: " + server.getUrl(), + "- type: " + TestHttpCall.class.getName(), + " brooklyn.config:", + " targetId: target-app", + " url:", + " $brooklyn:formatString:", + " - \"%s/index.html\"", + " - $brooklyn:entity(config(\"targetId\")).config(\"main.uri\")", + " applyAssertionTo: status", + " assert:", + " equals: 200" + ); + } }