blob: 8d3bc01b971876386ad29823e97b0cf21399f644 [file] [log] [blame]
package brooklyn.policy.basic;
import static brooklyn.util.GroovyJavaMethods.truth;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import brooklyn.config.ConfigKey;
import brooklyn.config.ConfigMap;
import brooklyn.entity.Entity;
import brooklyn.entity.Group;
import brooklyn.entity.basic.EntityInternal;
import brooklyn.entity.basic.EntityLocal;
import brooklyn.entity.proxying.InternalPolicyFactory;
import brooklyn.entity.trait.Configurable;
import brooklyn.event.AttributeSensor;
import brooklyn.event.Sensor;
import brooklyn.event.SensorEventListener;
import brooklyn.management.ExecutionContext;
import brooklyn.management.ManagementContext;
import brooklyn.management.SubscriptionContext;
import brooklyn.management.SubscriptionHandle;
import brooklyn.management.internal.SubscriptionTracker;
import brooklyn.policy.EntityAdjunct;
import brooklyn.util.config.ConfigBag;
import brooklyn.util.flags.FlagUtils;
import brooklyn.util.flags.SetFromFlag;
import brooklyn.util.flags.TypeCoercions;
import brooklyn.util.text.Identifiers;
import com.google.common.annotations.Beta;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
/**
* Common functionality for policies and enrichers
*/
public abstract class AbstractEntityAdjunct implements EntityAdjunct, Configurable {
private static final Logger log = LoggerFactory.getLogger(AbstractEntityAdjunct.class);
private volatile ManagementContext managementContext;
protected Map<String,Object> leftoverProperties = Maps.newLinkedHashMap();
private boolean _legacyConstruction;
private boolean _legacyNoConstructionInit;
// TODO not sure if we need this -- never read
@SuppressWarnings("unused")
private boolean inConstruction;
protected transient ExecutionContext execution;
/**
* The config values of this entity. Updating this map should be done
* via getConfig/setConfig.
*/
protected final ConfigMapImpl configsInternal = new ConfigMapImpl(this);
protected final AdjunctType adjunctType = new AdjunctType(this);
@SetFromFlag
protected String id = Identifiers.makeRandomId(8);
@SetFromFlag
protected String name;
protected transient EntityLocal entity;
/** not for direct access; refer to as 'subscriptionTracker' via getter so that it is initialized */
protected transient SubscriptionTracker _subscriptionTracker;
private AtomicBoolean destroyed = new AtomicBoolean(false);
public AbstractEntityAdjunct() {
this(Collections.emptyMap());
}
public AbstractEntityAdjunct(@SuppressWarnings("rawtypes") Map flags) {
inConstruction = true;
_legacyConstruction = !InternalPolicyFactory.FactoryConstructionTracker.isConstructing();
_legacyNoConstructionInit = (flags != null) && Boolean.TRUE.equals(flags.get("noConstructionInit"));
if (!_legacyConstruction && flags!=null && !flags.isEmpty()) {
log.debug("Using direct construction for "+getClass().getName()+" because properties were specified ("+flags+")");
_legacyConstruction = true;
}
if (_legacyConstruction) {
log.debug("Using direct construction for "+getClass().getName()+"; calling configure(Map) immediately");
configure(flags);
boolean deferConstructionChecks = (flags.containsKey("deferConstructionChecks") && TypeCoercions.coerce(flags.get("deferConstructionChecks"), Boolean.class));
if (!deferConstructionChecks) {
FlagUtils.checkRequiredFields(this);
}
}
inConstruction = false;
}
/** will set fields from flags, and put the remaining ones into the 'leftovers' map.
* can be subclassed for custom initialization but note the following.
* <p>
* if you require fields to be initialized you must do that in this method. You must
* *not* rely on field initializers because they may not run until *after* this method
* (this method is invoked by the constructor in this class, so initializers
* in subclasses will not have run when this overridden method is invoked.) */
protected void configure() {
configure(Collections.emptyMap());
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public void configure(Map flags) {
// TODO only set on first time through
boolean isFirstTime = true;
// allow config keys, and fields, to be set from these flags if they have a SetFromFlag annotation
// or if the value is a config key
for (Iterator<Map.Entry> iter = flags.entrySet().iterator(); iter.hasNext();) {
Map.Entry entry = iter.next();
if (entry.getKey() instanceof ConfigKey) {
ConfigKey key = (ConfigKey)entry.getKey();
if (adjunctType.getConfigKeys().contains(key)) {
setConfig(key, entry.getValue());
} else {
log.warn("Unknown configuration key {} for policy {}; ignoring", key, this);
iter.remove();
}
}
}
ConfigBag bag = new ConfigBag().putAll(flags);
FlagUtils.setFieldsFromFlags(this, bag, isFirstTime);
FlagUtils.setAllConfigKeys(this, bag, false);
leftoverProperties.putAll(bag.getUnusedConfig());
//replace properties _contents_ with leftovers so subclasses see leftovers only
flags.clear();
flags.putAll(leftoverProperties);
leftoverProperties = flags;
if (!truth(name) && flags.containsKey("displayName")) {
//TODO inconsistent with entity and location, where name is legacy and displayName is encouraged!
//'displayName' is a legacy way to refer to a policy's name
Preconditions.checkArgument(flags.get("displayName") instanceof CharSequence, "'displayName' property should be a string");
setName(flags.remove("displayName").toString());
}
}
protected boolean isLegacyConstruction() {
return _legacyConstruction;
}
/**
* Used for legacy-style policies/enrichers on rebind, to indicate that init() should not be called.
* Will likely be deleted in a future release; should not be called apart from by framework code.
*/
@Beta
protected boolean isLegacyNoConstructionInit() {
return _legacyNoConstructionInit;
}
public void setManagementContext(ManagementContext managementContext) {
this.managementContext = managementContext;
}
protected ManagementContext getManagementContext() {
return managementContext;
}
/**
* Called by framework (in new-style policies where PolicySpec was used) after configuring etc,
* but before a reference to this policy is shared.
*
* To preserve backwards compatibility for if the policy is constructed directly, one
* can call the code below, but that means it will be called after references to this
* policy have been shared with other entities.
* <pre>
* {@code
* if (isLegacyConstruction()) {
* init();
* }
* }
* </pre>
*/
public void init() {
// no-op
}
/**
* Called by framework (in new-style policies/enrichers where PolicySpec/EnricherSpec was used) on rebind,
* after configuring but before {@link #setEntity(EntityLocal)} and before a reference to this policy is shared.
* Note that {@link #init()} will not be called on rebind.
*/
public void rebind() {
// no-op
}
public <T> T getConfig(ConfigKey<T> key) {
return configsInternal.getConfig(key);
}
public Map<ConfigKey<?>, Object> getAllConfig() {
return configsInternal.getAllConfig();
}
protected <K> K getRequiredConfig(ConfigKey<K> key) {
return checkNotNull(getConfig(key), key.getName());
}
@SuppressWarnings("unchecked")
@Override
public <T> T setConfig(ConfigKey<T> key, T val) {
if (entity != null && isRunning()) {
doReconfigureConfig(key, val);
}
return (T) configsInternal.setConfig(key, val);
}
// TODO make immutable
/** for inspection only */
@Beta
public ConfigMap getConfigMap() {
return configsInternal;
}
protected <T> void doReconfigureConfig(ConfigKey<T> key, T val) {
throw new UnsupportedOperationException("reconfiguring "+key+" unsupported for "+this);
}
protected AdjunctType getAdjunctType() {
return adjunctType;
}
@Override
public String getName() {
if (name!=null && name.length()>0) return name;
return getClass().getCanonicalName();
}
public void setName(String name) { this.name = name; }
@Override
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public void setEntity(EntityLocal entity) {
if (destroyed.get()) throw new IllegalStateException("Cannot set entity on a destroyed entity adjunct");
this.entity = entity;
}
protected <T> void emit(Sensor<T> sensor, T val) {
checkState(entity != null, "entity must first be set");
if (sensor instanceof AttributeSensor) {
entity.setAttribute((AttributeSensor<T>)sensor, val);
} else {
entity.emit(sensor, val);
}
}
protected synchronized SubscriptionTracker getSubscriptionTracker() {
if (_subscriptionTracker!=null) return _subscriptionTracker;
if (entity==null) return null;
_subscriptionTracker = new SubscriptionTracker(((EntityInternal)entity).getManagementSupport().getSubscriptionContext());
return _subscriptionTracker;
}
/** @see SubscriptionContext#subscribe(Entity, Sensor, SensorEventListener) */
protected <T> SubscriptionHandle subscribe(Entity producer, Sensor<T> sensor, SensorEventListener<? super T> listener) {
if (!checkCanSubscribe()) return null;
return getSubscriptionTracker().subscribe(producer, sensor, listener);
}
/** @see SubscriptionContext#subscribe(Entity, Sensor, SensorEventListener) */
protected <T> SubscriptionHandle subscribeToMembers(Group producerGroup, Sensor<T> sensor, SensorEventListener<? super T> listener) {
if (!checkCanSubscribe(producerGroup)) return null;
return getSubscriptionTracker().subscribeToMembers(producerGroup, sensor, listener);
}
/** @see SubscriptionContext#subscribe(Entity, Sensor, SensorEventListener) */
protected <T> SubscriptionHandle subscribeToChildren(Entity producerParent, Sensor<T> sensor, SensorEventListener<? super T> listener) {
if (!checkCanSubscribe(producerParent)) return null;
return getSubscriptionTracker().subscribeToChildren(producerParent, sensor, listener);
}
/** @deprecated since 0.7.0 use {@link #checkCanSubscribe(Entity)} */
@Deprecated
protected boolean check(Entity requiredEntity) {
return checkCanSubscribe(requiredEntity);
}
/** returns false if deleted, throws exception if invalid state, otherwise true.
* okay if entity is not yet managed (but not if entity is no longer managed). */
protected boolean checkCanSubscribe(Entity producer) {
if (destroyed.get()) return false;
if (producer==null) throw new IllegalStateException(this+" given a null target for subscription");
if (entity==null) throw new IllegalStateException(this+" cannot subscribe to "+producer+" because it is not associated to an entity");
if (((EntityInternal)entity).getManagementSupport().isNoLongerManaged()) throw new IllegalStateException(this+" cannot subscribe to "+producer+" because the associated entity "+entity+" is no longer managed");
return true;
}
protected boolean checkCanSubscribe() {
if (destroyed.get()) return false;
if (entity==null) throw new IllegalStateException(this+" cannot subscribe because it is not associated to an entity");
if (((EntityInternal)entity).getManagementSupport().isNoLongerManaged()) throw new IllegalStateException(this+" cannot subscribe because the associated entity "+entity+" is no longer managed");
return true;
}
/**
* Unsubscribes the given producer.
*
* @see SubscriptionContext#unsubscribe(SubscriptionHandle)
*/
protected boolean unsubscribe(Entity producer) {
if (destroyed.get()) return false;
return getSubscriptionTracker().unsubscribe(producer);
}
/**
* Unsubscribes the given producer.
*
* @see SubscriptionContext#unsubscribe(SubscriptionHandle)
*/
protected boolean unsubscribe(Entity producer, SubscriptionHandle handle) {
if (destroyed.get()) return false;
return getSubscriptionTracker().unsubscribe(producer, handle);
}
/**
* @return a list of all subscription handles
*/
protected Collection<SubscriptionHandle> getAllSubscriptions() {
SubscriptionTracker tracker = getSubscriptionTracker();
return (tracker != null) ? tracker.getAllSubscriptions() : Collections.<SubscriptionHandle>emptyList();
}
/**
* Unsubscribes and clears all managed subscriptions; is called by the owning entity when a policy is removed
* and should always be called by any subclasses overriding this method
*/
public void destroy() {
destroyed.set(true);
SubscriptionTracker tracker = getSubscriptionTracker();
if (tracker != null) tracker.unsubscribeAll();
}
@Override
public boolean isDestroyed() {
return destroyed.get();
}
@Override
public boolean isRunning() {
return !isDestroyed();
}
@Override
public String toString() {
return Objects.toStringHelper(getClass())
.add("name", name)
.add("running", isRunning())
.toString();
}
}