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/08/25 12:10:36 UTC

[1/5] brooklyn-server git commit: Adds a target configuration to the SSH command executing member tracker

Repository: brooklyn-server
Updated Branches:
  refs/heads/master 434e26211 -> c57fb6004


Adds a target configuration to the SSH command executing member tracker


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

Branch: refs/heads/master
Commit: e8cce9bc7c0f6b86dec8feb1b5f57fac34f416c1
Parents: b73cb73
Author: Andrew Donald Kennedy <an...@cloudsoftcorp.com>
Authored: Fri Aug 19 12:23:06 2016 +0100
Committer: Andrew Donald Kennedy <an...@cloudsoftcorp.com>
Committed: Sat Aug 20 15:05:08 2016 +0100

----------------------------------------------------------------------
 .../SshCommandMembershipTrackingPolicy.java     | 73 ++++++++++++++++++--
 1 file changed, 67 insertions(+), 6 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/e8cce9bc/core/src/main/java/org/apache/brooklyn/entity/group/SshCommandMembershipTrackingPolicy.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/entity/group/SshCommandMembershipTrackingPolicy.java b/core/src/main/java/org/apache/brooklyn/entity/group/SshCommandMembershipTrackingPolicy.java
index e7dffb5..662202b 100644
--- a/core/src/main/java/org/apache/brooklyn/entity/group/SshCommandMembershipTrackingPolicy.java
+++ b/core/src/main/java/org/apache/brooklyn/entity/group/SshCommandMembershipTrackingPolicy.java
@@ -26,6 +26,8 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.google.common.base.CaseFormat;
+import com.google.common.base.Converter;
+import com.google.common.base.Preconditions;
 
 import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.location.Location;
@@ -54,6 +56,11 @@ import org.apache.brooklyn.util.text.Strings;
  * Sets the environment variable {@code EVENT_TYPE} to the value of the {@link EventType}
  * for the invocation, and {@code MEMBER_ID} to the ID of the entity that is being
  * added, removed or updated.
+ * <p>
+ * The {@code execution.target} configuration specifies where the command will be
+ * executed. This can be one of: the {@link ExecutionTarget#ENTITY owing entity};
+ * the {@link ExecutionTarget#MEMBER member} that was updated; or
+ * {@link ExecutionTarget#ALL_MEMBERS all members} of the group.
  */
 public class SshCommandMembershipTrackingPolicy extends AbstractMembershipTrackingPolicy {
 
@@ -62,6 +69,38 @@ public class SshCommandMembershipTrackingPolicy extends AbstractMembershipTracki
     public static final String EVENT_TYPE = "EVENT_TYPE";
     public static final String MEMBER_ID = "MEMBER_ID";
 
+    public static enum ExecutionTarget {
+        ENTITY,
+        MEMBER,
+        ALL_MEMBERS;
+
+        private static Converter<String, String> converter = CaseFormat.LOWER_CAMEL.converterTo(CaseFormat.UPPER_UNDERSCORE);
+
+        public static ExecutionTarget fromString(String name) {
+            Maybe<ExecutionTarget> parsed = tryFromString(name);
+            return parsed.get();
+        }
+
+        public static Maybe<ExecutionTarget> tryFromString(String name) {
+            try {
+                ExecutionTarget scope = valueOf(converter.convert(name));
+                return Maybe.of(scope);
+            } catch (Exception cause) {
+                return Maybe.absent(cause);
+            }
+        }
+
+        public static boolean isValid(String name) {
+            Maybe<ExecutionTarget> check = tryFromString(name);
+            return check.isPresentAndNonNull();
+        }
+
+        @Override
+        public String toString() {
+            return converter.reverse().convert(name());
+        }
+    }
+
     public static final ConfigKey<String> EXECUTION_DIR = ConfigKeys.newStringConfigKey("executionDir", "Directory where the command should run; "
         + "if not supplied, executes in the entity's run dir (or home dir if no run dir is defined); "
         + "use '~' to always execute in the home dir, or 'custom-feed/' to execute in a custom-feed dir relative to the run dir");
@@ -70,28 +109,50 @@ public class SshCommandMembershipTrackingPolicy extends AbstractMembershipTracki
 
     public static final ConfigKey<String> UPDATE_COMMAND = ConfigKeys.newStringConfigKey("update.command", "Command to run on membership change events");
 
+    public static final ConfigKey<ExecutionTarget> EXECUTION_TARGET = ConfigKeys.newConfigKey(ExecutionTarget.class,
+            "execution.target",
+            "Strategy for executing the command on different targets (default to the entity the policy is attached to)", ExecutionTarget.ENTITY);
+
     /**
      * Called when a member is updated or group membership changes.
      */
     @Override
     protected void onEntityEvent(EventType type, Entity member) {
         LOG.trace("Event {} received for {} in {}", new Object[] { type, member, getGroup() });
-        String command = config().get(UPDATE_COMMAND);
+        String command = Preconditions.checkNotNull(config().get(UPDATE_COMMAND));
+        ExecutionTarget where = Preconditions.checkNotNull(config().get(EXECUTION_TARGET));
+
         if (Strings.isNonBlank(command)) {
-            execute(command, type.name(), member.getId());
+            // Execute the command at the appropriate target entity
+            switch (where) {
+                case ENTITY:
+                    execute(entity, command, type.name(), member.getId());
+                    break;
+                case MEMBER:
+                    execute(member, command, type.name(), member.getId());
+                    break;
+                case ALL_MEMBERS:
+                    for (Entity each : getGroup().getMembers()) {
+                        execute(each, command, type.name(), member.getId());
+                    }
+                    break;
+                default:
+                    LOG.warn("Unknown value passed as execution target: {}", where.name());
+            }
         }
     }
 
-    public void execute(String command, String type, String memberId) {
-        Collection<? extends Location> locations = Locations.getLocationsCheckingAncestors(entity.getLocations(), entity);
+    public void execute(Entity target, String command, String type, String memberId) {
+        Collection<? extends Location> locations = Locations.getLocationsCheckingAncestors(target.getLocations(), target);
         Maybe<SshMachineLocation> machine = Machines.findUniqueMachineLocation(locations, SshMachineLocation.class);
         if (machine.isAbsentOrNull()) {
             LOG.debug("No machine available to execute command");
             return;
         }
+
         LOG.info("Executing command on {}: {}", machine.get(), command);
         String executionDir = config().get(EXECUTION_DIR);
-        String sshCommand = SshCommandSensor.makeCommandExecutingInDirectory(command, executionDir, entity);
+        String sshCommand = SshCommandSensor.makeCommandExecutingInDirectory(command, executionDir, (EntityInternal) target);
 
         // Set things from the entities defined shell environment, overriding with our config
         Map<String, Object> env = MutableMap.of();
@@ -117,7 +178,7 @@ public class SshCommandMembershipTrackingPolicy extends AbstractMembershipTracki
                 .summary("group-" + CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_HYPHEN, type))
                 .environmentVariables(serializer.serialize(env));
 
-        String output = DynamicTasks.submit(task.newTask(), entity).getUnchecked();
+        String output = DynamicTasks.submit(task.newTask(), target).getUnchecked();
         LOG.trace("Command returned: {}", output);
     }
 }


[4/5] brooklyn-server git commit: This closes #304

Posted by he...@apache.org.
This closes #304


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

Branch: refs/heads/master
Commit: 30cf2d2c5da3671f1d92fc94219ca2e34a27ba52
Parents: 434e262 6e1a00e
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Thu Aug 25 09:14:53 2016 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Thu Aug 25 09:14:53 2016 +0100

----------------------------------------------------------------------
 .../brooklyn/core/entity/AbstractEntity.java    | 11 ++-
 .../apache/brooklyn/core/entity/Attributes.java |  6 +-
 .../stock/AbstractMultipleSensorAggregator.java | 14 ++--
 .../brooklyn/enricher/stock/Enrichers.java      | 64 +++++++++------
 .../apache/brooklyn/enricher/stock/Joiner.java  | 40 +++++++---
 .../brooklyn/enricher/stock/MapAggregator.java  | 83 ++++++++++++++++++++
 .../SshCommandMembershipTrackingPolicy.java     | 73 +++++++++++++++--
 .../brooklyn/core/entity/EntityTypeTest.java    | 19 +++--
 .../brooklyn/enricher/stock/EnrichersTest.java  | 40 ++++++++++
 9 files changed, 292 insertions(+), 58 deletions(-)
----------------------------------------------------------------------



[2/5] brooklyn-server git commit: Update Aggregator and Joiner entichers to handle Map sensors

Posted by he...@apache.org.
Update Aggregator and Joiner entichers to handle Map sensors


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

Branch: refs/heads/master
Commit: c58fe29e3f44baf4e647aec1e3147edfbdddfd87
Parents: e8cce9b
Author: Andrew Donald Kennedy <an...@cloudsoftcorp.com>
Authored: Sat Aug 20 15:05:33 2016 +0100
Committer: Andrew Donald Kennedy <an...@cloudsoftcorp.com>
Committed: Sat Aug 20 17:09:12 2016 +0100

----------------------------------------------------------------------
 .../stock/AbstractMultipleSensorAggregator.java | 14 ++--
 .../brooklyn/enricher/stock/Enrichers.java      | 64 +++++++++------
 .../apache/brooklyn/enricher/stock/Joiner.java  | 40 +++++++---
 .../brooklyn/enricher/stock/MapAggregator.java  | 83 ++++++++++++++++++++
 .../brooklyn/enricher/stock/EnrichersTest.java  | 40 ++++++++++
 5 files changed, 199 insertions(+), 42 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/c58fe29e/core/src/main/java/org/apache/brooklyn/enricher/stock/AbstractMultipleSensorAggregator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/enricher/stock/AbstractMultipleSensorAggregator.java b/core/src/main/java/org/apache/brooklyn/enricher/stock/AbstractMultipleSensorAggregator.java
index e0f0d6d..c0cd36d 100644
--- a/core/src/main/java/org/apache/brooklyn/enricher/stock/AbstractMultipleSensorAggregator.java
+++ b/core/src/main/java/org/apache/brooklyn/enricher/stock/AbstractMultipleSensorAggregator.java
@@ -42,20 +42,19 @@ public abstract class AbstractMultipleSensorAggregator<U> extends AbstractAggreg
 
     private static final Logger LOG = LoggerFactory.getLogger(AbstractMultipleSensorAggregator.class);
 
-    
     /** access via {@link #getValues(Sensor)} */
     private final Map<String, Map<Entity,Object>> values = Collections.synchronizedMap(new LinkedHashMap<String, Map<Entity,Object>>());
 
-    public AbstractMultipleSensorAggregator() {}
+    public AbstractMultipleSensorAggregator() { }
 
     protected abstract Collection<Sensor<?>> getSourceSensors();
-    
+
     @Override
     protected void setEntityLoadingConfig() {
         super.setEntityLoadingConfig();
         Preconditions.checkNotNull(getSourceSensors(), "sourceSensors must be set");
     }
-    
+
     @Override
     protected void setEntityBeforeSubscribingProducerChildrenEvents() {
         BrooklynLogging.log(LOG, BrooklynLogging.levelDebugOrTraceIfReadOnly(producer),
@@ -95,7 +94,7 @@ public abstract class AbstractMultipleSensorAggregator<U> extends AbstractAggreg
                     vs = new LinkedHashMap<Entity,Object>();
                     values.put(sensor.getName(), vs);
                 }
-                
+
                 Object vo = vs.get(producer);
                 if (vo==null) {
                     Object initialVal;
@@ -107,11 +106,10 @@ public abstract class AbstractMultipleSensorAggregator<U> extends AbstractAggreg
                     vs.put(producer, initialVal != null ? initialVal : defaultMemberValue);
                     // NB: see notes on possible race, in Aggregator#onProducerAdded
                 }
-                
             }
         }
     }
-    
+
     @Override
     protected void onProducerRemoved(Entity producer) {
         synchronized (values) {
@@ -163,7 +161,7 @@ public abstract class AbstractMultipleSensorAggregator<U> extends AbstractAggreg
             return MutableMap.copyOf(sv).asUnmodifiable();
         }
     }
-    
+
     @Override
     protected abstract Object compute();
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/c58fe29e/core/src/main/java/org/apache/brooklyn/enricher/stock/Enrichers.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/enricher/stock/Enrichers.java b/core/src/main/java/org/apache/brooklyn/enricher/stock/Enrichers.java
index 3dfabcf..973933c 100644
--- a/core/src/main/java/org/apache/brooklyn/enricher/stock/Enrichers.java
+++ b/core/src/main/java/org/apache/brooklyn/enricher/stock/Enrichers.java
@@ -161,7 +161,11 @@ public class Enrichers {
         }
         /** as {@link #combining(Collection)} but the collection of values comes from the given sensor on multiple entities */
         public <S> AggregatorBuilder<S, Object> aggregating(AttributeSensor<S> val) {
-            return new AggregatorBuilder<S,Object>(val);
+            return new AggregatorBuilder<S, Object>(val);
+        }
+        /** as {@link #combining(Collection)} but the collection of values comes from the given sensor on multiple entities */
+        public <S> AggregatorBuilder<S, Object> aggregating(AttributeSensor<S> keySensor, AttributeSensor<S> valueSensor) {
+            return new AggregatorBuilder<S, Object>(keySensor, valueSensor);
         }
         /** creates an {@link UpdatingMap} enricher: 
          * {@link UpdatingMapBuilder#from(AttributeSensor)} and {@link UpdatingMapBuilder#computing(Function)} are required
@@ -182,13 +186,15 @@ public class Enrichers {
 
 
     protected abstract static class AbstractAggregatorBuilder<S, T, B extends AbstractAggregatorBuilder<S, T, B>> extends AbstractEnricherBuilder<B> {
-        protected final AttributeSensor<S> aggregating;
+        protected AttributeSensor<S> aggregating;
+        protected AttributeSensor<S> keySensor;
+        protected AttributeSensor<S> valueSensor;
         protected AttributeSensor<T> publishing;
         protected Entity fromEntity;
         /** @deprecated since 0.7.0, kept for backwards compatibility for rebind, but not used otherwise */
         @Deprecated protected Function<? super Collection<S>, ? extends T> computing;
         // use supplier so latest values of other fields can be used
-        protected Supplier<Function<? super Collection<S>, ? extends T>> computingSupplier;
+        protected Supplier<Function<? super Collection<S>, ? extends T>> computingSupplier = Suppliers.ofInstance(null);
         protected Boolean fromMembers;
         protected Boolean fromChildren;
         protected Boolean excludingBlank;
@@ -197,11 +203,16 @@ public class Enrichers {
         protected Predicate<Object> valueFilter;
         protected Object defaultValueForUnreportedSensors;
         protected Object valueToReportIfNoSensors;
-        
+
         public AbstractAggregatorBuilder(AttributeSensor<S> aggregating) {
             super(Aggregator.class);
             this.aggregating = aggregating;
         }
+        public AbstractAggregatorBuilder(AttributeSensor<S> keySensor, AttributeSensor<S> valueSensor) {
+            super(MapAggregator.class);
+            this.keySensor = keySensor;
+            this.valueSensor = valueSensor;
+        }
         @SuppressWarnings({ "unchecked", "rawtypes" })
         public <T2 extends T> AggregatorBuilder<S,T2> publishing(AttributeSensor<? extends T2> val) {
             this.publishing = (AttributeSensor) checkNotNull(val);
@@ -259,7 +270,6 @@ public class Enrichers {
             };
             return self();
         }
-
         public B computingAverage() {
             this.computingSupplier = new Supplier<Function<? super Collection<S>, ? extends T>>() {
                 @Override
@@ -283,6 +293,10 @@ public class Enrichers {
             this.entityFilter = val;
             return self();
         }
+        public B valueFilter(Predicate<Object> val) {
+            this.valueFilter = val;
+            return self();
+        }
         public B excludingBlank() {
             this.excludingBlank = true;
             return self();
@@ -293,42 +307,33 @@ public class Enrichers {
             return "aggregator:"+publishing.getName();
         }
         public EnricherSpec<?> build() {
-            Predicate<Object> valueFilter;
-            if (Boolean.TRUE.equals(excludingBlank)) {
-                valueFilter = new Predicate<Object>() {
-                    @Override public boolean apply(Object input) {
-                        return (input != null) &&
-                                ((input instanceof CharSequence) ? Strings.isNonBlank((CharSequence)input) : true);
-                    }
-                };
-                // above kept for deserialization; not sure necessary
-                valueFilter = StringPredicates.isNonBlank(); 
-            } else {
-                valueFilter = null;
-            }
-            // FIXME excludingBlank; use valueFilter? exclude means ignored entirely or substituted for defaultMemberValue?
             return super.build().configure(MutableMap.builder()
                             .putIfNotNull(Aggregator.PRODUCER, fromEntity)
                             .put(Aggregator.TARGET_SENSOR, publishing)
-                            .put(Aggregator.SOURCE_SENSOR, aggregating)
+                            .putIfNotNull(Aggregator.SOURCE_SENSOR, aggregating)
+                            .putIfNotNull(MapAggregator.KEY_SENSOR, keySensor)
+                            .putIfNotNull(MapAggregator.VALUE_SENSOR, valueSensor)
                             .putIfNotNull(Aggregator.FROM_CHILDREN, fromChildren)
                             .putIfNotNull(Aggregator.FROM_MEMBERS, fromMembers)
                             .putIfNotNull(Aggregator.TRANSFORMATION, computingSupplier.get())
                             .putIfNotNull(Aggregator.FROM_HARDCODED_PRODUCERS, fromHardcodedProducers)
+                            .putIfNotNull(Aggregator.EXCLUDE_BLANK, excludingBlank)
                             .putIfNotNull(Aggregator.ENTITY_FILTER, entityFilter)
                             .putIfNotNull(Aggregator.VALUE_FILTER, valueFilter)
                             .putIfNotNull(Aggregator.DEFAULT_MEMBER_VALUE, defaultValueForUnreportedSensors)
                             .build());
         }
-        
+
         @Override
         public String toString() {
             return Objects.toStringHelper(this)
                     .omitNullValues()
                     .add("aggregating", aggregating)
+                    .add("keySensor", keySensor)
+                    .add("valueSensor", valueSensor)
                     .add("publishing", publishing)
                     .add("fromEntity", fromEntity)
-                    .add("computing", computingSupplier)
+                    .add("computing", computingSupplier.get())
                     .add("fromMembers", fromMembers)
                     .add("fromChildren", fromChildren)
                     .add("excludingBlank", excludingBlank)
@@ -638,6 +643,8 @@ public class Enrichers {
         protected AttributeSensor<String> publishing;
         protected Entity fromEntity;
         protected String separator;
+        protected String keyValueSeparator;
+        protected Boolean joinMapEntries;
         protected Boolean quote;
         protected Integer minimum;
         protected Integer maximum;
@@ -654,6 +661,14 @@ public class Enrichers {
             this.separator = separator;
             return self();
         }
+        public B keyValueSeparator(String keyValueSeparator) {
+            this.keyValueSeparator = keyValueSeparator;
+            return self();
+        }
+        public B joinMapEntries(Boolean joinMapEntries) {
+            this.joinMapEntries = joinMapEntries;
+            return self();
+        }
         public B quote(Boolean quote) {
             this.quote = quote;
             return self();
@@ -677,6 +692,8 @@ public class Enrichers {
                             .put(Joiner.TARGET_SENSOR, publishing)
                             .put(Joiner.SOURCE_SENSOR, transforming)
                             .putIfNotNull(Joiner.SEPARATOR, separator)
+                            .putIfNotNull(Joiner.KEY_VALUE_SEPARATOR, keyValueSeparator)
+                            .putIfNotNull(Joiner.JOIN_MAP_ENTRIES, joinMapEntries)
                             .putIfNotNull(Joiner.QUOTE, quote)
                             .putIfNotNull(Joiner.MINIMUM, minimum)
                             .putIfNotNull(Joiner.MAXIMUM, maximum)
@@ -757,6 +774,9 @@ public class Enrichers {
         public AggregatorBuilder(AttributeSensor<S> aggregating) {
             super(aggregating);
         }
+        public AggregatorBuilder(AttributeSensor<S> keySensor, AttributeSensor<S> valueSensor) {
+            super(keySensor, valueSensor);
+        }
     }
 
     public static class PropagatorBuilder extends AbstractPropagatorBuilder<PropagatorBuilder> {

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/c58fe29e/core/src/main/java/org/apache/brooklyn/enricher/stock/Joiner.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/enricher/stock/Joiner.java b/core/src/main/java/org/apache/brooklyn/enricher/stock/Joiner.java
index 2e24676..02654bc 100644
--- a/core/src/main/java/org/apache/brooklyn/enricher/stock/Joiner.java
+++ b/core/src/main/java/org/apache/brooklyn/enricher/stock/Joiner.java
@@ -50,6 +50,10 @@ public class Joiner<T> extends AbstractEnricher implements SensorEventListener<T
     public static ConfigKey<Sensor<?>> TARGET_SENSOR = ConfigKeys.newConfigKey(new TypeToken<Sensor<?>>() {}, "enricher.targetSensor");
     @SetFromFlag("separator")
     public static ConfigKey<String> SEPARATOR = ConfigKeys.newStringConfigKey("enricher.joiner.separator", "Separator string to insert between each argument", ",");
+    @SetFromFlag("keyValueSeparator")
+    public static ConfigKey<String> KEY_VALUE_SEPARATOR = ConfigKeys.newStringConfigKey("enricher.joiner.keyValueSeparator", "Separator string to insert between each key-value pair", "=");
+    @SetFromFlag("joinMapEntries")
+    public static ConfigKey<Boolean> JOIN_MAP_ENTRIES = ConfigKeys.newBooleanConfigKey("enricher.joiner.joinMapEntries", "Whether to add map entries as key-value pairs or just use the value, defaulting to false", false);
     @SetFromFlag("quote")
     public static ConfigKey<Boolean> QUOTE = ConfigKeys.newBooleanConfigKey("enricher.joiner.quote", "Whether to bash-escape each parameter and wrap in double-quotes, defaulting to true", true);
     @SetFromFlag("minimum")
@@ -72,12 +76,12 @@ public class Joiner<T> extends AbstractEnricher implements SensorEventListener<T
         this.producer = getConfig(PRODUCER) == null ? entity: getConfig(PRODUCER);
         this.sourceSensor = (AttributeSensor<T>) getRequiredConfig(SOURCE_SENSOR);
         this.targetSensor = (Sensor<String>) getRequiredConfig(TARGET_SENSOR);
-        
+
         subscriptions().subscribe(producer, sourceSensor, this);
-        
-        Object value = producer.getAttribute((AttributeSensor<?>)sourceSensor);
+
+        Object value = producer.getAttribute((AttributeSensor<?>) sourceSensor);
         // TODO would be useful to have a convenience to "subscribeAndThenIfItIsAlreadySetRunItOnce"
-        if (value!=null) {
+        if (value != null) {
             onEvent(new BasicSensorEvent(sourceSensor, producer, value, -1));
         }
     }
@@ -92,36 +96,48 @@ public class Joiner<T> extends AbstractEnricher implements SensorEventListener<T
         Object result = null;
         if (v!=null) {
             if (v instanceof Map) {
-                v = ((Map<?,?>)v).values();
+                if (config().get(JOIN_MAP_ENTRIES)) {
+                    v = ((Map<?,?>) v).entrySet();
+                } else {
+                    v = ((Map<?,?>) v).values();
+                }
             }
             if (!(v instanceof Iterable)) {
                 LOG.warn("Enricher "+this+" received a non-iterable value "+v.getClass()+" "+v+"; refusing to join");
             } else {
                 MutableList<Object> c1 = MutableList.of();
-                Integer maximum = getConfig(MAXIMUM);
+                Integer maximum = config().get(MAXIMUM);
                 for (Object ci: (Iterable<?>)v) {
                     if (maximum!=null && maximum>=0) {
                         if (c1.size()>=maximum) break;
                     }
-                    c1.appendIfNotNull(Strings.toString(ci));
+                    if (ci instanceof Map.Entry) {
+                        String key = Strings.toString(((Map.Entry) ci).getKey());
+                        Object value = ((Map.Entry) ci).getValue();
+                        String keyValueSeparator = config().get(KEY_VALUE_SEPARATOR);
+                        if (value != null) {
+                            c1.append(String.format("%s%s%s", key, keyValueSeparator, Strings.toString(value)));
+                        }
+                    } else {
+                        c1.appendIfNotNull(Strings.toString(ci));
+                    }
                 }
-                Integer minimum = getConfig(MINIMUM);
+                Integer minimum = config().get(MINIMUM);
                 if (minimum!=null && c1.size() < minimum) {
                     // use default null return value
                 } else {
-                    if (getConfig(QUOTE)) {
+                    if (config().get(QUOTE)) {
                         MutableList<Object> c2 = MutableList.of();
                         for (Object ci: c1) {
                             c2.add(StringEscapes.BashStringEscapes.wrapBash((String)ci));
                         }
                         c1 = c2;
                     }
-                    result = Strings.join(c1, getConfig(SEPARATOR));
+                    result = Strings.join(c1, config().get(SEPARATOR));
                 }
             }
         }
-        if (LOG.isTraceEnabled())
-            LOG.trace("Enricher "+this+" computed "+result+" from "+event);
+        LOG.trace("Enricher "+this+" computed "+result+" from "+event);
         return result;
     }
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/c58fe29e/core/src/main/java/org/apache/brooklyn/enricher/stock/MapAggregator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/enricher/stock/MapAggregator.java b/core/src/main/java/org/apache/brooklyn/enricher/stock/MapAggregator.java
new file mode 100644
index 0000000..af1cf20
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/enricher/stock/MapAggregator.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.enricher.stock;
+
+import java.util.Collection;
+import java.util.Map;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Maps;
+import com.google.common.reflect.TypeToken;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.sensor.Sensor;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.text.StringPredicates;
+
+/**
+ * Building on {@link AbstractMultipleSensorAggregator} for a pair of source sensors(on multiple children and/or members)
+ * that are used as key-value pairs in a generated Map.
+ */
+@SuppressWarnings("serial")
+public class MapAggregator<U> extends AbstractMultipleSensorAggregator<U> {
+
+    public static final ConfigKey<Sensor<?>> KEY_SENSOR = ConfigKeys.newConfigKey(new TypeToken<Sensor<?>>() {}, "enricher.keySensor");
+    public static final ConfigKey<Sensor<?>> VALUE_SENSOR = ConfigKeys.newConfigKey(new TypeToken<Sensor<?>>() {}, "enricher.valueSensor");
+
+    public static final ConfigKey<Boolean> EXCLUDE_BLANK = Aggregator.EXCLUDE_BLANK;
+
+    private Sensor<?> keySensor;
+    private Sensor<?> valueSensor;
+
+    public MapAggregator() { }
+
+    @Override
+    protected Predicate<?> getDefaultValueFilter() {
+        if (config().get(EXCLUDE_BLANK)) {
+            return StringPredicates.isNonBlank();
+        } else {
+            return Predicates.alwaysTrue();
+        }
+    }
+
+    @Override
+    protected Object compute() {
+        Map<Entity, Object> ks = MutableMap.copyOf(Maps.filterValues(getValues(keySensor), valueFilter));
+        Map<Entity, Object> vs = MutableMap.copyOf(Maps.filterValues(getValues(valueSensor), valueFilter));
+        MutableMap<Object, Object> result = MutableMap.of();
+        for (Entity entity : ks.keySet()) {
+            if (vs.containsKey(entity)) {
+                result.put(ks.get(entity), vs.get(entity));
+            }
+        }
+        return result;
+    }
+
+    @Override
+    protected Collection<Sensor<?>> getSourceSensors() {
+        keySensor = config().get(KEY_SENSOR);
+        valueSensor = config().get(VALUE_SENSOR);
+        return ImmutableList.<Sensor<?>>of(keySensor, valueSensor);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/c58fe29e/core/src/test/java/org/apache/brooklyn/enricher/stock/EnrichersTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/enricher/stock/EnrichersTest.java b/core/src/test/java/org/apache/brooklyn/enricher/stock/EnrichersTest.java
index 6e5cc16..e20664b 100644
--- a/core/src/test/java/org/apache/brooklyn/enricher/stock/EnrichersTest.java
+++ b/core/src/test/java/org/apache/brooklyn/enricher/stock/EnrichersTest.java
@@ -393,6 +393,29 @@ public class EnrichersTest extends BrooklynAppUnitTestSupport {
         entity.sensors().set(NUM1, 987654);
         EntityAsserts.assertAttributeEqualsContinually(group, LONG1, Long.valueOf(123));
     }
+
+    @Test
+    public void testAggregatingMap() {
+        group.addMember(entity);
+        group.enrichers().add(Enrichers.builder()
+                .aggregating(STR1, STR2)
+                .publishing(MAP1)
+                .fromMembers()
+                .excludingBlank()
+                .build());
+
+        EntityAsserts.assertAttributeEqualsEventually(group, MAP1, ImmutableMap.<String, String>of());
+
+        entity.sensors().set(STR1, "a");
+        EntityAsserts.assertAttributeEqualsEventually(group, MAP1, ImmutableMap.<String, String>of());
+
+        entity.sensors().set(STR2, "b");
+        EntityAsserts.assertAttributeEqualsEventually(group, MAP1, ImmutableMap.<String, String>of("a", "b"));
+
+        entity.sensors().set(STR2, "c");
+        EntityAsserts.assertAttributeEqualsEventually(group, MAP1, ImmutableMap.<String, String>of("a", "c"));
+    }
+
     @Test
     public void testUpdatingMap1() {
         entity.enrichers().add(Enrichers.builder()
@@ -429,6 +452,7 @@ public class EnrichersTest extends BrooklynAppUnitTestSupport {
     }
 
     private static AttributeSensor<Object> LIST_SENSOR = Sensors.newSensor(Object.class, "sensor.list");
+    private static AttributeSensor<Object> MAP_SENSOR = Sensors.newSensor(Object.class, "sensor.map");
     
     @Test
     public void testJoinerDefault() {
@@ -490,5 +514,21 @@ public class EnrichersTest extends BrooklynAppUnitTestSupport {
         EntityAsserts.assertAttributeEqualsEventually(entity, TestEntity.NAME, "a,b,c,d");
     }
 
+    @Test
+    public void testJoinerMap() {
+        entity.enrichers().add(Enrichers.builder()
+                .joining(MAP_SENSOR)
+                .keyValueSeparator("=")
+                .joinMapEntries(true)
+                .publishing(TestEntity.NAME)
+                .build());
+        // check quotes
+        entity.sensors().set(MAP_SENSOR, MutableMap.<String, String>of("a", "\"v", "b", "w x y"));
+        EntityAsserts.assertAttributeEqualsEventually(entity, TestEntity.NAME, "\"a=\\\"v\",\"b=w x y\"");
+
+        // empty map causes ""
+        entity.sensors().set(MAP_SENSOR, MutableMap.<String, String>of());
+        EntityAsserts.assertAttributeEqualsEventually(entity, TestEntity.NAME, "");
+    }
 
 }


[3/5] brooklyn-server git commit: Add sensors for IDs to entity

Posted by he...@apache.org.
Add sensors for IDs to entity


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

Branch: refs/heads/master
Commit: 6e1a00eb44f195dbeea39eeb430584aa14702f16
Parents: c58fe29
Author: Andrew Donald Kennedy <an...@cloudsoftcorp.com>
Authored: Sat Aug 20 16:03:52 2016 +0100
Committer: Andrew Donald Kennedy <an...@cloudsoftcorp.com>
Committed: Sat Aug 20 17:09:13 2016 +0100

----------------------------------------------------------------------
 .../brooklyn/core/entity/AbstractEntity.java     | 11 +++++++++--
 .../apache/brooklyn/core/entity/Attributes.java  |  6 +++++-
 .../brooklyn/core/entity/EntityTypeTest.java     | 19 ++++++++++++-------
 3 files changed, 26 insertions(+), 10 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6e1a00eb/core/src/main/java/org/apache/brooklyn/core/entity/AbstractEntity.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/AbstractEntity.java b/core/src/main/java/org/apache/brooklyn/core/entity/AbstractEntity.java
index 8836f0a..49c1967 100644
--- a/core/src/main/java/org/apache/brooklyn/core/entity/AbstractEntity.java
+++ b/core/src/main/java/org/apache/brooklyn/core/entity/AbstractEntity.java
@@ -196,7 +196,11 @@ public abstract class AbstractEntity extends AbstractBrooklynObject implements E
             "entity.group.added", "Group dynamically added to entity");
     public static final BasicNotificationSensor<Group> GROUP_REMOVED = new BasicNotificationSensor<Group>(Group.class,
             "entity.group.removed", "Group dynamically removed from entity");
-    
+
+    public static final AttributeSensor<String> ENTITY_ID = Attributes.ENTITY_ID;
+    public static final AttributeSensor<String> APPLICATION_ID = Attributes.APPLICATION_ID;
+    public static final AttributeSensor<String> CATALOG_ID = Attributes.CATALOG_ID;
+
     static {
         RendererHints.register(Entity.class, RendererHints.displayValue(EntityFunctions.displayName()));
     }
@@ -1615,7 +1619,7 @@ public abstract class AbstractEntity extends AbstractBrooklynObject implements E
     // -------- INITIALIZATION --------------
 
     /**
-     * Default entity initialization, just calls {@link #initEnrichers()}.
+     * Default entity initialization sets ID sensors and calls {@link #initEnrichers()}.
      */
     public void init() {
         super.init();
@@ -1623,6 +1627,9 @@ public abstract class AbstractEntity extends AbstractBrooklynObject implements E
         if (Strings.isNonBlank(getConfig(DEFAULT_DISPLAY_NAME))) {
             setDefaultDisplayName(getConfig(DEFAULT_DISPLAY_NAME));
         }
+        sensors().set(ENTITY_ID, getId());
+        sensors().set(APPLICATION_ID, getApplicationId());
+        sensors().set(CATALOG_ID, getCatalogItemId());
     }
     
     /**

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6e1a00eb/core/src/main/java/org/apache/brooklyn/core/entity/Attributes.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/Attributes.java b/core/src/main/java/org/apache/brooklyn/core/entity/Attributes.java
index 06fb7ab..a59c484 100644
--- a/core/src/main/java/org/apache/brooklyn/core/entity/Attributes.java
+++ b/core/src/main/java/org/apache/brooklyn/core/entity/Attributes.java
@@ -42,7 +42,11 @@ import com.google.common.reflect.TypeToken;
  * This interface should be used to access {@link Sensor} definitions.
  */
 public interface Attributes {
-    
+
+    AttributeSensor<String> ENTITY_ID = Sensors.newStringSensor("entity.id");
+    AttributeSensor<String> APPLICATION_ID = Sensors.newStringSensor("application.id");
+    AttributeSensor<String> CATALOG_ID = Sensors.newStringSensor("catalog.id");
+
     BasicNotificationSensor<Void> LOCATION_CHANGED = new BasicNotificationSensor<Void>(
             Void.class, "entity.locationChanged", "Indicates that an entity's location has been changed");
 

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6e1a00eb/core/src/test/java/org/apache/brooklyn/core/entity/EntityTypeTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/entity/EntityTypeTest.java b/core/src/test/java/org/apache/brooklyn/core/entity/EntityTypeTest.java
index c6e3936..aac3cca 100644
--- a/core/src/test/java/org/apache/brooklyn/core/entity/EntityTypeTest.java
+++ b/core/src/test/java/org/apache/brooklyn/core/entity/EntityTypeTest.java
@@ -18,6 +18,8 @@
  */
 package org.apache.brooklyn.core.entity;
 
+import static org.apache.brooklyn.core.entity.AbstractEntity.APPLICATION_ID;
+import static org.apache.brooklyn.core.entity.AbstractEntity.CATALOG_ID;
 import static org.apache.brooklyn.core.entity.AbstractEntity.CHILD_ADDED;
 import static org.apache.brooklyn.core.entity.AbstractEntity.CHILD_REMOVED;
 import static org.apache.brooklyn.core.entity.AbstractEntity.CONFIG_KEY_ADDED;
@@ -25,6 +27,7 @@ import static org.apache.brooklyn.core.entity.AbstractEntity.CONFIG_KEY_REMOVED;
 import static org.apache.brooklyn.core.entity.AbstractEntity.EFFECTOR_ADDED;
 import static org.apache.brooklyn.core.entity.AbstractEntity.EFFECTOR_CHANGED;
 import static org.apache.brooklyn.core.entity.AbstractEntity.EFFECTOR_REMOVED;
+import static org.apache.brooklyn.core.entity.AbstractEntity.ENTITY_ID;
 import static org.apache.brooklyn.core.entity.AbstractEntity.GROUP_ADDED;
 import static org.apache.brooklyn.core.entity.AbstractEntity.GROUP_REMOVED;
 import static org.apache.brooklyn.core.entity.AbstractEntity.LOCATION_ADDED;
@@ -44,6 +47,14 @@ import java.util.Set;
 
 import javax.annotation.Nullable;
 
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+
 import org.apache.brooklyn.api.effector.Effector;
 import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.entity.EntitySpec;
@@ -58,13 +69,6 @@ import org.apache.brooklyn.core.test.entity.TestEntity;
 import org.apache.brooklyn.core.test.entity.TestEntityImpl;
 import org.apache.brooklyn.test.Asserts;
 import org.apache.brooklyn.util.collections.MutableSet;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import com.google.common.base.Predicate;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
 
 public class EntityTypeTest extends BrooklynAppUnitTestSupport {
     private static final AttributeSensor<String> TEST_SENSOR = Sensors.newStringSensor("test.sensor");
@@ -73,6 +77,7 @@ public class EntityTypeTest extends BrooklynAppUnitTestSupport {
     private RecordingSensorEventListener<Sensor> listener;
 
     public final static Set<Sensor<?>> DEFAULT_SENSORS = ImmutableSet.<Sensor<?>>of(
+            ENTITY_ID, APPLICATION_ID, CATALOG_ID,
             SENSOR_ADDED, SENSOR_REMOVED,
             CONFIG_KEY_ADDED, CONFIG_KEY_REMOVED,
             EFFECTOR_ADDED, EFFECTOR_REMOVED, EFFECTOR_CHANGED,


[5/5] brooklyn-server git commit: address PR comments on #304

Posted by he...@apache.org.
address PR comments on #304

mainly with TODO comments,
also promoting the default value filter
(it has no effect on the one other use of AbstractAggregator,
in ComputeService... -- which didn't previously use EXCLUDE_BLANK)


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

Branch: refs/heads/master
Commit: c57fb6004aa096488675507bef2e05df8776a590
Parents: 30cf2d2
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Thu Aug 25 09:26:17 2016 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Thu Aug 25 09:26:17 2016 +0100

----------------------------------------------------------------------
 .../enricher/stock/AbstractAggregator.java      |  9 +++++++--
 .../brooklyn/enricher/stock/Aggregator.java     | 13 +-----------
 .../apache/brooklyn/enricher/stock/Joiner.java  |  1 +
 .../brooklyn/enricher/stock/MapAggregator.java  | 11 ----------
 .../SshCommandMembershipTrackingPolicy.java     | 21 ++++++++++++--------
 5 files changed, 22 insertions(+), 33 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/c57fb600/core/src/main/java/org/apache/brooklyn/enricher/stock/AbstractAggregator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/enricher/stock/AbstractAggregator.java b/core/src/main/java/org/apache/brooklyn/enricher/stock/AbstractAggregator.java
index 1d9870d..5a36177 100644
--- a/core/src/main/java/org/apache/brooklyn/enricher/stock/AbstractAggregator.java
+++ b/core/src/main/java/org/apache/brooklyn/enricher/stock/AbstractAggregator.java
@@ -35,6 +35,7 @@ import org.apache.brooklyn.core.entity.AbstractEntity;
 import org.apache.brooklyn.core.entity.trait.Changeable;
 import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.guava.Maybe;
+import org.apache.brooklyn.util.text.StringPredicates;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -67,6 +68,7 @@ public abstract class AbstractAggregator<T,U> extends AbstractEnricher implement
     public static final ConfigKey<Predicate<? super Entity>> ENTITY_FILTER = ConfigKeys.newConfigKey(new TypeToken<Predicate<? super Entity>>() {}, "enricher.aggregating.entityFilter");
 
     public static final ConfigKey<Predicate<?>> VALUE_FILTER = ConfigKeys.newConfigKey(new TypeToken<Predicate<?>>() {}, "enricher.aggregating.valueFilter");
+    public static final ConfigKey<Boolean> EXCLUDE_BLANK = Aggregator.EXCLUDE_BLANK;
 
     protected Entity producer;
     protected Sensor<U> targetSensor;
@@ -123,9 +125,12 @@ public abstract class AbstractAggregator<T,U> extends AbstractEnricher implement
     }
     
     protected Predicate<?> getDefaultValueFilter() {
-        return Predicates.alwaysTrue();
+        if (getConfig(EXCLUDE_BLANK))
+            return StringPredicates.isNonBlank();
+        else
+            return Predicates.alwaysTrue();
     }
-
+    
     @SuppressWarnings({ "unchecked" })
     protected void setEntityLoadingTargetConfig() {
         this.targetSensor = (Sensor<U>) getRequiredConfig(TARGET_SENSOR);

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/c57fb600/core/src/main/java/org/apache/brooklyn/enricher/stock/Aggregator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/enricher/stock/Aggregator.java b/core/src/main/java/org/apache/brooklyn/enricher/stock/Aggregator.java
index f9a0988..a21aec3 100644
--- a/core/src/main/java/org/apache/brooklyn/enricher/stock/Aggregator.java
+++ b/core/src/main/java/org/apache/brooklyn/enricher/stock/Aggregator.java
@@ -38,13 +38,10 @@ import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.collections.QuorumCheck.QuorumChecks;
 import org.apache.brooklyn.util.core.flags.SetFromFlag;
 import org.apache.brooklyn.util.exceptions.Exceptions;
-import org.apache.brooklyn.util.text.StringPredicates;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Function;
-import com.google.common.base.Predicate;
-import com.google.common.base.Predicates;
 import com.google.common.collect.Iterables;
 import com.google.common.reflect.TypeToken;
 
@@ -63,7 +60,7 @@ public class Aggregator<T,U> extends AbstractAggregator<T,U> implements SensorEv
         + "such as 'average' (for numbers), 'sum' (for numbers), or 'list' (the default, putting any collection of items into a list)");
     public static final ConfigKey<Function<? super Collection<?>, ?>> TRANSFORMATION = ConfigKeys.newConfigKey(new TypeToken<Function<? super Collection<?>, ?>>() {}, "enricher.transformation");
     
-    public static final ConfigKey<Boolean> EXCLUDE_BLANK = ConfigKeys.newBooleanConfigKey("enricher.aggregator.excludeBlank", "Whether explicit nulls or blank strings should be excluded (default false); this only applies if no value filter set", false);
+    public static final ConfigKey<Boolean> EXCLUDE_BLANK = ConfigKeys.newBooleanConfigKey("enricher.aggregator.excludeBlank", "Whether explicit nulls or blank strings should be excluded (default false); may only apply if no value filter set", false);
 
     /**
      * @see QuorumChecks
@@ -176,14 +173,6 @@ public class Aggregator<T,U> extends AbstractAggregator<T,U> implements SensorEv
     }
     
     @Override
-    protected Predicate<?> getDefaultValueFilter() {
-        if (getConfig(EXCLUDE_BLANK))
-            return StringPredicates.isNonBlank();
-        else
-            return Predicates.alwaysTrue();
-    }
-    
-    @Override
     protected void onProducerRemoved(Entity producer) {
         values.remove(producer);
         onUpdated();

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/c57fb600/core/src/main/java/org/apache/brooklyn/enricher/stock/Joiner.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/enricher/stock/Joiner.java b/core/src/main/java/org/apache/brooklyn/enricher/stock/Joiner.java
index 02654bc..175c1df 100644
--- a/core/src/main/java/org/apache/brooklyn/enricher/stock/Joiner.java
+++ b/core/src/main/java/org/apache/brooklyn/enricher/stock/Joiner.java
@@ -115,6 +115,7 @@ public class Joiner<T> extends AbstractEnricher implements SensorEventListener<T
                         String key = Strings.toString(((Map.Entry) ci).getKey());
                         Object value = ((Map.Entry) ci).getValue();
                         String keyValueSeparator = config().get(KEY_VALUE_SEPARATOR);
+                        // TODO we might want to handle QUOTE=true specially for this case, to quote keys and values if needed
                         if (value != null) {
                             c1.append(String.format("%s%s%s", key, keyValueSeparator, Strings.toString(value)));
                         }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/c57fb600/core/src/main/java/org/apache/brooklyn/enricher/stock/MapAggregator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/enricher/stock/MapAggregator.java b/core/src/main/java/org/apache/brooklyn/enricher/stock/MapAggregator.java
index af1cf20..d5f79d3 100644
--- a/core/src/main/java/org/apache/brooklyn/enricher/stock/MapAggregator.java
+++ b/core/src/main/java/org/apache/brooklyn/enricher/stock/MapAggregator.java
@@ -44,23 +44,12 @@ public class MapAggregator<U> extends AbstractMultipleSensorAggregator<U> {
     public static final ConfigKey<Sensor<?>> KEY_SENSOR = ConfigKeys.newConfigKey(new TypeToken<Sensor<?>>() {}, "enricher.keySensor");
     public static final ConfigKey<Sensor<?>> VALUE_SENSOR = ConfigKeys.newConfigKey(new TypeToken<Sensor<?>>() {}, "enricher.valueSensor");
 
-    public static final ConfigKey<Boolean> EXCLUDE_BLANK = Aggregator.EXCLUDE_BLANK;
-
     private Sensor<?> keySensor;
     private Sensor<?> valueSensor;
 
     public MapAggregator() { }
 
     @Override
-    protected Predicate<?> getDefaultValueFilter() {
-        if (config().get(EXCLUDE_BLANK)) {
-            return StringPredicates.isNonBlank();
-        } else {
-            return Predicates.alwaysTrue();
-        }
-    }
-
-    @Override
     protected Object compute() {
         Map<Entity, Object> ks = MutableMap.copyOf(Maps.filterValues(getValues(keySensor), valueFilter));
         Map<Entity, Object> vs = MutableMap.copyOf(Maps.filterValues(getValues(valueSensor), valueFilter));

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/c57fb600/core/src/main/java/org/apache/brooklyn/entity/group/SshCommandMembershipTrackingPolicy.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/entity/group/SshCommandMembershipTrackingPolicy.java b/core/src/main/java/org/apache/brooklyn/entity/group/SshCommandMembershipTrackingPolicy.java
index 662202b..432364c 100644
--- a/core/src/main/java/org/apache/brooklyn/entity/group/SshCommandMembershipTrackingPolicy.java
+++ b/core/src/main/java/org/apache/brooklyn/entity/group/SshCommandMembershipTrackingPolicy.java
@@ -22,13 +22,6 @@ import java.util.Collection;
 import java.util.Map;
 import java.util.concurrent.ExecutionException;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.common.base.CaseFormat;
-import com.google.common.base.Converter;
-import com.google.common.base.Preconditions;
-
 import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.location.Location;
 import org.apache.brooklyn.config.ConfigKey;
@@ -48,6 +41,13 @@ 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.text.Strings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.CaseFormat;
+import com.google.common.base.Converter;
+import com.google.common.base.Preconditions;
 
 /**
  * Policy which tracks membership of a group, and executes SSH commands
@@ -62,6 +62,10 @@ import org.apache.brooklyn.util.text.Strings;
  * the {@link ExecutionTarget#MEMBER member} that was updated; or
  * {@link ExecutionTarget#ALL_MEMBERS all members} of the group.
  */
+// TODO might make sense to split up behaviour into two classes,
+// an InvokeEffectorMembershipTrackingPolicy and an SshMultiEntityCommandEffector -- 
+// where the latter has the configurable target introduced here
+@Beta
 public class SshCommandMembershipTrackingPolicy extends AbstractMembershipTrackingPolicy {
 
     private static final Logger LOG = LoggerFactory.getLogger(SshCommandMembershipTrackingPolicy.class);
@@ -76,11 +80,11 @@ public class SshCommandMembershipTrackingPolicy extends AbstractMembershipTracki
 
         private static Converter<String, String> converter = CaseFormat.LOWER_CAMEL.converterTo(CaseFormat.UPPER_UNDERSCORE);
 
+        // TODO these methods maybe not needed due to EnumTypeCoercions
         public static ExecutionTarget fromString(String name) {
             Maybe<ExecutionTarget> parsed = tryFromString(name);
             return parsed.get();
         }
-
         public static Maybe<ExecutionTarget> tryFromString(String name) {
             try {
                 ExecutionTarget scope = valueOf(converter.convert(name));
@@ -142,6 +146,7 @@ public class SshCommandMembershipTrackingPolicy extends AbstractMembershipTracki
         }
     }
 
+    @SuppressWarnings("unchecked")
     public void execute(Entity target, String command, String type, String memberId) {
         Collection<? extends Location> locations = Locations.getLocationsCheckingAncestors(target.getLocations(), target);
         Maybe<SshMachineLocation> machine = Machines.findUniqueMachineLocation(locations, SshMachineLocation.class);