blob: 93b30f6b65f68e125856a07a87ba6e1cd652c8ac [file] [log] [blame]
package brooklyn.policy.basic;
import static brooklyn.util.GroovyJavaMethods.truth;
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.entity.rebind.BasicPolicyRebindSupport;
import brooklyn.entity.rebind.RebindSupport;
import brooklyn.entity.trait.Configurable;
import brooklyn.management.ExecutionContext;
import brooklyn.mementos.PolicyMemento;
import brooklyn.policy.Policy;
import brooklyn.policy.PolicyType;
import brooklyn.util.config.ConfigBag;
import brooklyn.util.flags.FlagUtils;
import com.google.common.annotations.Beta;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
/**
* Base {@link Policy} implementation; all policies should extend this or its children
*/
public abstract class AbstractPolicy extends AbstractEntityAdjunct implements Policy, Configurable {
@SuppressWarnings("unused")
private static final Logger log = LoggerFactory.getLogger(AbstractPolicy.class);
protected String policyStatus;
protected Map leftoverProperties = Maps.newLinkedHashMap();
protected AtomicBoolean suspended = new AtomicBoolean(false);
protected transient ExecutionContext execution;
/**
* The config values of this entity. Updating this map should be done
* via getConfig/setConfig.
*/
protected final PolicyConfigMap configsInternal = new PolicyConfigMap(this);
private final PolicyType policyType = new PolicyTypeImpl(this);
public AbstractPolicy() {
this(Collections.emptyMap());
}
public AbstractPolicy(Map flags) {
configure(flags);
FlagUtils.checkRequiredFields(this);
}
/** 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")
protected 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 (getPolicyType().getConfigKeys().contains(key)) {
setConfig(key, entry.getValue());
} else {
log.warn("Unknown configuration key {} for policy {}; ignoring", key, this);
iter.remove();
}
}
}
// TODO use ConfigBag
ConfigBag bag = new ConfigBag().putAll(flags);
FlagUtils.setFieldsFromFlags(this, bag, isFirstTime);
FlagUtils.setAllConfigKeys(this, bag);
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")) {
//'displayName' is a legacy way to refer to a location's name
Preconditions.checkArgument(flags.get("displayName") instanceof CharSequence, "'displayName' property should be a string");
setName(flags.remove("displayName").toString());
}
}
public <T> T getConfig(ConfigKey<T> key) {
return configsInternal.getConfig(key);
}
public <T> T setConfig(ConfigKey<T> key, T val) {
if (entity != null && isRunning()) {
doReconfigureConfig(key, val);
}
return (T) configsInternal.setConfig(key, val);
}
public Map<ConfigKey<?>, Object> getAllConfig() {
return configsInternal.getAllConfig();
}
// TODO make immutable
/** for inspection only */
@Beta
public PolicyConfigMap getConfigMap() {
return configsInternal;
}
protected <T> void doReconfigureConfig(ConfigKey<T> key, T val) {
throw new UnsupportedOperationException("reconfiguring "+key+" unsupported for "+this);
}
@Override
public PolicyType getPolicyType() {
return policyType;
}
public void suspend() {
suspended.set(true);
}
public void resume() {
suspended.set(false);
}
public boolean isSuspended() {
return suspended.get();
}
@Override
public void destroy(){
suspend();
super.destroy();
}
@Override
public boolean isRunning() {
return !isSuspended() && !isDestroyed();
}
@Override
public RebindSupport<PolicyMemento> getRebindSupport() {
return new BasicPolicyRebindSupport(this);
}
@Override
public String toString() {
return Objects.toStringHelper(getClass())
.add("name", name)
.add("running", isRunning())
.toString();
}
}