/*
* 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.location;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.apache.brooklyn.util.JavaGroovyEquivalents.groovyTruth;
import static org.apache.brooklyn.util.groovy.GroovyJavaMethods.elvis;
import java.io.Closeable;
import java.util.Collection;
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.Group;
import org.apache.brooklyn.api.location.Location;
import org.apache.brooklyn.api.location.LocationSpec;
import org.apache.brooklyn.api.mgmt.ExecutionContext;
import org.apache.brooklyn.api.mgmt.ManagementContext;
import org.apache.brooklyn.api.mgmt.SubscriptionContext;
import org.apache.brooklyn.api.mgmt.SubscriptionHandle;
import org.apache.brooklyn.api.mgmt.Task;
import org.apache.brooklyn.api.mgmt.rebind.RebindSupport;
import org.apache.brooklyn.api.mgmt.rebind.mementos.LocationMemento;
import org.apache.brooklyn.api.objs.Configurable;
import org.apache.brooklyn.api.sensor.Sensor;
import org.apache.brooklyn.api.sensor.SensorEventListener;
import org.apache.brooklyn.config.ConfigInheritance;
import org.apache.brooklyn.config.ConfigKey;
import org.apache.brooklyn.config.ConfigKey.HasConfigKey;
import org.apache.brooklyn.core.BrooklynFeatureEnablement;
import org.apache.brooklyn.core.config.BasicConfigKey;
import org.apache.brooklyn.core.config.ConfigConstraints;
import org.apache.brooklyn.core.config.ConfigKeys;
import org.apache.brooklyn.core.internal.storage.BrooklynStorage;
import org.apache.brooklyn.core.internal.storage.Reference;
import org.apache.brooklyn.core.internal.storage.impl.BasicReference;
import org.apache.brooklyn.core.location.geo.HasHostGeoInfo;
import org.apache.brooklyn.core.location.geo.HostGeoInfo;
import org.apache.brooklyn.core.location.internal.LocationDynamicType;
import org.apache.brooklyn.core.location.internal.LocationInternal;
import org.apache.brooklyn.core.mgmt.internal.LocalLocationManager;
import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
import org.apache.brooklyn.core.mgmt.internal.SubscriptionTracker;
import org.apache.brooklyn.core.mgmt.rebind.BasicLocationRebindSupport;
import org.apache.brooklyn.core.objs.AbstractBrooklynObject;
import org.apache.brooklyn.core.objs.AbstractConfigurationSupportInternal;
import org.apache.brooklyn.util.collections.SetFromLiveMap;
import org.apache.brooklyn.util.core.config.ConfigBag;
import org.apache.brooklyn.util.core.flags.FlagUtils;
import org.apache.brooklyn.util.core.flags.TypeCoercions;
import org.apache.brooklyn.util.core.task.DeferredSupplier;
import org.apache.brooklyn.util.core.task.Tasks;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.guava.Maybe;
import org.apache.brooklyn.util.stream.Streams;
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.Objects.ToStringHelper;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.reflect.TypeToken;
/**
* 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 extends AbstractBrooklynObject implements LocationInternal, HasHostGeoInfo, Configurable {
/** @deprecated since 0.7.0 shouldn't be public */
@Deprecated
public static final Logger LOG = LoggerFactory.getLogger(AbstractLocation.class);
public static final ConfigKey<Location> PARENT_LOCATION = new BasicConfigKey<Location>(Location.class, "parentLocation");
public static final ConfigKey<Boolean> TEMPORARY_LOCATION = ConfigKeys.newBooleanConfigKey("temporaryLocation",
"Indicates that the location is a temporary location that has been created to test connectivity, and that" +
"the location's events should not be recorded by usage listeners", false);
private final AtomicBoolean configured = new AtomicBoolean();
private Reference<Long> creationTimeUtc = new BasicReference<Long>(System.currentTimeMillis());
// _not_ set from flag; configured explicitly in configure, because we also need to update the parent's list of children
private Reference<Location> parent = new BasicReference<Location>();
// NB: all accesses should be synchronized
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 BasicConfigurationSupport config = new BasicConfigurationSupport();
private BasicSubscriptionSupport subscriptions = new BasicSubscriptionSupport();
private ConfigBag configBag = new ConfigBag();
/** not for direct access; refer to as 'subscriptionTracker' via getter so that it is initialized */
protected transient SubscriptionTracker _subscriptionTracker;
private volatile boolean managed;
private boolean inConstruction;
private Reference<Map<Class<?>, Object>> extensions = new BasicReference<Map<Class<?>, Object>>(Maps.<Class<?>, Object>newConcurrentMap());
private final LocationDynamicType locationType;
/**
* 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:
* <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>
*/
public AbstractLocation(Map<?,?> properties) {
super(properties);
inConstruction = true;
// When one calls getConfig(key), we want to use the default value specified on *this* location
// if it overrides the default config, by using the type object
locationType = new LocationDynamicType(this);
if (isLegacyConstruction()) {
AbstractLocation 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);
}
}
inConstruction = false;
}
protected void assertNotYetManaged() {
if (!inConstruction && Locations.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) {
super.setManagementContext(managementContext);
if (displayNameAutoGenerated && getId() != null) name.set(getClass().getSimpleName()+":"+getId().substring(0, Math.min(getId().length(),4)));
if (BrooklynFeatureEnablement.isEnabled(BrooklynFeatureEnablement.FEATURE_USE_BROOKLYN_LIVE_OBJECTS_DATAGRID_STORAGE)) {
Location oldParent = parent.get();
Set<Location> oldChildren = children;
Map<String, Object> oldConfig = configBag.getAllConfig();
Long oldCreationTimeUtc = creationTimeUtc.get();
String oldDisplayName = name.get();
HostGeoInfo oldHostGeoInfo = hostGeoInfo.get();
parent = managementContext.getStorage().getReference(getId()+"-parent");
children = SetFromLiveMap.create(managementContext.getStorage().<Location,Boolean>getMap(getId()+"-children"));
creationTimeUtc = managementContext.getStorage().getReference(getId()+"-creationTime");
hostGeoInfo = managementContext.getStorage().getReference(getId()+"-hostGeoInfo");
name = managementContext.getStorage().getReference(getId()+"-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 = ConfigBag.newLiveInstance(managementContext.getStorage().<String,Object>getMap(getId()+"-config"));
if (oldConfig.size() > 0) {
configBag.putAll(oldConfig);
}
}
}
/**
* @deprecated since 0.7.0; only used for legacy brooklyn types where constructor is called directly;
* see overridden method for more info
*/
@SuppressWarnings("serial")
@Override
@Deprecated
public AbstractLocation 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);
FlagUtils.setAllConfigKeys(this, configBag, false);
if (properties.containsKey("displayName")) {
name.set((String) removeIfPossible(properties, "displayName"));
displayNameAutoGenerated = false;
} else if (properties.containsKey("name")) {
name.set((String) removeIfPossible(properties, "name"));
displayNameAutoGenerated = false;
} else if (isLegacyConstruction()) {
name.set(getClass().getSimpleName()+":"+getId().substring(0, Math.min(getId().length(),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 = removeIfPossible(properties, "iso3166");
Set<String> codes;
if (rawCodes instanceof CharSequence) {
codes = ImmutableSet.copyOf(Splitter.on(",").trimResults().split((CharSequence)rawCodes));
} else {
codes = TypeCoercions.coerce(rawCodes, new TypeToken<Set<String>>() {});
}
configBag.put(LocationConfigKeys.ISO_3166, codes);
}
return this;
}
// 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);
}
}
public boolean isManaged() {
return getManagementContext() != null && managed;
}
public void onManagementStarted() {
if (displayNameAutoGenerated) name.set(getClass().getSimpleName()+":"+getId().substring(0, Math.min(getId().length(),4)));
this.managed = true;
}
public void onManagementStopped() {
this.managed = false;
if (getManagementContext().isRunning()) {
BrooklynStorage storage = ((ManagementContextInternal)getManagementContext()).getStorage();
storage.remove(getId()+"-parent");
storage.remove(getId()+"-children");
storage.remove(getId()+"-creationTime");
storage.remove(getId()+"-hostGeoInfo");
storage.remove(getId()+"-displayName");
storage.remove(getId()+"-config");
}
}
@Override
public String getDisplayName() {
return name.get();
}
protected boolean isDisplayNameAutoGenerated() {
return displayNameAutoGenerated;
}
@Override
public Location getParent() {
return parent.get();
}
@Override
public Collection<Location> getChildren() {
synchronized (children) {
return ImmutableList.copyOf(children);
}
}
@Override
public void setParent(Location newParent) {
setParent(newParent, true);
}
public void setParent(Location newParent, boolean updateChildListParents) {
if (newParent == this) {
throw new IllegalArgumentException("Location cannot be its own parent: "+this);
}
if (newParent == parent.get()) {
return; // no-op; already have desired parent
}
if (parent.get() != null) {
Location oldParent = parent.get();
parent.set(null);
if (updateChildListParents)
((AbstractLocation)oldParent).removeChild(this);
}
// TODO Should we support a location changing parent? The resulting unmanage/manage might cause problems.
// The code above suggests we do, but maybe we should warn or throw error, or at least test it!
parent.set(newParent);
if (newParent != null) {
if (updateChildListParents)
((AbstractLocation)newParent).addChild(this);
}
onChanged();
}
@Override
public ConfigurationSupportInternal config() {
return config;
}
// the concrete type rather than an interface is returned because Groovy subclasses
// complain (incorrectly) if we return SubscriptionSupportInternal
// TODO revert to SubscriptionSupportInternal when groovy subclasses work without this (eg new groovy version)
@Override
@Beta
public BasicSubscriptionSupport subscriptions() {
return subscriptions;
}
private class BasicConfigurationSupport extends AbstractConfigurationSupportInternal {
@Override
public <T> T get(ConfigKey<T> key) {
Object result = null;
if (hasConfig(key, false)) {
result = getLocalBag().getAllConfigRaw().get(key.getName());
} else if (getParent() != null && isInherited(key)) {
result = getParent().getConfig(key);
} else {
// In case this entity class has overridden the given key (e.g. to set default), then retrieve this entity's key
// TODO when locations become entities, the duplication of this compared to EntityConfigMap.getConfig will disappear.
@SuppressWarnings("unchecked")
ConfigKey<T> ownKey = (ConfigKey<T>) elvis(locationType.getConfigKey(key.getName()), key);
result = ownKey.getDefaultValue();
}
if (result instanceof DeferredSupplier<?>) {
try {
ManagementContext mgmt = AbstractLocation.this.getManagementContext();
ExecutionContext exec = mgmt.getServerExecutionContext();
result = Tasks.resolveValue(result, key.getType(), exec);
} catch (Exception e) {
throw Exceptions.propagate(e);
}
}
return TypeCoercions.coerce(result, key.getTypeToken());
}
@Override
public <T> T set(ConfigKey<T> key, T val) {
ConfigConstraints.assertValid(AbstractLocation.this, key, val);
T result = configBag.put(key, val);
onChanged();
return result;
}
@Override
public <T> T set(ConfigKey<T> key, Task<T> val) {
// TODO Support for locations
throw new UnsupportedOperationException();
}
@Override
public ConfigBag getBag() {
ConfigBag result = ConfigBag.newInstanceExtending(configBag, ImmutableMap.of());
Location p = getParent();
if (p!=null) result.putIfAbsent(((LocationInternal)p).config().getBag());
return result;
}
@Override
public ConfigBag getLocalBag() {
return configBag;
}
@Override
public Maybe<Object> getRaw(ConfigKey<?> key) {
if (hasConfig(key, false)) return Maybe.of(getLocalBag().getStringKey(key.getName()));
if (getParent() != null && isInherited(key)) return ((LocationInternal)getParent()).config().getRaw(key);
return Maybe.absent();
}
@Override
public Maybe<Object> getLocalRaw(ConfigKey<?> key) {
if (hasConfig(key, false)) return Maybe.of(getLocalBag().getStringKey(key.getName()));
return Maybe.absent();
}
@Override
public void addToLocalBag(Map<String, ?> vals) {
configBag.putAll(vals);
}
@Override
public void removeFromLocalBag(String key) {
configBag.remove(key);
}
@Override
public void refreshInheritedConfig() {
// no-op for location
}
@Override
public void refreshInheritedConfigOfChildren() {
// no-op for location
}
private boolean hasConfig(ConfigKey<?> key, boolean includeInherited) {
if (includeInherited && isInherited(key)) {
return getBag().containsKey(key);
} else {
return getLocalBag().containsKey(key);
}
}
private boolean isInherited(ConfigKey<?> key) {
ConfigInheritance inheritance = key.getInheritance();
if (inheritance==null) inheritance = getDefaultInheritance();
return inheritance.isInherited(key, getParent(), AbstractLocation.this);
}
private ConfigInheritance getDefaultInheritance() {
return ConfigInheritance.ALWAYS;
}
@Override
protected ExecutionContext getContext() {
return AbstractLocation.this.getManagementContext().getServerExecutionContext();
}
}
public class BasicSubscriptionSupport implements SubscriptionSupportInternal {
@Override
public <T> SubscriptionHandle subscribe(Entity producer, Sensor<T> sensor, SensorEventListener<? super T> listener) {
return getSubscriptionTracker().subscribe(producer, sensor, listener);
}
@Override
public <T> SubscriptionHandle subscribe(Map<String, ?> flags, Entity producer, Sensor<T> sensor, SensorEventListener<? super T> listener) {
return getSubscriptionTracker().subscribe(flags, producer, sensor, listener);
}
@Override
public <T> SubscriptionHandle subscribeToMembers(Group producerGroup, Sensor<T> sensor, SensorEventListener<? super T> listener) {
return getSubscriptionTracker().subscribeToMembers(producerGroup, sensor, listener);
}
@Override
public <T> SubscriptionHandle subscribeToChildren(Entity producerParent, Sensor<T> sensor, SensorEventListener<? super T> listener) {
return getSubscriptionTracker().subscribeToChildren(producerParent, sensor, listener);
}
@Override
public boolean unsubscribe(Entity producer) {
return getSubscriptionTracker().unsubscribe(producer);
}
@Override
public boolean unsubscribe(Entity producer, SubscriptionHandle handle) {
return getSubscriptionTracker().unsubscribe(producer, handle);
}
@Override
public boolean unsubscribe(SubscriptionHandle handle) {
return getSubscriptionTracker().unsubscribe(handle);
}
@Override
public void unsubscribeAll() {
getSubscriptionTracker().unsubscribeAll();
}
protected SubscriptionTracker getSubscriptionTracker() {
synchronized (AbstractLocation.this) {
if (_subscriptionTracker!=null) return _subscriptionTracker;
_subscriptionTracker = new SubscriptionTracker(newSubscriptionContext());
return _subscriptionTracker;
}
}
private SubscriptionContext newSubscriptionContext() {
synchronized (AbstractLocation.this) {
return getManagementContext().getSubscriptionContext(AbstractLocation.this);
}
}
}
@Override
public <T> T getConfig(HasConfigKey<T> key) {
return config().get(key);
}
@Override
public <T> T getConfig(ConfigKey<T> key) {
return config().get(key);
}
@Override
@Deprecated
public boolean hasConfig(ConfigKey<?> key, boolean includeInherited) {
return config.hasConfig(key, includeInherited);
}
@Override
@Deprecated
public Map<String,Object> getAllConfig(boolean includeInherited) {
// TODO Have no information about what to include/exclude inheritance wise.
// however few things use getAllConfigBag()
ConfigBag bag = (includeInherited ? config().getBag() : config().getLocalBag());
return bag.getAllConfig();
}
@Override
@Deprecated
public ConfigBag getAllConfigBag() {
// TODO see comments in EntityConfigMap and on interface methods.
// here ConfigBag is used exclusively so
// we have no information about what to include/exclude inheritance wise.
// however few things use getAllConfigBag()
return config().getBag();
}
@Override
public ConfigBag getLocalConfigBag() {
return config().getLocalBag();
}
/**
* @deprecated since 0.7; use {@link #getLocalConfigBag()}
* @since 0.6
*/
@Deprecated
public ConfigBag getRawLocalConfigBag() {
return config().getLocalBag();
}
@Override
@Deprecated
public <T> T setConfig(ConfigKey<T> key, T value) {
return config().set(key, value);
}
/**
* @since 0.6.0 (?) - use getDisplayName
* @deprecated since 0.7.0; use {@link #getDisplayName()}
*/
@Deprecated
public void setName(String newName) {
setDisplayName(newName);
}
public void setDisplayName(String newName) {
name.set(newName);
displayNameAutoGenerated = false;
onChanged();
}
@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;
}
protected <T extends Location> T addChild(LocationSpec<T> spec) {
T child = getManagementContext().getLocationManager().createLocation(spec);
addChild(child);
return child;
}
@SuppressWarnings("deprecation")
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 (children) {
for (Location contender : children) {
if (contender == child) {
// don't re-add; no-op
return;
}
}
children.add(child);
}
if (isManaged()) {
if (!getManagementContext().getLocationManager().isManaged(child)) {
Locations.manage(child, getManagementContext());
}
} else if (getManagementContext() != null) {
if (((LocalLocationManager)getManagementContext().getLocationManager()).getLocationEvenIfPreManaged(child.getId()) == null) {
((ManagementContextInternal)getManagementContext()).prePreManage(child);
}
}
children.add(child);
child.setParent(this);
onChanged();
}
public boolean removeChild(Location child) {
boolean removed;
synchronized (children) {
removed = children.remove(child);
}
if (removed) {
if (child instanceof Closeable) {
Streams.closeQuietly((Closeable)child);
}
child.setParent(null);
if (isManaged()) {
getManagementContext().getLocationManager().unmanage(child);
}
}
onChanged();
return removed;
}
protected void onChanged() {
// currently changes simply trigger re-persistence; there is no intermediate listener as we do for EntityChangeListener
if (isManaged()) {
getManagementContext().getRebindManager().getChangeListener().onChanged(this);
}
}
/** 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", getId()).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);
}
@SuppressWarnings("unchecked")
@Override
public RelationSupportInternal<Location> relations() {
return (RelationSupportInternal<Location>) super.relations();
}
@Override
public boolean hasExtension(Class<?> extensionType) {
return extensions.get().containsKey(checkNotNull(extensionType, "extensionType"));
}
@Override
@SuppressWarnings("unchecked")
public <T> T getExtension(Class<T> extensionType) {
Object extension = extensions.get().get(checkNotNull(extensionType, "extensionType"));
if (extension == null) {
throw new IllegalArgumentException("No extension of type "+extensionType+" registered for location "+this);
}
return (T) extension;
}
@Override
public <T> void addExtension(Class<T> extensionType, T extension) {
checkNotNull(extensionType, "extensionType");
checkNotNull(extension, "extension");
checkArgument(extensionType.isInstance(extension), "extension %s does not implement %s", extension, extensionType);
extensions.get().put(extensionType, extension);
}
@Override
public Map<String, String> toMetadataRecord() {
ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
if (getDisplayName() != null) builder.put("displayName", getDisplayName());
if (getParent() != null && getParent().getDisplayName() != null) {
builder.put("parentDisplayName", getParent().getDisplayName());
}
return builder.build();
}
}