/* * 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.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.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; // 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 = 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>(); 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. */ // 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. */ // 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() { 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() { 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) { if (newLocations==null || newLocations.isEmpty()) { return; } synchronized (locations) { List<Location> oldLocations = locations.get(); Set<Location> trulyNewLocations = Sets.newLinkedHashSet(newLocations); trulyNewLocations.removeAll(oldLocations); if (trulyNewLocations.size() > 0) { locations.set(ImmutableList.<Location>builder().addAll(oldLocations).addAll(trulyNewLocations).build()); } for (Location loc : trulyNewLocations) { 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 managed at a particular management node, * including the initial management started and subsequent management node master-change for this entity. * @deprecated since 0.4.0 override EntityManagementSupport.onManagementStarted if customization needed */ public void onManagementBecomingMaster() {} /** * Invoked by {@link ManagementContext} when this entity becomes mastered at a particular management node, * including the final management end and subsequent management node master-change for this entity. * @deprecated since 0.4.0 override EntityManagementSupport.onManagementStopped if customization needed */ public void onManagementNoLongerMaster() {} /** * Invoked by {@link EntityManagementSupport} when this entity is fully unmanaged. * <p> * Note that the activies possible here (when unmanaged) are limited, * and that this event may be caused by either a brooklyn node itself being demoted * (so the entity is managed elsewhere) or by a controlled shutdown. */ public void onManagementStopped() { if (getManagementContext().isRunning()) { BrooklynStorage storage = ((ManagementContextInternal)getManagementContext()).getStorage(); storage.remove(getId()+"-parent"); storage.remove(getId()+"-groups"); storage.remove(getId()+"-children"); storage.remove(getId()+"-locations"); storage.remove(getId()+"-creationTime"); storage.remove(getId()+"-displayName"); storage.remove(getId()+"-config"); storage.remove(getId()+"-attributes"); } } /** For use by management plane, to invalidate all fields (e.g. when an entity is changing to being proxied) */ public void invalidateReferences() { // TODO Just rely on GC of this entity instance, to get rid of the children map etc. // Don't clear it, as it's persisted. // TODO move this to EntityMangementSupport, application = null; } @Override public EntityManagementSupport getManagementSupport() { return managementSupport; } @Override public void requestPersist() { getManagementSupport().getEntityChangeListener().onChanged(); } /** * As described in {@link EntityInternal#getRebindSupport()}... * Users are strongly discouraged to call or override this method. * It is for internal calls only, relating to persisting/rebinding entities. * This method may change (or be removed) in a future release without notice. */ @Override @Beta public RebindSupport<EntityMemento> getRebindSupport() { return new BasicEntityRebindSupport(this); } @Override protected void onTagsChanged() { super.onTagsChanged(); getManagementSupport().getEntityChangeListener().onTagsChanged(); } @SuppressWarnings("unchecked") @Override public RelationSupportInternal<Entity> relations() { return (RelationSupportInternal<Entity>) super.relations(); } }