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 <[email protected]> Authored: Sun Apr 12 19:56:16 2015 -0500 Committer: Alex Heneveld <[email protected]> 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) {
