You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2016/02/01 18:50:02 UTC

[05/50] [abbrv] brooklyn-server git commit: improve ValueResolver task+timeout, and related work on tasks

improve ValueResolver task+timeout, and related work on tasks

ValueResolver will now look if it is already in a task when computing the execution context,
so more things which need a context and/or have timeouts can be handled correctly --
esp when $brooklyn:config is passed from yaml to various places

More tasks are marked transient so don't clutter the GUI.

Cancellations are treated better, with fewer errors.

Also shell env vars where values are null are now treated as "" rather than "null",
and a few other string handling goodies


Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo
Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/ea8da379
Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/ea8da379
Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/ea8da379

Branch: refs/heads/0.7.0-incubating
Commit: ea8da3793d49a7a147cc1f8f9711bd899caad3b1
Parents: 53b820e
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Thu Jun 18 08:56:17 2015 -0700
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Wed Jun 24 00:40:32 2015 -0700

----------------------------------------------------------------------
 api/src/main/java/brooklyn/event/Sensor.java    |  4 +-
 .../main/java/brooklyn/enricher/Enrichers.java  |  5 +-
 .../internal/LocalSubscriptionManager.java      |  4 +-
 .../java/brooklyn/util/flags/TypeCoercions.java |  2 +-
 .../java/brooklyn/util/task/ValueResolver.java  | 65 +++++++++++++++-----
 .../util/internal/TypeCoercionsTest.java        |  2 +-
 .../policy/ha/AbstractFailureDetector.java      |  4 +-
 .../brooklyn/policy/ha/ServiceRestarter.java    |  3 +-
 .../policy/ha/SshMachineFailureDetector.java    |  1 -
 .../basic/AbstractSoftwareProcessSshDriver.java |  2 +-
 .../brooklyn/entity/software/StaticSensor.java  | 19 +++++-
 .../entity/software/ssh/SshCommandEffector.java |  2 +-
 .../entity/software/ssh/SshCommandSensor.java   |  2 +-
 .../spi/dsl/methods/BrooklynDslCommon.java      | 31 +++++-----
 .../brooklyn/spi/dsl/methods/DslComponent.java  | 25 +++++---
 .../java/brooklyn/util/text/StringEscapes.java  | 20 ++++--
 .../main/java/brooklyn/util/text/Strings.java   | 28 ++++++++-
 .../main/java/brooklyn/util/time/Durations.java |  3 +
 18 files changed, 159 insertions(+), 63 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/ea8da379/api/src/main/java/brooklyn/event/Sensor.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/event/Sensor.java b/api/src/main/java/brooklyn/event/Sensor.java
index 88b0d13..2057020 100644
--- a/api/src/main/java/brooklyn/event/Sensor.java
+++ b/api/src/main/java/brooklyn/event/Sensor.java
@@ -39,8 +39,8 @@ public interface Sensor<T> extends Serializable {
      * <p>
      * This returns a "super" of T only in the case where T is generified, 
      * and in such cases it returns the Class instance for the unadorned T ---
-     * i.e. for List<String> this returns Class<List> ---
-     * this is of course because there is no actual Class<List<String>> instance.
+     * i.e. for List&lt;String&gt; this returns Class<List> ---
+     * this is of course because there is no actual Class&lt;List&lt;String&gt;&gt; instance.
      */
     Class<? super T> getType();
     

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/ea8da379/core/src/main/java/brooklyn/enricher/Enrichers.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/enricher/Enrichers.java b/core/src/main/java/brooklyn/enricher/Enrichers.java
index d036b11..b34fc76 100644
--- a/core/src/main/java/brooklyn/enricher/Enrichers.java
+++ b/core/src/main/java/brooklyn/enricher/Enrichers.java
@@ -157,7 +157,8 @@ public class Enrichers {
             return new CombinerBuilder<S, Object>(vals);
         }
         /** as {@link #combining(Collection)} */
-        public <S> CombinerBuilder<S, Object> combining(AttributeSensor<? extends S>... vals) {
+        @SafeVarargs
+        public final <S> CombinerBuilder<S, Object> combining(AttributeSensor<? extends S>... vals) {
             return new CombinerBuilder<S, Object>(vals);
         }
         /** as {@link #combining(Collection)} but the collection of values comes from the given sensor on multiple entities */
@@ -351,6 +352,7 @@ public class Enrichers {
         // For summing/averaging
         protected Object defaultValueForUnreportedSensors;
         
+        @SafeVarargs
         public AbstractCombinerBuilder(AttributeSensor<? extends S>... vals) {
             this(ImmutableList.copyOf(vals));
         }
@@ -706,6 +708,7 @@ public class Enrichers {
     }
 
     public static class CombinerBuilder<S, T> extends AbstractCombinerBuilder<S, T, CombinerBuilder<S, T>> {
+        @SafeVarargs
         public CombinerBuilder(AttributeSensor<? extends S>... vals) {
             super(vals);
         }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/ea8da379/core/src/main/java/brooklyn/management/internal/LocalSubscriptionManager.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/LocalSubscriptionManager.java b/core/src/main/java/brooklyn/management/internal/LocalSubscriptionManager.java
index 805bf85..7121fc3 100644
--- a/core/src/main/java/brooklyn/management/internal/LocalSubscriptionManager.java
+++ b/core/src/main/java/brooklyn/management/internal/LocalSubscriptionManager.java
@@ -200,9 +200,9 @@ public class LocalSubscriptionManager extends AbstractSubscriptionManager {
                             sAtClosureCreation.listener.onEvent(event);
                         } catch (Throwable t) {
                             if (event!=null && event.getSource()!=null && Entities.isNoLongerManaged(event.getSource())) {
-                                LOG.debug("Error in "+this+", after entity unmanaged: "+t, t);
+                                LOG.debug("Error processing subscriptions to "+this+", after entity unmanaged: "+t, t);
                             } else {
-                                LOG.warn("Error in "+this+": "+t, t);
+                                LOG.warn("Error processing subscriptions to "+this+": "+t, t);
                             }
                         }
                     }});

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/ea8da379/core/src/main/java/brooklyn/util/flags/TypeCoercions.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/util/flags/TypeCoercions.java b/core/src/main/java/brooklyn/util/flags/TypeCoercions.java
index 67011b2..55649e8 100644
--- a/core/src/main/java/brooklyn/util/flags/TypeCoercions.java
+++ b/core/src/main/java/brooklyn/util/flags/TypeCoercions.java
@@ -430,7 +430,7 @@ public class TypeCoercions {
         try {
             return (T) wrappedType.getMethod("valueOf", String.class).invoke(null, value);
         } catch (Exception e) {
-            ClassCoercionException tothrow = new ClassCoercionException("Cannot coerce type String to "+targetType.getCanonicalName()+" ("+value+"): adapting failed");
+            ClassCoercionException tothrow = new ClassCoercionException("Cannot coerce "+JavaStringEscapes.wrapJavaString(value)+" to "+targetType.getCanonicalName()+" ("+value+"): adapting failed");
             tothrow.initCause(e);
             throw tothrow;
         }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/ea8da379/core/src/main/java/brooklyn/util/task/ValueResolver.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/util/task/ValueResolver.java b/core/src/main/java/brooklyn/util/task/ValueResolver.java
index 3a15bbd..71b34a0 100644
--- a/core/src/main/java/brooklyn/util/task/ValueResolver.java
+++ b/core/src/main/java/brooklyn/util/task/ValueResolver.java
@@ -29,6 +29,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import brooklyn.entity.Entity;
+import brooklyn.entity.basic.BrooklynTaskTags;
 import brooklyn.entity.basic.EntityInternal;
 import brooklyn.management.ExecutionContext;
 import brooklyn.management.Task;
@@ -36,6 +37,7 @@ import brooklyn.management.TaskAdaptable;
 import brooklyn.util.exceptions.Exceptions;
 import brooklyn.util.flags.TypeCoercions;
 import brooklyn.util.guava.Maybe;
+import brooklyn.util.javalang.JavaClassNames;
 import brooklyn.util.time.CountdownTimer;
 import brooklyn.util.time.Duration;
 import brooklyn.util.time.Durations;
@@ -66,6 +68,7 @@ public class ValueResolver<T> implements DeferredSupplier<T> {
     Boolean embedResolutionInTask;
     /** timeout on execution, if possible, or if embedResolutionInTask is true */
     Duration timeout;
+    boolean isTransientTask = true;
     
     T defaultValue = null;
     boolean returnDefaultOnGet = false;
@@ -170,6 +173,12 @@ public class ValueResolver<T> implements DeferredSupplier<T> {
         return this;
     }
     
+    /** whether the task should be marked as transient; defaults true */
+    public ValueResolver<T> transientTask(boolean isTransientTask) {
+        this.isTransientTask = isTransientTask;
+        return this;
+    }
+    
     public Maybe<T> getDefault() {
         if (returnDefaultOnGet) return Maybe.of(defaultValue);
         else return Maybe.absent("No default value set");
@@ -213,13 +222,27 @@ public class ValueResolver<T> implements DeferredSupplier<T> {
         return m.get();
     }
     
-    @SuppressWarnings({ "unchecked", "rawtypes" })
     public Maybe<T> getMaybe() {
+        Maybe<T> result = getMaybeInternal();
+        if (log.isTraceEnabled()) {
+            log.trace(this+" evaluated as "+result);
+        }
+        return result;
+    }
+    
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    protected Maybe<T> getMaybeInternal() {
         if (started.getAndSet(true))
             throw new IllegalStateException("ValueResolver can only be used once");
         
         if (expired) return Maybe.absent("Nested resolution of "+getOriginalValue()+" did not complete within "+timeout);
         
+        ExecutionContext exec = this.exec;
+        if (exec==null) {
+            // if execution context not specified, take it from the current task if present
+            exec = BasicExecutionContext.getCurrentExecutionContext();
+        }
+        
         CountdownTimer timerU = parentTimer;
         if (timerU==null && timeout!=null)
             timerU = timeout.countdownTimer();
@@ -240,7 +263,6 @@ public class ValueResolver<T> implements DeferredSupplier<T> {
             if (v instanceof TaskAdaptable<?>) {
                 //if it's a task, we make sure it is submitted
                 if (!((TaskAdaptable<?>) v).asTask().isSubmitted() ) {
-                    // TODO could try to get exec context from Tasks.current() ... should we?
                     if (exec==null)
                         return Maybe.absent("Value for unsubmitted task '"+getDescription()+"' requested but no execution context available");
                     exec.submit(((TaskAdaptable<?>) v).asTask());
@@ -269,22 +291,24 @@ public class ValueResolver<T> implements DeferredSupplier<T> {
 
             } else if (v instanceof DeferredSupplier<?>) {
                 final Object vf = v;
-                Callable<Object> callable = new Callable<Object>() {
-                    public Object call() throws Exception {
-                        try {
-                            Tasks.setBlockingDetails("Retrieving "+vf);
-                            return ((DeferredSupplier<?>) vf).get();
-                        } finally {
-                            Tasks.resetBlockingDetails();
-                        }
-                    } };
-                    
-                if (Boolean.TRUE.equals(embedResolutionInTask) || timeout!=null) {
+
+                if ((!Boolean.FALSE.equals(embedResolutionInTask) && (exec!=null || timeout!=null)) || Boolean.TRUE.equals(embedResolutionInTask)) {
                     if (exec==null)
                         return Maybe.absent("Embedding in task needed for '"+getDescription()+"' but no execution context available");
                         
+                    Callable<Object> callable = new Callable<Object>() {
+                        public Object call() throws Exception {
+                            try {
+                                Tasks.setBlockingDetails("Retrieving "+vf);
+                                return ((DeferredSupplier<?>) vf).get();
+                            } finally {
+                                Tasks.resetBlockingDetails();
+                            }
+                        } };
                     String description = getDescription();
-                    Task<Object> vt = exec.submit(Tasks.<Object>builder().body(callable).name("Resolving dependent value").description(description).build());
+                    TaskBuilder<Object> vb = Tasks.<Object>builder().body(callable).name("Resolving dependent value").description(description);
+                    if (isTransientTask) vb.tag(BrooklynTaskTags.TRANSIENT_TASK_TAG);
+                    Task<Object> vt = exec.submit(vb.build());
                     // TODO to handle immediate resolution, it would be nice to be able to submit 
                     // so it executes in the current thread,
                     // or put a marker in the target thread or task while it is running that the task 
@@ -296,8 +320,12 @@ public class ValueResolver<T> implements DeferredSupplier<T> {
                     v = vm.get();
                     
                 } else {
-                    v = callable.call();
-                    
+                    try {
+                        Tasks.setBlockingDetails("Retrieving (non-task) "+vf);
+                        v = ((DeferredSupplier<?>) vf).get();
+                    } finally {
+                        Tasks.resetBlockingDetails();
+                    }
                 }
 
             } else if (v instanceof Map) {
@@ -370,4 +398,9 @@ public class ValueResolver<T> implements DeferredSupplier<T> {
         if (parentOriginalValue!=null) return parentOriginalValue;
         return value;
     }
+    
+    @Override
+    public String toString() {
+        return JavaClassNames.cleanSimpleClassName(this)+"["+JavaClassNames.cleanSimpleClassName(type)+" "+value+"]";
+    }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/ea8da379/core/src/test/java/brooklyn/util/internal/TypeCoercionsTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/util/internal/TypeCoercionsTest.java b/core/src/test/java/brooklyn/util/internal/TypeCoercionsTest.java
index 9b2f6b0..66e7d2c 100644
--- a/core/src/test/java/brooklyn/util/internal/TypeCoercionsTest.java
+++ b/core/src/test/java/brooklyn/util/internal/TypeCoercionsTest.java
@@ -121,7 +121,7 @@ public class TypeCoercionsTest {
     public void testCoercePrimitiveFailures() {
         // error messages don't have to be this exactly, but they should include sufficient information...
         assertCoercionFailsWithErrorMatching("maybe", boolean.class, StringPredicates.containsAllLiterals("String", "boolean", "maybe"));
-        assertCoercionFailsWithErrorMatching("NaN", int.class, StringPredicates.containsAllLiterals("String", "int", "NaN"));
+        assertCoercionFailsWithErrorMatching("NaN", int.class, StringPredicates.containsAllLiterals("int", "NaN"));
         assertCoercionFailsWithErrorMatching('c', boolean.class, StringPredicates.containsAllLiterals("boolean", "(c)"));  // will say 'string' rather than 'char'
         assertCoercionFailsWithErrorMatching(0, boolean.class, StringPredicates.containsAllLiterals("Integer", "boolean", "0"));
     }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/ea8da379/policy/src/main/java/brooklyn/policy/ha/AbstractFailureDetector.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/brooklyn/policy/ha/AbstractFailureDetector.java b/policy/src/main/java/brooklyn/policy/ha/AbstractFailureDetector.java
index 6a1324c..2c772b8 100644
--- a/policy/src/main/java/brooklyn/policy/ha/AbstractFailureDetector.java
+++ b/policy/src/main/java/brooklyn/policy/ha/AbstractFailureDetector.java
@@ -192,10 +192,11 @@ public abstract class AbstractFailureDetector extends AbstractPolicy {
         doStartPolling();
     }
 
+    @SuppressWarnings("unchecked")
     protected void doStartPolling() {
         if (scheduledTask == null || scheduledTask.isDone()) {
             ScheduledTask task = new ScheduledTask(MutableMap.of("period", getPollPeriod(), "displayName", getTaskName()), pollingTaskFactory);
-            scheduledTask = ((EntityInternal)entity).getExecutionContext().submit(task);;
+            scheduledTask = ((EntityInternal)entity).getExecutionContext().submit(task);
         }
     }
 
@@ -271,6 +272,7 @@ public abstract class AbstractFailureDetector extends AbstractPolicy {
         schedulePublish(0);
     }
 
+    @SuppressWarnings("unchecked")
     protected void schedulePublish(long delay) {
         if (isRunning() && executorQueued.compareAndSet(false, true)) {
             long now = System.currentTimeMillis();

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/ea8da379/policy/src/main/java/brooklyn/policy/ha/ServiceRestarter.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/brooklyn/policy/ha/ServiceRestarter.java b/policy/src/main/java/brooklyn/policy/ha/ServiceRestarter.java
index b71d6d6..02ea8e4 100644
--- a/policy/src/main/java/brooklyn/policy/ha/ServiceRestarter.java
+++ b/policy/src/main/java/brooklyn/policy/ha/ServiceRestarter.java
@@ -26,7 +26,6 @@ import org.slf4j.LoggerFactory;
 
 import brooklyn.catalog.Catalog;
 import brooklyn.config.ConfigKey;
-import brooklyn.entity.basic.Attributes;
 import brooklyn.entity.basic.ConfigKeys;
 import brooklyn.entity.basic.Entities;
 import brooklyn.entity.basic.EntityInternal;
@@ -76,7 +75,7 @@ public class ServiceRestarter extends AbstractPolicy {
 
     /** monitors this sensor, by default ENTITY_FAILED */
     @SetFromFlag("failureSensorToMonitor")
-    @SuppressWarnings("rawtypes")
+    @SuppressWarnings({ "rawtypes", "unchecked" })
     public static final ConfigKey<Sensor<?>> FAILURE_SENSOR_TO_MONITOR = (ConfigKey) ConfigKeys.newConfigKey(Sensor.class, "failureSensorToMonitor", "", HASensors.ENTITY_FAILED); 
     
     protected final AtomicReference<Long> lastFailureTime = new AtomicReference<Long>();

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/ea8da379/policy/src/main/java/brooklyn/policy/ha/SshMachineFailureDetector.java
----------------------------------------------------------------------
diff --git a/policy/src/main/java/brooklyn/policy/ha/SshMachineFailureDetector.java b/policy/src/main/java/brooklyn/policy/ha/SshMachineFailureDetector.java
index ee1c6ee..edb71b9 100644
--- a/policy/src/main/java/brooklyn/policy/ha/SshMachineFailureDetector.java
+++ b/policy/src/main/java/brooklyn/policy/ha/SshMachineFailureDetector.java
@@ -29,7 +29,6 @@ import brooklyn.entity.basic.ConfigKeys;
 import brooklyn.event.basic.BasicNotificationSensor;
 import brooklyn.location.basic.Machines;
 import brooklyn.location.basic.SshMachineLocation;
-import brooklyn.policy.ha.AbstractFailureDetector.LastPublished;
 import brooklyn.policy.ha.HASensors.FailureDescriptor;
 import brooklyn.util.exceptions.Exceptions;
 import brooklyn.util.guava.Maybe;

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/ea8da379/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessSshDriver.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessSshDriver.java b/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessSshDriver.java
index 3ebad57..954b842 100644
--- a/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessSshDriver.java
+++ b/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessSshDriver.java
@@ -314,7 +314,7 @@ public abstract class AbstractSoftwareProcessSshDriver extends AbstractSoftwareP
      * @see SoftwareProcess#SHELL_ENVIRONMENT
      */
     public Map<String, String> getShellEnvironment() {
-        return Strings.toStringMap(entity.getConfig(SoftwareProcess.SHELL_ENVIRONMENT));
+        return Strings.toStringMap(entity.getConfig(SoftwareProcess.SHELL_ENVIRONMENT), "");
     }
 
 

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/ea8da379/software/base/src/main/java/brooklyn/entity/software/StaticSensor.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/software/StaticSensor.java b/software/base/src/main/java/brooklyn/entity/software/StaticSensor.java
index 8c20253..8688ec8 100644
--- a/software/base/src/main/java/brooklyn/entity/software/StaticSensor.java
+++ b/software/base/src/main/java/brooklyn/entity/software/StaticSensor.java
@@ -18,15 +18,22 @@
  */
 package brooklyn.entity.software;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import brooklyn.config.ConfigKey;
 import brooklyn.entity.basic.ConfigKeys;
 import brooklyn.entity.basic.EntityLocal;
 import brooklyn.entity.effector.AddSensor;
 import brooklyn.util.config.ConfigBag;
-import brooklyn.util.flags.TypeCoercions;
+import brooklyn.util.guava.Maybe;
+import brooklyn.util.task.Tasks;
+import brooklyn.util.time.Duration;
 
 public class StaticSensor<T> extends AddSensor<T> {
 
+    private static final Logger log = LoggerFactory.getLogger(StaticSensor.class);
+    
     public static final ConfigKey<Object> STATIC_VALUE = ConfigKeys.newConfigKey(Object.class, "static.value");
 
     private final Object value;
@@ -36,9 +43,17 @@ public class StaticSensor<T> extends AddSensor<T> {
         value = params.get(STATIC_VALUE);
     }
 
+    @SuppressWarnings("unchecked")
     @Override
     public void apply(EntityLocal entity) {
         super.apply(entity);
-        entity.setAttribute(sensor, (T) TypeCoercions.coerce(value, sensor.getType()));
+        
+        Maybe<T> v = Tasks.resolving(value).as((Class<T>)sensor.getType()).timeout(Duration.millis(200)).getMaybe();
+        if (v.isPresent()) {
+            log.debug(this+" setting sensor "+sensor+" to "+v.get());
+            entity.setAttribute(sensor, v.get());
+        } else {
+            log.debug(this+" not setting sensor "+sensor+"; cannot resolve "+value);
+        }
     }
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/ea8da379/software/base/src/main/java/brooklyn/entity/software/ssh/SshCommandEffector.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/software/ssh/SshCommandEffector.java b/software/base/src/main/java/brooklyn/entity/software/ssh/SshCommandEffector.java
index 200d0ce..9a86dce 100644
--- a/software/base/src/main/java/brooklyn/entity/software/ssh/SshCommandEffector.java
+++ b/software/base/src/main/java/brooklyn/entity/software/ssh/SshCommandEffector.java
@@ -82,7 +82,7 @@ public final class SshCommandEffector extends AddEffector {
             }
             
             // then set things from the entities defined shell environment, if applicable
-            env.putAll(Strings.toStringMap(entity().getConfig(SoftwareProcess.SHELL_ENVIRONMENT)));
+            env.putAll(Strings.toStringMap(entity().getConfig(SoftwareProcess.SHELL_ENVIRONMENT), ""));
             
             // if we wanted to resolve the surrounding environment in real time -- see above
 //            Map<String,Object> paramsResolved = (Map<String, Object>) Tasks.resolveDeepValue(effectorShellEnv, Map.class, entity().getExecutionContext());

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/ea8da379/software/base/src/main/java/brooklyn/entity/software/ssh/SshCommandSensor.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/software/ssh/SshCommandSensor.java b/software/base/src/main/java/brooklyn/entity/software/ssh/SshCommandSensor.java
index 2c918f9..2d9e88d 100644
--- a/software/base/src/main/java/brooklyn/entity/software/ssh/SshCommandSensor.java
+++ b/software/base/src/main/java/brooklyn/entity/software/ssh/SshCommandSensor.java
@@ -87,7 +87,7 @@ public final class SshCommandSensor<T> extends AddSensor<T> {
         Supplier<Map<String,String>> envSupplier = new Supplier<Map<String,String>>() {
             @Override
             public Map<String, String> get() {
-                return MutableMap.copyOf(Strings.toStringMap(entity.getConfig(SoftwareProcess.SHELL_ENVIRONMENT)));
+                return MutableMap.copyOf(Strings.toStringMap(entity.getConfig(SoftwareProcess.SHELL_ENVIRONMENT), ""));
             }
         };
 

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/ea8da379/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/dsl/methods/BrooklynDslCommon.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/dsl/methods/BrooklynDslCommon.java b/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/dsl/methods/BrooklynDslCommon.java
index 4c3f40b..f488d61 100644
--- a/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/dsl/methods/BrooklynDslCommon.java
+++ b/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/dsl/methods/BrooklynDslCommon.java
@@ -46,6 +46,7 @@ import brooklyn.util.flags.FlagUtils;
 import brooklyn.util.flags.TypeCoercions;
 import brooklyn.util.javalang.Reflections;
 import brooklyn.util.task.DeferredSupplier;
+import brooklyn.util.text.StringEscapes.JavaStringEscapes;
 import brooklyn.util.text.Strings;
 
 import com.google.common.base.Function;
@@ -57,27 +58,27 @@ public class BrooklynDslCommon {
 
     // Access specific entities
 
-    public static DslComponent entity(String scopeOrId) {
-        return new DslComponent(Scope.GLOBAL, scopeOrId);
+    public static DslComponent entity(String id) {
+        return new DslComponent(Scope.GLOBAL, id);
     }
     public static DslComponent parent() {
         return new DslComponent(Scope.PARENT, null);
     }
-    public static DslComponent child(String scopeOrId) {
-        return new DslComponent(Scope.CHILD, scopeOrId);
+    public static DslComponent child(String id) {
+        return new DslComponent(Scope.CHILD, id);
     }
-    public static DslComponent sibling(String scopeOrId) {
-        return new DslComponent(Scope.SIBLING, scopeOrId);
+    public static DslComponent sibling(String id) {
+        return new DslComponent(Scope.SIBLING, id);
     }
-    public static DslComponent descendant(String scopeOrId) {
-        return new DslComponent(Scope.DESCENDANT, scopeOrId);
+    public static DslComponent descendant(String id) {
+        return new DslComponent(Scope.DESCENDANT, id);
     }
-    public static DslComponent ancestor(String scopeOrId) {
-        return new DslComponent(Scope.ANCESTOR, scopeOrId);
+    public static DslComponent ancestor(String id) {
+        return new DslComponent(Scope.ANCESTOR, id);
     }
     // prefer the syntax above to the below now, but not deprecating the below
-    public static DslComponent component(String scopeOrId) {
-        return component("global", scopeOrId);
+    public static DslComponent component(String id) {
+        return component("global", id);
     }
     public static DslComponent component(String scope, String id) {
         if (!DslComponent.Scope.isValid(scope)) {
@@ -205,7 +206,9 @@ public class BrooklynDslCommon {
 
         @Override
         public String toString() {
-            return "$brooklyn:formatString("+pattern+(args==null || args.length==0 ? "" : ","+Strings.join(args, ","))+")";
+            return "$brooklyn:formatString("+
+                JavaStringEscapes.wrapJavaString(pattern)+
+                (args==null || args.length==0 ? "" : ","+Strings.join(args, ","))+")";
         }
     }
 
@@ -291,7 +294,7 @@ public class BrooklynDslCommon {
 
         @Override
         public String toString() {
-            return "$brooklyn:object("+type+")";
+            return "$brooklyn:object(\""+type.getName()+"\")";
         }
     }
 

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/ea8da379/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/dsl/methods/DslComponent.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/dsl/methods/DslComponent.java b/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/dsl/methods/DslComponent.java
index 7b1ab4b..1cb52b3 100644
--- a/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/dsl/methods/DslComponent.java
+++ b/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/dsl/methods/DslComponent.java
@@ -26,6 +26,7 @@ import java.util.Set;
 import java.util.concurrent.Callable;
 
 import brooklyn.entity.Entity;
+import brooklyn.entity.basic.BrooklynTaskTags;
 import brooklyn.entity.basic.ConfigKeys;
 import brooklyn.entity.basic.Entities;
 import brooklyn.entity.basic.EntityInternal;
@@ -39,6 +40,7 @@ import brooklyn.management.internal.EntityManagerInternal;
 import brooklyn.util.guava.Maybe;
 import brooklyn.util.task.TaskBuilder;
 import brooklyn.util.task.Tasks;
+import brooklyn.util.text.StringEscapes.JavaStringEscapes;
 
 import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
@@ -72,8 +74,8 @@ public class DslComponent extends BrooklynDslDeferredSupplier<Entity> {
     
     @Override
     public Task<Entity> newTask() {
-        return TaskBuilder.<Entity>builder().name("component("+componentId+")").body(
-            new EntityInScopeFinder(scopeComponent, scope, componentId)).build();
+        return TaskBuilder.<Entity>builder().name(toString()).tag(BrooklynTaskTags.TRANSIENT_TASK_TAG)
+            .body(new EntityInScopeFinder(scopeComponent, scope, componentId)).build();
     }
     
     protected static class EntityInScopeFinder implements Callable<Entity> {
@@ -197,7 +199,8 @@ public class DslComponent extends BrooklynDslDeferredSupplier<Entity> {
         }
         @Override
         public String toString() {
-            return component.toString()+"."+"attributeWhenReady("+sensorName+")";
+            return (component.scope==Scope.THIS ? "" : component.toString()+".") +
+                "attributeWhenReady("+JavaStringEscapes.wrapJavaString(sensorName)+")";
         }
     }
 
@@ -216,7 +219,7 @@ public class DslComponent extends BrooklynDslDeferredSupplier<Entity> {
 
         @Override
         public Task<Object> newTask() {
-            return Tasks.builder().name("retrieving config for "+keyName).dynamic(false).body(new Callable<Object>() {
+            return Tasks.builder().name("retrieving config for "+keyName).tag(BrooklynTaskTags.TRANSIENT_TASK_TAG).dynamic(false).body(new Callable<Object>() {
                 @Override
                 public Object call() throws Exception {
                     Entity targetEntity = component.get();
@@ -227,7 +230,8 @@ public class DslComponent extends BrooklynDslDeferredSupplier<Entity> {
 
         @Override
         public String toString() {
-            return component.toString()+"."+"config("+keyName+")";
+            return (component.scope==Scope.THIS ? "" : component.toString()+".") + 
+                "config("+JavaStringEscapes.wrapJavaString(keyName)+")";
         }
     }
     
@@ -262,7 +266,8 @@ public class DslComponent extends BrooklynDslDeferredSupplier<Entity> {
 
         @Override
         public String toString() {
-            return component.toString()+"."+"sensor("+sensorName+")";
+            return (component.scope==Scope.THIS ? "" : component.toString()+".") + 
+                "sensor("+JavaStringEscapes.wrapJavaString(sensorName)+")";
         }
     }
 
@@ -305,10 +310,10 @@ public class DslComponent extends BrooklynDslDeferredSupplier<Entity> {
 
     @Override
     public String toString() {
-        return "$brooklyn:component("+
-            (scopeComponent==null ? "" : scopeComponent+", ")+
-            (scope==Scope.GLOBAL ? "" : scope+", ")+
-            componentId+
+        return "$brooklyn:entity("+
+            (scopeComponent==null ? "" : JavaStringEscapes.wrapJavaString(scopeComponent.toString())+", ")+
+            (scope==Scope.GLOBAL ? "" : JavaStringEscapes.wrapJavaString(scope.toString())+", ")+
+            JavaStringEscapes.wrapJavaString(componentId)+
             ")";
     }
 

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/ea8da379/utils/common/src/main/java/brooklyn/util/text/StringEscapes.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/text/StringEscapes.java b/utils/common/src/main/java/brooklyn/util/text/StringEscapes.java
index 6d35361..b5b4976 100644
--- a/utils/common/src/main/java/brooklyn/util/text/StringEscapes.java
+++ b/utils/common/src/main/java/brooklyn/util/text/StringEscapes.java
@@ -24,6 +24,8 @@ import java.net.URLEncoder;
 import java.util.ArrayList;
 import java.util.List;
 
+import javax.annotation.Nonnull;
+
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -205,6 +207,12 @@ public class StringEscapes {
             }
             return out.toString();
         }
+        public static List<String> wrapJavaStrings(Iterable<String> values) {
+            if (values==null) return null;
+            List<String> result = MutableList.of();
+            for (String v: values) result.add(wrapJavaString(v));
+            return result;
+        }
 
         /** as {@link #unwrapJavaString(String)} if the given string is wrapped in double quotes;
          * otherwise just returns the given string */
@@ -215,13 +223,17 @@ public class StringEscapes {
 
         /** converts normal string to java escaped for double-quotes and wrapped in those double quotes */
         public static void wrapJavaString(String value, Appendable out) throws IOException {
-            out.append('"');
-            escapeJavaString(value, out);
-            out.append('"');
+            if (value==null) {
+                out.append("null");
+            } else {
+                out.append('"');
+                escapeJavaString(value, out);
+                out.append('"');
+            }
         }
 
         /** converts normal string to java escaped for double-quotes (but not wrapped in double quotes) */
-        public static void escapeJavaString(String value, Appendable out) throws IOException {
+        public static void escapeJavaString(@Nonnull String value, Appendable out) throws IOException {
             for (int i=0; i<value.length(); i++) {
                 char c = value.charAt(i);
                 if (c=='\\' || c=='"') {

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/ea8da379/utils/common/src/main/java/brooklyn/util/text/Strings.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/text/Strings.java b/utils/common/src/main/java/brooklyn/util/text/Strings.java
index cf61fad..5aa2089 100644
--- a/utils/common/src/main/java/brooklyn/util/text/Strings.java
+++ b/utils/common/src/main/java/brooklyn/util/text/Strings.java
@@ -687,7 +687,12 @@ public class Strings {
 
     /** returns toString of the object if it is not null, otherwise null */
     public static String toString(Object o) {
-        if (o==null) return null;
+        return toStringWithValueForNull(o, null);
+    }
+
+    /** returns toString of the object if it is not null, otherwise the given value */
+    public static String toStringWithValueForNull(Object o, String valueIfNull) {
+        if (o==null) return valueIfNull;
         return o.toString();
     }
 
@@ -807,15 +812,32 @@ public class Strings {
         return ies(count);
     }
 
-    /** converts a map of any objects to a map of strings, preserving nulls and invoking toString where needed */
+    /** converts a map of any objects to a map of strings, using the tostring, and returning "null" for nulls 
+     * @deprecated since 0.7.0 use {@link #toStringMap(Map, String)} to remove ambiguity about how to handle null */
+    // NB previously the javadoc here was wrong, said it returned null not "null"
+    @Deprecated
     public static Map<String, String> toStringMap(Map<?,?> map) {
+        return toStringMap(map, "null");
+    }
+    /** converts a map of any objects to a map of strings, using {@link Object#toString()},
+     * with the second argument used where a value (or key) is null */
+    public static Map<String, String> toStringMap(Map<?,?> map, String valueIfNull) {
         if (map==null) return null;
         Map<String,String> result = MutableMap.<String, String>of();
         for (Map.Entry<?,?> e: map.entrySet()) {
-            result.put(String.valueOf(e.getKey()), String.valueOf(e.getValue()));
+            result.put(toStringWithValueForNull(e.getKey(), valueIfNull), toStringWithValueForNull(e.getValue(), valueIfNull));
         }
         return result;
     }
+    
+    /** converts a list of any objects to a list of strings, using {@link Object#toString()},
+     * with the second argument used where an entry is null */
+    public static List<String> toStringList(List<?> list, String valueIfNull) {
+        if (list==null) return null;
+        List<String> result = MutableList.of();
+        for (Object v: list) result.add(toStringWithValueForNull(v, valueIfNull));
+        return result;
+    }
 
     /** returns base repeated count times */
     public static String repeat(String base, int count) {

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/ea8da379/utils/common/src/main/java/brooklyn/util/time/Durations.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/time/Durations.java b/utils/common/src/main/java/brooklyn/util/time/Durations.java
index 3553ff4..21450a6 100644
--- a/utils/common/src/main/java/brooklyn/util/time/Durations.java
+++ b/utils/common/src/main/java/brooklyn/util/time/Durations.java
@@ -18,6 +18,7 @@
  */
 package brooklyn.util.time;
 
+import java.util.concurrent.CancellationException;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
@@ -52,6 +53,8 @@ public class Durations {
             return Maybe.of(t.get(timeout.toMilliseconds(), TimeUnit.MILLISECONDS));
         } catch (TimeoutException e) {
             return Maybe.absent("Task "+t+" did not complete within "+timeout);
+        } catch (CancellationException e) {
+            return Maybe.absent("Task "+t+" was cancelled");
         } catch (Exception e) {
             throw Exceptions.propagate(e);
         }