/*
* 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.objs;
import static com.google.common.base.Preconditions.checkState;
import static org.apache.brooklyn.util.groovy.GroovyJavaMethods.truth;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.api.entity.EntityLocal;
import org.apache.brooklyn.api.entity.Group;
import org.apache.brooklyn.api.mgmt.ExecutionContext;
import org.apache.brooklyn.api.mgmt.SubscriptionHandle;
import org.apache.brooklyn.api.mgmt.Task;
import org.apache.brooklyn.api.objs.BrooklynObject;
import org.apache.brooklyn.api.objs.Configurable;
import org.apache.brooklyn.api.objs.EntityAdjunct;
import org.apache.brooklyn.api.sensor.AttributeSensor;
import org.apache.brooklyn.api.sensor.Sensor;
import org.apache.brooklyn.api.sensor.SensorEventListener;
import org.apache.brooklyn.config.ConfigKey;
import org.apache.brooklyn.config.ConfigMap;
import org.apache.brooklyn.core.config.ConfigConstraints;
import org.apache.brooklyn.core.config.ConfigKeys;
import org.apache.brooklyn.core.enricher.AbstractEnricher;
import org.apache.brooklyn.core.entity.Entities;
import org.apache.brooklyn.core.entity.EntityInternal;
import org.apache.brooklyn.core.mgmt.internal.SubscriptionTracker;
import org.apache.brooklyn.util.core.config.ConfigBag;
import org.apache.brooklyn.util.core.flags.FlagUtils;
import org.apache.brooklyn.util.core.flags.SetFromFlag;
import org.apache.brooklyn.util.core.flags.TypeCoercions;
import org.apache.brooklyn.util.guava.Maybe;
import org.apache.brooklyn.util.text.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.annotations.Beta;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
/**
* Common functionality for policies and enrichers
*/
public abstract class AbstractEntityAdjunct extends AbstractBrooklynObject implements BrooklynObjectInternal, EntityAdjunct, Configurable {
private static final Logger log = LoggerFactory.getLogger(AbstractEntityAdjunct.class);
private boolean _legacyNoConstructionInit;
/**
* @deprecated since 0.7.0; leftover properties are put into config, since when coming from yaml this is normal.
*/
@Deprecated
protected Map<String,Object> leftoverProperties = Maps.newLinkedHashMap();
protected transient ExecutionContext execution;
private final BasicConfigurationSupport config = new BasicConfigurationSupport();
private final BasicSubscriptionSupport subscriptions = new BasicSubscriptionSupport();
/**
* The config values of this entity. Updating this map should be done
* via {@link #config()}.
*
* @deprecated since 0.7.0; use {@link #config()} instead; this field may be made private or deleted in a future release.
*/
@Deprecated
protected final AdjunctConfigMap configsInternal = new AdjunctConfigMap(this);
/**
* @deprecated since 0.7.0; use {@link #getAdjunctType()} instead; this field may be made private or deleted in a future release.
*/
@Deprecated
protected final AdjunctType adjunctType = new AdjunctType(this);
@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);
@SetFromFlag(value="uniqueTag")
protected String uniqueTag;
public AbstractEntityAdjunct() {
this(Collections.emptyMap());
}
public AbstractEntityAdjunct(@SuppressWarnings("rawtypes") Map properties) {
super(properties);
_legacyNoConstructionInit = (properties != null) && Boolean.TRUE.equals(properties.get("noConstructionInit"));
if (isLegacyConstruction()) {
AbstractEntityAdjunct checkWeGetThis = configure(properties);
assert this.equals(checkWeGetThis) : this+" configure method does not return itself; returns "+checkWeGetThis+" instead of "+this;
boolean deferConstructionChecks = (properties.containsKey("deferConstructionChecks") && TypeCoercions.coerce(properties.get("deferConstructionChecks"), Boolean.class));
if (!deferConstructionChecks) {
FlagUtils.checkRequiredFields(this);
}
}
}
/**
* @deprecated since 0.7.0; only used for legacy brooklyn types where constructor is called directly
*/
@Override
@Deprecated
@SuppressWarnings({ "unchecked", "rawtypes" })
public AbstractEntityAdjunct 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)) {
config().set(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());
if (!truth(name) && leftoverProperties.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(leftoverProperties.get("displayName") instanceof CharSequence, "'displayName' property should be a string");
setDisplayName(leftoverProperties.remove("displayName").toString());
}
// set leftover leftoverProperties should as config items; particularly useful when these have come from a brooklyn.config map
for (Object flag: leftoverProperties.keySet()) {
ConfigKey<Object> key = ConfigKeys.newConfigKey(Object.class, Strings.toString(flag));
if (config().getRaw(key).isPresent()) {
log.warn("Config '"+flag+"' on "+this+" conflicts with key already set; ignoring");
} else {
config().set(key, leftoverProperties.get(flag));
}
}
return this;
}
/**
* 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;
}
@Override
public ConfigurationSupportInternal config() {
return config;
}
@Override
public BasicSubscriptionSupport subscriptions() {
return subscriptions;
}
public class BasicSubscriptionSupport implements SubscriptionSupportInternal {
@Override
public <T> SubscriptionHandle subscribe(Entity producer, Sensor<T> sensor, SensorEventListener<? super T> listener) {
if (!checkCanSubscribe()) return null;
return getSubscriptionTracker().subscribe(producer, sensor, listener);
}
@Override
public <T> SubscriptionHandle subscribe(Map<String, ?> flags, Entity producer, Sensor<T> sensor, SensorEventListener<? super T> listener) {
if (!checkCanSubscribe()) return null;
return getSubscriptionTracker().subscribe(flags, producer, sensor, listener);
}
@Override
public <T> SubscriptionHandle subscribeToMembers(Group producerGroup, Sensor<T> sensor, SensorEventListener<? super T> listener) {
if (!checkCanSubscribe(producerGroup)) return null;
return getSubscriptionTracker().subscribeToMembers(producerGroup, sensor, listener);
}
@Override
public <T> SubscriptionHandle subscribeToChildren(Entity producerParent, Sensor<T> sensor, SensorEventListener<? super T> listener) {
if (!checkCanSubscribe(producerParent)) return null;
return getSubscriptionTracker().subscribeToChildren(producerParent, sensor, listener);
}
@Override
public boolean unsubscribe(Entity producer) {
if (destroyed.get()) return false;
return getSubscriptionTracker().unsubscribe(producer);
}
@Override
public boolean unsubscribe(Entity producer, SubscriptionHandle handle) {
if (destroyed.get()) return false;
return getSubscriptionTracker().unsubscribe(producer, handle);
}
@Override
public boolean unsubscribe(SubscriptionHandle handle) {
if (destroyed.get()) return false;
return getSubscriptionTracker().unsubscribe(handle);
}
@Override
public void unsubscribeAll() {
if (destroyed.get()) return;
getSubscriptionTracker().unsubscribeAll();
}
protected SubscriptionTracker getSubscriptionTracker() {
synchronized (AbstractEntityAdjunct.this) {
if (_subscriptionTracker!=null) return _subscriptionTracker;
if (entity==null) return null;
_subscriptionTracker = new SubscriptionTracker(((EntityInternal)entity).getManagementSupport().getSubscriptionContext());
return _subscriptionTracker;
}
}
/** 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;
}
/**
* @return a list of all subscription handles
*/
protected Collection<SubscriptionHandle> getAllSubscriptions() {
SubscriptionTracker tracker = getSubscriptionTracker();
return (tracker != null) ? tracker.getAllSubscriptions() : Collections.<SubscriptionHandle>emptyList();
}
}
private class BasicConfigurationSupport extends AbstractConfigurationSupportInternal {
@Override
public <T> T get(ConfigKey<T> key) {
return configsInternal.getConfig(key);
}
@SuppressWarnings("unchecked")
@Override
public <T> T set(ConfigKey<T> key, T val) {
ConfigConstraints.assertValid(entity, key, val);
if (entity != null && isRunning()) {
doReconfigureConfig(key, val);
}
T result = (T) configsInternal.setConfig(key, val);
onChanged();
return result;
}
@SuppressWarnings("unchecked")
@Override
public <T> T set(ConfigKey<T> key, Task<T> val) {
if (entity != null && isRunning()) {
// TODO Support for AbstractEntityAdjunct
throw new UnsupportedOperationException();
}
T result = (T) configsInternal.setConfig(key, val);
onChanged();
return result;
}
@Override
public ConfigBag getBag() {
return getLocalBag();
}
@Override
public ConfigBag getLocalBag() {
return ConfigBag.newInstance(configsInternal.getAllConfig());
}
@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() {
// no-op for location
}
@Override
public void refreshInheritedConfigOfChildren() {
// no-op for location
}
@Override
protected ExecutionContext getContext() {
return AbstractEntityAdjunct.this.execution;
}
}
@Override
public <T> T getConfig(ConfigKey<T> key) {
return config().get(key);
}
protected <K> K getRequiredConfig(ConfigKey<K> key) {
K result = config().get(key);
if (result==null)
throw new NullPointerException("Value required for '"+key.getName()+"' in "+this);
return result;
}
@Override
@Deprecated
public <T> T setConfig(ConfigKey<T> key, T val) {
return config().set(key, val);
}
// TODO make immutable
/** for inspection only */
@Beta
@Deprecated
public ConfigMap getConfigMap() {
return configsInternal;
}
/**
* Invoked whenever a config change is applied after management is started.
* Default implementation throws an exception to disallow the change.
* Can be overridden to return (allowing the change) or to make other changes
* (if necessary), and of course it can do this selectively and call the super to disallow any others. */
protected <T> void doReconfigureConfig(ConfigKey<T> key, T val) {
throw new UnsupportedOperationException("reconfiguring "+key+" unsupported for "+this);
}
@Override
protected void onTagsChanged() {
onChanged();
}
protected abstract void onChanged();
public AdjunctType getAdjunctType() {
return adjunctType;
}
@Override
public String getDisplayName() {
if (name!=null && name.length()>0) return name;
return getClass().getCanonicalName();
}
@Override
public void setDisplayName(String name) {
this.name = name;
}
public void setEntity(EntityLocal entity) {
if (destroyed.get()) throw new IllegalStateException("Cannot set entity on a destroyed entity adjunct");
this.entity = entity;
if (entity!=null && getCatalogItemId() == null) {
setCatalogItemId(entity.getCatalogItemId());
}
}
/** @deprecated since 0.7.0 only {@link AbstractEnricher} has emit convenience */
protected <T> void emit(Sensor<T> sensor, Object val) {
checkState(entity != null, "entity must first be set");
if (val == Entities.UNCHANGED) {
return;
}
if (val == Entities.REMOVE) {
((EntityInternal)entity).removeAttribute((AttributeSensor<T>) sensor);
return;
}
T newVal = TypeCoercions.coerce(val, sensor.getTypeToken());
if (sensor instanceof AttributeSensor) {
entity.sensors().set((AttributeSensor<T>)sensor, newVal);
} else {
entity.sensors().emit(sensor, newVal);
}
}
/**
* @deprecated since 0.9.0; for internal use only
*/
@Deprecated
protected synchronized SubscriptionTracker getSubscriptionTracker() {
if (_subscriptionTracker!=null) return _subscriptionTracker;
if (entity==null) return null;
_subscriptionTracker = new SubscriptionTracker(((EntityInternal)entity).getManagementSupport().getSubscriptionContext());
return _subscriptionTracker;
}
/**
* @deprecated since 0.9.0; see {@link SubscriptionSupport#subscribe(Entity, Sensor, SensorEventListener)} and {@link BrooklynObject#subscriptions()}
*/
@Deprecated
public <T> SubscriptionHandle subscribe(Entity producer, Sensor<T> sensor, SensorEventListener<? super T> listener) {
if (!checkCanSubscribe()) return null;
return getSubscriptionTracker().subscribe(producer, sensor, listener);
}
/**
* @deprecated since 0.9.0; see {@link SubscriptionSupport#subscribeToMembers(Entity, Sensor, SensorEventListener)} and {@link BrooklynObject#subscriptions()}
*/
@Deprecated
public <T> SubscriptionHandle subscribeToMembers(Group producerGroup, Sensor<T> sensor, SensorEventListener<? super T> listener) {
if (!checkCanSubscribe(producerGroup)) return null;
return getSubscriptionTracker().subscribeToMembers(producerGroup, sensor, listener);
}
/**
* @deprecated since 0.9.0; see {@link SubscriptionSupport#subscribeToChildren(Entity, Sensor, SensorEventListener)} and {@link BrooklynObject#subscriptions()}
*/
@Deprecated
public <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 BasicSubscriptionSupport#checkCanSubscribe(Entity)
*/
@Deprecated
protected boolean check(Entity requiredEntity) {
return checkCanSubscribe(requiredEntity);
}
/**
* @deprecated since 0.9.0; for internal use only
*/
@Deprecated
protected boolean checkCanSubscribe(Entity producer) {
return subscriptions().checkCanSubscribe(producer);
}
/**
* @deprecated since 0.9.0; for internal use only
*/
@Deprecated
protected boolean checkCanSubscribe() {
return subscriptions().checkCanSubscribe();
}
/**
* @deprecated since 0.9.0; see {@link SubscriptionSupport#unsubscribe(Entity)} and {@link BrooklynObject#subscriptions()}
*/
@Deprecated
public boolean unsubscribe(Entity producer) {
return subscriptions().unsubscribe(producer);
}
/**
* @deprecated since 0.9.0; see {@link SubscriptionSupport#unsubscribe(Entity, SubscriptionHandle)} and {@link BrooklynObject#subscriptions()}
*/
@Deprecated
public boolean unsubscribe(Entity producer, SubscriptionHandle handle) {
return subscriptions().unsubscribe(producer, handle);
}
/**
* @deprecated since 0.9.0; for internal use only
*/
@Deprecated
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 getUniqueTag() {
return uniqueTag;
}
@Override
public TagSupport tags() {
return new AdjunctTagSupport();
}
public class AdjunctTagSupport extends BasicTagSupport {
@Override
public Set<Object> getTags() {
ImmutableSet.Builder<Object> rb = ImmutableSet.builder().addAll(super.getTags());
if (getUniqueTag()!=null) rb.add(getUniqueTag());
return rb.build();
}
public String getUniqueTag() {
return AbstractEntityAdjunct.this.getUniqueTag();
}
public void setUniqueTag(String uniqueTag) {
AbstractEntityAdjunct.this.uniqueTag = uniqueTag;
}
}
@Override
public String toString() {
return Objects.toStringHelper(getClass()).omitNullValues()
.add("name", name)
.add("uniqueTag", uniqueTag)
.add("running", isRunning())
.add("entity", entity)
.add("id", getId())
.toString();
}
}