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:",

Reply via email to