Provide mixins/facade DSL methods Allows us to call custom DSL methods on any object we are interested in without the object knowing about DSL.
Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/b75b45c7 Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/b75b45c7 Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/b75b45c7 Branch: refs/heads/master Commit: b75b45c77aeb51ee8549ae0aab9f6bf040daa935 Parents: eb4ac4c Author: Svetoslav Neykov <svetoslav.ney...@cloudsoftcorp.com> Authored: Tue Dec 13 12:06:30 2016 +0200 Committer: Svetoslav Neykov <svetoslav.ney...@cloudsoftcorp.com> Committed: Tue Dec 13 12:06:30 2016 +0200 ---------------------------------------------------------------------- .../spi/dsl/BrooklynDslDeferredSupplier.java | 1 - .../spi/dsl/DslDeferredFunctionCall.java | 62 ++++++---- .../spi/dsl/methods/BrooklynDslCommon.java | 117 ++++++++++++++++++- .../brooklyn/spi/dsl/methods/DslComponent.java | 5 +- .../camp/brooklyn/spi/dsl/DslYamlTest.java | 72 +++++++++++- 5 files changed, 229 insertions(+), 28 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/b75b45c7/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/BrooklynDslDeferredSupplier.java ---------------------------------------------------------------------- diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/BrooklynDslDeferredSupplier.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/BrooklynDslDeferredSupplier.java index 86f1c82..4e3437d 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/BrooklynDslDeferredSupplier.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/BrooklynDslDeferredSupplier.java @@ -37,7 +37,6 @@ import org.apache.brooklyn.util.core.task.DeferredSupplier; import org.apache.brooklyn.util.core.task.ImmediateSupplier; 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; http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/b75b45c7/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslDeferredFunctionCall.java ---------------------------------------------------------------------- diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslDeferredFunctionCall.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslDeferredFunctionCall.java index 9835be6..ad8c4b1 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslDeferredFunctionCall.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslDeferredFunctionCall.java @@ -16,6 +16,7 @@ package org.apache.brooklyn.camp.brooklyn.spi.dsl; import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.List; import java.util.concurrent.Callable; @@ -30,6 +31,7 @@ import org.apache.brooklyn.util.javalang.Reflections; import com.google.common.base.Joiner; import com.google.common.base.Objects; +import com.google.common.collect.ImmutableList; public class DslDeferredFunctionCall extends BrooklynDslDeferredSupplier<Object> { @@ -114,31 +116,51 @@ public class DslDeferredFunctionCall extends BrooklynDslDeferredSupplier<Object> } protected static Object invokeOn(Object obj, String fnName, List<?> args) { - checkCallAllowed(obj, fnName, args); - - Maybe<Object> v; - try { - v = Reflections.invokeMethodFromArgs(obj, fnName, args); - } catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) { - Exceptions.propagateIfFatal(e); - throw Exceptions.propagate(new InvocationTargetException(e, "Error invoking '"+fnName+"("+toString(args)+")' on '"+obj+"'")); + Object instance = obj; + List<?> instanceArgs = args; + Maybe<Method> method = Reflections.getMethodFromArgs(instance, fnName, instanceArgs); + + if (method.isAbsent()) { + instance = BrooklynDslCommon.class; + instanceArgs = ImmutableList.builder().add(obj).addAll(args).build(); + method = Reflections.getMethodFromArgs(instance, fnName, instanceArgs); } - if (v.isPresent()) { - // Value is most likely another BrooklynDslDeferredSupplier - let the caller handle it, - return v.get(); + + if (method.isAbsent()) { + Maybe<?> facade; + try { + facade = Reflections.invokeMethodFromArgs(BrooklynDslCommon.DslFacades.class, "wrap", ImmutableList.of(obj)); + } catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) { + facade = Maybe.absent(); + } + + if (facade.isPresent()) { + instance = facade.get(); + instanceArgs = args; + method = Reflections.getMethodFromArgs(instance, fnName, instanceArgs); + } + } + + if (method.isPresent()) { + Method m = method.get(); + + checkCallAllowed(m); + + try { + // Value is most likely another BrooklynDslDeferredSupplier - let the caller handle it, + return Reflections.invokeMethodFromArgs(instance, m, instanceArgs); + } catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) { + throw Exceptions.propagate(new InvocationTargetException(e, "Error invoking '"+fnName+"("+toString(instanceArgs)+")' on '"+instance+"'")); + } } else { throw new IllegalArgumentException("No such function '"+fnName+"("+toString(args)+")' on "+obj); } } - private static void checkCallAllowed(Object obj, String fnName2, List<?> args2) { - Class<?> clazz; - if (obj instanceof Class) { - clazz = (Class<?>)obj; - } else { - clazz = obj.getClass(); - } - if (!(clazz.getPackage().getName().startsWith(BrooklynDslCommon.class.getPackage().getName()))) + private static void checkCallAllowed(Method m) { + Class<?> clazz = m.getDeclaringClass(); + if (clazz.getPackage() == null || // Proxy objects don't have a package + !(clazz.getPackage().getName().startsWith(BrooklynDslCommon.class.getPackage().getName()))) throw new IllegalArgumentException("Not permitted to invoke function on '"+clazz+"' (outside allowed package scope)"); } @@ -161,7 +183,7 @@ public class DslDeferredFunctionCall extends BrooklynDslDeferredSupplier<Object> public String toString() { return object + "." + fnName + "(" + toString(args) + ")"; } - + private static String toString(List<?> args) { if (args == null) return ""; return Joiner.on(", ").join(args); http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/b75b45c7/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 36b274c..bd2cbaf 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 @@ -19,6 +19,7 @@ package org.apache.brooklyn.camp.brooklyn.spi.dsl.methods; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; import static org.apache.brooklyn.camp.brooklyn.spi.dsl.DslUtils.resolved; import java.util.Arrays; @@ -27,8 +28,6 @@ import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; -import javax.annotation.Nullable; - import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.mgmt.ExecutionContext; import org.apache.brooklyn.api.mgmt.Task; @@ -39,6 +38,8 @@ 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.config.ConfigKey; +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; @@ -46,6 +47,8 @@ import org.apache.brooklyn.core.mgmt.BrooklynTaskTags; import org.apache.brooklyn.core.mgmt.internal.ExternalConfigSupplierRegistry; import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; import org.apache.brooklyn.core.mgmt.persist.DeserializingClassRenamesProvider; +import org.apache.brooklyn.core.objs.AbstractConfigurationSupportInternal; +import org.apache.brooklyn.core.objs.BrooklynObjectInternal; import org.apache.brooklyn.core.sensor.DependentConfiguration; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableMap; @@ -53,12 +56,12 @@ 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.ImmediateSupplier; import org.apache.brooklyn.util.core.task.Tasks; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.javalang.Reflections; import org.apache.brooklyn.util.text.StringEscapes.JavaStringEscapes; -import org.apache.brooklyn.util.text.StringFunctions.RegexReplacer; import org.apache.brooklyn.util.text.Strings; import org.apache.commons.beanutils.BeanUtils; import org.slf4j.Logger; @@ -70,7 +73,10 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Maps; -/** static import functions which can be used in `$brooklyn:xxx` contexts */ +/** + * static import functions which can be used in `$brooklyn:xxx` contexts + * WARNING: Don't overload methods - the DSL evaluator will pick any one that matches, not the best match. + */ public class BrooklynDslCommon { private static final Logger LOG = LoggerFactory.getLogger(BrooklynDslCommon.class); @@ -121,6 +127,73 @@ public class BrooklynDslCommon { return new DslComponent(Scope.THIS, "").config(keyName); } + public static BrooklynDslDeferredSupplier<?> config(BrooklynObjectInternal obj, String keyName) { + return new DslBrooklynObjectConfigSupplier(obj, keyName); + } + + public static class DslBrooklynObjectConfigSupplier extends BrooklynDslDeferredSupplier<Object> { + private static final long serialVersionUID = -2378555915585603381L; + + // Keep in mind this object gets serialized so is the following reference + private BrooklynObjectInternal obj; + private String keyName; + + public DslBrooklynObjectConfigSupplier(BrooklynObjectInternal obj, String keyName) { + checkNotNull(obj, "obj"); + checkNotNull(keyName, "keyName"); + + this.obj = obj; + this.keyName = keyName; + } + + @Override + public Maybe<Object> getImmediately() { + if (obj instanceof Entity) { + // Shouldn't worry too much about it since DSL can fetch objects from same app only. + // Just in case check whether it's same app for entities. + checkState(entity().getApplicationId().equals(((Entity)obj).getApplicationId())); + } + ConfigKey<Object> key = ConfigKeys.newConfigKey(Object.class, keyName); + Maybe<? extends Object> result = ((AbstractConfigurationSupportInternal)obj.config()).getNonBlocking(key); + return Maybe.<Object>cast(result); + } + + @Override + public Task<Object> newTask() { + return Tasks.builder() + .displayName("retrieving config for "+keyName+" on "+obj) + .tag(BrooklynTaskTags.TRANSIENT_TASK_TAG) + .dynamic(false) + .body(new Callable<Object>() { + @Override + public Object call() throws Exception { + ConfigKey<Object> key = ConfigKeys.newConfigKey(Object.class, keyName); + return obj.getConfig(key); + }}) + .build(); + } + + @Override + public int hashCode() { + return Objects.hashCode(obj, keyName); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + DslBrooklynObjectConfigSupplier that = DslBrooklynObjectConfigSupplier.class.cast(obj); + return Objects.equal(this.obj, that.obj) && + Objects.equal(this.keyName, that.keyName); + } + + @Override + public String toString() { + return (obj.toString()+".") + + "config("+JavaStringEscapes.wrapJavaString(keyName)+")"; + } + } + public static BrooklynDslDeferredSupplier<?> attributeWhenReady(String sensorName) { return new DslComponent(Scope.THIS, "").attributeWhenReady(sensorName); } @@ -684,4 +757,40 @@ public class BrooklynDslCommon { } } + // The results of the following methods are not supposed to get serialized. They are + // only intermediate values for the DSL evaluator to apply function calls on. There + // will always be a next method that gets executed on the return value. + public static class DslFacades { + private static class EntitySupplier implements DeferredSupplier<Entity>, ImmediateSupplier<Entity> { + private String entityId; + + public EntitySupplier(String entityId) { + this.entityId = entityId; + } + + @Override + public Maybe<Entity> getImmediately() { + EntityInternal entity = entity(); + if (entity == null) { + return Maybe.absent(); + } + Entity targetEntity = entity.getManagementContext().getEntityManager().getEntity(entityId); + return Maybe.of(targetEntity); + } + + @Override + public Entity get() { + return getImmediately().orNull(); + } + + private EntityInternal entity() { + return (EntityInternal) BrooklynTaskTags.getTargetOrContextEntity(Tasks.current()); + } + } + + public static Object wrap(Entity entity) { + return DslComponent.newInstance(Scope.GLOBAL, new EntitySupplier(entity.getId())); + } + } + } http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/b75b45c7/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 ea8e818..d6859fa 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 @@ -528,7 +528,10 @@ public class DslComponent extends BrooklynDslDeferredSupplier<Entity> implements "config("+JavaStringEscapes.wrapJavaString(keyName)+")"; } } - + + // TODO + // public BrooklynDslDeferredSupplier<?> relation(BrooklynObjectInternal obj, final String relationName) {...} + public BrooklynDslDeferredSupplier<Sensor<?>> sensor(final Object sensorIndicator) { return new DslSensorSupplier(this, sensorIndicator); } http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/b75b45c7/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslYamlTest.java ---------------------------------------------------------------------- diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslYamlTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslYamlTest.java index d266ed2..65fd66d 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslYamlTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslYamlTest.java @@ -21,6 +21,7 @@ import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.location.Location; import org.apache.brooklyn.api.mgmt.Task; import org.apache.brooklyn.api.sensor.AttributeSensor; import org.apache.brooklyn.camp.brooklyn.AbstractYamlTest; @@ -575,8 +576,22 @@ public class DslYamlTest extends AbstractYamlTest { assertEquals(getConfigEventually(app, DEST), Boolean.TRUE); } - @Test(groups="WIP") - public void testDeferredDslWrapsIntermediates() throws Exception { + @Test + public void testDeferredDslObjectAsFirstArgument() throws Exception { + final Entity app = createAndStartApplication( + "services:", + "- type: " + BasicApplication.class.getName(), + " location: localhost", + " brooklyn.config:", + " dest: $brooklyn:attributeWhenReady(\"targetValue\").config(\"spec.final\")"); + AttributeSensor<Location> targetValueSensor = Sensors.newSensor(Location.class, "targetValue"); + app.sensors().set(targetValueSensor, Iterables.getOnlyElement(app.getLocations())); + assertEquals(getConfigEventually(app, DEST), "localhost"); + } + + + @Test + public void testDeferredDslAttributeFacade() throws Exception { final Entity app = createAndStartApplication( "services:", "- type: " + BasicApplication.class.getName(), @@ -588,6 +603,59 @@ public class DslYamlTest extends AbstractYamlTest { } @Test + public void testDeferredDslConfigFacade() throws Exception { + final Entity app = createAndStartApplication( + "services:", + "- type: " + BasicApplication.class.getName(), + " brooklyn.config:", + " testValue: myvalue", + " targetEntity: $brooklyn:self()", + " dest: $brooklyn:config(\"targetEntity\").config(\"testValue\")"); + AttributeSensor<Entity> targetEntitySensor = Sensors.newSensor(Entity.class, "targetEntity"); + app.sensors().set(targetEntitySensor, app); + assertEquals(getConfigEventually(app, DEST), "myvalue"); + } + + @Test + public void testDeferredDslConfigFacadeCrossAppFails() throws Exception { + final Entity app0 = createAndStartApplication( + "services:", + "- type: " + BasicApplication.class.getName()); + final Entity app = createAndStartApplication( + "services:", + "- type: " + BasicApplication.class.getName(), + " brooklyn.config:", + " dest: $brooklyn:config(\"targetEntity\").attributeWhenReady(\"entity.id\")"); + app.config().set(ConfigKeys.newConfigKey(Entity.class, "targetEntity"), app0); + try { + getConfigEventually(app, DEST); + Asserts.shouldHaveFailedPreviously("Cross-app DSL not allowed"); + } catch (ExecutionException e) { + Asserts.expectedFailureContains(e, "not in scope 'global'"); + } + } + + @Test + public void testDeferredDslAttributeFacadeCrossAppFails() throws Exception { + final Entity app0 = createAndStartApplication( + "services:", + "- type: " + BasicApplication.class.getName()); + final Entity app = createAndStartApplication( + "services:", + "- type: " + BasicApplication.class.getName(), + " brooklyn.config:", + " dest: $brooklyn:attributeWhenReady(\"targetEntity\").attributeWhenReady(\"entity.id\")"); + AttributeSensor<Entity> targetEntitySensor = Sensors.newSensor(Entity.class, "targetEntity"); + app.sensors().set(targetEntitySensor, app0); + try { + getConfigEventually(app, DEST); + Asserts.shouldHaveFailedPreviously("Cross-app DSL not allowed"); + } catch (ExecutionException e) { + Asserts.expectedFailureContains(e, "not in scope 'global'"); + } + } + + @Test public void testDeferredDslChainingOnNullConfig() throws Exception { final Entity app = createAndStartApplication( "services:",