blob: a76c5f0a97e585f0ceb2060a2679497bd032c428 [file] [log] [blame]
package brooklyn.location.basic;
import static brooklyn.util.GroovyJavaMethods.truth;
import java.io.Closeable;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
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.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 Location, HasHostGeoInfo, Configurable {
public static final Logger LOG = LoggerFactory.getLogger(AbstractLocation.class);
public static final ConfigKey<Location> PARENT_LOCATION = new BasicConfigKey<Location>(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;
private final Collection<Location> childLocations = Lists.newArrayList();
private final Collection<Location> childLocationsReadOnly = Collections.unmodifiableCollection(childLocations);
@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;
/**
* Construct a new instance of an AbstractLocation.
*
* The properties map recognizes the following keys:
* <ul>
* <li>name - a name for the location
* <li>parentLocation - the parent {@link Location}
* </ul>
*
* Other common properties (retrieved via get/findLocationProperty) include:
* <ul>
* <li>latitude
* <li>longitude
* <li>displayName
* <li>iso3166 - list of iso3166-2 code strings
* <li>timeZone
* <li>abbreviatedName
* </ul>
*
* @param properties
*/
public AbstractLocation() {
this(Maps.newLinkedHashMap());
}
public AbstractLocation(Map properties) {
inConstruction = true;
_legacyConstruction = !InternalLocationFactory.FactoryConstructionTracker.isConstructing();
if (_legacyConstruction) {
LOG.warn("Deprecated use of old-style location construction for "+getClass().getName()+"; instead use LocationManager().createLocation(spec)");
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.
* <p>
* For new-style locations (i.e. not calling constructor directly, this will
* be invoked automatically by brooklyn-core post-construction).
* <p>
* 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) properties.remove("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 = properties.remove("iso3166");
Set<String> 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);
}
}
/**
* 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.
* <pre>
* {@code
* if (isLegacyConstruction()) {
* init();
* }
* }
* </pre>
*/
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<Location> getChildren() {
return childLocationsReadOnly;
}
@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<Location> getChildLocations() {
return getChildren();
}
@Override
@Deprecated
public void setParentLocation(Location parent) {
setParent(parent);
}
@Override
public <T> T getConfig(ConfigKey<T> 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<String,Object> getAllConfig() {
return getAllConfig(false);
}
@Override
public Map<String,Object> getAllConfig(boolean includeInherited) {
Map<String,Object> result = null;
if (includeInherited) {
Location p = getParent();
if (p!=null) result = getParent().getAllConfig(true);
}
if (result==null) {
result = new LinkedHashMap<String, Object>();
}
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> T setConfig(ConfigKey<T> 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 extends Location> T addChild(LocationSpec<T> 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.
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 = 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<String,?> getLocationProperties() {
// return Collections.<String,Object>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<LocationMemento> getRebindSupport() {
return new BasicLocationRebindSupport(this);
}
}