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/incubator-brooklyn/repo
Commit: 
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/78cbc22f
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/78cbc22f
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/78cbc22f

Branch: refs/heads/master
Commit: 78cbc22fff260ba13c8db622d8cfb8fad458e02f
Parents: ceee1c0
Author: Alex Heneveld <[email protected]>
Authored: Thu Jun 18 08:56:17 2015 -0700
Committer: Alex Heneveld <[email protected]>
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/incubator-brooklyn/blob/78cbc22f/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/incubator-brooklyn/blob/78cbc22f/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/incubator-brooklyn/blob/78cbc22f/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/incubator-brooklyn/blob/78cbc22f/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/incubator-brooklyn/blob/78cbc22f/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/incubator-brooklyn/blob/78cbc22f/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/incubator-brooklyn/blob/78cbc22f/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/incubator-brooklyn/blob/78cbc22f/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/incubator-brooklyn/blob/78cbc22f/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/incubator-brooklyn/blob/78cbc22f/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/incubator-brooklyn/blob/78cbc22f/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/incubator-brooklyn/blob/78cbc22f/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/incubator-brooklyn/blob/78cbc22f/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/incubator-brooklyn/blob/78cbc22f/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/incubator-brooklyn/blob/78cbc22f/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/incubator-brooklyn/blob/78cbc22f/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/incubator-brooklyn/blob/78cbc22f/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/incubator-brooklyn/blob/78cbc22f/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);
         }

Reply via email to