add support for suppressing duplicates in enrichers, defaulting false for most, but true for UpdatingMap; and in Enrichers.builder() use better superclasses to support tags, uniqueTags
Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/41deca4d Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/41deca4d Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/41deca4d Branch: refs/heads/master Commit: 41deca4d9428867d9c1d54ec3e5334fe183cfea2 Parents: 3c714ed Author: Alex Heneveld <[email protected]> Authored: Wed Aug 6 22:38:11 2014 -0400 Committer: Alex Heneveld <[email protected]> Committed: Wed Aug 27 02:07:49 2014 -0400 ---------------------------------------------------------------------- .../main/java/brooklyn/enricher/Enrichers.java | 111 +++++++++++++++---- .../enricher/basic/AbstractEnricher.java | 35 ++++++ .../brooklyn/enricher/basic/Transformer.java | 7 +- .../brooklyn/enricher/basic/UpdatingMap.java | 13 ++- .../policy/basic/AbstractEntityAdjunct.java | 2 + .../java/brooklyn/enricher/EnrichersTest.java | 43 ++++--- 6 files changed, 163 insertions(+), 48 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/41deca4d/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 0d3a0d5..23b1b83 100644 --- a/core/src/main/java/brooklyn/enricher/Enrichers.java +++ b/core/src/main/java/brooklyn/enricher/Enrichers.java @@ -26,6 +26,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import brooklyn.enricher.basic.AbstractEnricher; import brooklyn.enricher.basic.Aggregator; import brooklyn.enricher.basic.Combiner; import brooklyn.enricher.basic.Propagator; @@ -39,12 +40,14 @@ import brooklyn.policy.Enricher; import brooklyn.policy.EnricherSpec; import brooklyn.util.collections.MutableList; import brooklyn.util.collections.MutableMap; +import brooklyn.util.collections.MutableSet; import brooklyn.util.flags.TypeCoercions; 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.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -67,6 +70,48 @@ public class Enrichers { } } + public abstract static class AbstractEnricherBuilder<B extends AbstractEnricherBuilder<B>> extends Builder<B> { + final Class<? extends Enricher> enricherType; + Boolean suppressDuplicates; + String uniqueTag; + Set<Object> tags = MutableSet.of(); + + public AbstractEnricherBuilder(Class<? extends Enricher> enricherType) { + this.enricherType = enricherType; + } + + public B uniqueTag(String tag) { + uniqueTag = Preconditions.checkNotNull(tag); + return self(); + } + public B addTag(Object tag) { + tags.add(Preconditions.checkNotNull(tag)); + return self(); + } + public B suppressDuplicates(Boolean suppressDuplicates) { + this.suppressDuplicates = suppressDuplicates; + return self(); + } + + protected abstract String getDefaultUniqueTag(); + + protected EnricherSpec<?> build() { + EnricherSpec<? extends Enricher> spec = EnricherSpec.create(enricherType); + + String uniqueTag2 = uniqueTag; + if (uniqueTag!=null) + uniqueTag2 = getDefaultUniqueTag(); + if (uniqueTag2!=null) + spec.uniqueTag(uniqueTag2); + + if (!tags.isEmpty()) spec.tags(tags); + if (suppressDuplicates!=null) + spec.configure(AbstractEnricher.SUPPRESS_DUPLICATES, suppressDuplicates); + + return spec; + } + } + protected abstract static class AbstractInitialBuilder<B extends AbstractInitialBuilder<B>> extends Builder<B> { public PropagatorBuilder propagating(Map<? extends Sensor<?>, ? extends Sensor<?>> vals) { return new PropagatorBuilder(vals); @@ -117,7 +162,7 @@ public class Enrichers { } - protected abstract static class AbstractAggregatorBuilder<S, T, B extends AbstractAggregatorBuilder<S, T, B>> extends Builder<B> { + protected abstract static class AbstractAggregatorBuilder<S, T, B extends AbstractAggregatorBuilder<S, T, B>> extends AbstractEnricherBuilder<B> { protected final AttributeSensor<S> aggregating; protected AttributeSensor<T> publishing; protected Entity fromEntity; @@ -132,6 +177,7 @@ public class Enrichers { protected Object valueToReportIfNoSensors; public AbstractAggregatorBuilder(AttributeSensor<S> aggregating) { + super(Aggregator.class); this.aggregating = aggregating; } @SuppressWarnings({ "unchecked", "rawtypes" }) @@ -195,6 +241,11 @@ public class Enrichers { this.excludingBlank = true; return self(); } + @Override + protected String getDefaultUniqueTag() { + if (publishing==null) return null; + return "aggregator:"+publishing.getName(); + } public EnricherSpec<?> build() { Predicate<Object> valueFilter; if (Boolean.TRUE.equals(excludingBlank)) { @@ -208,9 +259,7 @@ public class Enrichers { valueFilter = null; } // FIXME excludingBlank; use valueFilter? exclude means ignored entirely or substituted for defaultMemberValue? - return EnricherSpec.create(Aggregator.class) - .uniqueTag("aggregator:"+publishing) - .configure(MutableMap.builder() + return super.build().configure(MutableMap.builder() .putIfNotNull(Aggregator.PRODUCER, fromEntity) .put(Aggregator.TARGET_SENSOR, publishing) .put(Aggregator.SOURCE_SENSOR, aggregating) @@ -244,7 +293,7 @@ public class Enrichers { } } - protected abstract static class AbstractCombinerBuilder<S, T, B extends AbstractCombinerBuilder<S, T, B>> extends Builder<B> { + protected abstract static class AbstractCombinerBuilder<S, T, B extends AbstractCombinerBuilder<S, T, B>> extends AbstractEnricherBuilder<B> { protected final List<AttributeSensor<? extends S>> combining; protected AttributeSensor<T> publishing; protected Entity fromEntity; @@ -260,6 +309,7 @@ public class Enrichers { this(ImmutableList.copyOf(vals)); } public AbstractCombinerBuilder(Collection<AttributeSensor<? extends S>> vals) { + super(Combiner.class); checkArgument(checkNotNull(vals).size() > 0, "combining-sensors must be non-empty"); this.combining = ImmutableList.<AttributeSensor<? extends S>>copyOf(vals); } @@ -306,10 +356,13 @@ public class Enrichers { this.excludingBlank = true; return self(); } + @Override + protected String getDefaultUniqueTag() { + if (publishing==null) return null; + return "combiner:"+publishing.getName(); + } public EnricherSpec<?> build() { - return EnricherSpec.create(Combiner.class) - .uniqueTag("combiner:"+publishing) - .configure(MutableMap.builder() + return super.build().configure(MutableMap.builder() .putIfNotNull(Combiner.PRODUCER, fromEntity) .put(Combiner.TARGET_SENSOR, publishing) .put(Combiner.SOURCE_SENSORS, combining) @@ -333,7 +386,7 @@ public class Enrichers { } } - protected abstract static class AbstractTransformerBuilder<S, T, B extends AbstractTransformerBuilder<S, T, B>> extends Builder<B> { + protected abstract static class AbstractTransformerBuilder<S, T, B extends AbstractTransformerBuilder<S, T, B>> extends AbstractEnricherBuilder<B> { protected final AttributeSensor<S> transforming; protected AttributeSensor<T> publishing; protected Entity fromEntity; @@ -341,6 +394,7 @@ public class Enrichers { protected Function<? super SensorEvent<S>, ?> computingFromEvent; public AbstractTransformerBuilder(AttributeSensor<S> val) { + super(Transformer.class); this.transforming = checkNotNull(val); } @SuppressWarnings({ "unchecked", "rawtypes" }) @@ -360,10 +414,13 @@ public class Enrichers { this.computingFromEvent = checkNotNull(val); return self(); } + @Override + protected String getDefaultUniqueTag() { + if (publishing==null) return null; + return "transformer:"+publishing.getName(); + } public EnricherSpec<?> build() { - return EnricherSpec.create(Transformer.class) - .uniqueTag("transformer:"+publishing) - .configure(MutableMap.builder() + return super.build().configure(MutableMap.builder() .putIfNotNull(Transformer.PRODUCER, fromEntity) .put(Transformer.TARGET_SENSOR, publishing) .put(Transformer.SOURCE_SENSOR, transforming) @@ -384,13 +441,14 @@ public class Enrichers { } } - protected abstract static class AbstractPropagatorBuilder<B extends AbstractPropagatorBuilder<B>> extends Builder<B> { + protected abstract static class AbstractPropagatorBuilder<B extends AbstractPropagatorBuilder<B>> extends AbstractEnricherBuilder<B> { protected final Map<? extends Sensor<?>, ? extends Sensor<?>> propagating; protected final Boolean propagatingAll; protected final Iterable<? extends Sensor<?>> propagatingAllBut; protected Entity fromEntity; public AbstractPropagatorBuilder(Map<? extends Sensor<?>, ? extends Sensor<?>> vals) { + super(Propagator.class); checkArgument(checkNotNull(vals).size() > 0, "propagating-sensors must be non-empty"); this.propagating = vals; this.propagatingAll = null; @@ -402,7 +460,8 @@ public class Enrichers { public AbstractPropagatorBuilder(Sensor<?>... vals) { this(newIdentityMap(ImmutableSet.copyOf(vals))); } - public AbstractPropagatorBuilder(boolean propagatingAll, Iterable<? extends Sensor<?>> butVals) { + AbstractPropagatorBuilder(boolean propagatingAll, Iterable<? extends Sensor<?>> butVals) { + super(Propagator.class); // Ugly constructor! Taking boolean to differentiate it from others; could use a static builder // but feels like overkill having a builder for a builder, being called by a builder! checkArgument(propagatingAll, "Not propagating all; use PropagatingAll(vals)"); @@ -414,7 +473,8 @@ public class Enrichers { this.fromEntity = checkNotNull(val); return self(); } - public EnricherSpec<? extends Enricher> build() { + @Override + protected String getDefaultUniqueTag() { List<String> summary = MutableList.of(); if (propagating!=null) { for (Map.Entry<? extends Sensor<?>, ? extends Sensor<?>> entry: propagating.entrySet()) { @@ -432,9 +492,10 @@ public class Enrichers { summary.add("ALL_BUT:"+Joiner.on(",").join(allBut)); } - return EnricherSpec.create(Propagator.class) - .uniqueTag("propagating["+fromEntity.getId()+":"+Joiner.on(",").join(summary)+"]") - .configure(MutableMap.builder() + return "propagating["+fromEntity.getId()+":"+Joiner.on(",").join(summary)+"]"; + } + public EnricherSpec<? extends Enricher> build() { + return super.build().configure(MutableMap.builder() .putIfNotNull(Propagator.PRODUCER, fromEntity) .putIfNotNull(Propagator.SENSOR_MAPPING, propagating) .putIfNotNull(Propagator.PROPAGATING_ALL, propagatingAll) @@ -454,7 +515,7 @@ public class Enrichers { } } - public abstract static class AbstractUpdatingMapBuilder<S, TKey, TVal, B extends AbstractUpdatingMapBuilder<S, TKey, TVal, B>> extends Builder<B> { + public abstract static class AbstractUpdatingMapBuilder<S, TKey, TVal, B extends AbstractUpdatingMapBuilder<S, TKey, TVal, B>> extends AbstractEnricherBuilder<B> { protected AttributeSensor<Map<TKey,TVal>> targetSensor; protected AttributeSensor<? extends S> fromSensor; protected TKey key; @@ -462,6 +523,7 @@ public class Enrichers { protected Boolean removingIfResultIsNull; public AbstractUpdatingMapBuilder(AttributeSensor<Map<TKey,TVal>> target) { + super(UpdatingMap.class); this.targetSensor = target; } @SuppressWarnings({ "unchecked", "rawtypes" }) @@ -484,10 +546,13 @@ public class Enrichers { this.removingIfResultIsNull = val; return self(); } + @Override + protected String getDefaultUniqueTag() { + if (targetSensor==null || fromSensor==null) return null; + return "updating:"+targetSensor.getName()+"<-"+fromSensor.getName(); + } public EnricherSpec<?> build() { - return EnricherSpec.create(UpdatingMap.class) - .uniqueTag("updating:"+targetSensor+"<-"+fromSensor) - .configure(MutableMap.builder() + return super.build().configure(MutableMap.builder() .put(UpdatingMap.TARGET_SENSOR, targetSensor) .put(UpdatingMap.SOURCE_SENSOR, fromSensor) .putIfNotNull(UpdatingMap.KEY_IN_TARGET_SENSOR, key) @@ -528,7 +593,7 @@ public class Enrichers { public PropagatorBuilder(Sensor<?>... vals) { super(vals); } - public PropagatorBuilder(boolean propagatingAll, Iterable<? extends Sensor<?>> butVals) { + PropagatorBuilder(boolean propagatingAll, Iterable<? extends Sensor<?>> butVals) { super(propagatingAll, butVals); } } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/41deca4d/core/src/main/java/brooklyn/enricher/basic/AbstractEnricher.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/enricher/basic/AbstractEnricher.java b/core/src/main/java/brooklyn/enricher/basic/AbstractEnricher.java index 9cba5b9..52a924a 100644 --- a/core/src/main/java/brooklyn/enricher/basic/AbstractEnricher.java +++ b/core/src/main/java/brooklyn/enricher/basic/AbstractEnricher.java @@ -18,15 +18,23 @@ */ package brooklyn.enricher.basic; +import static com.google.common.base.Preconditions.checkState; + import java.util.Map; +import brooklyn.config.ConfigKey; +import brooklyn.entity.basic.ConfigKeys; +import brooklyn.entity.basic.EntityLocal; import brooklyn.entity.rebind.BasicEnricherRebindSupport; import brooklyn.entity.rebind.RebindSupport; +import brooklyn.event.AttributeSensor; +import brooklyn.event.Sensor; import brooklyn.mementos.EnricherMemento; import brooklyn.policy.Enricher; import brooklyn.policy.EnricherType; import brooklyn.policy.basic.AbstractEntityAdjunct; +import com.google.common.base.Objects; import com.google.common.collect.Maps; /** @@ -34,7 +42,11 @@ import com.google.common.collect.Maps; */ public abstract class AbstractEnricher extends AbstractEntityAdjunct implements Enricher { + public static final ConfigKey<Boolean> SUPPRESS_DUPLICATES = ConfigKeys.newBooleanConfigKey("enricher.suppressDuplicates", + "Whether duplicate values published by this enricher should be suppressed"); + private final EnricherDynamicType enricherType; + protected Boolean suppressDuplicates; public AbstractEnricher() { this(Maps.newLinkedHashMap()); @@ -61,8 +73,31 @@ public abstract class AbstractEnricher extends AbstractEntityAdjunct implements } @Override + public void setEntity(EntityLocal entity) { + super.setEntity(entity); + this.suppressDuplicates = getConfig(SUPPRESS_DUPLICATES); + } + + @Override protected void onChanged() { requestPersist(); } + + @Override + protected <T> void emit(Sensor<T> sensor, T val) { + checkState(entity != null, "entity must first be set"); + + if (sensor instanceof AttributeSensor) { + if (Boolean.TRUE.equals(suppressDuplicates)) { + T oldValue = entity.getAttribute((AttributeSensor<T>)sensor); + if (Objects.equal(oldValue, val)) + return; + } + entity.setAttribute((AttributeSensor<T>)sensor, val); + } else { + entity.emit(sensor, val); + } + + } } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/41deca4d/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 768e7c5..6877ec9 100644 --- a/core/src/main/java/brooklyn/enricher/basic/Transformer.java +++ b/core/src/main/java/brooklyn/enricher/basic/Transformer.java @@ -52,7 +52,7 @@ public class Transformer<T,U> extends AbstractEnricher implements SensorEventLis 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"); - + protected Function<? super SensorEvent<T>, ? extends U> transformation; protected Entity producer; protected Sensor<T> sourceSensor; @@ -65,6 +65,7 @@ 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()); @@ -107,7 +108,9 @@ public class Transformer<T,U> extends AbstractEnricher implements SensorEventLis if (v == Entities.UNCHANGED) { // nothing } else { - emit(targetSensor, TypeCoercions.coerce(v, targetSensor.getTypeToken())); + U newValue = TypeCoercions.coerce(v, targetSensor.getTypeToken()); +// oldValue = entity. + emit(targetSensor, newValue); } } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/41deca4d/core/src/main/java/brooklyn/enricher/basic/UpdatingMap.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/enricher/basic/UpdatingMap.java b/core/src/main/java/brooklyn/enricher/basic/UpdatingMap.java index f85852c..60bbe4b 100644 --- a/core/src/main/java/brooklyn/enricher/basic/UpdatingMap.java +++ b/core/src/main/java/brooklyn/enricher/basic/UpdatingMap.java @@ -36,6 +36,7 @@ import brooklyn.util.exceptions.Exceptions; import brooklyn.util.flags.SetFromFlag; import com.google.common.base.Function; +import com.google.common.collect.Maps; import com.google.common.reflect.TypeToken; /** @@ -47,7 +48,8 @@ import com.google.common.reflect.TypeToken; * with default behaviour being to remove an entry if <code>null</code> is returned * but this can be overriden by setting {@link #REMOVING_IF_RESULT_IS_NULL} false. * {@link Entities#REMOVE} and {@link Entities#UNCHANGED} are also respeced as return values for the computation - * (ignoring generics). + * (ignoring generics). + * Unlike most other enrichers, this defaults to {@link AbstractEnricher#SUPPRESS_DUPLICATES} being true * * @author alex * @@ -80,6 +82,15 @@ public class UpdatingMap<S,TKey,TVal> extends AbstractEnricher implements Sensor protected Boolean removingIfResultIsNull; public UpdatingMap() { + this(Maps.newLinkedHashMap()); + } + + public UpdatingMap(Map<Object, Object> flags) { + super(flags); + if (suppressDuplicates==null) { + // this defaults to suppressing duplicates + suppressDuplicates = true; + } } @SuppressWarnings({ "unchecked", "rawtypes" }) http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/41deca4d/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java b/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java index 4797b59..126968e 100644 --- a/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java +++ b/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java @@ -36,6 +36,7 @@ import brooklyn.basic.AbstractBrooklynObject; import brooklyn.basic.BrooklynObjectInternal; import brooklyn.config.ConfigKey; import brooklyn.config.ConfigMap; +import brooklyn.enricher.basic.AbstractEnricher; import brooklyn.entity.Entity; import brooklyn.entity.Group; import brooklyn.entity.basic.EntityInternal; @@ -238,6 +239,7 @@ public abstract class AbstractEntityAdjunct extends AbstractBrooklynObject imple this.entity = entity; } + /** @deprecated since 0.7.0 only {@link AbstractEnricher} has emit convenience */ protected <T> void emit(Sensor<T> sensor, T val) { checkState(entity != null, "entity must first be set"); if (sensor instanceof AttributeSensor) { http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/41deca4d/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 ab3f199..4257c36 100644 --- a/core/src/test/java/brooklyn/enricher/EnrichersTest.java +++ b/core/src/test/java/brooklyn/enricher/EnrichersTest.java @@ -214,28 +214,27 @@ public class EnrichersTest extends BrooklynAppUnitTestSupport { Asserts.eventually(Suppliers.ofInstance(record.events), CollectionFunctionals.sizeEquals(5)); } - // TODO if we had something like suppressDuplicates(true) : -// public void testTransformingSuppressDuplicates() { -// RecordingSensorEventListener record = new RecordingSensorEventListener(); -// app.getManagementContext().getSubscriptionManager().subscribe(entity, STR2, record); -// -// entity.addEnricher(Enrichers.builder() -// .transforming(STR1) -// .publishing(STR2) -// .computing(Functions.<String>identity()) -// .suppressDuplicates(true) -// .build()); -// -// entity.setAttribute(STR1, "myval"); -// Asserts.eventually(Suppliers.ofInstance(record.events), CollectionFunctionals.sizeEquals(1)); -// EntityTestUtils.assertAttributeEquals(entity, STR2, "myval"); -// -// entity.setAttribute(STR1, "myval2"); -// entity.setAttribute(STR1, "myval2"); -// entity.setAttribute(STR1, "myval3"); -// EntityTestUtils.assertAttributeEqualsContinually(entity, STR2, "myval3"); -// Asserts.assertThat(record.events, CollectionFunctionals.sizeEquals(3)); -// } + public void testTransformingSuppressDuplicates() { + RecordingSensorEventListener record = new RecordingSensorEventListener(); + app.getManagementContext().getSubscriptionManager().subscribe(entity, STR2, record); + + entity.addEnricher(Enrichers.builder() + .transforming(STR1) + .publishing(STR2) + .computing(Functions.<String>identity()) + .suppressDuplicates(true) + .build()); + + entity.setAttribute(STR1, "myval"); + Asserts.eventually(Suppliers.ofInstance(record.events), CollectionFunctionals.sizeEquals(1)); + EntityTestUtils.assertAttributeEquals(entity, STR2, "myval"); + + entity.setAttribute(STR1, "myval2"); + entity.setAttribute(STR1, "myval2"); + entity.setAttribute(STR1, "myval3"); + EntityTestUtils.assertAttributeEqualsContinually(entity, STR2, "myval3"); + Asserts.assertThat(record.events, CollectionFunctionals.sizeEquals(3)); + } @Test public void testPropagating() {
