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/12/23 12:06:40 UTC

[17/71] [abbrv] incubator-brooklyn git commit: Merge commit 'e430723' into reorg2

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/entity/AbstractEntity.java
----------------------------------------------------------------------
diff --cc brooklyn-server/core/src/main/java/org/apache/brooklyn/core/entity/AbstractEntity.java
index 0000000,36b425f..0599373
mode 000000,100644..100644
--- a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/entity/AbstractEntity.java
+++ b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/entity/AbstractEntity.java
@@@ -1,0 -1,2137 +1,2141 @@@
+ /*
+  * 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.entity;
+ 
+ import static com.google.common.base.Preconditions.checkNotNull;
+ 
+ import java.util.Collection;
+ import java.util.Collections;
+ import java.util.Iterator;
+ import java.util.List;
+ import java.util.Map;
+ import java.util.Set;
+ 
+ import org.apache.brooklyn.api.effector.Effector;
+ import org.apache.brooklyn.api.entity.Application;
+ import org.apache.brooklyn.api.entity.Entity;
+ import org.apache.brooklyn.api.entity.EntityLocal;
+ import org.apache.brooklyn.api.entity.EntitySpec;
+ import org.apache.brooklyn.api.entity.EntityType;
+ import org.apache.brooklyn.api.entity.Group;
+ import org.apache.brooklyn.api.location.Location;
+ import org.apache.brooklyn.api.mgmt.EntityManager;
+ import org.apache.brooklyn.api.mgmt.ExecutionContext;
+ import org.apache.brooklyn.api.mgmt.ManagementContext;
+ import org.apache.brooklyn.api.mgmt.SubscriptionContext;
+ import org.apache.brooklyn.api.mgmt.SubscriptionHandle;
+ import org.apache.brooklyn.api.mgmt.Task;
+ import org.apache.brooklyn.api.mgmt.rebind.RebindSupport;
+ import org.apache.brooklyn.api.mgmt.rebind.mementos.EntityMemento;
+ import org.apache.brooklyn.api.objs.EntityAdjunct;
 -import org.apache.brooklyn.api.objs.SpecParameter;
+ import org.apache.brooklyn.api.policy.Policy;
+ import org.apache.brooklyn.api.policy.PolicySpec;
+ import org.apache.brooklyn.api.sensor.AttributeSensor;
+ import org.apache.brooklyn.api.sensor.Enricher;
+ import org.apache.brooklyn.api.sensor.EnricherSpec;
+ import org.apache.brooklyn.api.sensor.Feed;
+ 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.config.ConfigKey.HasConfigKey;
+ import org.apache.brooklyn.core.BrooklynFeatureEnablement;
+ import org.apache.brooklyn.core.BrooklynLogging;
+ import org.apache.brooklyn.core.catalog.internal.CatalogUtils;
+ import org.apache.brooklyn.core.config.ConfigConstraints;
+ import org.apache.brooklyn.core.config.render.RendererHints;
+ import org.apache.brooklyn.core.enricher.AbstractEnricher;
+ import org.apache.brooklyn.core.entity.internal.EntityConfigMap;
+ import org.apache.brooklyn.core.entity.lifecycle.PolicyDescriptor;
+ import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic;
+ import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic.ServiceNotUpLogic;
+ import org.apache.brooklyn.core.feed.AbstractFeed;
+ import org.apache.brooklyn.core.feed.ConfigToAttributes;
+ import org.apache.brooklyn.core.internal.BrooklynInitialization;
+ import org.apache.brooklyn.core.internal.storage.BrooklynStorage;
+ import org.apache.brooklyn.core.internal.storage.Reference;
+ import org.apache.brooklyn.core.internal.storage.impl.BasicReference;
+ import org.apache.brooklyn.core.location.Locations;
+ import org.apache.brooklyn.core.mgmt.internal.EffectorUtils;
+ import org.apache.brooklyn.core.mgmt.internal.EntityManagementSupport;
+ import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
+ import org.apache.brooklyn.core.mgmt.internal.SubscriptionTracker;
+ import org.apache.brooklyn.core.mgmt.rebind.BasicEntityRebindSupport;
+ import org.apache.brooklyn.core.objs.AbstractBrooklynObject;
+ import org.apache.brooklyn.core.objs.AbstractConfigurationSupportInternal;
+ import org.apache.brooklyn.core.objs.AbstractEntityAdjunct;
+ import org.apache.brooklyn.core.objs.AbstractEntityAdjunct.AdjunctTagSupport;
+ import org.apache.brooklyn.core.policy.AbstractPolicy;
+ import org.apache.brooklyn.core.sensor.AttributeMap;
+ import org.apache.brooklyn.core.sensor.AttributeSensorAndConfigKey;
+ import org.apache.brooklyn.core.sensor.BasicNotificationSensor;
+ import org.apache.brooklyn.core.sensor.Sensors;
+ import org.apache.brooklyn.util.collections.MutableList;
+ import org.apache.brooklyn.util.collections.MutableMap;
+ import org.apache.brooklyn.util.collections.MutableSet;
+ import org.apache.brooklyn.util.collections.SetFromLiveMap;
+ import org.apache.brooklyn.util.core.config.ConfigBag;
+ import org.apache.brooklyn.util.core.flags.FlagUtils;
+ import org.apache.brooklyn.util.core.flags.TypeCoercions;
+ import org.apache.brooklyn.util.core.task.DeferredSupplier;
+ import org.apache.brooklyn.util.guava.Maybe;
 -import org.apache.brooklyn.util.guava.TypeTokens;
+ import org.apache.brooklyn.util.javalang.Equals;
+ import org.apache.brooklyn.util.text.Strings;
+ import org.apache.commons.lang3.builder.EqualsBuilder;
+ import org.slf4j.Logger;
+ import org.slf4j.LoggerFactory;
+ 
+ import com.google.common.annotations.Beta;
+ import com.google.common.base.Function;
+ import com.google.common.base.Objects;
+ import com.google.common.base.Objects.ToStringHelper;
+ import com.google.common.collect.ImmutableList;
+ import com.google.common.collect.ImmutableMap;
+ import com.google.common.collect.ImmutableSet;
+ import com.google.common.collect.Iterables;
+ import com.google.common.collect.Lists;
+ import com.google.common.collect.Maps;
+ import com.google.common.collect.Sets;
+ 
+ /**
+  * Default {@link Entity} implementation, which should be extended whenever implementing an entity.
+  * <p>
+  * Provides several common fields ({@link #displayName}, {@link #id}), and supports the core features of
+  * an entity such as configuration keys, attributes, subscriptions and effector invocation.
+  * <p>
+  * If a sub-class is creating other entities, this should be done in an overridden {@link #init()}
+  * method.
+  * <p>
+  * Note that config is typically inherited by children, whereas the fields and attributes are not.
+  * <p>
+  * Sub-classes should have a no-argument constructor. When brooklyn creates an entity, it will:
+  * <ol>
+  *   <li>Construct the entity via the no-argument constructor
+  *   <li>Call {@link #setDisplayName(String)}
+  *   <li>Call {@link #setManagementContext(ManagementContextInternal)}
+  *   <li>Call {@link #setProxy(Entity)}; the proxy should be used by everything else when referring 
+  *       to this entity (except for drivers/policies that are attached to the entity, which can be  
+  *       given a reference to this entity itself).
+  *   <li>Call {@link #configure(Map)} and then {@link #setConfig(ConfigKey, Object)}
+  *   <li>Call {@link #init()}
+  *   <li>Call {@link #addPolicy(Policy)} (for any policies defined in the {@link EntitySpec})
+  *   <li>Call {@link #setParent(Entity)}, if a parent is specified in the {@link EntitySpec}
+  * </ol>
+  * <p>
+  * The legacy (pre 0.5) mechanism for creating entities is for others to call the constructor directly.
+  * This is now deprecated.
+  */
+ public abstract class AbstractEntity extends AbstractBrooklynObject implements EntityLocal, EntityInternal {
+     
+     private static final Logger LOG = LoggerFactory.getLogger(AbstractEntity.class);
+     
+     static { BrooklynInitialization.initAll(); }
+     
+     public static final BasicNotificationSensor<Location> LOCATION_ADDED = new BasicNotificationSensor<Location>(
+             Location.class, "entity.location.added", "Location dynamically added to entity");
+     public static final BasicNotificationSensor<Location> LOCATION_REMOVED = new BasicNotificationSensor<Location>(
+             Location.class, "entity.location.removed", "Location dynamically removed from entity");
+ 
+     @SuppressWarnings("rawtypes")
+     public static final BasicNotificationSensor<Sensor> SENSOR_ADDED = new BasicNotificationSensor<Sensor>(Sensor.class,
+             "entity.sensor.added", "Sensor dynamically added to entity");
+     @SuppressWarnings("rawtypes")
+     public static final BasicNotificationSensor<Sensor> SENSOR_REMOVED = new BasicNotificationSensor<Sensor>(Sensor.class,
+             "entity.sensor.removed", "Sensor dynamically removed from entity");
+ 
+     public static final BasicNotificationSensor<String> EFFECTOR_ADDED = new BasicNotificationSensor<String>(String.class,
+             "entity.effector.added", "Effector dynamically added to entity");
+     public static final BasicNotificationSensor<String> EFFECTOR_REMOVED = new BasicNotificationSensor<String>(String.class,
+             "entity.effector.removed", "Effector dynamically removed from entity");
+     public static final BasicNotificationSensor<String> EFFECTOR_CHANGED = new BasicNotificationSensor<String>(String.class,
+             "entity.effector.changed", "Effector dynamically changed on entity");
+ 
+     @SuppressWarnings("rawtypes")
+     public static final BasicNotificationSensor<ConfigKey> CONFIG_KEY_ADDED = new BasicNotificationSensor<ConfigKey>(ConfigKey.class,
+             "entity.config_key.added", "ConfigKey dynamically added to entity");
+     @SuppressWarnings("rawtypes")
+     public static final BasicNotificationSensor<ConfigKey> CONFIG_KEY_REMOVED = new BasicNotificationSensor<ConfigKey>(ConfigKey.class,
+             "entity.config_key.removed", "ConfigKey dynamically removed from entity");
+ 
+     public static final BasicNotificationSensor<PolicyDescriptor> POLICY_ADDED = new BasicNotificationSensor<PolicyDescriptor>(PolicyDescriptor.class,
+             "entity.policy.added", "Policy dynamically added to entity");
+     public static final BasicNotificationSensor<PolicyDescriptor> POLICY_REMOVED = new BasicNotificationSensor<PolicyDescriptor>(PolicyDescriptor.class,
+             "entity.policy.removed", "Policy dynamically removed from entity");
+ 
+     public static final BasicNotificationSensor<Entity> CHILD_ADDED = new BasicNotificationSensor<Entity>(Entity.class,
+             "entity.children.added", "Child dynamically added to entity");
+     public static final BasicNotificationSensor<Entity> CHILD_REMOVED = new BasicNotificationSensor<Entity>(Entity.class,
+             "entity.children.removed", "Child dynamically removed from entity");
+ 
+     public static final BasicNotificationSensor<Group> GROUP_ADDED = new BasicNotificationSensor<Group>(Group.class,
+             "entity.group.added", "Group dynamically added to entity");
+     public static final BasicNotificationSensor<Group> GROUP_REMOVED = new BasicNotificationSensor<Group>(Group.class,
+             "entity.group.removed", "Group dynamically removed from entity");
+     
+     static {
+         RendererHints.register(Entity.class, RendererHints.displayValue(EntityFunctions.displayName()));
+     }
+         
+     private boolean displayNameAutoGenerated = true;
+     
+     private Entity selfProxy;
+     private volatile Application application;
+     
 -    // TODO Because some things still don't use EntitySpec (e.g. the EntityFactory stuff for cluster/fabric),
 -    // then we need temp vals here. When setManagementContext is called, we'll switch these out for the read-deal;
 -    // i.e. for the values backed by storage
++    // If FEATURE_USE_BROOKLYN_LIVE_OBJECTS_DATAGRID_STORAGE, then these are just temporary values 
++    // (but may still be needed if something, such as an EntityFactory in a cluster/fabric, did not
++    // use EntitySpec.
++    // If that feature is disabled, then these are not "temporary" values - these are the production
++    // values. They must be thread-safe, and where necessary (e.g. group) they should preserve order
++    // if possible.
+     private Reference<Entity> parent = new BasicReference<Entity>();
 -    private Set<Group> groupsInternal = Sets.newLinkedHashSet();
 -    private Set<Entity> children = Sets.newLinkedHashSet();
++    private Set<Group> groupsInternal = Collections.synchronizedSet(Sets.<Group>newLinkedHashSet());
++    private Set<Entity> children = Collections.synchronizedSet(Sets.<Entity>newLinkedHashSet());
+     private Reference<List<Location>> locations = new BasicReference<List<Location>>(ImmutableList.<Location>of()); // dups removed in addLocations
+     private Reference<Long> creationTimeUtc = new BasicReference<Long>(System.currentTimeMillis());
+     private Reference<String> displayName = new BasicReference<String>();
+     private Reference<String> iconUrl = new BasicReference<String>();
+ 
 -    Map<String,Object> presentationAttributes = Maps.newLinkedHashMap();
+     private Collection<AbstractPolicy> policiesInternal = Lists.newCopyOnWriteArrayList();
+     private Collection<AbstractEnricher> enrichersInternal = Lists.newCopyOnWriteArrayList();
+     Collection<Feed> feeds = Lists.newCopyOnWriteArrayList();
+ 
+     // FIXME we do not currently support changing parents, but to implement a cluster that can shrink we need to support at least
+     // orphaning (i.e. removing ownership). This flag notes if the entity has previously had a parent, and if an attempt is made to
+     // set a new parent an exception will be thrown.
+     boolean previouslyOwned = false;
+ 
+     /**
+      * Whether we are still being constructed, in which case never warn in "assertNotYetOwned"
+      */
+     private boolean inConstruction = true;
+     
+     private final EntityDynamicType entityType;
+     
+     protected final EntityManagementSupport managementSupport = new EntityManagementSupport(this);
+ 
+     private final BasicConfigurationSupport config = new BasicConfigurationSupport();
+ 
+     private final BasicSensorSupport sensors = new BasicSensorSupport();
+ 
+     private final BasicSubscriptionSupport subscriptions = new BasicSubscriptionSupport();
+ 
+     private final BasicPolicySupport policies = new BasicPolicySupport();
+ 
+     private final BasicEnricherSupport enrichers = new BasicEnricherSupport();
+ 
+     private final BasicGroupSupport groups = new BasicGroupSupport();
+ 
+     /**
+      * The config values of this entity. Updating this map should be done
+      * via getConfig/setConfig.
+      */
 -    // TODO Assigning temp value because not everything uses EntitySpec; see setManagementContext()
 -    private EntityConfigMap configsInternal = new EntityConfigMap(this, Maps.<ConfigKey<?>, Object>newLinkedHashMap());
++    // If FEATURE_USE_BROOKLYN_LIVE_OBJECTS_DATAGRID_STORAGE, this value will be only temporary.
++    private EntityConfigMap configsInternal = new EntityConfigMap(this);
+ 
+     /**
+      * The sensor-attribute values of this entity. Updating this map should be done
+      * via getAttribute/setAttribute; it will automatically emit an attribute-change event.
+      */
 -    // TODO Assigning temp value because not everything uses EntitySpec; see setManagementContext()
 -    private AttributeMap attributesInternal = new AttributeMap(this, Maps.<Collection<String>, Object>newLinkedHashMap());
++    // If FEATURE_USE_BROOKLYN_LIVE_OBJECTS_DATAGRID_STORAGE, this value will be only temporary.
++    private AttributeMap attributesInternal = new AttributeMap(this);
+ 
+     /**
+      * For temporary data, e.g. timestamps etc for calculating real attribute values, such as when
+      * calculating averages over time etc.
+      * 
+      * @deprecated since 0.6; use attributes
+      */
+     @Deprecated
+     protected final Map<String,Object> tempWorkings = Maps.newLinkedHashMap();
+ 
+     protected transient SubscriptionTracker _subscriptionTracker;
+     
+     public AbstractEntity() {
+         this(Maps.newLinkedHashMap(), null);
+     }
+ 
+     /**
+      * @deprecated since 0.5; instead use no-arg constructor with EntityManager().createEntity(spec)
+      */
+     @Deprecated
+     public AbstractEntity(Map flags) {
+         this(flags, null);
+     }
+ 
+     /**
+      * @deprecated since 0.5; instead use no-arg constructor with EntityManager().createEntity(spec)
+      */
+     @Deprecated
+     public AbstractEntity(Entity parent) {
+         this(Maps.newLinkedHashMap(), parent);
+     }
+ 
+     // FIXME don't leak this reference in constructor - even to utils
+     /**
+      * @deprecated since 0.5; instead use no-arg constructor with EntityManager().createEntity(spec)
+      */
+     @Deprecated
+     public AbstractEntity(@SuppressWarnings("rawtypes") Map flags, Entity parent) {
+         super(checkConstructorFlags(flags, parent));
+ 
+         // TODO Don't let `this` reference escape during construction
+         entityType = new EntityDynamicType(this);
+         
+         if (isLegacyConstruction()) {
+             AbstractEntity checkWeGetThis = configure(flags);
+             assert this.equals(checkWeGetThis) : this+" configure method does not return itself; returns "+checkWeGetThis+" instead of "+this;
+ 
+             boolean deferConstructionChecks = (flags.containsKey("deferConstructionChecks") && TypeCoercions.coerce(flags.get("deferConstructionChecks"), Boolean.class));
+             if (!deferConstructionChecks) {
+                 FlagUtils.checkRequiredFields(this);
+             }
+         }
+     }
+     
+     private static Map<?,?> checkConstructorFlags(Map flags, Entity parent) {
+         if (flags==null) {
+             throw new IllegalArgumentException("Flags passed to entity must not be null (try no-arguments or empty map)");
+         }
+         if (flags.get("parent") != null && parent != null && flags.get("parent") != parent) {
+             throw new IllegalArgumentException("Multiple parents supplied, "+flags.get("parent")+" and "+parent);
+         }
+         if (flags.get("owner") != null && parent != null && flags.get("owner") != parent) {
+             throw new IllegalArgumentException("Multiple parents supplied with flags.parent, "+flags.get("owner")+" and "+parent);
+         }
+         if (flags.get("parent") != null && flags.get("owner") != null && flags.get("parent") != flags.get("owner")) {
+             throw new IllegalArgumentException("Multiple parents supplied with flags.parent and flags.owner, "+flags.get("parent")+" and "+flags.get("owner"));
+         }
+         if (parent != null) {
+             flags.put("parent", parent);
+         }
+         if (flags.get("owner") != null) {
+             LOG.warn("Use of deprecated \"flags.owner\" instead of \"flags.parent\" for entity");
+             flags.put("parent", flags.get("owner"));
+             flags.remove("owner");
+         }
+         return flags;
+     }
+ 
+     /**
+      * @deprecated since 0.7.0; only used for legacy brooklyn types where constructor is called directly
+      */
+     @Override
+     @Deprecated
+     public AbstractEntity configure(Map flags) {
+         if (!inConstruction && getManagementSupport().isDeployed()) {
+             LOG.warn("bulk/flag configuration being made to {} after deployment: may not be supported in future versions ({})", 
+                     new Object[] { this, flags });
+         }
+         // TODO use a config bag instead
+ //        ConfigBag bag = new ConfigBag().putAll(flags);
+         
+         // FIXME Need to set parent with proxy, rather than `this`
+         Entity suppliedParent = (Entity) flags.remove("parent");
+         if (suppliedParent != null) {
+             suppliedParent.addChild(getProxyIfAvailable());
+         }
+         
+         Map<ConfigKey,?> suppliedOwnConfig = (Map<ConfigKey, ?>) flags.remove("config");
+         if (suppliedOwnConfig != null) {
+             for (Map.Entry<ConfigKey, ?> entry : suppliedOwnConfig.entrySet()) {
+                 setConfigEvenIfOwned(entry.getKey(), entry.getValue());
+             }
+         }
+ 
+         if (flags.get("displayName") != null) {
+             displayName.set((String) flags.remove("displayName"));
+             displayNameAutoGenerated = false;
+         } else if (flags.get("name") != null) {
+             displayName.set((String) flags.remove("name"));
+             displayNameAutoGenerated = false;
+         } else if (isLegacyConstruction()) {
+             displayName.set(getClass().getSimpleName()+":"+Strings.maxlen(getId(), 4));
+             displayNameAutoGenerated = true;
+         }
+ 
+         if (flags.get("iconUrl") != null) {
+             iconUrl.set((String) flags.remove("iconUrl"));
+         }
+         
+         // allow config keys, and fields, to be set from these flags if they have a SetFromFlag annotation
+         // TODO the default values on flags are not used? (we should remove that support, since ConfigKeys gives a better way)
+         FlagUtils.setFieldsFromFlags(flags, this);
+         flags = FlagUtils.setAllConfigKeys(flags, this, false);
+         
+         // finally all config keys specified in map should be set as config
+         // TODO use a config bag and remove the ones set above in the code below
+         for (Iterator<Map.Entry> fi = flags.entrySet().iterator(); fi.hasNext();) {
+             Map.Entry entry = fi.next();
+             Object k = entry.getKey();
+             if (k instanceof HasConfigKey) k = ((HasConfigKey)k).getConfigKey();
+             if (k instanceof ConfigKey) {
+                 setConfigEvenIfOwned((ConfigKey)k, entry.getValue());
+                 fi.remove();
+             }
+         }
+         
+         if (!flags.isEmpty()) {
+             LOG.warn("Unsupported flags when configuring {}; storing: {}", this, flags);
+             configsInternal.addToLocalBag(flags);
+         }
+ 
+         return this;
+     }
+ 
+     /**
+      * Adds the config keys to the entity dynamic type
+      * @since 0.9.0
+      */
+     public void configure(Iterable<ConfigKey<?>> configKeys) {
+         entityType.addConfigKeys(configKeys);
+     }
+ 
+     @Override
+     public int hashCode() {
+         return getId().hashCode();
+     }
+     
+     @Override
+     public boolean equals(Object o) {
+         return (o == this || o == selfProxy) || 
+                 (o instanceof Entity && Objects.equal(getId(), ((Entity)o).getId()));
+     }
+     
+     /** internal use only */ @Beta
+     public void setProxy(Entity proxy) {
+         if (selfProxy != null) 
+             throw new IllegalStateException("Proxy is already set; cannot reset proxy for "+toString());
+         resetProxy(proxy);
+     }
+     /** internal use only */ @Beta
+     public void resetProxy(Entity proxy) {
+         selfProxy = checkNotNull(proxy, "proxy");
+     }
+     
+     public Entity getProxy() {
+         return selfProxy;
+     }
+     
+     /**
+      * Returns the proxy, or if not available (because using legacy code) then returns the real entity.
+      * This method will be deleted in a future release; it will be kept while deprecated legacy code
+      * still exists that creates entities without setting the proxy.
+      */
+     @Beta
+     public Entity getProxyIfAvailable() {
+         return getProxy()!=null ? getProxy() : this;
+     }
+     
+     /**
+      * Sets a config key value, and returns this Entity instance for use in fluent-API style coding.
+      * 
+      * @deprecated since 0.7.0; see {@link #config()}, such as {@code config().set(key, value)}
+      */
+     @Deprecated
+     public <T> AbstractEntity configure(ConfigKey<T> key, T value) {
+         setConfig(key, value);
+         return this;
+     }
+     
+     /**
+      * @deprecated since 0.7.0; see {@link #config()}, such as {@code config().set(key, value)}
+      */
+     @SuppressWarnings("unchecked")
+     @Deprecated
+     public <T> AbstractEntity configure(ConfigKey<T> key, String value) {
+         config().set((ConfigKey)key, value);
+         return this;
+     }
+     
+     /**
+      * @deprecated since 0.7.0; see {@link #config()}, such as {@code config().set(key, value)}
+      */
+     @Deprecated
+     public <T> AbstractEntity configure(HasConfigKey<T> key, T value) {
+         config().set(key, value);
+         return this;
+     }
+     
+     /**
+      * @deprecated since 0.7.0; see {@link #config()}, such as {@code config().set(key, value)}
+      */
+     @SuppressWarnings("unchecked")
+     @Deprecated
+     public <T> AbstractEntity configure(HasConfigKey<T> key, String value) {
+         config().set((ConfigKey)key, value);
+         return this;
+     }
+ 
+     public void setManagementContext(ManagementContextInternal managementContext) {
+         super.setManagementContext(managementContext);
+         getManagementSupport().setManagementContext(managementContext);
+         entityType.setName(getEntityTypeName());
+         if (displayNameAutoGenerated) displayName.set(getEntityType().getSimpleName()+":"+Strings.maxlen(getId(), 4));
+ 
+         if (BrooklynFeatureEnablement.isEnabled(BrooklynFeatureEnablement.FEATURE_USE_BROOKLYN_LIVE_OBJECTS_DATAGRID_STORAGE)) {
+             Entity oldParent = parent.get();
+             Set<Group> oldGroups = groupsInternal;
+             Set<Entity> oldChildren = children;
+             List<Location> oldLocations = locations.get();
+             EntityConfigMap oldConfig = configsInternal;
+             AttributeMap oldAttribs = attributesInternal;
+             long oldCreationTimeUtc = creationTimeUtc.get();
+             String oldDisplayName = displayName.get();
+             String oldIconUrl = iconUrl.get();
+ 
+             parent = managementContext.getStorage().getReference(getId()+"-parent");
+             groupsInternal = SetFromLiveMap.create(managementContext.getStorage().<Group,Boolean>getMap(getId()+"-groups"));
+             children = SetFromLiveMap.create(managementContext.getStorage().<Entity,Boolean>getMap(getId()+"-children"));
+             locations = managementContext.getStorage().getNonConcurrentList(getId()+"-locations");
+             creationTimeUtc = managementContext.getStorage().getReference(getId()+"-creationTime");
+             displayName = managementContext.getStorage().getReference(getId()+"-displayName");
+             iconUrl = managementContext.getStorage().getReference(getId()+"-iconUrl");
+ 
+             // Only override stored defaults if we have actual values. We might be in setManagementContext
+             // because we are reconstituting an existing entity in a new brooklyn management-node (in which
+             // case believe what is already in the storage), or we might be in the middle of creating a new 
+             // entity. Normally for a new entity (using EntitySpec creation approach), this will get called
+             // before setting the parent etc. However, for backwards compatibility we still support some
+             // things calling the entity's constructor directly.
+             if (oldParent != null) parent.set(oldParent);
+             if (oldGroups.size() > 0) groupsInternal.addAll(oldGroups);
+             if (oldChildren.size() > 0) children.addAll(oldChildren);
+             if (oldLocations.size() > 0) locations.set(ImmutableList.copyOf(oldLocations));
+             if (creationTimeUtc.isNull()) creationTimeUtc.set(oldCreationTimeUtc);
+             if (displayName.isNull()) {
+                 displayName.set(oldDisplayName);
+             } else {
+                 displayNameAutoGenerated = false;
+             }
+             if (iconUrl.isNull()) iconUrl.set(oldIconUrl);
+ 
+             configsInternal = new EntityConfigMap(this, managementContext.getStorage().<ConfigKey<?>, Object>getMap(getId()+"-config"));
+             if (oldConfig.getLocalConfig().size() > 0) {
+                 configsInternal.setLocalConfig(oldConfig.getLocalConfig());
+             }
+             config().refreshInheritedConfig();
+ 
+             attributesInternal = new AttributeMap(this, managementContext.getStorage().<Collection<String>, Object>getMap(getId()+"-attributes"));
+             if (oldAttribs.asRawMap().size() > 0) {
+                 for (Map.Entry<Collection<String>,Object> entry : oldAttribs.asRawMap().entrySet()) {
+                     attributesInternal.update(entry.getKey(), entry.getValue());
+                 }
+             }
+         }
+     }
+ 
+     @Override
+     public Map<String, String> toMetadataRecord() {
+         return ImmutableMap.of();
+     }
+ 
+     @Override
+     public long getCreationTime() {
+         return creationTimeUtc.get();
+     }
+ 
+     @Override
+     public String getDisplayName() {
+         return displayName.get();
+     }
+     
+     @Override
+     public String getIconUrl() {
+         return iconUrl.get();
+     }
+     
+     @Override
+     public void setDisplayName(String newDisplayName) {
+         displayName.set(newDisplayName);
+         displayNameAutoGenerated = false;
+         getManagementSupport().getEntityChangeListener().onChanged();
+     }
+     
+     /** allows subclasses to set the default display name to use if none is provided */
+     protected void setDefaultDisplayName(String displayNameIfDefault) {
+         if (displayNameAutoGenerated) {
+             displayName.set(displayNameIfDefault);
+         }
+     }
+     
+     /**
+      * Gets the entity type name, to be returned by {@code getEntityType().getName()}.
+      * To be called by brooklyn internals only.
+      * Can be overridden to customize the name.
+      */
+     protected String getEntityTypeName() {
+         try {
+             Class<?> typeClazz = getManagementContext().getEntityManager().getEntityTypeRegistry().getEntityTypeOf(getClass());
+             String typeName = typeClazz.getCanonicalName();
+             if (typeName == null) typeName = typeClazz.getName();
+             return typeName;
+         } catch (IllegalArgumentException e) {
+             String typeName = getClass().getCanonicalName();
+             if (typeName == null) typeName = getClass().getName();
+             LOG.debug("Entity type interface not found for entity "+this+"; instead using "+typeName+" as entity type name");
+             return typeName;
+         }
+     }
+     
+     /**
+      * Adds this as a child of the given entity; registers with application if necessary.
+      */
+     @Override
+     public AbstractEntity setParent(Entity entity) {
+         if (!parent.isNull()) {
+             // If we are changing to the same parent...
+             if (parent.contains(entity)) return this;
+             // If we have a parent but changing to orphaned...
+             if (entity==null) { clearParent(); return this; }
+             
+             // We have a parent and are changing to another parent...
+             throw new UnsupportedOperationException("Cannot change parent of "+this+" from "+parent+" to "+entity+" (parent change not supported)");
+         }
+         // If we have previously had a parent and are trying to change to another one...
+         if (previouslyOwned && entity != null)
+             throw new UnsupportedOperationException("Cannot set a parent of "+this+" because it has previously had a parent");
+         // We don't have a parent, never have and are changing to having a parent...
+ 
+         //make sure there is no loop
+         if (this.equals(entity)) throw new IllegalStateException("entity "+this+" cannot own itself");
+         //this may be expensive, but preferable to throw before setting the parent!
+         if (Entities.isDescendant(this, entity))
+             throw new IllegalStateException("loop detected trying to set parent of "+this+" as "+entity+", which is already a descendent");
+         
+         parent.set(entity);
+         entity.addChild(getProxyIfAvailable());
+         config().refreshInheritedConfig();
+         previouslyOwned = true;
+         
+         getApplication();
+         
+         return this;
+     }
+ 
+     @Override
+     public void clearParent() {
+         if (parent.isNull()) return;
+         Entity oldParent = parent.get();
+         parent.clear();
+         if (oldParent != null) {
+             if (!Entities.isNoLongerManaged(oldParent)) 
+                 oldParent.removeChild(getProxyIfAvailable());
+         }
+     }
+     
+     /**
+      * Adds the given entity as a child of this parent <em>and</em> sets this entity as the parent of the child;
+      * returns argument passed in, for convenience.
+      * <p>
+      * The child is NOT managed, even if the parent is already managed at this point
+      * (e.g. the child is added *after* the parent's {@link AbstractEntity#init()} is invoked)
+      * and so will need an explicit <code>getEntityManager().manage(childReturnedFromThis)</code> call.
+      * <i>These semantics are currently under review.</i>
+      */
+     @Override
+     public <T extends Entity> T addChild(T child) {
+         checkNotNull(child, "child must not be null (for entity %s)", this);
+         CatalogUtils.setCatalogItemIdOnAddition(this, child);
+         
+         boolean changed;
+         synchronized (children) {
+             if (Entities.isAncestor(this, child)) throw new IllegalStateException("loop detected trying to add child "+child+" to "+this+"; it is already an ancestor");
+             child.setParent(getProxyIfAvailable());
+             changed = children.add(child);
+             
+             getManagementSupport().getEntityChangeListener().onChildrenChanged();
+         }
+         
+         // TODO not holding synchronization lock while notifying risks out-of-order if addChild+removeChild called in rapid succession.
+         // But doing notification in synchronization block may risk deadlock?
+         if (changed) {
+             sensors().emit(AbstractEntity.CHILD_ADDED, child);
+         }
+         return child;
+     }
+ 
+     /**
+      * Creates an entity using the given spec, and adds it as a child of this entity.
+      * 
+      * @see #addChild(Entity)
+      * @see EntityManager#createEntity(EntitySpec)
+      * 
+      * @throws IllegalArgumentException If {@code spec.getParent()} is set and is different from this entity
+      */
+     @Override
+     public <T extends Entity> T addChild(EntitySpec<T> spec) {
+         if (spec.getParent()==null) {
+             spec = EntitySpec.create(spec).parent(getProxyIfAvailable());
+         }
+         if (!this.equals(spec.getParent())) {
+             throw new IllegalArgumentException("Attempt to create child of "+this+" with entity spec "+spec+
+                 " failed because spec has different parent: "+spec.getParent());
+         }
+         
+         // The spec now includes this as the parent, so no need to call addChild; 
+         // that is done by InternalEntityFactory.
+         return getEntityManager().createEntity(spec);
+     }
+     
+     @Override
+     public boolean removeChild(Entity child) {
+         boolean changed;
+         synchronized (children) {
+             changed = children.remove(child);
+             child.clearParent();
+             
+             if (changed) {
+                 getManagementSupport().getEntityChangeListener().onChildrenChanged();
+             }
+         }
+         
+         if (changed) {
+             sensors().emit(AbstractEntity.CHILD_REMOVED, child);
+         }
+         return changed;
+     }
+ 
+     // -------- GROUPS --------------
+ 
+     @Override 
+     @Beta
+     // the concrete type rather than an interface is returned because Groovy subclasses
+     // complain (incorrectly) if we return EnricherSupportInternal
+     // TODO revert to EnricherSupportInternal when groovy subclasses work without this (eg new groovy version)
+     public BasicGroupSupport groups() {
+         return groups;
+     }
+ 
+     /**
+      * Direct use of this class is strongly discouraged. It will become private in a future release,
+      * once {@link #groups()} is reverted to return {@link {GroupSupport} instead of
+      * {@link BasicGroupSupport}.
+      */
+     @Beta
+     // TODO revert to private when groups() is reverted to return GroupSupport
+     public class BasicGroupSupport implements GroupSupportInternal {
+         @Override
+         public Iterator<Group> iterator() { 
+             return asList().iterator();
+         }
+         @Override
+         public int size() {
+             return asList().size();
+         }
+         @Override
+         public boolean isEmpty() {
+             return asList().isEmpty();
+         }
+         
 -        protected List<Group> asList() { 
 -            return ImmutableList.copyOf(groupsInternal);
++        protected List<Group> asList() {
++            synchronized (groupsInternal) {
++                return ImmutableList.copyOf(groupsInternal);
++            }
+         }
+         
+         @Override
+         public void add(Group group) {
+             boolean changed = groupsInternal.add(group);
+             getApplication();
+             
+             if (changed) {
+                 sensors().emit(AbstractEntity.GROUP_ADDED, group);
+             }
+         }
+ 
+         @Override
+         public void remove(Group group) {
+             boolean changed = groupsInternal.remove(group);
+             getApplication();
+             
+             if (changed) {
+                 sensors().emit(AbstractEntity.GROUP_REMOVED, group);
+             }
+         }
+     }
+     
+     /**
+      * @deprecated since 0.9.0; see {@link #groups()} and {@link GroupSupport#addGroup(Group)}
+      */
+     @Override
+     @Deprecated
+     public void addGroup(Group group) {
+         groups().add(group);
+     }
+ 
+     /**
+      * @deprecated since 0.9.0; see {@link #groups()} and {@link GroupSupport#removeGroup(Group)}
+      */
+     @Override
+     @Deprecated
+     public void removeGroup(Group group) {
+         groups().remove(group);
+     }
+ 
+     /**
+      * @deprecated since 0.9.0; see {@link #groups()} and {@link GroupSupport#iterator()}
+      */
+     @Override
+     @Deprecated
+     public Collection<Group> getGroups() { 
+         return groups().asList();
+     }
+     
+     @Override
+     public Entity getParent() {
+         return parent.get();
+     }
+ 
+     @Override
+     public Collection<Entity> getChildren() {
 -        return ImmutableList.copyOf(children);
++        synchronized (children) {
++            return ImmutableList.copyOf(children);
++        }
+     }
+     
+     /**
+      * Returns the application, looking it up if not yet known (registering if necessary)
+      */
+     @Override
+     public Application getApplication() {
+         if (application != null) return application;
+         Entity parent = getParent();
+         Application app = (parent != null) ? parent.getApplication() : null;
+         if (app != null) {
+             if (getManagementSupport().isFullyManaged())
+                 // only do this once fully managed, in case root app becomes parented
+                 setApplication(app);
+         }
+         return app;
+     }
+ 
+     // FIXME Can this really be deleted? Overridden by AbstractApplication; needs careful review
+     /** @deprecated since 0.4.0 should not be needed / leaked outwith brooklyn internals / mgmt support? */
+     protected synchronized void setApplication(Application app) {
+         if (application != null) {
+             if (application.getId() != app.getId()) {
+                 throw new IllegalStateException("Cannot change application of entity (attempted for "+this+" from "+getApplication()+" to "+app);
+             }
+         }
+         this.application = app;
+     }
+ 
+     @Override
+     public String getApplicationId() {
+         Application app = getApplication();
+         return (app == null) ? null : app.getId();
+     }
+ 
+     @Override
+     public ManagementContext getManagementContext() {
+         // NB Sept 2014 - removed synch keyword above due to deadlock;
+         // it also synchs in ManagementSupport.getManagementContext();
+         // no apparent reason why it was here also
+         return getManagementSupport().getManagementContext();
+     }
+ 
+     protected EntityManager getEntityManager() {
+         return getManagementContext().getEntityManager();
+     }
+     
+     @Override
+     public EntityType getEntityType() {
+         if (entityType==null) return null;
+         return entityType.getSnapshot();
+     }
+ 
+     @Override
+     public EntityDynamicType getMutableEntityType() {
+         return entityType;
+     }
+     
+     @Override
+     public Collection<Location> getLocations() {
+         synchronized (locations) {
+             return ImmutableList.copyOf(locations.get());
+         }
+     }
+ 
+     @Override
+     public void addLocations(Collection<? extends Location> newLocations) {
+         synchronized (locations) {
+             List<Location> oldLocations = locations.get();
+             Set<Location> truelyNewLocations = Sets.newLinkedHashSet(newLocations);
+             truelyNewLocations.removeAll(oldLocations);
+             if (truelyNewLocations.size() > 0) {
+                 locations.set(ImmutableList.<Location>builder().addAll(oldLocations).addAll(truelyNewLocations).build());
+             }
+             
+             for (Location loc : truelyNewLocations) {
+                 sensors().emit(AbstractEntity.LOCATION_ADDED, loc);
+             }
+         }
+         
+         if (getManagementSupport().isDeployed()) {
+             for (Location newLocation : newLocations) {
+                 // Location is now reachable, so manage it
+                 // TODO will not be required in future releases when creating locations always goes through LocationManager.createLocation(LocationSpec).
+                 Locations.manage(newLocation, getManagementContext());
+             }
+         }
+         getManagementSupport().getEntityChangeListener().onLocationsChanged();
+     }
+ 
+     @Override
+     public void removeLocations(Collection<? extends Location> removedLocations) {
+         synchronized (locations) {
+             List<Location> oldLocations = locations.get();
+             Set<Location> trulyRemovedLocations = Sets.intersection(ImmutableSet.copyOf(removedLocations), ImmutableSet.copyOf(oldLocations));
+             locations.set(MutableList.<Location>builder().addAll(oldLocations).removeAll(removedLocations).buildImmutable());
+             
+             for (Location loc : trulyRemovedLocations) {
+                 sensors().emit(AbstractEntity.LOCATION_REMOVED, loc);
+             }
+         }
+         
+         // TODO Not calling `Entities.unmanage(removedLocation)` because this location might be shared with other entities.
+         // Relying on abstractLocation.removeChildLocation unmanaging it, but not ideal as top-level locations will stick
+         // around forever, even if not referenced.
+         // Same goes for AbstractEntity#clearLocations().
+         
+         getManagementSupport().getEntityChangeListener().onLocationsChanged();
+     }
+     
+     @Override
+     public void clearLocations() {
+         synchronized (locations) {
+             locations.set(ImmutableList.<Location>of());
+         }
+         getManagementSupport().getEntityChangeListener().onLocationsChanged();
+     }
+ 
+     public Location firstLocation() {
+         synchronized (locations) {
+             return Iterables.get(locations.get(), 0);
+         }
+     }
+     
+     /**
+      * Should be invoked at end-of-life to clean up the item.
+      */
+     @Override
+     public void destroy() {
+     }
+ 
+     @Override
+     public <T> T getAttribute(AttributeSensor<T> attribute) {
+         return sensors().get(attribute);
+     }
+ 
+     /**
+      * @deprecated since 0.8.0; use {@link SensorSupport#get(AttributeSensor)}, 
+      *             which may require constructing a temporary sensor using {@link Sensors#newSensor(Class, String)}.
+      */
+     @SuppressWarnings("unchecked")
+     @Deprecated
+     public <T> T getAttributeByNameParts(List<String> nameParts) {
+         return (T) attributesInternal.getValue(nameParts);
+     }
+     
+     static Set<String> WARNED_READ_ONLY_ATTRIBUTES = Collections.synchronizedSet(MutableSet.<String>of());
+     
+     @Override
+     @Deprecated
+     public <T> T setAttribute(AttributeSensor<T> attribute, T val) {
+         return sensors().set(attribute, val);
+     }
+ 
+     @Override
+     @Deprecated
+     public <T> T setAttributeWithoutPublishing(AttributeSensor<T> attribute, T val) {
+         return sensors().setWithoutPublishing(attribute, val);
+     }
+ 
+     @Beta
+     @Override
+     @Deprecated
+     public <T> T modifyAttribute(AttributeSensor<T> attribute, Function<? super T, Maybe<T>> modifier) {
+         return sensors().modify(attribute, modifier);
+     }
+ 
+     @Override
+     @Deprecated
+     public void removeAttribute(AttributeSensor<?> attribute) {
+         sensors().remove(attribute);
+     }
+ 
+     /** sets the value of the given attribute sensor from the config key value herein
+      * if the attribtue sensor is not-set or null
+      * <p>
+      * returns old value 
+      * @deprecated on interface since 0.5.0; use {@link ConfigToAttributes#apply(EntityLocal, AttributeSensorAndConfigKey)} */
+     public <T> T setAttribute(AttributeSensorAndConfigKey<?,T> configuredSensor) {
+         T v = getAttribute(configuredSensor);
+         if (v!=null) return v;
+         v = configuredSensor.getAsSensorValue(this);
+         if (v!=null) return setAttribute(configuredSensor, v);
+         return null;
+     }
+ 
+     @Override
+     @Deprecated
+     @SuppressWarnings("rawtypes")
+     public Map<AttributeSensor, Object> getAllAttributes() {
+         return Collections.<AttributeSensor, Object>unmodifiableMap(sensors().getAll());
+     }
+ 
+     
+     // -------- CONFIGURATION --------------
+ 
+     @Override 
+     @Beta
+     // the concrete type rather than an interface is returned because Groovy subclasses
+     // complain (incorrectly) if we return ConfigurationSupportInternal
+     // TODO revert to ConfigurationSupportInternal when groovy subclasses work without this (eg new groovy version)
+     public BasicConfigurationSupport config() {
+         return config;
+     }
+ 
+     @Override 
+     @Beta
+     // the concrete type rather than an interface is returned because Groovy subclasses
+     // complain (incorrectly) if we return SensorsSupport
+     // TODO revert to SensorsSupportInternal when groovy subclasses work without this (eg new groovy version)
+     public BasicSensorSupport sensors() {
+         return sensors;
+     }
+ 
+     /**
+      * Direct use of this class is strongly discouraged. It will become private in a future release,
+      * once {@link #sensors()} is reverted to return {@link SensorSupport} instead of
+      * {@link BasicSensorSupport}.
+      */
+     @Beta
+     // TODO revert to private when config() is reverted to return SensorSupportInternal
+     public class BasicSensorSupport implements SensorSupportInternal {
+ 
+         @Override
+         public <T> T get(AttributeSensor<T> attribute) {
+             return attributesInternal.getValue(attribute);
+         }
+ 
+         @Override
+         public <T> T set(AttributeSensor<T> attribute, T val) {
+             if (LOG.isTraceEnabled())
+                 LOG.trace(""+AbstractEntity.this+" setAttribute "+attribute+" "+val);
+             
+             if (Boolean.TRUE.equals(getManagementSupport().isReadOnlyRaw())) {
+                 T oldVal = getAttribute(attribute);
+                 if (Equals.approximately(val, oldVal)) {
+                     // ignore, probably an enricher resetting values or something on init
+                 } else {
+                     String message = AbstractEntity.this+" setting "+attribute+" = "+val+" (was "+oldVal+") in read only mode; will have very little effect"; 
+                     if (!getManagementSupport().isDeployed()) {
+                         if (getManagementSupport().wasDeployed()) message += " (no longer deployed)"; 
+                         else message += " (not yet deployed)";
+                     }
+                     if (WARNED_READ_ONLY_ATTRIBUTES.add(attribute.getName())) {
+                         LOG.warn(message + " (future messages for this sensor logged at trace)");
+                     } else if (LOG.isTraceEnabled()) {
+                         LOG.trace(message);
+                     }
+                 }
+             }
+             T result = attributesInternal.update(attribute, val);
+             if (result == null) {
+                 // could be this is a new sensor
+                 entityType.addSensorIfAbsent(attribute);
+             }
+             
+             getManagementSupport().getEntityChangeListener().onAttributeChanged(attribute);
+             return result;
+         }
+ 
+         @Override
+         public <T> T setWithoutPublishing(AttributeSensor<T> attribute, T val) {
+             if (LOG.isTraceEnabled())
+                 LOG.trace(""+AbstractEntity.this+" setAttributeWithoutPublishing "+attribute+" "+val);
+             
+             T result = attributesInternal.updateWithoutPublishing(attribute, val);
+             if (result == null) {
+                 // could be this is a new sensor
+                 entityType.addSensorIfAbsentWithoutPublishing(attribute);
+             }
+             
+             getManagementSupport().getEntityChangeListener().onAttributeChanged(attribute);
+             return result;
+         }
+ 
+         @Beta
+         @Override
+         public <T> T modify(AttributeSensor<T> attribute, Function<? super T, Maybe<T>> modifier) {
+             if (LOG.isTraceEnabled())
+                 LOG.trace(""+AbstractEntity.this+" modifyAttribute "+attribute+" "+modifier);
+             
+             if (Boolean.TRUE.equals(getManagementSupport().isReadOnlyRaw())) {
+                 String message = AbstractEntity.this+" modifying "+attribute+" = "+modifier+" in read only mode; will have very little effect"; 
+                 if (!getManagementSupport().isDeployed()) {
+                     if (getManagementSupport().wasDeployed()) message += " (no longer deployed)"; 
+                     else message += " (not yet deployed)";
+                 }
+                 if (WARNED_READ_ONLY_ATTRIBUTES.add(attribute.getName())) {
+                     LOG.warn(message + " (future messages for this sensor logged at trace)");
+                 } else if (LOG.isTraceEnabled()) {
+                     LOG.trace(message);
+                 }
+             }
+             T result = attributesInternal.modify(attribute, modifier);
+             if (result == null) {
+                 // could be this is a new sensor
+                 entityType.addSensorIfAbsent(attribute);
+             }
+             
+             // TODO Conditionally set onAttributeChanged, only if was modified
+             getManagementSupport().getEntityChangeListener().onAttributeChanged(attribute);
+             return result;
+         }
+ 
+         @Override
+         public void remove(AttributeSensor<?> attribute) {
+             if (LOG.isTraceEnabled())
+                 LOG.trace(""+AbstractEntity.this+" removeAttribute "+attribute);
+             
+             attributesInternal.remove(attribute);
+             entityType.removeSensor(attribute);
+         }
+ 
+         @Override
+         public Map<AttributeSensor<?>, Object> getAll() {
+             Map<AttributeSensor<?>, Object> result = Maps.newLinkedHashMap();
+             Map<String, Object> attribs = attributesInternal.asMap();
+             for (Map.Entry<String,Object> entry : attribs.entrySet()) {
+                 AttributeSensor<?> attribKey = (AttributeSensor<?>) entityType.getSensor(entry.getKey());
+                 if (attribKey == null) {
+                     // Most likely a race: e.g. persister thread calling getAllAttributes; writer thread
+                     // has written attribute value and is in process of calling entityType.addSensorIfAbsent(attribute)
+                     // Just use a synthetic AttributeSensor, rather than ignoring value.
+                     // TODO If it's not a race, then don't log.warn every time!
+                     LOG.warn("When retrieving all attributes of {}, no AttributeSensor for attribute {} (creating synthetic)", AbstractEntity.this, entry.getKey());
+                     attribKey = Sensors.newSensor(Object.class, entry.getKey());
+                 }
+                 result.put(attribKey, entry.getValue());
+             }
+             return result;
+         }
+         
+         @Override
+         public <T> void emit(Sensor<T> sensor, T val) {
+             if (sensor instanceof AttributeSensor) {
+                 LOG.warn("Strongly discouraged use of emit with attribute sensor "+sensor+" "+val+"; use setAttribute instead!",
+                     new Throwable("location of discouraged attribute "+sensor+" emit"));
+             }
+             if (val instanceof SensorEvent) {
+                 LOG.warn("Strongly discouraged use of emit with sensor event as value "+sensor+" "+val+"; value should be unpacked!",
+                     new Throwable("location of discouraged event "+sensor+" emit"));
+             }
+             BrooklynLogging.log(LOG, BrooklynLogging.levelDebugOrTraceIfReadOnly(AbstractEntity.this),
+                 "Emitting sensor notification {} value {} on {}", sensor.getName(), val, AbstractEntity.this);
+             emitInternal(sensor, val);
+         }
+         
+         public <T> void emitInternal(Sensor<T> sensor, T val) {
+             if (getManagementSupport().isNoLongerManaged())
+                 throw new IllegalStateException("Entity "+AbstractEntity.this+" is no longer managed, when trying to publish "+sensor+" "+val);
+ 
+             SubscriptionContext subsContext = subscriptions().getSubscriptionContext();
+             if (subsContext != null) subsContext.publish(sensor.newEvent(getProxyIfAvailable(), val));
+         }
+     }
+     
+     /**
+      * Direct use of this class is strongly discouraged. It will become private in a future release,
+      * once {@link #config()} is reverted to return {@link ConfigurationSupportInternal} instead of
+      * {@link BasicConfigurationSupport}.
+      */
+     @Beta
+     // TODO revert to private when config() is reverted to return ConfigurationSupportInternal
+     public class BasicConfigurationSupport extends AbstractConfigurationSupportInternal {
+ 
+         @Override
+         public <T> T get(ConfigKey<T> key) {
+             return configsInternal.getConfig(key);
+         }
+ 
+         @Override
+         public <T> T set(ConfigKey<T> key, T val) {
+             ConfigConstraints.assertValid(AbstractEntity.this, key, val);
+             return setConfigInternal(key, val);
+         }
+ 
+         @Override
+         public <T> T set(ConfigKey<T> key, Task<T> val) {
+             return setConfigInternal(key, val);
+         }
+ 
+         @Override
+         public ConfigBag getBag() {
+             return configsInternal.getAllConfigBag();
+         }
+ 
+         @Override
+         public ConfigBag getLocalBag() {
+             return configsInternal.getLocalConfigBag();
+         }
+ 
+         @Override
+         public Maybe<Object> getRaw(ConfigKey<?> key) {
+             return configsInternal.getConfigRaw(key, true);
+         }
+ 
+         @Override
+         public Maybe<Object> getLocalRaw(ConfigKey<?> key) {
+             return configsInternal.getConfigRaw(key, false);
+         }
+ 
+         @Override
+         public void addToLocalBag(Map<String, ?> vals) {
+             configsInternal.addToLocalBag(vals);
+         }
+ 
+         @Override
+         public void removeFromLocalBag(String key) {
+             configsInternal.removeFromLocalBag(key);
+         }
+ 
+         @Override
+         public void refreshInheritedConfig() {
+             if (getParent() != null) {
+                 configsInternal.setInheritedConfig(((EntityInternal)getParent()).getAllConfig(), ((EntityInternal)getParent()).config().getBag());
+             } else {
+                 configsInternal.clearInheritedConfig();
+             }
+ 
+             refreshInheritedConfigOfChildren();
+         }
+         
+         @Override
+         public void refreshInheritedConfigOfChildren() {
+             for (Entity it : getChildren()) {
+                 ((EntityInternal)it).config().refreshInheritedConfig();
+             }
+         }
+         
+         @SuppressWarnings("unchecked")
+         private <T> T setConfigInternal(ConfigKey<T> key, Object val) {
+             if (!inConstruction && getManagementSupport().isDeployed()) {
+                 // previously we threw, then warned, but it is still quite common;
+                 // so long as callers don't expect miracles, it should be fine.
+                 // i (Alex) think the way to be stricter about this (if that becomes needed) 
+                 // would be to introduce a 'mutable' field on config keys
+                 LOG.debug("configuration being made to {} after deployment: {} = {}; change may not be visible in other contexts", 
+                         new Object[] { AbstractEntity.this, key, val });
+             }
+             T result = (T) configsInternal.setConfig(key, val);
+             
+             getManagementSupport().getEntityChangeListener().onConfigChanged(key);
+             return result;
+         }
+ 
+         @Override
+         protected ExecutionContext getContext() {
+             return AbstractEntity.this.getExecutionContext();
+         }
+     }
+     
+     @Override
+     public <T> T getConfig(ConfigKey<T> key) {
+         return config().get(key);
+     }
+     
+     @Override
+     public <T> T getConfig(HasConfigKey<T> key) {
+         return config().get(key);
+     }
+     
+     @Override
+     @Deprecated
+     public <T> T getConfig(HasConfigKey<T> key, T defaultValue) {
+         return configsInternal.getConfig(key, defaultValue);
+     }
+     
+     //don't use groovy defaults for defaultValue as that doesn't implement the contract; we need the above
+     @Override
+     @Deprecated
+     public <T> T getConfig(ConfigKey<T> key, T defaultValue) {
+         return configsInternal.getConfig(key, defaultValue);
+     }
+     
+     @Override
+     @Deprecated
+     public Maybe<Object> getConfigRaw(ConfigKey<?> key, boolean includeInherited) {
+         return (includeInherited) ? config().getRaw(key) : config().getLocalRaw(key);
+     }
+     
+     @Override
+     @Deprecated
+     public Maybe<Object> getConfigRaw(HasConfigKey<?> key, boolean includeInherited) {
+         return (includeInherited) ? config().getRaw(key) : config().getLocalRaw(key);
+     }
+ 
+     @Override
+     @Deprecated
+     public <T> T setConfig(ConfigKey<T> key, T val) {
+         return config().set(key, val);
+     }
+ 
+     @Override
+     @Deprecated
+     public <T> T setConfig(ConfigKey<T> key, Task<T> val) {
+         return config().set(key, val);
+     }
+ 
+     /**
+      * @deprecated since 0.7.0; use {@code config().set(key, task)}, with {@link Task} instead of {@link DeferredSupplier}
+      */
+     @Deprecated
+     public <T> T setConfig(ConfigKey<T> key, DeferredSupplier val) {
+         return config.setConfigInternal(key, val);
+     }
+ 
+     @Override
+     @Deprecated
+     public <T> T setConfig(HasConfigKey<T> key, T val) {
+         return config().set(key, val);
+     }
+ 
+     @Override
+     @Deprecated
+     public <T> T setConfig(HasConfigKey<T> key, Task<T> val) {
+         return (T) config().set(key, val);
+     }
+ 
+     /**
+      * @deprecated since 0.7.0; use {@code config().set(key, task)}, with {@link Task} instead of {@link DeferredSupplier}
+      */
+     @Deprecated
+     public <T> T setConfig(HasConfigKey<T> key, DeferredSupplier val) {
+         return setConfig(key.getConfigKey(), val);
+     }
+ 
+     @SuppressWarnings("unchecked")
+     public <T> T setConfigEvenIfOwned(ConfigKey<T> key, T val) {
+         return (T) configsInternal.setConfig(key, val);
+     }
+ 
+     public <T> T setConfigEvenIfOwned(HasConfigKey<T> key, T val) {
+         return setConfigEvenIfOwned(key.getConfigKey(), val);
+     }
+ 
+     /**
+      * @deprecated since 0.7.0; use {@code if (val != null) config().set(key, val)}
+      */
+     @Deprecated
+     @SuppressWarnings({ "unchecked", "rawtypes" })
+     protected void setConfigIfValNonNull(ConfigKey key, Object val) {
+         if (val != null) config().set(key, val);
+     }
+ 
+     /**
+      * @deprecated since 0.7.0; use {@code if (val != null) config().set(key, val)}
+      */
+     @Deprecated
+     @SuppressWarnings({ "unchecked", "rawtypes" })
+     protected void setConfigIfValNonNull(HasConfigKey key, Object val) {
+         if (val != null) config().set(key, val);
+     }
+ 
+     /**
+      * @deprecated since 0.7.0; see {@code config().refreshInheritedConfig()}
+      */
+     @Override
+     @Deprecated
+     public void refreshInheritedConfig() {
+         config().refreshInheritedConfig();
+     }
+ 
+     /**
+      * @deprecated since 0.7.0; see {@code config().refreshInheritedConfigOfChildren()}
+      */
+     @Deprecated
+     void refreshInheritedConfigOfChildren() {
+         config().refreshInheritedConfigOfChildren();
+     }
+ 
+     @Override
+     @Deprecated
+     public EntityConfigMap getConfigMap() {
+         return configsInternal;
+     }
+     
+     @Override
+     @Deprecated
+     public Map<ConfigKey<?>,Object> getAllConfig() {
+         return configsInternal.getAllConfig();
+     }
+ 
+     @Beta
+     @Override
+     @Deprecated
+     public ConfigBag getAllConfigBag() {
+         return config().getBag();
+     }
+ 
+     @Beta
+     @Override
+     @Deprecated
+     public ConfigBag getLocalConfigBag() {
+         return config().getLocalBag();
+     }
+ 
+     
+     // -------- SUBSCRIPTIONS --------------
+ 
+     @Override 
+     @Beta
+     // the concrete type rather than an interface is returned because Groovy subclasses
+     // complain (incorrectly) if we return SubscriptionSupportInternal
+     // TODO revert to SubscriptionSupportInternal when groovy subclasses work without this (eg new groovy version)
+     public BasicSubscriptionSupport subscriptions() {
+         return subscriptions;
+     }
+ 
+     /**
+      * Direct use of this class is strongly discouraged. It will become private in a future release,
+      * once {@link #subscriptions()} is reverted to return {@link SubscriptionSupportInternal} instead of
+      * {@link BasicSubscriptionSupport}.
+      */
+     @Beta
+     // TODO revert to private when config() is reverted to return SensorSupportInternal
+     public class BasicSubscriptionSupport implements SubscriptionSupportInternal {
+         
+         @Override
+         public <T> SubscriptionHandle subscribe(Entity producer, Sensor<T> sensor, SensorEventListener<? super T> listener) {
+             return getSubscriptionTracker().subscribe(producer, sensor, listener);
+         }
+ 
+         @Override
+         public <T> SubscriptionHandle subscribe(Map<String, ?> flags, Entity producer, Sensor<T> sensor, SensorEventListener<? super T> listener) {
+             return getSubscriptionTracker().subscribe(flags, producer, sensor, listener);
+         }
+ 
+         @Override
+         public <T> SubscriptionHandle subscribeToChildren(Entity parent, Sensor<T> sensor, SensorEventListener<? super T> listener) {
+             return getSubscriptionTracker().subscribeToChildren(parent, sensor, listener);
+         }
+ 
+         @Override
+         public <T> SubscriptionHandle subscribeToMembers(Group group, Sensor<T> sensor, SensorEventListener<? super T> listener) {
+             return getSubscriptionTracker().subscribeToMembers(group, sensor, listener);
+         }
+ 
+         /**
+          * Unsubscribes the given producer.
+          *
+          * @see SubscriptionContext#unsubscribe(SubscriptionHandle)
+          */
+         @Override
+         public boolean unsubscribe(Entity producer) {
+             return getSubscriptionTracker().unsubscribe(producer);
+         }
+ 
+         /**
+          * Unsubscribes the given handle.
+          *
+          * @see SubscriptionContext#unsubscribe(SubscriptionHandle)
+          */
+         @Override
+         public boolean unsubscribe(Entity producer, SubscriptionHandle handle) {
+             return getSubscriptionTracker().unsubscribe(producer, handle);
+         }
+ 
+         /**
+          * Unsubscribes the given handle.
+          * 
+          * It is (currently) more efficient to also pass in the producer -
+          * see {@link BasicSubscriptionSupport#unsubscribe(Entity, SubscriptionHandle)} 
+          */
+         @Override
+         public boolean unsubscribe(SubscriptionHandle handle) {
+             return getSubscriptionTracker().unsubscribe(handle);
+         }
+ 
+         @Override
+         public void unsubscribeAll() {
+             getSubscriptionTracker().unsubscribeAll();
+         }
+         
+         protected SubscriptionContext getSubscriptionContext() {
+             synchronized (AbstractEntity.this) {
+                 return getManagementSupport().getSubscriptionContext();
+             }
+         }
+ 
+         protected SubscriptionTracker getSubscriptionTracker() {
+             synchronized (AbstractEntity.this) {
+                 if (_subscriptionTracker == null) {
+                     _subscriptionTracker = new SubscriptionTracker(getSubscriptionContext());
+                 }
+                 return _subscriptionTracker;
+             }
+         }
+     }
+     
+     /**
+      * @deprecated since 0.9.0; see {@code subscriptions().subscribe(producer, sensor, listener)}
+      */
+     @Override
+     @Deprecated
+     public <T> SubscriptionHandle subscribe(Entity producer, Sensor<T> sensor, SensorEventListener<? super T> listener) {
+         return subscriptions().subscribe(producer, sensor, listener);
+     }
+ 
+     /**
+      * @deprecated since 0.9.0; see {@code subscriptions().subscribeToChildren(parent, sensor, listener)}
+      */
+     @Override
+     @Deprecated
+     public <T> SubscriptionHandle subscribeToChildren(Entity parent, Sensor<T> sensor, SensorEventListener<? super T> listener) {
+         return subscriptions().subscribeToChildren(parent, sensor, listener);
+     }
+ 
+     /**
+      * @deprecated since 0.9.0; see {@code subscriptions().subscribeToMembers(producer, sensor, listener)}
+      */
+     @Override
+     @Deprecated
+     public <T> SubscriptionHandle subscribeToMembers(Group group, Sensor<T> sensor, SensorEventListener<? super T> listener) {
+         return subscriptions().subscribeToMembers(group, sensor, listener);
+     }
+ 
+     /**
+      * @deprecated since 0.9.0; see {@code subscriptions().unsubscribe(producer)}
+      */
+     @Override
+     @Deprecated
+     public boolean unsubscribe(Entity producer) {
+         return subscriptions().unsubscribe(producer);
+     }
+ 
+     /**
+      * @deprecated since 0.9.0; see {@code subscriptions().unsubscribe(producer, handle)}
+      */
+     @Override
+     @Deprecated
+     public boolean unsubscribe(Entity producer, SubscriptionHandle handle) {
+         return subscriptions().unsubscribe(producer, handle);
+     }
+ 
+     /**
+      * @deprecated since 0.9.0; for internal use only
+      */
+     @Deprecated
+     protected synchronized SubscriptionTracker getSubscriptionTracker() {
+         return subscriptions().getSubscriptionTracker();
+     }
+     
+     @Override
+     public synchronized ExecutionContext getExecutionContext() {
+         return getManagementSupport().getExecutionContext();
+     }
+ 
+     /** Default String representation is simplified name of class, together with selected fields. */
+     @Override
+     public String toString() {
+         return toStringHelper().toString();
+     }
+     
+     /**
+      * Override this to add to the toString(), e.g. {@code return super.toStringHelper().add("port", port);}
+      *
+      * Cannot be used in combination with overriding the deprecated toStringFieldsToInclude.
+      */
+     protected ToStringHelper toStringHelper() {
+         return Objects.toStringHelper(this).omitNullValues().add("id", getId());
+     }
+     
+     // -------- INITIALIZATION --------------
+ 
+     /**
+      * Default entity initialization, just calls {@link #initEnrichers()}.
+      */
+     public void init() {
+         super.init();
+         initEnrichers();
+     }
+     
+     /**
+      * By default, adds enrichers to populate {@link Attributes#SERVICE_UP} and {@link Attributes#SERVICE_STATE_ACTUAL}
+      * based on {@link Attributes#SERVICE_NOT_UP_INDICATORS}, 
+      * {@link Attributes#SERVICE_STATE_EXPECTED} and {@link Attributes#SERVICE_PROBLEMS}
+      * (doing nothing if these sensors are not used).
+      * <p>
+      * Subclasses may go further and populate the {@link Attributes#SERVICE_NOT_UP_INDICATORS} 
+      * and {@link Attributes#SERVICE_PROBLEMS} from children and members or other sources.
+      */
+     // these enrichers do nothing unless Attributes.SERVICE_NOT_UP_INDICATORS are used
+     // and/or SERVICE_STATE_EXPECTED 
+     protected void initEnrichers() {
+         enrichers().add(ServiceNotUpLogic.newEnricherForServiceUpIfNotUpIndicatorsEmpty());
+         enrichers().add(ServiceStateLogic.newEnricherForServiceStateFromProblemsAndUp());
+     }
+     
+     // -------- POLICIES --------------------
+ 
+     @Override 
+     @Beta
+     // the concrete type rather than an interface is returned because Groovy subclasses
+     // complain (incorrectly) if we return PolicySupportInternal
+     // TODO revert to PolicySupportInternal when groovy subclasses work without this (eg new groovy version)
+     public BasicPolicySupport policies() {
+         return policies;
+     }
+ 
+     @Override 
+     @Beta
+     // the concrete type rather than an interface is returned because Groovy subclasses
+     // complain (incorrectly) if we return EnricherSupportInternal
+     // TODO revert to EnricherSupportInternal when groovy subclasses work without this (eg new groovy version)
+     public BasicEnricherSupport enrichers() {
+         return enrichers;
+     }
+ 
+     /**
+      * Direct use of this class is strongly discouraged. It will become private in a future release,
+      * once {@link #policies()} is reverted to return {@link {PolicySupportInternal} instead of
+      * {@link BasicPolicySupport}.
+      */
+     @Beta
+     // TODO revert to private when config() is reverted to return SensorSupportInternal
+     public class BasicPolicySupport implements PolicySupportInternal {
+         
+         @Override
+         public Iterator<Policy> iterator() {
+             return asList().iterator();
+         }
+ 
+         @Override
+         public int size() {
+             return policiesInternal.size();
+         }
+         @Override
+         public boolean isEmpty() {
+             return policiesInternal.isEmpty();
+         }
+         
+         protected List<Policy> asList() {
+             return ImmutableList.<Policy>copyOf(policiesInternal);
+         }
+ 
+         @Override
+         public void add(Policy policy) {
+             Policy old = findApparentlyEqualAndWarnIfNotSameUniqueTag(policiesInternal, policy);
+             if (old!=null) {
+                 LOG.debug("Removing "+old+" when adding "+policy+" to "+AbstractEntity.this);
+                 remove(old);
+             }
+             
+             CatalogUtils.setCatalogItemIdOnAddition(AbstractEntity.this, policy);
+             policiesInternal.add((AbstractPolicy)policy);
+             ((AbstractPolicy)policy).setEntity(AbstractEntity.this);
+             
+             getManagementSupport().getEntityChangeListener().onPolicyAdded(policy);
+             sensors().emit(AbstractEntity.POLICY_ADDED, new PolicyDescriptor(policy));
+         }
+ 
+         @Override
+         public <T extends Policy> T add(PolicySpec<T> spec) {
+             T policy = getManagementContext().getEntityManager().createPolicy(spec);
+             add(policy);
+             return policy;
+         }
+         
+         @Override
+         public boolean remove(Policy policy) {
+             ((AbstractPolicy)policy).destroy();
+             boolean changed = policiesInternal.remove(policy);
+             
+             if (changed) {
+                 getManagementSupport().getEntityChangeListener().onPolicyRemoved(policy);
+                 sensors().emit(AbstractEntity.POLICY_REMOVED, new PolicyDescriptor(policy));
+             }
+             return changed;
+         }
+         
+         @Override
+         public boolean removeAllPolicies() {
+             boolean changed = false;
+             for (Policy policy : policiesInternal) {
+                 remove(policy);
+                 changed = true;
+             }
+             return changed;
+         }
+     }
+ 
+     /**
+      * Direct use of this class is strongly discouraged. It will become private in a future release,
+      * once {@link #enrichers()} is reverted to return {@link EnricherSupportInternal} instead of
+      * {@link BasicEnricherSupport}.
+      */
+     @Beta
+     // TODO revert to private when config() is reverted to return SensorSupportInternal
+     public class BasicEnricherSupport implements EnricherSupportInternal {
+         @Override
+         public Iterator<Enricher> iterator() {
+             return asList().iterator();
+         }
+ 
+         @Override
+         public int size() {
+             return enrichersInternal.size();
+         }
+         @Override
+         public boolean isEmpty() {
+             return enrichersInternal.isEmpty();
+         }
+         
+         protected List<Enricher> asList() {
+             return ImmutableList.<Enricher>copyOf(enrichersInternal);
+         }
+ 
+         @Override
+         public <T extends Enricher> T add(EnricherSpec<T> spec) {
+             T enricher = getManagementContext().getEntityManager().createEnricher(spec);
+             add(enricher);
+             return enricher;
+         }
+ 
+         @Override
+         public void add(Enricher enricher) {
+             Enricher old = findApparentlyEqualAndWarnIfNotSameUniqueTag(enrichersInternal, enricher);
+             if (old!=null) {
+                 LOG.debug("Removing "+old+" when adding "+enricher+" to "+AbstractEntity.this);
+                 remove(old);
+             }
+             
+             CatalogUtils.setCatalogItemIdOnAddition(AbstractEntity.this, enricher);
+             enrichersInternal.add((AbstractEnricher) enricher);
+             ((AbstractEnricher)enricher).setEntity(AbstractEntity.this);
+             
+             getManagementSupport().getEntityChangeListener().onEnricherAdded(enricher);
+             // TODO Could add equivalent of AbstractEntity.POLICY_ADDED for enrichers; no use-case for that yet
+         }
+         
+         @Override
+         public boolean remove(Enricher enricher) {
+             ((AbstractEnricher)enricher).destroy();
+             boolean changed = enrichersInternal.remove(enricher);
+             
+             if (changed) {
+                 getManagementSupport().getEntityChangeListener().onEnricherRemoved(enricher);
+             }
+             return changed;
+ 
+         }
+ 
+         @Override
+         public boolean removeAll() {
+             boolean changed = false;
+             for (AbstractEnricher enricher : enrichersInternal) {
+                 changed = remove(enricher) || changed;
+             }
+             return changed;
+         }
+     }
+     
+     /**
+      * @deprecated since 0.9.0; see {@link BasicPolicySupport#iterator()}; e.g. {@code policies().iterator()}
+      */
+     @Override
+     @Deprecated
+     public Collection<Policy> getPolicies() {
+         return policies().asList();
+     }
+ 
+     /**
+      * @deprecated since 0.9.0; see {@link BasicPolicySupport#addPolicy(Policy)}; e.g. {@code policies().addPolicy(policy)}
+      */
+     @Override
+     @Deprecated
+     public void addPolicy(Policy policy) {
+         policies().add(policy);
+     }
+ 
+     /**
+      * @deprecated since 0.9.0; see {@link BasicPolicySupport#addPolicy(PolicySpec)}; e.g. {@code policies().addPolicy(spec)}
+      */
+     @Override
+     @Deprecated
+     public <T extends Policy> T addPolicy(PolicySpec<T> spec) {
+         return policies().add(spec);
+     }
+ 
+     /**
+      * @deprecated since 0.9.0; see {@link BasicEnricherSupport#; e.g. {@code enrichers().addEnricher(spec)}
+      */
+     @Override
+     @Deprecated
+     public <T extends Enricher> T addEnricher(EnricherSpec<T> spec) {
+         return enrichers().add(spec);
+     }
+ 
+     /**
+      * @deprecated since 0.9.0; see {@link BasicPolicySupport#removePolicy(Policy)}; e.g. {@code policies().removePolicy(policy)}
+      */
+     @Override
+     @Deprecated
+     public boolean removePolicy(Policy policy) {
+         return policies().remove(policy);
+     }
+     
+     /**
+      * @deprecated since 0.9.0; see {@link BasicPolicySupport#removeAllPolicies()}; e.g. {@code policies().removeAllPolicies()}
+      */
+     @Override
+     @Deprecated
+     public boolean removeAllPolicies() {
+         return policies().removeAllPolicies();
+     }
+     
+     /**
+      * @deprecated since 0.9.0; see {@link BasicEnricherSupport#iterator()}; e.g. {@code enrichers().iterator()}
+      */
+     @Override
+     @Deprecated
+     public Collection<Enricher> getEnrichers() {
+         return enrichers().asList();
+     }
+ 
+     /**
+      * @deprecated since 0.9.0; see {@link BasicEnricherSupport#addEnricher(Enricher)}; e.g. {@code enrichers().addEnricher(enricher)}
+      */
+     @Override
+     @Deprecated
+     public void addEnricher(Enricher enricher) {
+         enrichers().add(enricher);
+     }
+     
+     private <T extends EntityAdjunct> T findApparentlyEqualAndWarnIfNotSameUniqueTag(Collection<? extends T> items, T newItem) {
+         T oldItem = findApparentlyEqual(items, newItem, true);
+         
+         if (oldItem!=null) {
+             String oldItemTag = oldItem.getUniqueTag();
+             String newItemTag = newItem.getUniqueTag();
+             if (oldItemTag!=null || newItemTag!=null) {
+                 if (Objects.equal(oldItemTag, newItemTag)) {
+                     // if same tag, return old item for replacing without comment
+                     return oldItem;
+                 }
+                 // if one has a tag bug not the other, and they are apparently equal,
+                 // transfer the tag across
+                 T tagged = oldItemTag!=null ? oldItem : newItem;
+                 T tagless = oldItemTag!=null ? newItem : oldItem;
+                 LOG.warn("Apparently equal items "+oldItem+" and "+newItem+"; but one has a unique tag "+tagged.getUniqueTag()+"; applying to the other");
+                 ((AdjunctTagSupport)tagless.tags()).setUniqueTag(tagged.getUniqueTag());
+             }
+             
+             if (isRebinding()) {
+                 LOG.warn("Adding to "+this+", "+newItem+" appears identical to existing "+oldItem+"; will replace. "
+                     + "Underlying addition should be modified so it is not added twice during rebind or unique tag should be used to indicate it is identical.");
+                 return oldItem;
+             } else {
+                 LOG.warn("Adding to "+this+", "+newItem+" appears identical to existing "+oldItem+"; may get removed on rebind. "
+                     + "Underlying addition should be modified so it is not added twice.");
+                 return null;
+             }
+         } else {
+             return null;
+         }
+     }
+     private <T extends EntityAdjunct> T findApparentlyEqual(Collection<? extends T> itemsCopy, T newItem, boolean transferUniqueTag) {
+         // TODO workaround for issue where enrichers/feeds/policies can get added multiple times on rebind,
+         // if it's added in onBecomingManager or connectSensors; 
+         // the right fix will be more disciplined about how/where these are added;
+         // furthermore unique tags should be preferred;
+         // when they aren't supplied, a reflection equals is done ignoring selected fields,
+         // which is okay but not great ... and if it misses something (e.g. because an 'equals' isn't implemented)
+         // then you can get a new instance on every rebind
+         // (and currently these aren't readily visible, except looking at the counts or in persisted state) 
+         Class<?> beforeEntityAdjunct = newItem.getClass();
+         while (beforeEntityAdjunct.getSuperclass()!=null && !beforeEntityAdjunct.getSuperclass().equals(AbstractEntityAdjunct.class))
+             beforeEntityAdjunct = beforeEntityAdjunct.getSuperclass();
+         
+         String newItemTag = newItem.getUniqueTag();
+         for (T oldItem: itemsCopy) {
+             String oldItemTag = oldItem.getUniqueTag();
+             if (oldItemTag!=null && newItemTag!=null) { 
+                 if (oldItemTag.equals(newItemTag)) {
+                     return oldItem;
+                 } else {
+                     continue;
+                 }
+             }
+             // either does not have a unique tag, do deep equality
+             if (oldItem.getClass().equals(newItem.getClass())) {
+                 if (EqualsBuilder.reflectionEquals(oldItem, newItem, false,
+                         // internal admin in 'beforeEntityAdjunct' should be ignored
+                         beforeEntityAdjunct,
+                         // known fields which shouldn't block equality checks:
+                         // from aggregator
+                         "transformation",
+                         // from averager
+                         "values", "timestamps", "lastAverage",
+                         // from some feeds
+                         "poller",
+                         "pollerStateMutex"
+                         )) {
+                     
+                     return oldItem;
+                 }
+             }
+         }
+         return null;
+     }
+ 
+     /**
+      * @deprecated since 0.9.0; see {@link BasicEnricherSupport#removeEnricher(Enricher)}; e.g. {@code enrichers().removeEnricher(enricher)}
+      */
+     @Override
+     @Deprecated
+     public boolean removeEnricher(Enricher enricher) {
+         return enrichers().remove(enricher);
+     }
+ 
+     /**
+      * @deprecated since 0.9.0; see {@link BasicEnricherSupport#removeAllEnrichers()}; e.g. {@code enrichers().removeAllEnrichers()}
+      */
+     @Override
+     @Deprecated
+     public boolean removeAllEnrichers() {
+         return enrichers().removeAll();
+     }
+     
+     // -------- FEEDS --------------------
+ 
+     /**
+      * Convenience, which calls {@link EntityInternal#feeds()} and {@link FeedSupport#addFeed(Feed)}.
+      */
+     @Override
+     public <T extends Feed> T addFeed(T feed) {
+         return feeds().addFeed(feed);
+     }
+ 
+     @Override
+     public FeedSupport feeds() {
+         return new BasicFeedSupport();
+     }
+     
+     @Override
+     @Deprecated
+     public FeedSupport getFeedSupport() {
+         return feeds();
+     }
+     
+     protected class BasicFeedSupport implements FeedSupport {
+         @Override
+         public Collection<Feed> getFeeds() {
+             return ImmutableList.<Feed>copyOf(feeds);
+         }
+ 
+         @Override
+         public <T extends Feed> T addFeed(T feed) {
+             Feed old = findApparentlyEqualAndWarnIfNotSameUniqueTag(feeds, feed);
+             if (old != null) {
+                 if (old == feed) {
+                     if (!BrooklynFeatureEnablement.isEnabled(BrooklynFeatureEnablement.FEATURE_FEED_REGISTRATION_PROPERTY)) {
+                         LOG.debug("Feed " + feed + " already added, not adding a second time.");
+                     } // else expected to be added a second time through addFeed, ignore
+                     return feed;
+                 } else {
+                     // Different feed object with (seemingly) same functionality, remove previous one, will stop it.
+                     LOG.debug("Removing "+old+" when adding "+feed+" to "+this);
+                     removeFeed(old);
+                 }
+             }
+             
+             CatalogUtils.setCatalogItemIdOnAddition(AbstractEntity.this, feed);
+             feeds.add(feed);
+             if (!AbstractEntity.this.equals(((AbstractFeed)feed).getEntity()))
+                 ((AbstractFeed)feed).setEntity(AbstractEntity.this);
+ 
+             getManagementContext().getRebindManager().getChangeListener().onManaged(feed);
+             getManagementSupport().getEntityChangeListener().onFeedAdded(feed);
+             // TODO Could add equivalent of AbstractEntity.POLICY_ADDED for feeds; no use-case for that yet
+ 
+             return feed;
+         }
+ 
+         @Override
+         public boolean removeFeed(Feed feed) {
+             feed.stop();
+             boolean changed = feeds.remove(feed);
+             
+             if (changed) {
+                 getManagementContext().getRebindManager().getChangeListener().onUnmanaged(feed);
+                 getManagementSupport().getEntityChangeListener().onFeedRemoved(feed);
+             }
+             return changed;
+         }
+ 
+         @Override
+         public boolean removeAllFeeds() {
+             boolean changed = false;
+             for (Feed feed : feeds) {
+                 changed = removeFeed(feed) || changed;
+             }
+             return changed;
+         }
+     }
+     
+     // -------- SENSORS --------------------
+ 
+     @Override
+     @Deprecated
+     public <T> void emit(Sensor<T> sensor, T val) {
+         sensors().emit(sensor, val);
+     }
+     
+     /**
+      * Warning: for internal purposes only; this method may be deleted without notice in future releases.
+      */
+     public <T> void emitInternal(Sensor<T> sensor, T val) {
+         sensors().emitInternal(sensor, val);
+     }
+ 
+     // -------- EFFECTORS --------------
+ 
+     /** Convenience for finding named effector in {@link EntityType#getEffectors()} {@link Map}. */
+     public Effector<?> getEffector(String effectorName) {
+         return entityType.getEffector(effectorName);
+     }
+ 
+     /** Invoke an {@link Effector} directly. */
+     public <T> Task<T> invoke(Effector<T> eff) {
+         return invoke(MutableMap.of(), eff);
+     }
+     
+     public <T> Task<T> invoke(Map parameters, Effector<T> eff) {
+         return invoke(eff, parameters);
+     }
+ 
+     /**
+      * Additional form supplied for when the parameter map needs to be made explicit.
+      *
+      * @see #invoke(Effector)
+      */
+     @Override
+     public <T> Task<T> invoke(Effector<T> eff, Map<String,?> parameters) {
+         return EffectorUtils.invokeEffectorAsync(this, eff, parameters);
+     }
+ 
+     /**
+      * Invoked by {@link EntityManagementSupport} when this entity is becoming managed (i.e. it has a working
+      * management context, but before the entity is visible to other entities), including during a rebind.
+      */
+     public void onManagementStarting() {
+         if (isLegacyConstruction()) {
+             entityType.setName(getEntityTypeName());
+             if (displayNameAutoGenerated) displayName.set(getEntityType().getSimpleName()+":"+Strings.maxlen(getId(), 4));
+         }
+     }
+     
+     /**
+      * Invoked by {@link EntityManagementSupport} when this entity is fully managed and visible to other entities
+      * through the management context.
+      */
+     public void onManagementStarted() {}
+     
+     /**
+      * Invoked by {@link ManagementContext} when this entity becomes m

<TRUNCATED>