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 2015/04/17 16:34:59 UTC
[3/8] incubator-brooklyn git commit: make enrichers easier to
configure from yaml
make enrichers easier to configure from yaml
* entity spec keeps the list of specs, for things like enrichers, because equality (set duplication) is not very good for specs
* makes many of the basic enrichers easier to configure from yaml, with more flexible config
* in particular `Transformer` can be given a value supplier, e.g. `$brooklyn:formatString`
* adds a `Joiner` enricher which does `Strings.join`, handy for converting a list to something which can be used in bash
* good example of all of these in test-app-with-enrichers-slightly-simpler.yaml, referenced in the docs reference page
Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/f7142a33
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/f7142a33
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/f7142a33
Branch: refs/heads/master
Commit: f7142a3333fdabdbec0e6eb606e7b595fd8491ef
Parents: 18b6529
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Sun Apr 12 19:56:16 2015 -0500
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Sun Apr 12 20:00:53 2015 -0500
----------------------------------------------------------------------
.../brooklyn/entity/proxying/EntitySpec.java | 11 +-
.../main/java/brooklyn/enricher/Enrichers.java | 83 +++++++++++-
.../enricher/basic/AbstractAggregator.java | 7 +-
.../brooklyn/enricher/basic/Aggregator.java | 17 ++-
.../java/brooklyn/enricher/basic/Joiner.java | 128 +++++++++++++++++++
.../brooklyn/enricher/basic/Propagator.java | 51 +++++---
.../brooklyn/enricher/basic/Transformer.java | 81 +++++++++---
.../java/brooklyn/enricher/EnrichersTest.java | 45 +++++++
...est-app-with-enrichers-slightly-simpler.yaml | 57 +++++++++
docs/guide/yaml/yaml-reference.md | 5 +-
.../spi/dsl/BrooklynDslInterpreter.java | 6 +-
.../spi/dsl/methods/BrooklynDslCommon.java | 3 +-
.../EnrichersSlightlySimplerYamlTest.java | 96 ++++++++++++++
...est-app-with-enrichers-slightly-simpler.yaml | 74 +++++++++++
.../brooklyn/util/text/StringPredicates.java | 22 ++++
15 files changed, 636 insertions(+), 50 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f7142a33/api/src/main/java/brooklyn/entity/proxying/EntitySpec.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/entity/proxying/EntitySpec.java b/api/src/main/java/brooklyn/entity/proxying/EntitySpec.java
index 6fa8e73..4d8a643 100644
--- a/api/src/main/java/brooklyn/entity/proxying/EntitySpec.java
+++ b/api/src/main/java/brooklyn/entity/proxying/EntitySpec.java
@@ -41,6 +41,7 @@ import brooklyn.policy.Enricher;
import brooklyn.policy.EnricherSpec;
import brooklyn.policy.Policy;
import brooklyn.policy.PolicySpec;
+import brooklyn.util.collections.MutableList;
import com.google.common.base.Supplier;
import com.google.common.base.Throwables;
@@ -428,14 +429,14 @@ public class EntitySpec<T extends Entity> extends AbstractBrooklynObjectSpec<T,E
/** adds the supplied policies to the spec */
public <V> EntitySpec<T> policySpecs(Iterable<? extends PolicySpec<?>> val) {
checkMutable();
- policySpecs.addAll(Sets.newLinkedHashSet(checkNotNull(val, "policySpecs")));
+ policySpecs.addAll(MutableList.copyOf(checkNotNull(val, "policySpecs")));
return this;
}
/** adds the supplied policies to the spec */
public <V> EntitySpec<T> policies(Iterable<? extends Policy> val) {
checkMutable();
- policies.addAll(Sets.newLinkedHashSet(checkNotNull(val, "policies")));
+ policies.addAll(MutableList.copyOf(checkNotNull(val, "policies")));
return this;
}
@@ -456,14 +457,14 @@ public class EntitySpec<T extends Entity> extends AbstractBrooklynObjectSpec<T,E
/** adds the supplied policies to the spec */
public <V> EntitySpec<T> enricherSpecs(Iterable<? extends EnricherSpec<?>> val) {
checkMutable();
- enricherSpecs.addAll(Sets.newLinkedHashSet(checkNotNull(val, "enricherSpecs")));
+ enricherSpecs.addAll(MutableList.copyOf(checkNotNull(val, "enricherSpecs")));
return this;
}
/** adds the supplied policies to the spec */
public <V> EntitySpec<T> enrichers(Iterable<? extends Enricher> val) {
checkMutable();
- enrichers.addAll(Sets.newLinkedHashSet(checkNotNull(val, "enrichers")));
+ enrichers.addAll(MutableList.copyOf(checkNotNull(val, "enrichers")));
return this;
}
@@ -477,7 +478,7 @@ public class EntitySpec<T extends Entity> extends AbstractBrooklynObjectSpec<T,E
/** adds the supplied locations to the spec */
public <V> EntitySpec<T> locations(Iterable<? extends Location> val) {
checkMutable();
- locations.addAll(Sets.newLinkedHashSet(checkNotNull(val, "locations")));
+ locations.addAll(MutableList.copyOf(checkNotNull(val, "locations")));
return this;
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f7142a33/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 a474f37..9b34b13 100644
--- a/core/src/main/java/brooklyn/enricher/Enrichers.java
+++ b/core/src/main/java/brooklyn/enricher/Enrichers.java
@@ -29,6 +29,7 @@ import java.util.Set;
import brooklyn.enricher.basic.AbstractEnricher;
import brooklyn.enricher.basic.Aggregator;
import brooklyn.enricher.basic.Combiner;
+import brooklyn.enricher.basic.Joiner;
import brooklyn.enricher.basic.Propagator;
import brooklyn.enricher.basic.Transformer;
import brooklyn.enricher.basic.UpdatingMap;
@@ -42,13 +43,14 @@ import brooklyn.util.collections.MutableList;
import brooklyn.util.collections.MutableMap;
import brooklyn.util.collections.MutableSet;
import brooklyn.util.flags.TypeCoercions;
+import brooklyn.util.text.StringPredicates;
import brooklyn.util.text.Strings;
import com.google.common.base.Function;
-import com.google.common.base.Joiner;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
@@ -166,6 +168,12 @@ public class Enrichers {
public <S,TKey,TVal> UpdatingMapBuilder<S, TKey, TVal> updatingMap(AttributeSensor<Map<TKey,TVal>> target) {
return new UpdatingMapBuilder<S, TKey, TVal>(target);
}
+ /** creates a {@link brooklyn.enricher.basic.Joiner} enricher builder
+ * which joins entries in a list to produce a String
+ **/
+ public JoinerBuilder joining(AttributeSensor<?> source) {
+ return new JoinerBuilder(source);
+ }
}
@@ -262,6 +270,8 @@ public class Enrichers {
((input instanceof CharSequence) ? Strings.isNonBlank((CharSequence)input) : true);
}
};
+ // above kept for deserialization; not sure necessary
+ valueFilter = StringPredicates.isNonBlank();
} else {
valueFilter = null;
}
@@ -496,10 +506,10 @@ public class Enrichers {
if (propagatingAllBut!=null && !Iterables.isEmpty(propagatingAllBut)) {
List<String> allBut = MutableList.of();
for (Sensor<?> s: propagatingAllBut) allBut.add(s.getName());
- summary.add("ALL_BUT:"+Joiner.on(",").join(allBut));
+ summary.add("ALL_BUT:"+com.google.common.base.Joiner.on(",").join(allBut));
}
- return "propagating["+fromEntity.getId()+":"+Joiner.on(",").join(summary)+"]";
+ return "propagating["+fromEntity.getId()+":"+com.google.common.base.Joiner.on(",").join(summary)+"]";
}
public EnricherSpec<? extends Enricher> build() {
return super.build().configure(MutableMap.builder()
@@ -581,6 +591,67 @@ public class Enrichers {
}
}
+ protected abstract static class AbstractJoinerBuilder<B extends AbstractJoinerBuilder<B>> extends AbstractEnricherBuilder<B> {
+ protected final AttributeSensor<?> transforming;
+ protected AttributeSensor<String> publishing;
+ protected Entity fromEntity;
+ protected String separator;
+ protected Boolean quote;
+ protected Integer minimum;
+ protected Integer maximum;
+
+ public AbstractJoinerBuilder(AttributeSensor<?> source) {
+ super(Joiner.class);
+ this.transforming = checkNotNull(source);
+ }
+ public B publishing(AttributeSensor<String> target) {
+ this.publishing = checkNotNull(target);
+ return self();
+ }
+ public B separator(String separator) {
+ this.separator = separator;
+ return self();
+ }
+ public B quote(Boolean quote) {
+ this.quote = quote;
+ return self();
+ }
+ public B minimum(Integer minimum) {
+ this.minimum = minimum;
+ return self();
+ }
+ public B maximum(Integer maximum) {
+ this.maximum = maximum;
+ return self();
+ }
+ @Override
+ protected String getDefaultUniqueTag() {
+ if (transforming==null || publishing==null) return null;
+ return "joiner:"+transforming.getName()+"->"+publishing.getName();
+ }
+ public EnricherSpec<?> build() {
+ return super.build().configure(MutableMap.builder()
+ .putIfNotNull(Joiner.PRODUCER, fromEntity)
+ .put(Joiner.TARGET_SENSOR, publishing)
+ .put(Joiner.SOURCE_SENSOR, transforming)
+ .putIfNotNull(Joiner.SEPARATOR, separator)
+ .putIfNotNull(Joiner.QUOTE, quote)
+ .putIfNotNull(Joiner.MINIMUM, minimum)
+ .putIfNotNull(Joiner.MAXIMUM, maximum)
+ .build());
+ }
+
+ @Override
+ public String toString() {
+ return Objects.toStringHelper(this)
+ .omitNullValues()
+ .add("publishing", publishing)
+ .add("transforming", transforming)
+ .add("separator", separator)
+ .toString();
+ }
+ }
+
public static class InitialBuilder extends AbstractInitialBuilder<InitialBuilder> {
}
@@ -626,6 +697,12 @@ public class Enrichers {
}
}
+ public static class JoinerBuilder extends AbstractJoinerBuilder<JoinerBuilder> {
+ public JoinerBuilder(AttributeSensor<?> source) {
+ super(source);
+ }
+ }
+
protected static <T extends Number> T average(Collection<T> vals, Number defaultValueForUnreportedSensors, Number valueToReportIfNoSensors, TypeToken<T> type) {
Double doubleValueToReportIfNoSensors = (valueToReportIfNoSensors == null) ? null : valueToReportIfNoSensors.doubleValue();
int count = count(vals, defaultValueForUnreportedSensors!=null);
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f7142a33/core/src/main/java/brooklyn/enricher/basic/AbstractAggregator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/enricher/basic/AbstractAggregator.java b/core/src/main/java/brooklyn/enricher/basic/AbstractAggregator.java
index 9568332..a76a602 100644
--- a/core/src/main/java/brooklyn/enricher/basic/AbstractAggregator.java
+++ b/core/src/main/java/brooklyn/enricher/basic/AbstractAggregator.java
@@ -117,10 +117,15 @@ public abstract class AbstractAggregator<T,U> extends AbstractEnricher implement
this.fromMembers = Maybe.fromNullable(getConfig(FROM_MEMBERS)).or(fromMembers);
this.fromChildren = Maybe.fromNullable(getConfig(FROM_CHILDREN)).or(fromChildren);
this.entityFilter = (Predicate<? super Entity>) (getConfig(ENTITY_FILTER) == null ? Predicates.alwaysTrue() : getConfig(ENTITY_FILTER));
- this.valueFilter = (Predicate<? super T>) (getConfig(VALUE_FILTER) == null ? Predicates.alwaysTrue() : getConfig(VALUE_FILTER));
+ this.valueFilter = (Predicate<? super T>) (getConfig(VALUE_FILTER) == null ? getDefaultValueFilter() : getConfig(VALUE_FILTER));
setEntityLoadingTargetConfig();
}
+
+ protected Predicate<?> getDefaultValueFilter() {
+ return Predicates.alwaysTrue();
+ }
+
@SuppressWarnings({ "unchecked" })
protected void setEntityLoadingTargetConfig() {
this.targetSensor = (Sensor<U>) getRequiredConfig(TARGET_SENSOR);
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f7142a33/core/src/main/java/brooklyn/enricher/basic/Aggregator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/enricher/basic/Aggregator.java b/core/src/main/java/brooklyn/enricher/basic/Aggregator.java
index 3e896db..cb80431 100644
--- a/core/src/main/java/brooklyn/enricher/basic/Aggregator.java
+++ b/core/src/main/java/brooklyn/enricher/basic/Aggregator.java
@@ -27,10 +27,8 @@ import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import brooklyn.catalog.Catalog;
import brooklyn.config.BrooklynLogging;
import brooklyn.config.ConfigKey;
-import brooklyn.config.BrooklynLogging.LoggingLevel;
import brooklyn.entity.Entity;
import brooklyn.entity.basic.ConfigKeys;
import brooklyn.event.AttributeSensor;
@@ -40,8 +38,11 @@ import brooklyn.event.SensorEventListener;
import brooklyn.util.collections.MutableList;
import brooklyn.util.collections.MutableMap;
import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.text.StringPredicates;
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;
@@ -54,6 +55,7 @@ public class Aggregator<T,U> extends AbstractAggregator<T,U> implements SensorEv
public static final ConfigKey<Sensor<?>> SOURCE_SENSOR = ConfigKeys.newConfigKey(new TypeToken<Sensor<?>>() {}, "enricher.sourceSensor");
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);
protected Sensor<T> sourceSensor;
protected Function<? super Collection<T>, ? extends U> transformation;
@@ -71,7 +73,7 @@ public class Aggregator<T,U> extends AbstractAggregator<T,U> implements SensorEv
protected void setEntityLoadingConfig() {
super.setEntityLoadingConfig();
this.sourceSensor = (Sensor<T>) getRequiredConfig(SOURCE_SENSOR);
- this.transformation = (Function<? super Collection<T>, ? extends U>) getRequiredConfig(TRANSFORMATION);
+ this.transformation = (Function<? super Collection<T>, ? extends U>) config().get(TRANSFORMATION);
}
@Override
@@ -124,6 +126,14 @@ 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();
@@ -156,6 +166,7 @@ public class Aggregator<T,U> extends AbstractAggregator<T,U> implements SensorEv
synchronized (values) {
// TODO Could avoid copying when filter not needed
List<T> vs = MutableList.copyOf(Iterables.filter(values.values(), valueFilter));
+ if (transformation==null) return vs;
return transformation.apply(vs);
}
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f7142a33/core/src/main/java/brooklyn/enricher/basic/Joiner.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/enricher/basic/Joiner.java b/core/src/main/java/brooklyn/enricher/basic/Joiner.java
new file mode 100644
index 0000000..5189273
--- /dev/null
+++ b/core/src/main/java/brooklyn/enricher/basic/Joiner.java
@@ -0,0 +1,128 @@
+/*
+ * 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 brooklyn.enricher.basic;
+
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.basic.EntityLocal;
+import brooklyn.event.AttributeSensor;
+import brooklyn.event.Sensor;
+import brooklyn.event.SensorEvent;
+import brooklyn.event.SensorEventListener;
+import brooklyn.event.basic.BasicSensorEvent;
+import brooklyn.util.collections.MutableList;
+import brooklyn.util.flags.SetFromFlag;
+import brooklyn.util.text.StringEscapes;
+import brooklyn.util.text.Strings;
+
+import com.google.common.reflect.TypeToken;
+
+//@Catalog(name="Transformer", description="Transforms attributes of an entity; see Enrichers.builder().transforming(...)")
+@SuppressWarnings("serial")
+public class Joiner<T> extends AbstractEnricher implements SensorEventListener<T> {
+
+ private static final Logger LOG = LoggerFactory.getLogger(Joiner.class);
+
+ public static ConfigKey<Entity> PRODUCER = ConfigKeys.newConfigKey(Entity.class, "enricher.producer");
+ public static ConfigKey<Sensor<?>> SOURCE_SENSOR = ConfigKeys.newConfigKey(new TypeToken<Sensor<?>>() {}, "enricher.sourceSensor");
+ 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("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")
+ public static ConfigKey<Integer> MINIMUM = ConfigKeys.newIntegerConfigKey("enricher.joiner.minimum", "Minimum number of elements to join; if fewer than this, sets null; default 0 (no minimum)");
+ @SetFromFlag("maximum")
+ public static ConfigKey<Integer> MAXIMUM = ConfigKeys.newIntegerConfigKey("enricher.joiner.maximum", "Maximum number of elements to join; default null means all elements always taken");
+
+// protected Function<? super SensorEvent<T>, ? extends U> transformation;
+ protected Entity producer;
+ protected AttributeSensor<T> sourceSensor;
+ protected Sensor<String> targetSensor;
+
+ public Joiner() {
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ @Override
+ public void setEntity(EntityLocal entity) {
+ super.setEntity(entity);
+
+ this.producer = getConfig(PRODUCER) == null ? entity: getConfig(PRODUCER);
+ this.sourceSensor = (AttributeSensor<T>) getRequiredConfig(SOURCE_SENSOR);
+ this.targetSensor = (Sensor<String>) getRequiredConfig(TARGET_SENSOR);
+
+ subscribe(producer, sourceSensor, this);
+
+ Object value = producer.getAttribute((AttributeSensor<?>)sourceSensor);
+ // TODO would be useful to have a convenience to "subscribeAndThenIfItIsAlreadySetRunItOnce"
+ if (value!=null) {
+ onEvent(new BasicSensorEvent(sourceSensor, producer, value, -1));
+ }
+ }
+
+ @Override
+ public void onEvent(SensorEvent<T> event) {
+ emit(targetSensor, compute(event));
+ }
+
+ protected Object compute(SensorEvent<T> event) {
+ Object v = event.getValue();
+ Object result = null;
+ if (v!=null) {
+ if (v instanceof Map) {
+ 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);
+ for (Object ci: (Iterable<?>)v) {
+ if (maximum!=null && maximum>=0) {
+ if (c1.size()>=maximum) break;
+ }
+ c1.appendIfNotNull(Strings.toString(ci));
+ }
+ Integer minimum = getConfig(MINIMUM);
+ if (minimum!=null && c1.size() < minimum) {
+ // use default null return value
+ } else {
+ if (getConfig(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));
+ }
+ }
+ }
+ if (LOG.isTraceEnabled())
+ LOG.trace("Enricher "+this+" computed "+result+" from "+event);
+ return result;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f7142a33/core/src/main/java/brooklyn/enricher/basic/Propagator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/enricher/basic/Propagator.java b/core/src/main/java/brooklyn/enricher/basic/Propagator.java
index 5e3ae23..fd012a3 100644
--- a/core/src/main/java/brooklyn/enricher/basic/Propagator.java
+++ b/core/src/main/java/brooklyn/enricher/basic/Propagator.java
@@ -34,6 +34,7 @@ import brooklyn.event.AttributeSensor;
import brooklyn.event.Sensor;
import brooklyn.event.SensorEvent;
import brooklyn.event.SensorEventListener;
+import brooklyn.util.collections.MutableMap;
import brooklyn.util.flags.SetFromFlag;
import com.google.common.base.Preconditions;
@@ -42,7 +43,6 @@ import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
-import com.google.common.collect.Maps;
import com.google.common.reflect.TypeToken;
@SuppressWarnings("serial")
@@ -73,6 +73,7 @@ public class Propagator extends AbstractEnricher implements SensorEventListener<
protected Entity producer;
protected Map<? extends Sensor<?>, ? extends Sensor<?>> sensorMapping;
protected boolean propagatingAll;
+ protected Collection<Sensor<?>> propagatingAllBut;
protected Predicate<Sensor<?>> sensorFilter;
public Propagator() {
@@ -83,44 +84,62 @@ public class Propagator extends AbstractEnricher implements SensorEventListener<
super.setEntity(entity);
this.producer = getConfig(PRODUCER) == null ? entity : getConfig(PRODUCER);
+ boolean sensorMappingSet = getConfig(SENSOR_MAPPING)!=null;
+ MutableMap<Sensor<?>,Sensor<?>> sensorMappingTemp = MutableMap.copyOf(getConfig(SENSOR_MAPPING));
+ this.propagatingAll = Boolean.TRUE.equals(getConfig(PROPAGATING_ALL)) || getConfig(PROPAGATING_ALL_BUT)!=null;
+
if (getConfig(PROPAGATING) != null) {
- if (Boolean.TRUE.equals(getConfig(PROPAGATING_ALL)) || getConfig(PROPAGATING_ALL_BUT) != null) {
+ if (propagatingAll) {
throw new IllegalStateException("Propagator enricher "+this+" must not have 'propagating' set at same time as either 'propagatingAll' or 'propagatingAllBut'");
}
- Map<Sensor<?>, Sensor<?>> sensorMappingTemp = Maps.newLinkedHashMap();
- if (getConfig(SENSOR_MAPPING) != null) {
- sensorMappingTemp.putAll(getConfig(SENSOR_MAPPING));
- }
for (Sensor<?> sensor : getConfig(PROPAGATING)) {
if (!sensorMappingTemp.containsKey(sensor)) {
sensorMappingTemp.put(sensor, sensor);
}
}
this.sensorMapping = ImmutableMap.copyOf(sensorMappingTemp);
- this.propagatingAll = false;
this.sensorFilter = new Predicate<Sensor<?>>() {
@Override public boolean apply(Sensor<?> input) {
- return input != null && sensorMapping.keySet().contains(input);
+ // TODO kept for deserialization of inner classes, but shouldn't be necessary, as with other inner classes (qv);
+ // NB: previously this did this check:
+// return input != null && sensorMapping.keySet().contains(input);
+ // but those clauses seems wrong (when would input be null?) and unnecessary (we are doing an explicit subscribe in this code path)
+ return true;
}
};
- } else if (getConfig(PROPAGATING_ALL_BUT) == null) {
- this.sensorMapping = getConfig(SENSOR_MAPPING) == null ? ImmutableMap.<Sensor<?>, Sensor<?>>of() : getConfig(SENSOR_MAPPING);
- this.propagatingAll = Boolean.TRUE.equals(getConfig(PROPAGATING_ALL));
+ } else if (sensorMappingSet) {
+ if (propagatingAll) {
+ throw new IllegalStateException("Propagator enricher "+this+" must not have 'sensorMapping' set at same time as either 'propagatingAll' or 'propagatingAllBut'");
+ }
+ this.sensorMapping = ImmutableMap.copyOf(sensorMappingTemp);
this.sensorFilter = Predicates.alwaysTrue();
} else {
- this.sensorMapping = getConfig(SENSOR_MAPPING) == null ? ImmutableMap.<Sensor<?>, Sensor<?>>of() : getConfig(SENSOR_MAPPING);
- this.propagatingAll = true;
+ this.sensorMapping = ImmutableMap.<Sensor<?>, Sensor<?>>of();
+ if (!propagatingAll) {
+ // default if nothing specified is to do all but the ones not usually propagated
+ propagatingAll = true;
+ // user specified nothing, so *set* the all_but to the default set
+ // if desired, we could allow this to be dynamically reconfigurable, remove this field and always look up;
+ // slight performance hit (always looking up), and might need to recompute subscriptions, so not supported currently
+ propagatingAllBut = SENSORS_NOT_USUALLY_PROPAGATED;
+ } else {
+ propagatingAllBut = getConfig(PROPAGATING_ALL_BUT);
+ }
this.sensorFilter = new Predicate<Sensor<?>>() {
@Override public boolean apply(Sensor<?> input) {
- Collection<Sensor<?>> exclusions = getConfig(PROPAGATING_ALL_BUT);
- return input != null && !exclusions.contains(input);
+ Collection<Sensor<?>> exclusions = propagatingAllBut;
+ // TODO this anonymous inner class and getConfig check kept should be removed / confirmed for rebind compatibility.
+ // we *should* be regenerating these fields on each rebind (calling to this method),
+ // so serialization of this class shouldn't be needed (and should be skipped), but that needs to be checked.
+ if (propagatingAllBut==null) exclusions = getConfig(PROPAGATING_ALL_BUT);
+ return input != null && (exclusions==null || !exclusions.contains(input));
}
};
}
Preconditions.checkState(propagatingAll ^ sensorMapping.size() > 0,
- "Exactly one must be set of propagatingAll (%s, excluding %s), sensorMapping (%s)", propagatingAll, getConfig(PROPAGATING_ALL_BUT), sensorMapping);
+ "Nothing to propagate; detected: propagatingAll (%s, excluding %s), sensorMapping (%s)", propagatingAll, getConfig(PROPAGATING_ALL_BUT), sensorMapping);
if (propagatingAll) {
subscribe(producer, null, this);
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f7142a33/core/src/main/java/brooklyn/enricher/basic/Transformer.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/enricher/basic/Transformer.java b/core/src/main/java/brooklyn/enricher/basic/Transformer.java
index 86911e7..0811fcc 100644
--- a/core/src/main/java/brooklyn/enricher/basic/Transformer.java
+++ b/core/src/main/java/brooklyn/enricher/basic/Transformer.java
@@ -26,24 +26,29 @@ import org.slf4j.LoggerFactory;
import brooklyn.config.ConfigKey;
import brooklyn.entity.Entity;
import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.basic.EntityInternal;
import brooklyn.entity.basic.EntityLocal;
import brooklyn.event.AttributeSensor;
import brooklyn.event.Sensor;
import brooklyn.event.SensorEvent;
import brooklyn.event.SensorEventListener;
import brooklyn.event.basic.BasicSensorEvent;
+import brooklyn.util.collections.MutableSet;
+import brooklyn.util.task.Tasks;
+import brooklyn.util.time.Duration;
import com.google.common.base.Function;
import com.google.common.reflect.TypeToken;
-//@Catalog(name="Transformer", description="Transformers attributes of an entity; see Enrichers.builder().transforming(...)")
+//@Catalog(name="Transformer", description="Transforms attributes of an entity; see Enrichers.builder().transforming(...)")
@SuppressWarnings("serial")
public class Transformer<T,U> extends AbstractEnricher implements SensorEventListener<T> {
private static final Logger LOG = LoggerFactory.getLogger(Transformer.class);
+ // exactly one of these should be supplied to set a value
+ public static ConfigKey<?> TARGET_VALUE = ConfigKeys.newConfigKey(Object.class, "enricher.targetValue");
public static ConfigKey<Function<?, ?>> TRANSFORMATION_FROM_VALUE = ConfigKeys.newConfigKey(new TypeToken<Function<?, ?>>() {}, "enricher.transformation");
-
public static ConfigKey<Function<?, ?>> TRANSFORMATION_FROM_EVENT = ConfigKeys.newConfigKey(new TypeToken<Function<?, ?>>() {}, "enricher.transformation.fromevent");
public static ConfigKey<Entity> PRODUCER = ConfigKeys.newConfigKey(Entity.class, "enricher.producer");
@@ -52,7 +57,7 @@ public class Transformer<T,U> extends AbstractEnricher implements SensorEventLis
public static ConfigKey<Sensor<?>> TARGET_SENSOR = ConfigKeys.newConfigKey(new TypeToken<Sensor<?>>() {}, "enricher.targetSensor");
- protected Function<? super SensorEvent<T>, ? extends U> transformation;
+// protected Function<? super SensorEvent<T>, ? extends U> transformation;
protected Entity producer;
protected Sensor<T> sourceSensor;
protected Sensor<U> targetSensor;
@@ -64,20 +69,8 @@ public class Transformer<T,U> extends AbstractEnricher implements SensorEventLis
@Override
public void setEntity(EntityLocal entity) {
super.setEntity(entity);
-
- final Function<? super T, ? extends U> transformationFromValue = (Function<? super T, ? extends U>) getConfig(TRANSFORMATION_FROM_VALUE);
- final Function<? super SensorEvent<T>, ? extends U> transformationFromEvent = (Function<? super SensorEvent<T>, ? extends U>) getConfig(TRANSFORMATION_FROM_EVENT);
- checkArgument(transformationFromEvent != null ^ transformationFromValue != null, "must set exactly one of %s or %s", TRANSFORMATION_FROM_VALUE.getName(), TRANSFORMATION_FROM_EVENT.getName());
- if (transformationFromEvent != null) {
- transformation = transformationFromEvent;
- } else {
- // TODO new named class
- transformation = new Function<SensorEvent<T>, U>() {
- @Override public U apply(SensorEvent<T> input) {
- return transformationFromValue.apply(input.getValue());
- }
- };
- }
+
+ Function<SensorEvent<T>, U> transformation = getTransformation();
this.producer = getConfig(PRODUCER) == null ? entity: getConfig(PRODUCER);
this.sourceSensor = (Sensor<T>) getRequiredConfig(SOURCE_SENSOR);
Sensor<?> targetSensorSpecified = getConfig(TARGET_SENSOR);
@@ -102,13 +95,65 @@ public class Transformer<T,U> extends AbstractEnricher implements SensorEventLis
}
}
+ /** returns a function for transformation, for immediate use only (not for caching, as it may change) */
+ @SuppressWarnings("unchecked")
+ protected Function<SensorEvent<T>, U> getTransformation() {
+ MutableSet<Object> suppliers = MutableSet.of();
+ suppliers.addIfNotNull(config().getRaw(TARGET_VALUE).orNull());
+ suppliers.addIfNotNull(config().getRaw(TRANSFORMATION_FROM_EVENT).orNull());
+ suppliers.addIfNotNull(config().getRaw(TRANSFORMATION_FROM_VALUE).orNull());
+ checkArgument(suppliers.size()==1,
+ "Must set exactly one of: %s, %s, %s", TARGET_VALUE.getName(), TRANSFORMATION_FROM_VALUE.getName(), TRANSFORMATION_FROM_EVENT.getName());
+
+ Function<?, ?> fromEvent = config().get(TRANSFORMATION_FROM_EVENT);
+ if (fromEvent != null) {
+ return (Function<SensorEvent<T>, U>) fromEvent;
+ }
+
+ final Function<T, U> fromValueFn = (Function<T, U>) config().get(TRANSFORMATION_FROM_VALUE);
+ if (fromValueFn != null) {
+ // named class not necessary as result should not be serialized
+ return new Function<SensorEvent<T>, U>() {
+ @Override public U apply(SensorEvent<T> input) {
+ return fromValueFn.apply(input.getValue());
+ }
+ @Override
+ public String toString() {
+ return ""+fromValueFn;
+ }
+ };
+ }
+
+ // from target value
+ // named class not necessary as result should not be serialized
+ final Object targetValueRaw = config().getRaw(TARGET_VALUE).orNull();
+ return new Function<SensorEvent<T>, U>() {
+ @Override public U apply(SensorEvent<T> input) {
+ // evaluate immediately, or return null
+ // 200ms seems a reasonable compromise for tasks which require BG evaluation
+ // but which are non-blocking
+ // TODO better would be to have a mode in which tasks are not permitted to block on
+ // external events; they can submit tasks and block on them (or even better, have a callback architecture);
+ // however that is a non-trivial refactoring
+ return (U) Tasks.resolving(targetValueRaw).as(targetSensor.getType())
+ .context( ((EntityInternal)entity).getExecutionContext() )
+ .description("Computing sensor "+targetSensor+" from "+targetValueRaw)
+ .timeout(Duration.millis(200))
+ .getMaybe().orNull();
+ }
+ public String toString() {
+ return ""+targetValueRaw;
+ }
+ };
+ }
+
@Override
public void onEvent(SensorEvent<T> event) {
emit(targetSensor, compute(event));
}
protected Object compute(SensorEvent<T> event) {
- U result = transformation.apply(event);
+ U result = getTransformation().apply(event);
if (LOG.isTraceEnabled())
LOG.trace("Enricher "+this+" computed "+result+" from "+event);
return result;
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f7142a33/core/src/test/java/brooklyn/enricher/EnrichersTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/enricher/EnrichersTest.java b/core/src/test/java/brooklyn/enricher/EnrichersTest.java
index 12f1cad..43d55c3 100644
--- a/core/src/test/java/brooklyn/enricher/EnrichersTest.java
+++ b/core/src/test/java/brooklyn/enricher/EnrichersTest.java
@@ -40,6 +40,7 @@ import brooklyn.test.Asserts;
import brooklyn.test.EntityTestUtils;
import brooklyn.test.entity.TestEntity;
import brooklyn.util.collections.CollectionFunctionals;
+import brooklyn.util.collections.MutableList;
import brooklyn.util.collections.MutableMap;
import brooklyn.util.collections.MutableSet;
import brooklyn.util.guava.Functionals;
@@ -250,6 +251,9 @@ public class EnrichersTest extends BrooklynAppUnitTestSupport {
entity2.setAttribute(STR1, "myval");
EntityTestUtils.assertAttributeEqualsEventually(entity, STR1, "myval");
+
+ entity2.setAttribute(STR1, null);
+ EntityTestUtils.assertAttributeEqualsEventually(entity, STR1, null);
}
@Test
@@ -431,4 +435,45 @@ public class EnrichersTest extends BrooklynAppUnitTestSupport {
EntityTestUtils.assertAttributeEqualsEventually(entity, mapSensor, MutableMap.<String,String>of());
}
+ private static AttributeSensor<Object> LIST_SENSOR = Sensors.newSensor(Object.class, "sensor.list");
+
+ @Test
+ public void testJoinerDefault() {
+ entity.addEnricher(Enrichers.builder()
+ .joining(LIST_SENSOR)
+ .publishing(TestEntity.NAME)
+ .build());
+ // null values ignored, and it quotes
+ entity.setAttribute(LIST_SENSOR, MutableList.<String>of("a", "\"b").append(null));
+ EntityTestUtils.assertAttributeEqualsEventually(entity, TestEntity.NAME, "\"a\",\"\\\"b\"");
+
+ // empty list causes ""
+ entity.setAttribute(LIST_SENSOR, MutableList.<String>of().append(null));
+ EntityTestUtils.assertAttributeEqualsEventually(entity, TestEntity.NAME, "");
+
+ // null causes null
+ entity.setAttribute(LIST_SENSOR, null);
+ EntityTestUtils.assertAttributeEqualsEventually(entity, TestEntity.NAME, null);
+ }
+
+
+ @Test
+ public void testJoinerUnquoted() {
+ entity.setAttribute(LIST_SENSOR, MutableList.<String>of("a", "\"b", "ccc").append(null));
+ entity.addEnricher(Enrichers.builder()
+ .joining(LIST_SENSOR)
+ .publishing(TestEntity.NAME)
+ .minimum(1)
+ .maximum(2)
+ .separator(":")
+ .quote(false)
+ .build());
+ // in this case, it should be immediately available upon adding the enricher
+ EntityTestUtils.assertAttributeEquals(entity, TestEntity.NAME, "a:\"b");
+
+ // empty list causes null here, because below the minimum
+ entity.setAttribute(LIST_SENSOR, MutableList.<String>of().append(null));
+ EntityTestUtils.assertAttributeEqualsEventually(entity, TestEntity.NAME, null);
+ }
+
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f7142a33/docs/guide/yaml/example_yaml/test-app-with-enrichers-slightly-simpler.yaml
----------------------------------------------------------------------
diff --git a/docs/guide/yaml/example_yaml/test-app-with-enrichers-slightly-simpler.yaml b/docs/guide/yaml/example_yaml/test-app-with-enrichers-slightly-simpler.yaml
new file mode 100644
index 0000000..a6a8116
--- /dev/null
+++ b/docs/guide/yaml/example_yaml/test-app-with-enrichers-slightly-simpler.yaml
@@ -0,0 +1,57 @@
+#
+# example showing how enrichers can be set
+#
+name: test-app-with-enrichers
+description: Testing many enrichers
+services:
+- type: brooklyn.entity.group.DynamicCluster
+ id: cluster
+ initialSize: 3
+ location: localhost
+ memberSpec:
+ $brooklyn:entitySpec:
+ type: brooklyn.test.entity.TestEntity
+ brooklyn.enrichers:
+ - type: brooklyn.enricher.basic.Transformer
+ # transform "ip" (which we expect a feed, not shown here, to set) to a URL;
+ # you can curl an address string to the sensors/ip endpoint an entity to trigger these enrichers
+ brooklyn.config:
+ enricher.sourceSensor: $brooklyn:sensor("ip")
+ enricher.targetSensor: $brooklyn:sensor("url")
+ enricher.targetValue: $brooklyn:formatString("http://%s/", $brooklyn:attributeWhenReady("ip"))
+ - type: brooklyn.enricher.basic.Propagator
+ # use propagator to duplicate one sensor as another, giving the supplied sensor mapping;
+ # the other use of Propagator is where you specify a producer (using $brooklyn:entity(...) as below)
+ # from which to take sensors; in that mode you can specify `propagate` as a list of sensors whose names are unchanged,
+ # instead of (or in addition to) this map
+ brooklyn.config:
+ sensorMapping:
+ $brooklyn:sensor("url"): $brooklyn:sensor("brooklyn.entity.basic.Attributes", "main.uri")
+ brooklyn.enrichers:
+ - type: brooklyn.enricher.basic.Aggregator
+ # aggregate `url` sensors from children into a list
+ brooklyn.config:
+ enricher.sourceSensor: $brooklyn:sensor("url")
+ enricher.targetSensor: $brooklyn:sensor("urls.list")
+ enricher.aggregating.fromMembers: true
+ - type: brooklyn.enricher.basic.Joiner
+ # create a string from that list, for use e.g. in bash scripts
+ brooklyn.config:
+ enricher.sourceSensor: $brooklyn:sensor("urls.list")
+ enricher.targetSensor: $brooklyn:sensor("urls.list.comma_separated.max_2")
+ maximum: 2
+ # TODO infer uniqueTag, name etc
+ uniqueTag: urls.list.comma_separated.max_2
+ - type: brooklyn.enricher.basic.Joiner
+ # pick one uri as the main one to use
+ brooklyn.config:
+ enricher.sourceSensor: $brooklyn:sensor("urls.list")
+ enricher.targetSensor: $brooklyn:sensor("brooklyn.entity.basic.Attributes", "main.uri")
+ quote: false
+ maximum: 1
+brooklyn.enrichers:
+- type: brooklyn.enricher.basic.Propagator
+ # if nothing specified for `propagating` or `sensorMapping` then
+ # Propagator will do all but the usual lifecycle defaults, handy at the root!
+ brooklyn.config:
+ producer: $brooklyn:entity("cluster")
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f7142a33/docs/guide/yaml/yaml-reference.md
----------------------------------------------------------------------
diff --git a/docs/guide/yaml/yaml-reference.md b/docs/guide/yaml/yaml-reference.md
index 07fffc9..71993ae 100644
--- a/docs/guide/yaml/yaml-reference.md
+++ b/docs/guide/yaml/yaml-reference.md
@@ -37,7 +37,10 @@ the entity being defined, with these being the most common:
* `brooklyn.policies`: a list of policies, each as a map described with their `type` and their `brooklyn.config` as keys
-* `brooklyn.enrichers`: a list of enrichers, each as a map described with their `type` and their `brooklyn.config` as keys
+* `brooklyn.enrichers`: a list of enrichers, each as a map described with their `type` and their `brooklyn.config` as keys;
+ see the keys declared on individual enrichers;
+ also see [this enricher example](example_yaml/test-app-with-enrichers-slightly-simpler.yaml) for a detailed and commented illustration
+ <!-- TODO assert that this yaml maches the yaml we test against -->
* `brooklyn.initializers`: a list of `EntityInitializer` instances to be constructed and run against the entity,
each as a map described with their `type` and their `brooklyn.config` as keys.
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f7142a33/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/dsl/BrooklynDslInterpreter.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/dsl/BrooklynDslInterpreter.java b/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/dsl/BrooklynDslInterpreter.java
index 9cf3233..d661403 100644
--- a/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/dsl/BrooklynDslInterpreter.java
+++ b/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/dsl/BrooklynDslInterpreter.java
@@ -115,8 +115,10 @@ public class BrooklynDslInterpreter extends PlanInterpreterAdapter {
try {
// TODO in future we should support functions of the form 'Maps.clear', 'Maps.reset', 'Maps.remove', etc;
// default approach only supported if mapIn has single item and mapOut is empty
- if (mapIn.size()!=1) throw new IllegalStateException("Map-entry DSL syntax only supported with single item in map, not "+mapIn);
- if (mapOut.size()!=0) throw new IllegalStateException("Map-entry DSL syntax only supported with empty output map-so-far, not "+mapOut);
+ if (mapIn.size()!=1)
+ throw new IllegalStateException("Map-entry DSL syntax only supported with single item in map, not "+mapIn);
+ if (mapOut.size()!=0)
+ throw new IllegalStateException("Map-entry DSL syntax only supported with empty output map-so-far, not "+mapOut);
node.setNewValue( evaluate(new FunctionWithArgs(f.getFunction(), args), false) );
return false;
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f7142a33/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 da19f6e..108cd98 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
@@ -101,7 +101,7 @@ public class BrooklynDslCommon {
return new DslComponent(Scope.THIS, "").sensor(sensorName);
}
- /** Returns a {@link Sensor} from the given entity type. */
+ /** Returns a {@link Sensor} declared on the type (e.g. entity class) declared in the first argument. */
@SuppressWarnings({ "unchecked", "rawtypes" })
public static Sensor<?> sensor(String clazzName, String sensorName) {
try {
@@ -117,6 +117,7 @@ public class BrooklynDslCommon {
sensor = sensors.get(sensorName);
}
if (sensor == null) {
+ // TODO could extend API to return a sensor of the given type; useful but makes API ambiguous in theory (unlikely in practise, but still...)
throw new IllegalArgumentException("Sensor " + sensorName + " not found on class " + clazzName);
}
return sensor;
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f7142a33/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/EnrichersSlightlySimplerYamlTest.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/EnrichersSlightlySimplerYamlTest.java b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/EnrichersSlightlySimplerYamlTest.java
new file mode 100644
index 0000000..99165c5
--- /dev/null
+++ b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/EnrichersSlightlySimplerYamlTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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 io.brooklyn.camp.brooklyn;
+
+import java.net.URI;
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.Attributes;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.basic.EntityInternal;
+import brooklyn.entity.group.DynamicCluster;
+import brooklyn.event.basic.Sensors;
+import brooklyn.test.EntityTestUtils;
+import brooklyn.util.collections.CollectionFunctionals;
+import brooklyn.util.text.StringPredicates;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.Iterables;
+
+/** Tests some improvements to enricher classes to make them a bit more yaml friendly.
+ * Called "SlightlySimpler" as it would be nice to make enrichers a lot more yaml friendly! */
+@Test
+public class EnrichersSlightlySimplerYamlTest extends AbstractYamlTest {
+ private static final Logger log = LoggerFactory.getLogger(EnrichersSlightlySimplerYamlTest.class);
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ @Test
+ public void testWithAppEnricher() throws Exception {
+ Entity app = createAndStartApplication(loadYaml("test-app-with-enrichers-slightly-simpler.yaml"));
+ waitForApplicationTasks(app);
+ log.info("Started "+app+":");
+ Entities.dumpInfo(app);
+
+ Entity cluster = Iterables.getOnlyElement( app.getChildren() );
+ Collection<Entity> leafs = ((DynamicCluster)cluster).getMembers();
+ Iterator<Entity> li = leafs.iterator();
+
+ Entity e1 = li.next();
+ ((EntityInternal)e1).setAttribute(Sensors.newStringSensor("ip"), "127.0.0.1");
+ EntityTestUtils.assertAttributeEqualsEventually(e1, Sensors.newStringSensor("url"), "http://127.0.0.1/");
+ EntityTestUtils.assertAttributeEqualsEventually(e1, Attributes.MAIN_URI, URI.create("http://127.0.0.1/"));
+
+ int i=2;
+ while (li.hasNext()) {
+ Entity ei = li.next();
+ ((EntityInternal)ei).setAttribute(Sensors.newStringSensor("ip"), "127.0.0."+i);
+ i++;
+ }
+
+ EntityTestUtils.assertAttributeEventually(cluster, Sensors.newSensor(Iterable.class, "urls.list"),
+ (Predicate)CollectionFunctionals.sizeEquals(3));
+
+ EntityTestUtils.assertAttributeEventually(cluster, Sensors.newSensor(String.class, "urls.list.comma_separated.max_2"),
+ StringPredicates.matchesRegex("\"http:\\/\\/127[^\"]*\\/\",\"http:\\/\\/127[^\"]*\\/\""));
+
+ EntityTestUtils.assertAttributeEventually(cluster, Attributes.MAIN_URI, Predicates.notNull());
+ URI main = cluster.getAttribute(Attributes.MAIN_URI);
+ Assert.assertTrue(main.toString().matches("http:\\/\\/127.0.0..\\/"), "Wrong URI: "+main);
+
+ EntityTestUtils.assertAttributeEventually(app, Attributes.MAIN_URI, Predicates.notNull());
+ main = app.getAttribute(Attributes.MAIN_URI);
+ Assert.assertTrue(main.toString().matches("http:\\/\\/127.0.0..\\/"), "Wrong URI: "+main);
+
+ // TODO would we want to allow "all-but-usual" as the default if nothing specified
+ }
+
+ @Override
+ protected Logger getLogger() {
+ return log;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f7142a33/usage/camp/src/test/resources/test-app-with-enrichers-slightly-simpler.yaml
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/resources/test-app-with-enrichers-slightly-simpler.yaml b/usage/camp/src/test/resources/test-app-with-enrichers-slightly-simpler.yaml
new file mode 100644
index 0000000..df725e3
--- /dev/null
+++ b/usage/camp/src/test/resources/test-app-with-enrichers-slightly-simpler.yaml
@@ -0,0 +1,74 @@
+#
+# 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.
+#
+# example showing how enrichers can be set
+#
+name: test-app-with-enrichers
+description: Testing many enrichers
+services:
+- type: brooklyn.entity.group.DynamicCluster
+ id: cluster
+ initialSize: 3
+ location: localhost
+ memberSpec:
+ $brooklyn:entitySpec:
+ type: brooklyn.test.entity.TestEntity
+ brooklyn.enrichers:
+ - type: brooklyn.enricher.basic.Transformer
+ # transform "ip" (which we expect a feed, not shown here, to set) to a URL;
+ # you can curl an address string to the sensors/ip endpoint an entity to trigger these enrichers
+ brooklyn.config:
+ enricher.sourceSensor: $brooklyn:sensor("ip")
+ enricher.targetSensor: $brooklyn:sensor("url")
+ enricher.targetValue: $brooklyn:formatString("http://%s/", $brooklyn:attributeWhenReady("ip"))
+ - type: brooklyn.enricher.basic.Propagator
+ # use propagator to duplicate one sensor as another, giving the supplied sensor mapping;
+ # the other use of Propagator is where you specify a producer (using $brooklyn:entity(...) as below)
+ # from which to take sensors; in that mode you can specify `propagate` as a list of sensors whose names are unchanged,
+ # instead of (or in addition to) this map
+ brooklyn.config:
+ sensorMapping:
+ $brooklyn:sensor("url"): $brooklyn:sensor("brooklyn.entity.basic.Attributes", "main.uri")
+ brooklyn.enrichers:
+ - type: brooklyn.enricher.basic.Aggregator
+ # aggregate `url` sensors from children into a list
+ brooklyn.config:
+ enricher.sourceSensor: $brooklyn:sensor("url")
+ enricher.targetSensor: $brooklyn:sensor("urls.list")
+ enricher.aggregating.fromMembers: true
+ - type: brooklyn.enricher.basic.Joiner
+ # create a string from that list, for use e.g. in bash scripts
+ brooklyn.config:
+ enricher.sourceSensor: $brooklyn:sensor("urls.list")
+ enricher.targetSensor: $brooklyn:sensor("urls.list.comma_separated.max_2")
+ maximum: 2
+ # TODO infer uniqueTag, name etc
+ uniqueTag: urls.list.comma_separated.max_2
+ - type: brooklyn.enricher.basic.Joiner
+ # pick one uri as the main one to use
+ brooklyn.config:
+ enricher.sourceSensor: $brooklyn:sensor("urls.list")
+ enricher.targetSensor: $brooklyn:sensor("brooklyn.entity.basic.Attributes", "main.uri")
+ quote: false
+ maximum: 1
+brooklyn.enrichers:
+- type: brooklyn.enricher.basic.Propagator
+ # if nothing specified for `propagating` or `sensorMapping` then
+ # Propagator will do all but the usual lifecycle defaults, handy at the root!
+ brooklyn.config:
+ producer: $brooklyn:entity("cluster")
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f7142a33/utils/common/src/main/java/brooklyn/util/text/StringPredicates.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/text/StringPredicates.java b/utils/common/src/main/java/brooklyn/util/text/StringPredicates.java
index 15a306a..f83b139 100644
--- a/utils/common/src/main/java/brooklyn/util/text/StringPredicates.java
+++ b/utils/common/src/main/java/brooklyn/util/text/StringPredicates.java
@@ -67,6 +67,28 @@ public class StringPredicates {
};
}
+
+ /** Tests if object is non-null and not a blank string.
+ * <p>
+ * Predicate form of {@link Strings#isNonBlank(CharSequence)} also accepting objects non-null, for convenience */
+ public static <T> Predicate<T> isNonBlank() {
+ return new IsNonBlank<T>();
+ }
+
+ private static final class IsNonBlank<T> implements Predicate<T> {
+ @Override
+ public boolean apply(@Nullable Object input) {
+ if (input==null) return false;
+ if (!(input instanceof CharSequence)) return true;
+ return Strings.isNonBlank((CharSequence)input);
+ }
+
+ @Override
+ public String toString() {
+ return "isNonBlank()";
+ }
+ }
+
// -----------------
public static <T extends CharSequence> Predicate<T> containsLiteralIgnoreCase(final String fragment) {