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/08/20 00:54:19 UTC

[31/36] incubator-brooklyn git commit: Rename o.a.b.sensor.enricher to o.a.b.core.enricher

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f15e8a6/core/src/main/java/org/apache/brooklyn/sensor/enricher/SensorPropagatingEnricher.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/sensor/enricher/SensorPropagatingEnricher.java b/core/src/main/java/org/apache/brooklyn/sensor/enricher/SensorPropagatingEnricher.java
deleted file mode 100644
index 51d3d48..0000000
--- a/core/src/main/java/org/apache/brooklyn/sensor/enricher/SensorPropagatingEnricher.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.sensor.enricher;
-
-import java.util.Collection;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.entity.EntityLocal;
-import org.apache.brooklyn.api.sensor.AttributeSensor;
-import org.apache.brooklyn.api.sensor.Sensor;
-import org.apache.brooklyn.api.sensor.SensorEvent;
-import org.apache.brooklyn.api.sensor.SensorEventListener;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Sets;
-
-/** 
- * an enricher policy which just listens for the target sensor(s) on a child entity and passes it up.
- * now superseded by syntax such as:
- * 
- * <pre>{@code Enrichers.builder().propagating(XXX).from(source).build()}</pre>
- * 
- * @deprecated since 0.7.0; use {@link Enrichers#builder()}
- * 
- * @see Propagator if need to sub-class
- */
-public class SensorPropagatingEnricher extends AbstractEnricher implements SensorEventListener<Object> {
-    
-    public static final Logger log = LoggerFactory.getLogger(SensorPropagatingEnricher.class);
-        
-    /** the entity to listen to */
-    private final Entity source;
-    
-    /** the sensors to listen to */
-    private final Set<Sensor<?>> sensors;
-
-    /** the sensors to listen to */
-    private final Map<Sensor<?>, Sensor<?>> sensorMappings;
-
-    public static SensorPropagatingEnricher newInstanceListeningToAllSensors(Entity source) {
-        return newInstanceListeningToAllSensorsBut(source);
-    }
-    public static SensorPropagatingEnricher newInstanceListeningToAllSensorsBut(Entity source, Sensor<?>... excludes) {
-        Set<Sensor<?>> excluded = ImmutableSet.copyOf(excludes);
-        Set<Sensor<?>> includes = Sets.newLinkedHashSet();
-        
-        for (Sensor<?> it : source.getEntityType().getSensors()) {
-            if (!excluded.contains(it)) includes.add(it);
-        }
-        return new SensorPropagatingEnricher(source, includes);
-    }
-
-    public static SensorPropagatingEnricher newInstanceListeningTo(Entity source, Sensor<?>... includes) {
-        return new SensorPropagatingEnricher(source, includes);
-    }
-
-    /**
-     * listens to sensors from source, propagates them here renamed according to the map
-     * 
-     * Instead, consider calling:
-     * <pre>
-     * {@code
-     * addEnricher(Enrichers.builder()
-     *         .propagating(mapOfOldSensorNamesToNewSensorNames)
-     *         .from(source)
-     *         .build());
-     * }
-     * </pre>
-     *
-     * @deprecated since 0.7.0; use {@link Enrichers#builder()}
-     */
-    public static SensorPropagatingEnricher newInstanceRenaming(Entity source, Map<? extends Sensor<?>, ? extends Sensor<?>> sensors) {
-        return new SensorPropagatingEnricher(source, sensors);
-    }
-
-    /**
-     * @deprecated since 0.7.0; use {@link Enrichers#builder()}
-     */
-    public SensorPropagatingEnricher(Entity source, Sensor<?>... sensors) {
-        this(source, ImmutableList.copyOf(sensors));
-    }
-    
-    /** 
-     * Instead, consider calling:
-     * <pre>
-     * {@code
-     * addEnricher(Enrichers.builder()
-     *         .propagating(sensors)
-     *         .from(source)
-     *         .build());
-     * }
-     * </pre>
-     *
-     * @deprecated since 0.7.0; use {@link Enrichers#builder()}
-     */
-    public SensorPropagatingEnricher(Entity source, Collection<Sensor<?>> sensors) {
-        this.source = source;
-        this.sensors = ImmutableSet.copyOf(sensors);
-        this.sensorMappings = ImmutableMap.of();
-    }
-    
-    public SensorPropagatingEnricher(Entity source, Map<? extends Sensor<?>, ? extends Sensor<?>> sensors) {
-        this.source = source;
-        this.sensors = ImmutableSet.copyOf(sensors.keySet());
-        this.sensorMappings = ImmutableMap.copyOf(sensors);
-    }
-    
-    public void setEntity(EntityLocal entity) {
-        super.setEntity(entity);
-        for (Sensor<?> s: sensors) {
-            subscribe(source, s, this);
-        }
-    }
-    
-    @Override
-    @SuppressWarnings({ "unchecked", "rawtypes" })
-    public void onEvent(SensorEvent<Object> event) {
-        // propagate upwards
-        Sensor<?> sourceSensor = event.getSensor();
-        Sensor<?> destinationSensor = getDestinationSensor(sourceSensor);
-        
-        if (log.isTraceEnabled()) log.trace("policy {} got {}, propagating via {}{}", 
-                new Object[] {this, event, entity, (sourceSensor == destinationSensor ? "" : " (as "+destinationSensor+")")});
-        
-        if (event.getSensor() instanceof AttributeSensor) {
-            entity.setAttribute((AttributeSensor)destinationSensor, event.getValue());
-        } else {
-            entity.emit((Sensor)destinationSensor, event.getValue());
-        }       
-    }
-
-    /** useful post-addition to emit current values */
-    public void emitAllAttributes() {
-        emitAllAttributes(false);
-    }
-
-    @SuppressWarnings({ "rawtypes", "unchecked" })
-    public void emitAllAttributes(boolean includeNullValues) {
-        for (Sensor s: sensors) {
-            if (s instanceof AttributeSensor) {
-                AttributeSensor destinationSensor = (AttributeSensor<?>) getDestinationSensor(s);
-                Object v = source.getAttribute((AttributeSensor)s);
-                if (v != null || includeNullValues) entity.setAttribute(destinationSensor, v);
-            }
-        }
-    }
-
-    /** convenience, to be called by the host */
-    public SensorPropagatingEnricher addToEntityAndEmitAll(Entity host) {
-        host.addEnricher(this);
-        emitAllAttributes();
-        return this;
-    }
-    
-    private Sensor<?> getDestinationSensor(Sensor<?> sourceSensor) {
-        return sensorMappings.containsKey(sourceSensor) ? sensorMappings.get(sourceSensor): sourceSensor;
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f15e8a6/core/src/main/java/org/apache/brooklyn/sensor/enricher/SensorTransformingEnricher.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/sensor/enricher/SensorTransformingEnricher.java b/core/src/main/java/org/apache/brooklyn/sensor/enricher/SensorTransformingEnricher.java
deleted file mode 100644
index 2ad1bdf..0000000
--- a/core/src/main/java/org/apache/brooklyn/sensor/enricher/SensorTransformingEnricher.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.sensor.enricher;
-
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.sensor.AttributeSensor;
-import org.apache.brooklyn.api.sensor.Sensor;
-import org.apache.brooklyn.api.sensor.SensorEvent;
-import org.apache.brooklyn.util.groovy.GroovyJavaMethods;
-import org.apache.brooklyn.util.javalang.JavaClassNames;
-import org.apache.brooklyn.util.time.Duration;
-
-import groovy.lang.Closure;
-
-import com.google.common.base.Function;
-
-/**
- * @deprecated since 0.7.0; use {@link Enrichers.builder()}
- * @see Transformer if need to sub-class
- */
-public class SensorTransformingEnricher<T,U> extends AbstractTypeTransformingEnricher {
-
-    private Function<? super T, ? extends U> transformation;
-
-    public SensorTransformingEnricher(Entity producer, Sensor<T> source, Sensor<U> target, Function<? super T, ? extends U> transformation) {
-        super(producer, source, target);
-        this.transformation = transformation;
-        this.uniqueTag = JavaClassNames.simpleClassName(getClass())+":"+source.getName()+"*->"+target.getName();;
-    }
-
-    public SensorTransformingEnricher(Entity producer, Sensor<T> source, Sensor<U> target, Closure transformation) {
-        this(producer, source, target, GroovyJavaMethods.functionFromClosure(transformation));
-    }
-
-    public SensorTransformingEnricher(Sensor<T> source, Sensor<U> target, Function<T,U> transformation) {
-        this(null, source, target, transformation);
-    }
-
-    public SensorTransformingEnricher(Sensor<T> source, Sensor<U> target, Closure transformation) {
-        this(null, source, target, GroovyJavaMethods.functionFromClosure(transformation));
-    }
-
-    @Override
-    public void onEvent(SensorEvent event) {
-        if (accept((T)event.getValue())) {
-            if (target instanceof AttributeSensor)
-                entity.setAttribute((AttributeSensor)target, compute((T)event.getValue()));
-            else 
-                entity.emit(target, compute((T)event.getValue()));
-        }
-    }
-
-    protected boolean accept(T value) {
-        return true;
-    }
-
-    protected U compute(T value) {
-        return transformation.apply(value);
-    }
-
-    /** 
-     * creates an enricher which listens to a source (from the producer), 
-     * transforms it and publishes it under the target
-     * 
-     * Instead, consider calling:
-     * <pre>
-     * {@code
-     * addEnricher(Enrichers.builder()
-     *         .transforming(source)
-     *         .publishing(target)
-     *         .from(producer)
-     *         .computing(transformation)
-     *         .build());
-     * }
-     * </pre>
-     * 
-     * @deprecated since 0.7.0; use {@link Enrichers.builder()}
-     */
-    public static <U,V> SensorTransformingEnricher<U,V> newInstanceTransforming(Entity producer, AttributeSensor<U> source,
-            Function<U,V> transformation, AttributeSensor<V> target) {
-        return new SensorTransformingEnricher<U,V>(producer, source, target, transformation);
-    }
-
-    /** as {@link #newInstanceTransforming(Entity, AttributeSensor, Function, AttributeSensor)}
-     * using the same sensor as the source and the target */
-    public static <T> SensorTransformingEnricher<T,T> newInstanceTransforming(Entity producer, AttributeSensor<T> sensor,
-            Function<T,T> transformation) {
-        return newInstanceTransforming(producer, sensor, transformation, sensor);
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f15e8a6/core/src/main/java/org/apache/brooklyn/sensor/enricher/Transformer.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/sensor/enricher/Transformer.java b/core/src/main/java/org/apache/brooklyn/sensor/enricher/Transformer.java
deleted file mode 100644
index f77bd8c..0000000
--- a/core/src/main/java/org/apache/brooklyn/sensor/enricher/Transformer.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.sensor.enricher;
-
-import static com.google.common.base.Preconditions.checkArgument;
-
-import org.apache.brooklyn.api.sensor.SensorEvent;
-import org.apache.brooklyn.config.ConfigKey;
-import org.apache.brooklyn.core.config.ConfigKeys;
-import org.apache.brooklyn.util.collections.MutableSet;
-import org.apache.brooklyn.util.core.task.Tasks;
-import org.apache.brooklyn.util.core.task.ValueResolver;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.common.base.Function;
-import com.google.common.reflect.TypeToken;
-
-//@Catalog(name="Transformer", description="Transforms attributes of an entity; see Enrichers.builder().transforming(...)")
-@SuppressWarnings("serial")
-public class Transformer<T,U> extends AbstractTransformer<T,U> {
-
-    @SuppressWarnings("unused")
-    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 Transformer() {
-    }
-
-    /** returns a function for transformation, for immediate use only (not for caching, as it may change) */
-    @Override
-    @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
-                // PRETTY_QUICK/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(entity)
-                    .description("Computing sensor "+targetSensor+" from "+targetValueRaw)
-                    .timeout(ValueResolver.PRETTY_QUICK_WAIT)
-                    .getMaybe().orNull();
-            }
-            public String toString() {
-                return ""+targetValueRaw;
-            }
-        };
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f15e8a6/core/src/main/java/org/apache/brooklyn/sensor/enricher/UpdatingMap.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/sensor/enricher/UpdatingMap.java b/core/src/main/java/org/apache/brooklyn/sensor/enricher/UpdatingMap.java
deleted file mode 100644
index 6ae48a7..0000000
--- a/core/src/main/java/org/apache/brooklyn/sensor/enricher/UpdatingMap.java
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.sensor.enricher;
-
-import java.util.Map;
-
-import org.apache.brooklyn.api.entity.EntityLocal;
-import org.apache.brooklyn.api.sensor.AttributeSensor;
-import org.apache.brooklyn.api.sensor.Sensor;
-import org.apache.brooklyn.api.sensor.SensorEvent;
-import org.apache.brooklyn.api.sensor.SensorEventListener;
-import org.apache.brooklyn.config.ConfigKey;
-import org.apache.brooklyn.core.config.ConfigKeys;
-import org.apache.brooklyn.core.entity.Entities;
-import org.apache.brooklyn.util.collections.MutableMap;
-import org.apache.brooklyn.util.core.flags.SetFromFlag;
-import org.apache.brooklyn.util.exceptions.Exceptions;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.common.base.Function;
-import com.google.common.collect.Maps;
-import com.google.common.reflect.TypeToken;
-
-/**
- * Enricher which updates an entry in a sensor map ({@link #TARGET_SENSOR}) 
- * based on the value of another sensor ({@link #SOURCE_SENSOR}.
- * <p>
- * The key used defaults to the name of the source sensor but can be specified with {@link #KEY_IN_TARGET_SENSOR}.
- * The value placed in the map is the result of applying the function in {@link #COMPUTING} to the sensor value,
- * 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).
- * Unlike most other enrichers, this defaults to {@link AbstractEnricher#SUPPRESS_DUPLICATES} being true
- *  
- * @author alex
- *
- * @param <S> source sensor type
- * @param <TKey> key type in target sensor map
- * @param <TVal> value type in target sensor map
- */
-@SuppressWarnings("serial")
-public class UpdatingMap<S,TKey,TVal> extends AbstractEnricher implements SensorEventListener<S> {
-
-    private static final Logger LOG = LoggerFactory.getLogger(UpdatingMap.class);
-
-    @SetFromFlag("fromSensor")
-    public static final ConfigKey<Sensor<?>> SOURCE_SENSOR = ConfigKeys.newConfigKey(new TypeToken<Sensor<?>>() {}, "enricher.sourceSensor");
-    @SetFromFlag("targetSensor")
-    public static final ConfigKey<Sensor<?>> TARGET_SENSOR = ConfigKeys.newConfigKey(new TypeToken<Sensor<?>>() {}, "enricher.targetSensor");
-    @SetFromFlag("key")
-    public static final ConfigKey<?> KEY_IN_TARGET_SENSOR = ConfigKeys.newConfigKey(Object.class, "enricher.updatingMap.keyInTargetSensor",
-        "Key to update in the target sensor map, defaulting to the name of the source sensor");
-    @SetFromFlag("computing")
-    public static final ConfigKey<Function<?, ?>> COMPUTING = ConfigKeys.newConfigKey(new TypeToken<Function<?,?>>() {}, "enricher.updatingMap.computing");
-    @SetFromFlag("removingIfResultIsNull")
-    public static final ConfigKey<Boolean> REMOVING_IF_RESULT_IS_NULL = ConfigKeys.newBooleanConfigKey("enricher.updatingMap.removingIfResultIsNull", 
-        "Whether the key in the target map is removed if the result if the computation is null");
-
-    protected AttributeSensor<S> sourceSensor;
-    protected AttributeSensor<Map<TKey,TVal>> targetSensor;
-    protected TKey key;
-    protected Function<S,? extends TVal> computing;
-    protected Boolean removingIfResultIsNull;
-
-    public UpdatingMap() {
-        this(Maps.newLinkedHashMap());
-    }
-
-    public UpdatingMap(Map<Object, Object> flags) {
-        super(flags);
-        // this always suppresses duplicates, but it updates the same map *in place* so the usual suppress duplicates logic should not be applied
-        // TODO clean up so that we have synchronization guarantees and can inspect the item to see whether it has changed
-        suppressDuplicates = false;
-    }
-
-    @SuppressWarnings({ "unchecked", "rawtypes" })
-    @Override
-    public void setEntity(EntityLocal entity) {
-        super.setEntity(entity);
-        this.sourceSensor = (AttributeSensor<S>) getRequiredConfig(SOURCE_SENSOR);
-        this.targetSensor = (AttributeSensor<Map<TKey,TVal>>) getRequiredConfig(TARGET_SENSOR);
-        this.key = (TKey) getConfig(KEY_IN_TARGET_SENSOR);
-        this.computing = (Function) getRequiredConfig(COMPUTING);
-        this.removingIfResultIsNull = getConfig(REMOVING_IF_RESULT_IS_NULL);
-
-        subscribe(entity, sourceSensor, this);
-        onUpdated();
-    }
-    
-    @Override
-    public void onEvent(SensorEvent<S> event) {
-        onUpdated();
-    }
-
-    /**
-     * Called whenever the values for the set of producers changes (e.g. on an event, or on a member added/removed).
-     */
-    @SuppressWarnings("unchecked")
-    protected void onUpdated() {
-        try {
-            Object v = computing.apply(entity.getAttribute(sourceSensor));
-            if (v == null && !Boolean.FALSE.equals(removingIfResultIsNull)) {
-                v = Entities.REMOVE;
-            }
-            if (v == Entities.UNCHANGED) {
-                // nothing
-            } else {
-                // TODO check synchronization
-                TKey key = this.key;
-                if (key==null) key = (TKey) sourceSensor.getName();
-                
-                Map<TKey, TVal> map = entity.getAttribute(targetSensor);
-
-                boolean created = (map==null);
-                if (created) map = MutableMap.of();
-                
-                boolean changed;
-                if (v == Entities.REMOVE) {
-                    changed = map.containsKey(key);
-                    if (changed)
-                        map.remove(key);
-                } else {
-                    TVal oldV = map.get(key);
-                    if (oldV==null)
-                        changed = (v!=null || !map.containsKey(key));
-                    else
-                        changed = !oldV.equals(v);
-                    if (changed)
-                        map.put(key, (TVal)v);
-                }
-                if (changed || created)
-                    emit(targetSensor, map);
-            }
-        } catch (Throwable t) {
-            LOG.warn("Error calculating map update for enricher "+this, t);
-            throw Exceptions.propagate(t);
-        }
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f15e8a6/core/src/main/java/org/apache/brooklyn/sensor/enricher/YamlRollingTimeWindowMeanEnricher.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/sensor/enricher/YamlRollingTimeWindowMeanEnricher.java b/core/src/main/java/org/apache/brooklyn/sensor/enricher/YamlRollingTimeWindowMeanEnricher.java
deleted file mode 100644
index dc65fa8..0000000
--- a/core/src/main/java/org/apache/brooklyn/sensor/enricher/YamlRollingTimeWindowMeanEnricher.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.sensor.enricher;
-
-import java.util.Iterator;
-import java.util.LinkedList;
-
-import org.apache.brooklyn.api.sensor.Sensor;
-import org.apache.brooklyn.api.sensor.SensorEvent;
-import org.apache.brooklyn.config.ConfigKey;
-import org.apache.brooklyn.core.config.ConfigKeys;
-import org.apache.brooklyn.util.time.Duration;
-
-import com.google.common.base.Function;
-
-/**
- * Transforms {@link Sensor} data into a rolling average based on a time window.
- * 
- * All values within the window are weighted or discarded based on the timestamps associated with
- * them (discards occur when a new value is added or an average is requested)
- * <p>
- * This will not extrapolate figures - it is assumed a value is valid and correct for the entire
- * time period between it and the previous value. Normally, the average attribute is only updated
- * when a new value arrives so it can give a fully informed average, but there is a danger of this
- * going stale.
- * <p>
- * When an average is requested, it is likely there will be a segment of the window for which there
- * isn't a value. Instead of extrapolating a value and providing different extrapolation techniques,
- * the average is reported with a confidence value which reflects the fraction of the time
- * window for which the values were valid.
- * <p>
- * Consumers of the average may ignore the confidence value and just use the last known average.
- * They could multiply the returned value by the confidence value to get a decay-type behavior as
- * the window empties. A third alternative is to, at a certain confidence threshold, report that
- * the average is no longer meaningful.
- * <p>
- * The default average when no data has been received is 0, with a confidence of 0
- */
-public class YamlRollingTimeWindowMeanEnricher<T extends Number> extends AbstractTransformer<T,Double> {
-    
-    public static ConfigKey<Duration> WINDOW_DURATION = ConfigKeys.newConfigKey(Duration.class, "enricher.window.duration",
-        "Duration for which this window should store data, default one minute", Duration.ONE_MINUTE);
-
-    public static ConfigKey<Double> CONFIDENCE_REQUIRED_TO_PUBLISH = ConfigKeys.newDoubleConfigKey("enricher.window.confidenceRequired",
-        "Minimum confidence level (ie period covered) required to publish a rolling average", 0.8d);
-
-    public static class ConfidenceQualifiedNumber {
-        final Double value;
-        final double confidence;
-        
-        public ConfidenceQualifiedNumber(Double value, double confidence) {
-            this.value = value;
-            this.confidence = confidence;
-        }
-        
-        @Override
-        public String toString() {
-            return ""+value+" ("+(int)(confidence*100)+"%)";
-        } 
-        
-    }
-    
-    private final LinkedList<T> values = new LinkedList<T>();
-    private final LinkedList<Long> timestamps = new LinkedList<Long>();
-    volatile ConfidenceQualifiedNumber lastAverage = new ConfidenceQualifiedNumber(0d,0d);
-    
-    @Override
-    protected Function<SensorEvent<T>, Double> getTransformation() {
-        return new Function<SensorEvent<T>, Double>() {
-            @Override
-            public Double apply(SensorEvent<T> event) {
-                long eventTime = event.getTimestamp();
-                if (event.getValue()==null) {
-                    return null;
-                }
-                values.addLast(event.getValue());
-                timestamps.addLast(eventTime);
-                if (eventTime>0) {
-                    ConfidenceQualifiedNumber average = getAverage(eventTime, 0);
-
-                    if (average.confidence > getConfig(CONFIDENCE_REQUIRED_TO_PUBLISH)) { 
-                        // without confidence, we might publish wildly varying estimates,
-                        // causing spurious resizes, so allow it to be configured, and
-                        // by default require a high value
-
-                        // TODO would be nice to include timestamp, etc
-                        return average.value; 
-                    }
-                }
-                return null;
-            }
-        };
-    }
-    
-    public ConfidenceQualifiedNumber getAverage(long fromTime, long graceAllowed) {
-        if (timestamps.isEmpty()) {
-            return lastAverage = new ConfidenceQualifiedNumber(lastAverage.value, 0.0d);
-        }
-        
-        long firstTimestamp = -1;
-        Iterator<Long> ti = timestamps.iterator();
-        while (ti.hasNext()) {
-            firstTimestamp = ti.next();
-            if (firstTimestamp>0) break;
-        }
-        if (firstTimestamp<=0) {
-            // no values with reasonable timestamps
-            return lastAverage = new ConfidenceQualifiedNumber(values.get(values.size()-1).doubleValue(), 0.0d);
-        }
-
-        long lastTimestamp = timestamps.get(timestamps.size()-1);
-
-        long now = fromTime;
-        if (lastTimestamp > fromTime - graceAllowed) {
-            // without this, if the computation takes place X seconds after the publish,
-            // we treat X seconds as time for which we have no confidence in the data
-            now = lastTimestamp;
-        }
-        pruneValues(now);
-        
-        Duration timePeriod = getConfig(WINDOW_DURATION);
-        long windowStart = Math.max(now-timePeriod.toMilliseconds(), firstTimestamp);
-        long windowEnd = Math.max(now-timePeriod.toMilliseconds(), lastTimestamp);
-        Double confidence = ((double)(windowEnd - windowStart)) / timePeriod.toMilliseconds();
-        if (confidence <= 0.0000001d) {
-            // not enough timestamps in window 
-            double lastValue = values.get(values.size()-1).doubleValue();
-            return lastAverage = new ConfidenceQualifiedNumber(lastValue, 0.0d);
-        }
-        
-        long start = windowStart;
-        long end;
-        double weightedAverage = 0.0d;
-        
-        Iterator<T> valuesIter = values.iterator();
-        Iterator<Long> timestampsIter = timestamps.iterator();
-        while (valuesIter.hasNext()) {
-            // Ignores null and out-of-date values (and also values that are received out-of-order, but that shouldn't happen!)
-            Number val = valuesIter.next();
-            Long timestamp = timestampsIter.next();
-            if (val!=null && timestamp >= start) {
-                end = timestamp;
-                weightedAverage += ((end - start) / (confidence * timePeriod.toMilliseconds())) * val.doubleValue();
-                start = timestamp;
-            }
-        }
-        
-        return lastAverage = new ConfidenceQualifiedNumber(weightedAverage, confidence);
-    }
-    
-    /**
-     * Discards out-of-date values, but keeps at least one value.
-     */
-    private void pruneValues(long now) {
-        // keep one value from before the period, so that we can tell the window's start time
-        Duration timePeriod = getConfig(WINDOW_DURATION);
-        while(timestamps.size() > 1 && timestamps.get(1) < (now - timePeriod.toMilliseconds())) {
-            timestamps.removeFirst();
-            values.removeFirst();
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f15e8a6/core/src/main/java/org/apache/brooklyn/sensor/enricher/YamlTimeWeightedDeltaEnricher.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/sensor/enricher/YamlTimeWeightedDeltaEnricher.java b/core/src/main/java/org/apache/brooklyn/sensor/enricher/YamlTimeWeightedDeltaEnricher.java
deleted file mode 100644
index c49ac26..0000000
--- a/core/src/main/java/org/apache/brooklyn/sensor/enricher/YamlTimeWeightedDeltaEnricher.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.sensor.enricher;
-
-import org.apache.brooklyn.api.sensor.SensorEvent;
-import org.apache.brooklyn.config.ConfigKey;
-import org.apache.brooklyn.core.config.ConfigKeys;
-import org.apache.brooklyn.sensor.enricher.AbstractTransformer;
-import org.apache.brooklyn.util.core.flags.TypeCoercions;
-import org.apache.brooklyn.util.time.Duration;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.common.base.Function;
-
-/**
- * Converts an absolute count sensor into a delta sensor (i.e. the diff between the current and previous value),
- * presented as a units/timeUnit based on the event timing.
- * <p>
- * For example, given a requests.count sensor, this can make a requests.per_sec sensor with {@link #DELTA_PERIOD} set to "1s" (the default).
- * <p>
- * Suitable for configuration from YAML.
- */
-public class YamlTimeWeightedDeltaEnricher<T extends Number> extends AbstractTransformer<T,Double> {
-    private static final Logger LOG = LoggerFactory.getLogger(YamlTimeWeightedDeltaEnricher.class);
-    
-    transient Object lock = new Object();
-    Number lastValue;
-    long lastTime = -1;
-    
-    public static ConfigKey<Duration> DELTA_PERIOD = ConfigKeys.newConfigKey(Duration.class, "enricher.delta.period",
-        "Duration that this delta should compute for, default per second", Duration.ONE_SECOND);
-    
-    @Override
-    protected Function<SensorEvent<T>, Double> getTransformation() {
-        return new Function<SensorEvent<T>, Double>() {
-            @Override
-            public Double apply(SensorEvent<T> event) {
-                synchronized (lock) {
-                    Double current = TypeCoercions.coerce(event.getValue(), Double.class);
-
-                    if (current == null) return null;
-
-                    long eventTime = event.getTimestamp();
-                    long unitMillis = getConfig(DELTA_PERIOD).toMilliseconds();
-                    Double result = null;
-
-                    if (eventTime > 0 && eventTime > lastTime) {
-                        if (lastValue == null || lastTime < 0) {
-                            // cannot calculate time-based delta with a single value
-                            if (LOG.isTraceEnabled()) LOG.trace("{} received event but no last value so will not emit, null -> {} at {}", new Object[] {this, current, eventTime}); 
-                        } else {
-                            double duration = eventTime - lastTime;
-                            result = (current - lastValue.doubleValue()) / (duration / unitMillis);
-                        }
-                    }
-
-                    lastValue = current;
-                    lastTime = eventTime;
-
-                    return result;
-                }
-            }
-        };
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f15e8a6/core/src/test/java/org/apache/brooklyn/core/enricher/BasicEnricherTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/enricher/BasicEnricherTest.java b/core/src/test/java/org/apache/brooklyn/core/enricher/BasicEnricherTest.java
new file mode 100644
index 0000000..be87f1f
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/core/enricher/BasicEnricherTest.java
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.core.enricher;
+
+import static org.testng.Assert.assertEquals;
+
+import java.util.Map;
+
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.sensor.Enricher;
+import org.apache.brooklyn.api.sensor.EnricherSpec;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.BasicConfigKey;
+import org.apache.brooklyn.core.enricher.AbstractEnricher;
+import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
+import org.apache.brooklyn.core.entity.factory.ApplicationBuilder;
+import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
+import org.apache.brooklyn.core.test.entity.TestApplication;
+import org.apache.brooklyn.core.test.entity.TestApplicationNoEnrichersImpl;
+import org.apache.brooklyn.util.collections.MutableSet;
+import org.apache.brooklyn.util.core.flags.SetFromFlag;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.Iterables;
+
+/**
+ * Test that enricher can be created and accessed, by construction and by spec
+ */
+public class BasicEnricherTest extends BrooklynAppUnitTestSupport {
+    
+    // TODO These tests are a copy of BasicPolicyTest, which is a code smell.
+    // However, the src/main/java code does not contain as much duplication.
+
+    protected void setUpApp() {
+        EntitySpec<TestApplication> appSpec = EntitySpec.create(TestApplication.class, TestApplicationNoEnrichersImpl.class)
+                .configure(BrooklynConfigKeys.SKIP_ON_BOX_BASE_DIR_RESOLUTION, shouldSkipOnBoxBaseDirResolution());
+        app = ApplicationBuilder.newManagedApp(appSpec, mgmt);
+    }
+
+    public static class MyEnricher extends AbstractEnricher {
+        @SetFromFlag("intKey")
+        public static final BasicConfigKey<Integer> INT_KEY = new BasicConfigKey<Integer>(Integer.class, "bkey", "b key");
+        
+        @SetFromFlag("strKey")
+        public static final ConfigKey<String> STR_KEY = new BasicConfigKey<String>(String.class, "akey", "a key");
+        public static final ConfigKey<Integer> INT_KEY_WITH_DEFAULT = new BasicConfigKey<Integer>(Integer.class, "ckey", "c key", 1);
+        public static final ConfigKey<String> STR_KEY_WITH_DEFAULT = new BasicConfigKey<String>(String.class, "strKey", "str key", "str key default");
+        
+        MyEnricher(Map<?,?> flags) {
+            super(flags);
+        }
+        
+        public MyEnricher() {
+            super();
+        }
+    }
+    
+    @Test
+    public void testAddInstance() throws Exception {
+        MyEnricher enricher = new MyEnricher();
+        enricher.setDisplayName("Bob");
+        enricher.config().set(MyEnricher.STR_KEY, "aval");
+        enricher.config().set(MyEnricher.INT_KEY, 2);
+        app.addEnricher(enricher);
+        
+        assertEquals(enricher.getDisplayName(), "Bob");
+        assertEquals(enricher.getConfig(MyEnricher.STR_KEY), "aval");
+        assertEquals(enricher.getConfig(MyEnricher.INT_KEY), (Integer)2);
+    }
+    
+    @Test
+    public void testAddSpec() throws Exception {
+        MyEnricher enricher = app.addEnricher(EnricherSpec.create(MyEnricher.class)
+            .displayName("Bob")
+            .configure(MyEnricher.STR_KEY, "aval").configure(MyEnricher.INT_KEY, 2));
+        
+        assertEquals(enricher.getDisplayName(), "Bob");
+        assertEquals(enricher.getConfig(MyEnricher.STR_KEY), "aval");
+        assertEquals(enricher.getConfig(MyEnricher.INT_KEY), (Integer)2);
+    }
+        
+    @Test
+    public void testTagsFromSpec() throws Exception {
+        MyEnricher enricher = app.addEnricher(EnricherSpec.create(MyEnricher.class).tag(99).uniqueTag("x"));
+
+        assertEquals(enricher.tags().getTags(), MutableSet.of("x", 99));
+        assertEquals(enricher.getUniqueTag(), "x");
+    }
+
+    @Test
+    public void testSameUniqueTagEnricherNotAddedTwice() throws Exception {
+        app.addEnricher(EnricherSpec.create(MyEnricher.class).tag(99).uniqueTag("x"));
+        app.addEnricher(EnricherSpec.create(MyEnricher.class).tag(94).uniqueTag("x"));
+        
+        assertEquals(app.getEnrichers().size(), 1);
+        // the more recent one should dominate
+        Enricher enricher = Iterables.getOnlyElement(app.getEnrichers());
+        Assert.assertTrue(enricher.tags().containsTag(94));
+        Assert.assertFalse(enricher.tags().containsTag(99));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f15e8a6/core/src/test/java/org/apache/brooklyn/core/enricher/EnricherConfigTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/enricher/EnricherConfigTest.java b/core/src/test/java/org/apache/brooklyn/core/enricher/EnricherConfigTest.java
new file mode 100644
index 0000000..150a09b
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/core/enricher/EnricherConfigTest.java
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.core.enricher;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.fail;
+
+import org.apache.brooklyn.core.config.BasicConfigKey;
+import org.apache.brooklyn.core.enricher.BasicEnricherTest.MyEnricher;
+import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.testng.annotations.Test;
+
+/**
+ * Test that configuration properties are usable and inherited correctly.
+ */
+public class EnricherConfigTest extends BrooklynAppUnitTestSupport {
+    
+    // TODO These tests are a copy of PolicyConfigTest, which is a code smell.
+    // However, the src/main/java code does not contain as much duplication.
+    
+    private BasicConfigKey<String> differentKey = new BasicConfigKey<String>(String.class, "differentkey", "diffval");
+
+    @Test
+    public void testConfigFlagsPassedInAtConstructionIsAvailable() throws Exception {
+        MyEnricher enricher = new MyEnricher(MutableMap.builder()
+                .put("strKey", "aval")
+                .put("intKey", 2)
+                .build());
+        app.addEnricher(enricher);
+        
+        assertEquals(enricher.getConfig(MyEnricher.STR_KEY), "aval");
+        assertEquals(enricher.getConfig(MyEnricher.INT_KEY), (Integer)2);
+        // this is set, because key name matches annotation on STR_KEY
+        assertEquals(enricher.getConfig(MyEnricher.STR_KEY_WITH_DEFAULT), "aval");
+    }
+    
+    @Test
+    public void testUnknownConfigPassedInAtConstructionIsWarnedAndIgnored() throws Exception {
+        // TODO Also assert it's warned
+        MyEnricher enricher = new MyEnricher(MutableMap.builder()
+                .put(differentKey, "aval")
+                .build());
+        app.addEnricher(enricher);
+        
+        assertEquals(enricher.getConfig(differentKey), null);
+        assertEquals(enricher.getEnricherType().getConfigKey(differentKey.getName()), null);
+    }
+    
+    @Test
+    public void testConfigPassedInAtConstructionIsAvailable() throws Exception {
+        MyEnricher enricher = new MyEnricher(MutableMap.builder()
+                .put(MyEnricher.STR_KEY, "aval")
+                .put(MyEnricher.INT_KEY, 2)
+                .build());
+        app.addEnricher(enricher);
+        
+        assertEquals(enricher.getConfig(MyEnricher.STR_KEY), "aval");
+        assertEquals(enricher.getConfig(MyEnricher.INT_KEY), (Integer)2);
+        // this is not set (contrast with above)
+        assertEquals(enricher.getConfig(MyEnricher.STR_KEY_WITH_DEFAULT), MyEnricher.STR_KEY_WITH_DEFAULT.getDefaultValue());
+    }
+    
+    @Test
+    public void testConfigSetToGroovyTruthFalseIsAvailable() throws Exception {
+        MyEnricher enricher = new MyEnricher(MutableMap.builder()
+                .put(MyEnricher.INT_KEY_WITH_DEFAULT, 0)
+                .build());
+        app.addEnricher(enricher);
+        
+        assertEquals(enricher.getConfig(MyEnricher.INT_KEY_WITH_DEFAULT), (Integer)0);
+    }
+    
+    @Test
+    public void testConfigSetToNullIsAvailable() throws Exception {
+        MyEnricher enricher = new MyEnricher(MutableMap.builder()
+                .put(MyEnricher.STR_KEY_WITH_DEFAULT, null)
+                .build());
+        app.addEnricher(enricher);
+        
+        assertEquals(enricher.getConfig(MyEnricher.STR_KEY_WITH_DEFAULT), null);
+    }
+    
+    @Test
+    public void testConfigCanBeSetOnEnricher() throws Exception {
+        MyEnricher enricher = new MyEnricher();
+        enricher.config().set(MyEnricher.STR_KEY, "aval");
+        enricher.config().set(MyEnricher.INT_KEY, 2);
+        app.addEnricher(enricher);
+        
+        assertEquals(enricher.getConfig(MyEnricher.STR_KEY), "aval");
+        assertEquals(enricher.getConfig(MyEnricher.INT_KEY), (Integer)2);
+    }
+    
+    @Test
+    public void testConfigSetterOverridesConstructorValue() throws Exception {
+        MyEnricher enricher = new MyEnricher(MutableMap.builder()
+                .put(MyEnricher.STR_KEY, "aval")
+                .build());
+        enricher.config().set(MyEnricher.STR_KEY, "diffval");
+        app.addEnricher(enricher);
+        
+        assertEquals(enricher.getConfig(MyEnricher.STR_KEY), "diffval");
+    }
+
+    @Test
+    public void testConfigCannotBeSetAfterApplicationIsStarted() throws Exception {
+        MyEnricher enricher = new MyEnricher(MutableMap.builder()
+                .put(MyEnricher.STR_KEY, "origval")
+                .build());
+        app.addEnricher(enricher);
+        
+        try {
+            enricher.config().set(MyEnricher.STR_KEY,"newval");
+            fail();
+        } catch (UnsupportedOperationException e) {
+            // success
+        }
+        
+        assertEquals(enricher.getConfig(MyEnricher.STR_KEY), "origval");
+    }
+    
+    @Test
+    public void testConfigReturnsDefaultValueIfNotSet() throws Exception {
+        MyEnricher enricher = new MyEnricher();
+        app.addEnricher(enricher);
+        
+        assertEquals(enricher.getConfig(MyEnricher.STR_KEY_WITH_DEFAULT), "str key default");
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f15e8a6/core/src/test/java/org/apache/brooklyn/core/entity/EntitySpecTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/entity/EntitySpecTest.java b/core/src/test/java/org/apache/brooklyn/core/entity/EntitySpecTest.java
index d53bc87..1fec2e8 100644
--- a/core/src/test/java/org/apache/brooklyn/core/entity/EntitySpecTest.java
+++ b/core/src/test/java/org/apache/brooklyn/core/entity/EntitySpecTest.java
@@ -30,6 +30,7 @@ import org.apache.brooklyn.api.sensor.EnricherSpec;
 import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.core.config.BasicConfigKey;
 import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.enricher.AbstractEnricher;
 import org.apache.brooklyn.core.location.SimulatedLocation;
 import org.apache.brooklyn.core.policy.AbstractPolicy;
 import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
@@ -37,7 +38,6 @@ import org.apache.brooklyn.core.test.entity.TestEntity;
 import org.apache.brooklyn.core.test.entity.TestEntityImpl;
 import org.apache.brooklyn.core.test.entity.TestEntityNoEnrichersImpl;
 import org.apache.brooklyn.entity.group.BasicGroup;
-import org.apache.brooklyn.sensor.enricher.AbstractEnricher;
 import org.apache.brooklyn.test.Asserts;
 import org.apache.brooklyn.util.core.flags.SetFromFlag;
 import org.testng.annotations.BeforeMethod;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f15e8a6/core/src/test/java/org/apache/brooklyn/core/mgmt/persist/BrooklynMementoPersisterTestFixture.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/mgmt/persist/BrooklynMementoPersisterTestFixture.java b/core/src/test/java/org/apache/brooklyn/core/mgmt/persist/BrooklynMementoPersisterTestFixture.java
index f751bcb..03a9c79 100644
--- a/core/src/test/java/org/apache/brooklyn/core/mgmt/persist/BrooklynMementoPersisterTestFixture.java
+++ b/core/src/test/java/org/apache/brooklyn/core/mgmt/persist/BrooklynMementoPersisterTestFixture.java
@@ -46,7 +46,7 @@ import org.apache.brooklyn.core.mgmt.rebind.RecordingRebindExceptionHandler;
 import org.apache.brooklyn.core.test.entity.TestApplication;
 import org.apache.brooklyn.core.test.entity.TestEntity;
 import org.apache.brooklyn.core.test.policy.TestPolicy;
-import org.apache.brooklyn.sensor.enricher.Enrichers;
+import org.apache.brooklyn.enricher.stock.Enrichers;
 import org.testng.SkipException;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeMethod;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f15e8a6/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindEnricherTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindEnricherTest.java b/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindEnricherTest.java
index c4d295a..e79ba8a 100644
--- a/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindEnricherTest.java
+++ b/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindEnricherTest.java
@@ -34,6 +34,7 @@ import org.apache.brooklyn.api.sensor.Enricher;
 import org.apache.brooklyn.api.sensor.EnricherSpec;
 import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.enricher.AbstractEnricher;
 import org.apache.brooklyn.core.entity.Entities;
 import org.apache.brooklyn.core.entity.EntityInternal;
 import org.apache.brooklyn.core.entity.EntityPredicates;
@@ -42,9 +43,8 @@ import org.apache.brooklyn.core.sensor.Sensors;
 import org.apache.brooklyn.core.test.entity.TestApplication;
 import org.apache.brooklyn.core.test.entity.TestEntity;
 import org.apache.brooklyn.core.test.entity.TestEntityImpl;
+import org.apache.brooklyn.enricher.stock.Enrichers;
 import org.apache.brooklyn.entity.group.DynamicCluster;
-import org.apache.brooklyn.sensor.enricher.AbstractEnricher;
-import org.apache.brooklyn.sensor.enricher.Enrichers;
 import org.apache.brooklyn.test.Asserts;
 import org.apache.brooklyn.test.EntityTestUtils;
 import org.apache.brooklyn.util.collections.MutableSet;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f15e8a6/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindFailuresTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindFailuresTest.java b/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindFailuresTest.java
index 26ee74f..e78f062 100644
--- a/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindFailuresTest.java
+++ b/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindFailuresTest.java
@@ -42,6 +42,7 @@ import org.apache.brooklyn.api.sensor.EnricherSpec;
 import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.config.ConfigMap;
 import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.enricher.AbstractEnricher;
 import org.apache.brooklyn.core.entity.EntityFunctions;
 import org.apache.brooklyn.core.entity.EntityPredicates;
 import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
@@ -49,7 +50,6 @@ import org.apache.brooklyn.core.mgmt.rebind.RebindEntityTest.MyEntity;
 import org.apache.brooklyn.core.mgmt.rebind.RebindEntityTest.MyEntityImpl;
 import org.apache.brooklyn.core.policy.AbstractPolicy;
 import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
-import org.apache.brooklyn.sensor.enricher.AbstractEnricher;
 import org.apache.brooklyn.util.core.flags.SetFromFlag;
 import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.os.Os;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f15e8a6/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindPolicyTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindPolicyTest.java b/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindPolicyTest.java
index 28c9ba8..faff0c9 100644
--- a/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindPolicyTest.java
+++ b/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindPolicyTest.java
@@ -35,6 +35,7 @@ import org.apache.brooklyn.api.policy.PolicySpec;
 import org.apache.brooklyn.api.sensor.EnricherSpec;
 import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.enricher.AbstractEnricher;
 import org.apache.brooklyn.core.entity.Entities;
 import org.apache.brooklyn.core.location.Locations;
 import org.apache.brooklyn.core.mgmt.rebind.RebindEnricherTest.MyEnricher;
@@ -42,7 +43,6 @@ import org.apache.brooklyn.core.policy.AbstractPolicy;
 import org.apache.brooklyn.core.test.entity.TestApplication;
 import org.apache.brooklyn.core.test.entity.TestEntity;
 import org.apache.brooklyn.entity.group.BasicGroup;
-import org.apache.brooklyn.sensor.enricher.AbstractEnricher;
 import org.apache.brooklyn.test.Asserts;
 import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.collections.MutableSet;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f15e8a6/core/src/test/java/org/apache/brooklyn/core/policy/basic/EnricherTypeTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/policy/basic/EnricherTypeTest.java b/core/src/test/java/org/apache/brooklyn/core/policy/basic/EnricherTypeTest.java
index a9f9655..12a6bff 100644
--- a/core/src/test/java/org/apache/brooklyn/core/policy/basic/EnricherTypeTest.java
+++ b/core/src/test/java/org/apache/brooklyn/core/policy/basic/EnricherTypeTest.java
@@ -22,7 +22,7 @@ import static org.testng.Assert.assertEquals;
 
 import org.apache.brooklyn.api.sensor.EnricherType;
 import org.apache.brooklyn.core.config.BasicConfigKey;
-import org.apache.brooklyn.sensor.enricher.AbstractEnricher;
+import org.apache.brooklyn.core.enricher.AbstractEnricher;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f15e8a6/core/src/test/java/org/apache/brooklyn/core/test/policy/TestEnricher.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/test/policy/TestEnricher.java b/core/src/test/java/org/apache/brooklyn/core/test/policy/TestEnricher.java
index 2340c51..42b52d2 100644
--- a/core/src/test/java/org/apache/brooklyn/core/test/policy/TestEnricher.java
+++ b/core/src/test/java/org/apache/brooklyn/core/test/policy/TestEnricher.java
@@ -26,7 +26,7 @@ import org.apache.brooklyn.api.sensor.AttributeSensor;
 import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.core.config.BasicConfigKey;
 import org.apache.brooklyn.core.config.ConfigKeys;
-import org.apache.brooklyn.sensor.enricher.AbstractEnricher;
+import org.apache.brooklyn.core.enricher.AbstractEnricher;
 import org.apache.brooklyn.util.core.flags.SetFromFlag;
 
 import com.google.common.reflect.TypeToken;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f15e8a6/core/src/test/java/org/apache/brooklyn/enricher/stock/CustomAggregatingEnricherDeprecatedTest.groovy
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/enricher/stock/CustomAggregatingEnricherDeprecatedTest.groovy b/core/src/test/java/org/apache/brooklyn/enricher/stock/CustomAggregatingEnricherDeprecatedTest.groovy
new file mode 100644
index 0000000..481e233
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/enricher/stock/CustomAggregatingEnricherDeprecatedTest.groovy
@@ -0,0 +1,368 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.enricher.stock
+
+import static org.testng.Assert.assertEquals
+
+import org.apache.brooklyn.api.entity.EntitySpec
+import org.apache.brooklyn.api.sensor.AttributeSensor
+import org.apache.brooklyn.core.test.entity.TestApplication
+import org.apache.brooklyn.core.test.entity.TestEntity
+import org.apache.brooklyn.enricher.stock.CustomAggregatingEnricher;
+import org.apache.brooklyn.core.entity.Entities
+import org.apache.brooklyn.entity.group.BasicGroup
+import org.apache.brooklyn.core.location.SimulatedLocation
+import org.apache.brooklyn.core.sensor.BasicAttributeSensor
+import org.apache.brooklyn.test.TestUtils
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import org.testng.annotations.AfterMethod
+import org.testng.annotations.BeforeMethod
+import org.testng.annotations.Test
+
+import com.google.common.base.Function
+
+class CustomAggregatingEnricherDeprecatedTest {
+
+    public static final Logger log = LoggerFactory.getLogger(CustomAggregatingEnricherDeprecatedTest.class);
+            
+    private static final long TIMEOUT_MS = 10*1000
+    private static final long SHORT_WAIT_MS = 250
+    
+    TestApplication app
+    TestEntity producer
+    
+    AttributeSensor<Integer> intSensor
+    AttributeSensor<Integer> target
+
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() {
+        app = TestApplication.Factory.newManagedInstanceForTests();
+        producer = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+        intSensor = new BasicAttributeSensor<Integer>(Integer.class, "int sensor")
+        target = new BasicAttributeSensor<Integer>(Long.class, "target sensor")
+        
+        app.start([new SimulatedLocation()])
+    }
+    
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() {
+        if (app!=null) Entities.destroyAll(app.getManagementContext());
+    }
+    
+    @Test
+    public void testEnrichersWithNoProducers() {
+        CustomAggregatingEnricher<Integer> cae = CustomAggregatingEnricher.<Integer>newSummingEnricher([:], intSensor, target, 11, 40)
+        producer.addEnricher(cae)
+        assertEquals cae.getAggregate(), 40
+    }
+
+    @Test
+    public void testSummingEnricherWhenNoSensorValuesYet() {
+        CustomAggregatingEnricher<Integer> cae = CustomAggregatingEnricher.<Integer>newSummingEnricher(
+                intSensor, target, producers:[producer], 11, 40)
+        producer.addEnricher(cae)
+        assertEquals cae.getAggregate(), 11
+    }
+
+    @Test
+    public void testSingleProducerSum() {
+        CustomAggregatingEnricher<Integer> cae = CustomAggregatingEnricher.<Integer>newSummingEnricher(
+                intSensor, target, null, null, producers:[producer])
+        producer.addEnricher(cae)
+        assertEquals cae.getAggregate(), null
+        cae.onEvent(intSensor.newEvent(producer, 1))
+        assertEquals cae.getAggregate(), 1
+    }
+    
+    @Test
+    public void testSummingEnricherWhenNoAndNullSensorValue() {
+        CustomAggregatingEnricher<Integer> cae = CustomAggregatingEnricher.<Integer>newSummingEnricher(
+                intSensor, target, null, null, producers:[producer])
+        producer.addEnricher(cae)
+        assertEquals cae.getAggregate(), null
+        cae.onEvent(intSensor.newEvent(producer, null))
+        assertEquals cae.getAggregate(), null
+    }
+    
+    @Test
+    public void testSummingEnricherWhenNoAndNullSensorValueExplicitValue() {
+        CustomAggregatingEnricher<Integer> cae = CustomAggregatingEnricher.<Integer>newSummingEnricher(
+                intSensor, target, 3 /** if null */, 5 /** if none */, producers:[producer])
+        producer.addEnricher(cae)
+        assertEquals cae.getAggregate(), 3
+        cae.onEvent(intSensor.newEvent(producer, null))
+        assertEquals cae.getAggregate(), 3
+        cae.onEvent(intSensor.newEvent(producer, 1))
+        assertEquals cae.getAggregate(), 1
+        cae.onEvent(intSensor.newEvent(producer, 7))
+        assertEquals cae.getAggregate(), 7
+    }
+    
+    @Test
+    public void testMultipleProducersSum() {
+        List<TestEntity> producers = [
+                app.createAndManageChild(EntitySpec.create(TestEntity.class)), 
+                app.createAndManageChild(EntitySpec.create(TestEntity.class)),
+                app.createAndManageChild(EntitySpec.create(TestEntity.class))
+                ]
+        CustomAggregatingEnricher<Integer> cae = CustomAggregatingEnricher.<Integer>newSummingEnricher(
+            intSensor, target, null, null, producers:producers)
+        
+        producer.addEnricher(cae)
+        assertEquals cae.getAggregate(), null
+        cae.onEvent(intSensor.newEvent(producers[2], 1))
+        assertEquals cae.getAggregate(), 1
+        cae.onEvent(intSensor.newEvent(producers[0], 3))
+        assertEquals cae.getAggregate(), 4
+        cae.onEvent(intSensor.newEvent(producers[1], 3))
+        assertEquals cae.getAggregate(), 7
+
+    }
+    
+    @Test
+    public void testAveragingEnricherWhenNoAndNullSensorValues() {
+        List<TestEntity> producers = [ 
+                app.createAndManageChild(EntitySpec.create(TestEntity.class))
+                ]
+        CustomAggregatingEnricher<Double> cae = CustomAggregatingEnricher.<Double>newAveragingEnricher(
+                intSensor, new BasicAttributeSensor<Double>(Double.class, "target sensor"), null, null, producers:producers)
+        producer.addEnricher(cae)
+        assertEquals cae.getAggregate(), null
+        cae.onEvent(intSensor.newEvent(producers[0], null))
+        assertEquals cae.getAggregate(), null
+    }
+
+    @Test
+    public void testAveragingEnricherWhenNoAndNullSensorValuesExplicit() {
+        List<TestEntity> producers = [
+                app.createAndManageChild(EntitySpec.create(TestEntity.class))
+                ]
+        CustomAggregatingEnricher<Double> cae = CustomAggregatingEnricher.<Double>newAveragingEnricher(
+                intSensor, new BasicAttributeSensor<Double>(Double.class, "target sensor"), 3 /** if null */, 5 /** if none */,
+                producers:producers)
+        producer.addEnricher(cae)
+        
+        assertEquals cae.getAggregate(), 3d
+        cae.onEvent(intSensor.newEvent(producers[0], null))
+        assertEquals cae.getAggregate(), 3d
+        cae.onEvent(intSensor.newEvent(producers[0], 4))
+        assertEquals cae.getAggregate(), 4d
+    }
+
+    @Test
+    public void testAveragingEnricherWhenNoSensors() {
+        List<TestEntity> producers = [
+                ]
+        CustomAggregatingEnricher<Double> cae = CustomAggregatingEnricher.<Double>newAveragingEnricher(
+                intSensor, new BasicAttributeSensor<Double>(Double.class, "target sensor"), 3 /** if null */, 5 /** if none */,
+                producers:producers)
+        producer.addEnricher(cae)
+        
+        assertEquals cae.getAggregate(), 5d
+    }
+
+    @Test
+    public void testMultipleProducersAverage() {
+        List<TestEntity> producers = [
+                app.createAndManageChild(EntitySpec.create(TestEntity.class)), 
+                app.createAndManageChild(EntitySpec.create(TestEntity.class)),
+                app.createAndManageChild(EntitySpec.create(TestEntity.class))
+                ]
+        CustomAggregatingEnricher<Double> cae = CustomAggregatingEnricher.<Double>newAveragingEnricher(
+                intSensor, new BasicAttributeSensor<Double>(Double.class, "target sensor"), null, null, producers:producers)
+        
+        producer.addEnricher(cae)
+        
+        assertEquals cae.getAggregate(), null
+        cae.onEvent(intSensor.newEvent(producers[0], 3))
+        assertEquals cae.getAggregate(), 3d
+        
+        cae.onEvent(intSensor.newEvent(producers[1], 3))
+        assertEquals cae.getAggregate(), 3d
+        
+        cae.onEvent(intSensor.newEvent(producers[2], 6))
+        assertEquals cae.getAggregate(), 4d
+
+        // change p2's value to 7.5, average increase of 0.5.
+        cae.onEvent(intSensor.newEvent(producers[2], 7.5))
+        assertEquals cae.getAggregate(), 4.5d
+    }
+    
+    @Test
+    public void testMultipleProducersAverageDefaultingZero() {
+        List<TestEntity> producers = [
+                app.createAndManageChild(EntitySpec.create(TestEntity.class)), 
+                app.createAndManageChild(EntitySpec.create(TestEntity.class)),
+                app.createAndManageChild(EntitySpec.create(TestEntity.class))
+                ]
+        CustomAggregatingEnricher<Double> cae = CustomAggregatingEnricher.<Double>newAveragingEnricher(
+                intSensor, new BasicAttributeSensor<Double>(Double.class, "target sensor"), 0, 0, producers:producers)
+        
+        producer.addEnricher(cae)
+        
+        assertEquals cae.getAggregate(), 0d
+        cae.onEvent(intSensor.newEvent(producers[0], 3))
+        assertEquals cae.getAggregate(), 1d
+        
+        cae.onEvent(intSensor.newEvent(producers[1], 3))
+        assertEquals cae.getAggregate(), 2d
+        
+        cae.onEvent(intSensor.newEvent(producers[2], 6))
+        assertEquals cae.getAggregate(), 4d
+
+        // change p2's value to 7.5, average increase of 0.5.
+        cae.onEvent(intSensor.newEvent(producers[2], 7.5))
+        assertEquals cae.getAggregate(), 4.5d
+    }
+    
+    @Test
+    public void testAddingAndRemovingProducers() {
+        TestEntity p1 = app.createAndManageChild(EntitySpec.create(TestEntity.class)); 
+        TestEntity p2 = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+        
+        CustomAggregatingEnricher<Integer> cae = CustomAggregatingEnricher.<Integer>newSummingEnricher(
+                intSensor, target, null, null, producers:[p1])
+
+        producer.addEnricher(cae)
+        assertEquals cae.getAggregate(), null
+        
+        // Event by initial producer
+        cae.onEvent(intSensor.newEvent(p1, 1))
+        assertEquals cae.getAggregate(), 1
+        
+        // Add producer and fire event
+        cae.addProducer(p2)
+        cae.onEvent(intSensor.newEvent(p2, 4))
+        assertEquals cae.getAggregate(), 5
+        
+        cae.removeProducer(p2)
+        assertEquals cae.getAggregate(), 1
+    }
+    
+    @Test
+    public void testAggregatesNewMembersOfGroup() {
+        try {
+            BasicGroup group = app.createAndManageChild(EntitySpec.create(BasicGroup.class));
+            TestEntity p1 = app.createAndManageChild(EntitySpec.create(TestEntity.class))
+            TestEntity p2 = app.createAndManageChild(EntitySpec.create(TestEntity.class))
+            log.debug("created $group and the entities it will contain $p1 $p2")
+
+            CustomAggregatingEnricher<Integer> cae = CustomAggregatingEnricher.<Integer>newSummingEnricher(intSensor, target, 0, 0, allMembers:true)
+            group.addEnricher(cae)
+
+            assertEquals cae.getAggregate(), 0
+
+            group.addMember(p1)
+            p1.setAttribute(intSensor, 1)
+            TestUtils.executeUntilSucceeds(timeout:TIMEOUT_MS) {
+                assertEquals cae.getAggregate(), 1
+            }
+
+            group.addMember(p2)
+            p2.setAttribute(intSensor, 2)
+            TestUtils.executeUntilSucceeds(timeout:TIMEOUT_MS) {
+                assertEquals cae.getAggregate(), 3
+            }
+
+            group.removeMember(p2)
+            TestUtils.executeUntilSucceeds(timeout:TIMEOUT_MS) {
+                assertEquals cae.getAggregate(), 1
+            }
+        } catch (Exception e) {
+            log.error("testAggregatesNewMembersOfGroup failed (now cleaning up): "+e)
+            throw e;
+        }
+    }
+    
+    @Test(groups = "Integration")
+    public void testAggregatesGroupMembersFiftyTimes() {
+        for (int i=0; i<50; i++) {
+            log.debug "testAggregatesNewMembersOfGroup $i"
+            testAggregatesNewMembersOfGroup();
+        }
+    }
+    
+    @Test
+    public void testAggregatesExistingMembersOfGroup() {
+        BasicGroup group = app.addChild(EntitySpec.create(BasicGroup.class));
+        TestEntity p1 = app.getManagementContext().getEntityManager().createEntity(EntitySpec.create(TestEntity.class).parent(group)); 
+        TestEntity p2 = app.getManagementContext().getEntityManager().createEntity(EntitySpec.create(TestEntity.class).parent(group)); 
+        group.addMember(p1)
+        group.addMember(p2)
+        p1.setAttribute(intSensor, 1)
+        Entities.manage(group);
+        
+        CustomAggregatingEnricher<Integer> cae = CustomAggregatingEnricher.<Integer>newSummingEnricher(intSensor, target, null, null, allMembers:true)
+        group.addEnricher(cae)
+        
+        assertEquals cae.getAggregate(), 1
+
+        p2.setAttribute(intSensor, 2)
+        TestUtils.executeUntilSucceeds(timeout:TIMEOUT_MS) {
+            assertEquals cae.getAggregate(), 3
+        }
+        
+        group.removeMember(p2)
+        TestUtils.executeUntilSucceeds(timeout:TIMEOUT_MS) {
+            assertEquals cae.getAggregate(), 1
+        }
+    }
+    
+    @Test
+    public void testAppliesFilterWhenAggregatingMembersOfGroup() {
+        BasicGroup group = app.createAndManageChild(EntitySpec.create(BasicGroup.class));
+        TestEntity p1 = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+        TestEntity p2 = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+        TestEntity p3 = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+        group.addMember(p1)
+        group.addMember(p2)
+        p1.setAttribute(intSensor, 1)
+        p2.setAttribute(intSensor, 2)
+        p3.setAttribute(intSensor, 4)
+        
+        CustomAggregatingEnricher<Integer> cae = CustomAggregatingEnricher.<Integer>newSummingEnricher(intSensor, target, null, null, allMembers:true, filter:{it == p1})
+        group.addEnricher(cae)
+        
+        assertEquals cae.getAggregate(), 1
+        
+        group.addMember(p3)
+        TestUtils.assertSucceedsContinually(timeout:SHORT_WAIT_MS) {
+            assertEquals cae.getAggregate(), 1
+        }
+    }
+    
+    @Test
+    public void testCustomAggregatingFunction() {
+        TestEntity p1 = app.createAndManageChild(EntitySpec.create(TestEntity.class)); 
+        Function<Collection<Integer>,Integer> aggregator = { Collection c -> 
+            int result = 0; c.each { result += it*it }; return result;
+        } as Function
+         
+        CustomAggregatingEnricher<Integer> cae = CustomAggregatingEnricher.<Integer>newEnricher(
+                intSensor, target, aggregator, 0, producers:[p1])
+
+        producer.addEnricher(cae)
+        assertEquals cae.getAggregate(), 0
+        
+        // Event by producer
+        cae.onEvent(intSensor.newEvent(p1, 2))
+        assertEquals cae.getAggregate(), 4
+    }
+}