brooklyn.location.basic.AbstractLocation Maven / Gradle / Ivy
Show all versions of brooklyn-core Show documentation
package brooklyn.location.basic;
import static brooklyn.util.GroovyJavaMethods.truth;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import java.io.Closeable;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import com.google.common.collect.ImmutableMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import brooklyn.config.ConfigKey;
import brooklyn.entity.proxying.InternalLocationFactory;
import brooklyn.entity.rebind.BasicLocationRebindSupport;
import brooklyn.entity.rebind.RebindSupport;
import brooklyn.entity.trait.Configurable;
import brooklyn.event.basic.BasicConfigKey;
import brooklyn.location.Location;
import brooklyn.location.LocationSpec;
import brooklyn.location.geo.HasHostGeoInfo;
import brooklyn.location.geo.HostGeoInfo;
import brooklyn.management.ManagementContext;
import brooklyn.mementos.LocationMemento;
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.base.Objects;
import com.google.common.base.Objects.ToStringHelper;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.io.Closeables;
/**
* A basic implementation of the {@link Location} interface.
*
* This provides an implementation which works according to the requirements of
* the interface documentation, and is ready to be extended to make more specialized locations.
*
* Override {@link #configure(Map)} to add special initialization logic.
*/
public abstract class AbstractLocation implements LocationInternal, HasHostGeoInfo, Configurable {
public static final Logger LOG = LoggerFactory.getLogger(AbstractLocation.class);
public static final ConfigKey PARENT_LOCATION = new BasicConfigKey(Location.class, "parentLocation");
@SetFromFlag
String id;
// _not_ set from flag; configured explicitly in configure, because we also need to update the parent's list of children
private Location parentLocation;
// NB: all accesses should be synchronized
private final Collection childLocations = Lists.newArrayList();
@SetFromFlag
protected String name;
protected HostGeoInfo hostGeoInfo;
final private ConfigBag configBag = new ConfigBag();
private volatile ManagementContext managementContext;
private volatile boolean managed;
private boolean _legacyConstruction;
private boolean inConstruction;
private final Map, Object> extensions = Maps.newConcurrentMap();
/**
* Construct a new instance of an AbstractLocation.
*/
public AbstractLocation() {
this(Maps.newLinkedHashMap());
}
/**
* Construct a new instance of an AbstractLocation.
*
* The properties map recognizes the following keys:
*
* - name - a name for the location
*
- parentLocation - the parent {@link Location}
*
*
* Other common properties (retrieved via get/findLocationProperty) include:
*
* - latitude
*
- longitude
*
- displayName
*
- iso3166 - list of iso3166-2 code strings
*
- timeZone
*
- abbreviatedName
*
*/
public AbstractLocation(Map properties) {
inConstruction = true;
_legacyConstruction = !InternalLocationFactory.FactoryConstructionTracker.isConstructing();
if (!_legacyConstruction && properties!=null && !properties.isEmpty()) {
LOG.warn("Forcing use of deprecated old-style location construction for "+getClass().getName()+" because properties were specified ("+properties+")");
_legacyConstruction = true;
}
if (_legacyConstruction) {
LOG.warn("Deprecated use of old-style location construction for "+getClass().getName()+"; instead use LocationManager().createLocation(spec)");
if (LOG.isDebugEnabled())
LOG.debug("Source of use of old-style location construction", new Throwable("Source of use of old-style location construction"));
configure(properties);
boolean deferConstructionChecks = (properties.containsKey("deferConstructionChecks") && TypeCoercions.coerce(properties.get("deferConstructionChecks"), Boolean.class));
if (!deferConstructionChecks) {
FlagUtils.checkRequiredFields(this);
}
}
inConstruction = false;
}
protected void assertNotYetManaged() {
if (!inConstruction && (managementContext != null && managementContext.getLocationManager().isManaged(this))) {
LOG.warn("Configuration being made to {} after deployment; may not be supported in future versions", this);
}
//throw new IllegalStateException("Cannot set configuration "+key+" on active location "+this)
}
public void setManagementContext(ManagementContext managementContext) {
this.managementContext = managementContext;
}
protected ManagementContext getManagementContext() {
return managementContext;
}
/**
* Will set fields from flags. The unused configuration can be found via the
* {@linkplain ConfigBag#getUnusedConfig()}.
* This can be overridden for custom initialization but note the following.
*
* For new-style locations (i.e. not calling constructor directly, this will
* be invoked automatically by brooklyn-core post-construction).
*
* For legacy location use, this will be invoked by the constructor in this class.
* Therefore if over-riding 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.) If you require fields to be initialized you must do that in
* this method with a guard (as in FixedListMachineProvisioningLocation).
*/
public void configure(Map properties) {
assertNotYetManaged();
boolean firstTime = (id==null);
if (firstTime) {
// pick a random ID if one not set
id = properties.containsKey("id") ? (String)properties.get("id") : Identifiers.makeRandomId(8);
}
configBag.putAll(properties);
if (properties.containsKey(PARENT_LOCATION.getName())) {
// need to ensure parent's list of children is also updated
setParent(configBag.get(PARENT_LOCATION));
// don't include parentLocation in configBag, as breaks rebind
configBag.remove(PARENT_LOCATION);
}
// NB: flag-setting done here must also be done in BasicLocationRebindSupport
FlagUtils.setFieldsFromFlagsWithBag(this, properties, configBag, firstTime);
if (!truth(name) && truth(properties.get("displayName"))) {
//'displayName' is a legacy way to refer to a location's name
//FIXME could this be a GString?
Preconditions.checkArgument(properties.get("displayName") instanceof String, "'displayName' property should be a string");
name = (String) removeIfPossible(properties, "displayName");
}
// TODO Explicitly dealing with iso3166 here because want custom splitter rule comma-separated string.
// Is there a better way to do it (e.g. more similar to latitude, where configKey+TypeCoercion is enough)?
if (truth(properties.get("iso3166"))) {
Object rawCodes = removeIfPossible(properties, "iso3166");
Set codes;
if (rawCodes instanceof CharSequence) {
codes = ImmutableSet.copyOf(Splitter.on(",").trimResults().split((CharSequence)rawCodes));
} else {
codes = TypeCoercions.coerce(rawCodes, Set.class);
}
configBag.put(LocationConfigKeys.ISO_3166, codes);
}
}
// TODO ensure no callers rely on 'remove' semantics, and don't remove;
// or perhaps better use a config bag so we know what is used v unused
private static Object removeIfPossible(Map map, Object key) {
try {
return map.remove(key);
} catch (Exception e) {
return map.get(key);
}
}
/**
* Called by framework (in new-style locations) after configuring, setting parent, etc,
* but before a reference to this location is shared with other locations.
*
* To preserve backwards compatibility for if the location is constructed directly, one
* can call the code below, but that means it will be called after references to this
* location have been shared with other entities.
*
* {@code
* if (isLegacyConstruction()) {
* init();
* }
* }
*
*/
public void init() {
// no-op
}
public boolean isManaged() {
return managementContext != null && managed;
}
public void onManagementStarted() {
this.managed = true;
}
public void onManagementStopped() {
this.managed = false;
}
protected boolean isLegacyConstruction() {
return _legacyConstruction;
}
@Override
public String getId() {
return id;
}
@Override
public String getDisplayName() {
return name;
}
@Override
@Deprecated
/** @since 0.6.0 (?) - use getDisplayName */
public String getName() {
return getDisplayName();
}
@Override
public Location getParent() {
return parentLocation;
}
@Override
public Collection getChildren() {
synchronized (childLocations) {
return ImmutableList.copyOf(childLocations);
}
}
@Override
public void setParent(Location parent) {
if (parent == this) {
throw new IllegalArgumentException("Location cannot be its own parent: "+this);
}
if (parent == parentLocation) {
return; // no-op; already have desired parent
}
// TODO Should we support a location changing parent? The resulting unmanage/manage might cause problems.
if (parentLocation != null) {
Location oldParent = parentLocation;
parentLocation = null;
((AbstractLocation)oldParent).removeChild(this); // FIXME Nasty cast
}
if (parent != null) {
parentLocation = parent;
((AbstractLocation)parentLocation).addChild(this); // FIXME Nasty cast
}
}
@Override
@Deprecated
public Location getParentLocation() {
return getParent();
}
@Override
@Deprecated
public Collection getChildLocations() {
return getChildren();
}
@Override
@Deprecated
public void setParentLocation(Location parent) {
setParent(parent);
}
@Override
public T getConfig(ConfigKey key) {
if (hasConfig(key, false)) return getConfigBag().get(key);
if (getParent()!=null) return getParent().getConfig(key);
return key.getDefaultValue();
}
@Override
@Deprecated
public boolean hasConfig(ConfigKey> key) {
return hasConfig(key, false);
}
@Override
public boolean hasConfig(ConfigKey> key, boolean includeInherited) {
boolean locally = getRawLocalConfigBag().containsKey(key);
if (locally) return true;
if (!includeInherited) return false;
if (getParent()!=null) return getParent().hasConfig(key, true);
return false;
}
@Override
@Deprecated
public Map getAllConfig() {
return getAllConfig(false);
}
@Override
public Map getAllConfig(boolean includeInherited) {
Map result = null;
if (includeInherited) {
Location p = getParent();
if (p!=null) result = getParent().getAllConfig(true);
}
if (result==null) {
result = new LinkedHashMap();
}
result.putAll(getConfigBag().getAllConfig());
return result;
}
/** @deprecated since 0.6.0 use {@link #getRawLocalConfigBag()} */
public ConfigBag getConfigBag() {
return configBag;
}
public ConfigBag getRawLocalConfigBag() {
return configBag;
}
@Override
public T setConfig(ConfigKey key, T value) {
return configBag.put(key, value);
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (! (o instanceof Location)) {
return false;
}
Location l = (Location) o;
return getId().equals(l.getId());
}
@Override
public int hashCode() {
return getId().hashCode();
}
@Override
public boolean containsLocation(Location potentialDescendent) {
Location loc = potentialDescendent;
while (loc != null) {
if (this == loc) return true;
loc = loc.getParent();
}
return false;
}
/**
* @deprecated since 0.6
* @see #addChild(Location)
*/
@Deprecated
public void addChildLocation(Location child) {
addChild(child);
}
protected T addChild(LocationSpec spec) {
T child = managementContext.getLocationManager().createLocation(spec);
addChild(child);
return child;
}
public void addChild(Location child) {
// Previously, setParent delegated to addChildLocation and we sometimes ended up with
// duplicate entries here. Instead this now uses a similar scheme to
// AbstractLocation.setParent/addChild (with any weaknesses for distribution that such a
// scheme might have...).
//
// We continue to use a list to allow identical-looking locations, but they must be different
// instances.
synchronized (childLocations) {
for (Location contender : childLocations) {
if (contender == child) {
// don't re-add; no-op
return;
}
}
childLocations.add(child);
}
child.setParent(this);
if (isManaged()) {
managementContext.getLocationManager().manage(child);
}
}
/**
* @deprecated since 0.6
* @see #removeChild(Location)
*/
@Deprecated
protected boolean removeChildLocation(Location child) {
return removeChild(child);
}
protected boolean removeChild(Location child) {
boolean removed;
synchronized (childLocations) {
removed = childLocations.remove(child);
}
if (removed) {
if (child instanceof Closeable) {
Closeables.closeQuietly((Closeable)child);
}
child.setParent(null);
if (isManaged()) {
managementContext.getLocationManager().unmanage(child);
}
}
return removed;
}
@Override
@Deprecated
public boolean hasLocationProperty(String key) { return configBag.containsKey(key); }
@Override
@Deprecated
public Object getLocationProperty(String key) { return configBag.getStringKey(key); }
@Override
@Deprecated
public Object findLocationProperty(String key) {
if (hasLocationProperty(key)) return getLocationProperty(key);
if (parentLocation != null) return parentLocation.findLocationProperty(key);
return null;
}
// @Override
// public Map getLocationProperties() {
// return Collections.unmodifiableMap(leftoverProperties);
// }
/** Default String representation is simplified name of class, together with selected fields. */
@Override
public String toString() {
return string().toString();
}
@Override
public String toVerboseString() {
return toString();
}
/** override this, adding to the returned value, to supply additional fields to include in the toString */
protected ToStringHelper string() {
return Objects.toStringHelper(getClass()).add("id", id).add("name", name);
}
@Override
public HostGeoInfo getHostGeoInfo() { return hostGeoInfo; }
public void setHostGeoInfo(HostGeoInfo hostGeoInfo) {
if (hostGeoInfo!=null) {
this.hostGeoInfo = hostGeoInfo;
setConfig(LocationConfigKeys.LATITUDE, hostGeoInfo.latitude);
setConfig(LocationConfigKeys.LONGITUDE, hostGeoInfo.longitude);
}
}
@Override
public RebindSupport getRebindSupport() {
return new BasicLocationRebindSupport(this);
}
@Override
public boolean hasExtension(Class> extensionType) {
return extensions.containsKey(checkNotNull(extensionType, "extensionType"));
}
@Override
@SuppressWarnings("unchecked")
public T getExtension(Class extensionType) {
Object extension = extensions.get(checkNotNull(extensionType, "extensionType"));
if (extension == null) {
throw new IllegalArgumentException("No extension of type "+extensionType+" registered for location "+this);
}
return (T) extension;
}
@Override
public void addExtension(Class extensionType, T extension) {
checkNotNull(extensionType, "extensionType");
checkNotNull(extension, "extension");
checkArgument(extensionType.isInstance(extension), "extension %s does not implement %s", extension, extensionType);
extensions.put(extensionType, extension);
}
@Override
public Map toMetadataRecord() {
ImmutableMap.Builder builder = ImmutableMap.builder();
builder.put("id", getId());
if (getDisplayName() != null) builder.put("displayName", getDisplayName());
return builder.build();
}
}