| package brooklyn.location.basic; |
| |
| import static brooklyn.util.JavaGroovyEquivalents.groovyTruth; |
| |
| 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 java.util.concurrent.atomic.AtomicBoolean; |
| |
| 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.internal.storage.Reference; |
| import brooklyn.internal.storage.impl.BasicReference; |
| import brooklyn.location.Location; |
| import brooklyn.location.LocationSpec; |
| import brooklyn.location.geo.HasHostGeoInfo; |
| import brooklyn.location.geo.HostGeoInfo; |
| import brooklyn.management.ManagementContext; |
| import brooklyn.management.internal.ManagementContextInternal; |
| import brooklyn.mementos.LocationMemento; |
| import brooklyn.util.collections.SetFromLiveMap; |
| 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.Splitter; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Maps; |
| import com.google.common.collect.Sets; |
| 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"); |
| |
| private final AtomicBoolean configured = new AtomicBoolean(false); |
| |
| @SetFromFlag(value="id") |
| private String id = Identifiers.makeRandomId(8); |
| |
| // _not_ set from flag; configured explicitly in configure, because we also need to update the parent's list of children |
| private Location parentLocation; |
| |
| private Reference<Long> creationTimeUtc = new BasicReference<Long>(System.currentTimeMillis()); |
| private Reference<Location> parent = new BasicReference<Location>(); |
| private Set<Location> children = Sets.newLinkedHashSet(); |
| |
| private Reference<String> name = new BasicReference<String>(); |
| private boolean displayNameAutoGenerated = true; |
| |
| private Reference<HostGeoInfo> hostGeoInfo = new BasicReference<HostGeoInfo>(); |
| |
| 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(ManagementContextInternal managementContext) { |
| this.managementContext = managementContext; |
| if (displayNameAutoGenerated && id != null) name.set(getClass().getSimpleName()+":"+id.substring(0, 4)); |
| |
| Location oldParent = parent.get(); |
| Set<Location> oldChildren = children; |
| Map<String, Object> oldConfig = configBag.getAllConfigRaw(); |
| long oldCreationTimeUtc = creationTimeUtc.get(); |
| String oldDisplayName = name.get(); |
| HostGeoInfo oldHostGeoInfo = hostGeoInfo.get(); |
| |
| parent = managementContext.getStorage().getReference(id+"-parent"); |
| children = SetFromLiveMap.create(managementContext.getStorage().<Location,Boolean>getMap(id+"-children")); |
| creationTimeUtc = managementContext.getStorage().getReference(id+"-creationTime"); |
| hostGeoInfo = managementContext.getStorage().getReference(id+"-hostGeoInfo"); |
| name = managementContext.getStorage().getReference(id+"-displayName"); |
| |
| // Only override stored defaults if we have actual values. We might be in setManagementContext |
| // because we are reconstituting an existing entity in a new brooklyn management-node (in which |
| // case believe what is already in the storage), or we might be in the middle of creating a new |
| // entity. Normally for a new entity (using EntitySpec creation approach), this will get called |
| // before setting the parent etc. However, for backwards compatibility we still support some |
| // things calling the entity's constructor directly. |
| if (oldParent != null) parent.set(oldParent); |
| if (oldChildren.size() > 0) children.addAll(oldChildren); |
| if (creationTimeUtc.isNull()) creationTimeUtc.set(oldCreationTimeUtc); |
| if (hostGeoInfo.isNull()) hostGeoInfo.set(oldHostGeoInfo); |
| if (name.isNull()) { |
| name.set(oldDisplayName); |
| } else { |
| displayNameAutoGenerated = false; |
| } |
| |
| configBag = new ConfigBag(managementContext.getStorage().<String,Object>getMap(id+"-config")); |
| if (oldConfig.size() > 0) { |
| configBag.putAll(oldConfig); |
| } |
| } |
| |
| 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 = configured.getAndSet(true); |
| |
| 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); |
| |
| // TODO For consistency with entity, we should use "displayName" as primary; |
| // previous comment was that "name" was primary and "displayName" was legacy - that's still the case... |
| if (properties.get("name") != null) { |
| name.set((String) properties.remove("name")); |
| displayNameAutoGenerated = false; |
| } else if (properties.get("displayName") != null) { |
| name.set((String) properties.remove("displayName")); |
| displayNameAutoGenerated = false; |
| } else if (isLegacyConstruction()) { |
| name.set(getClass().getSimpleName()+":"+id.substring(0, 4)); |
| displayNameAutoGenerated = true; |
| } |
| |
| // 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 (groovyTruth(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 |
| } |
| |
| /** |
| * Called by framework (in new-style entities) when recreating a location, on restart. |
| * |
| * To preserve backwards compatibility, the {@linke #getRebindSupport()}'s |
| * {@link BasicLocationRebindSupport#reconstruct(brooklyn.entity.rebind.RebindContext, LocationMemento)} |
| * will call this method. |
| */ |
| public void reconstruct() { |
| // no-op |
| } |
| |
| public boolean isManaged() { |
| return managementContext != null && managed; |
| } |
| |
| public void onManagementStarted() { |
| if (displayNameAutoGenerated) name.set(getClass().getSimpleName()+":"+id.substring(0, 4)); |
| 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.get(); |
| } |
| |
| @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 Collections.unmodifiableCollection(children); |
| } |
| |
| @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); |
| } |
| |
| /** @since 0.6.0 (?) - use getDisplayName */ |
| public void setName(String newName) { |
| setDisplayName(newName); |
| displayNameAutoGenerated = false; |
| } |
| |
| public void setDisplayName(String newName) { |
| name.set(newName); |
| displayNameAutoGenerated = false; |
| } |
| |
| @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 : children) { |
| if (contender == child) { |
| // don't re-add; no-op |
| return; |
| } |
| } |
| |
| // if (managementContext != null && !isManaged()) { |
| // // Some entities within their init/constructor will add child locations. |
| //// However, if we are not yet managed then when the chilld does |
| // // FIXME GOT HERE |
| // managementContext.getLocationManager().manage(child); |
| // } |
| if (isManaged()) { |
| managementContext.getLocationManager().manage(child); |
| } |
| |
| children.add(child); |
| child.setParent(this); |
| } |
| |
| /** |
| * @deprecated since 0.6 |
| * @see removeChild(Location) |
| */ |
| @Deprecated |
| protected boolean removeChildLocation(Location child) { |
| return removeChild(child); |
| } |
| |
| protected boolean removeChild(Location child) { |
| boolean removed = children.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.get(); } |
| |
| public void setHostGeoInfo(HostGeoInfo hostGeoInfo) { |
| if (hostGeoInfo!=null) { |
| this.hostGeoInfo.set(hostGeoInfo); |
| setConfig(LocationConfigKeys.LATITUDE, hostGeoInfo.latitude); |
| setConfig(LocationConfigKeys.LONGITUDE, hostGeoInfo.longitude); |
| } |
| } |
| |
| @Override |
| public RebindSupport<LocationMemento> getRebindSupport() { |
| return new BasicLocationRebindSupport(this); |
| } |
| } |