http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/location/BasicLocationRegistry.java ---------------------------------------------------------------------- diff --cc brooklyn-server/core/src/main/java/org/apache/brooklyn/core/location/BasicLocationRegistry.java index 0000000,202ecf4..4954393 mode 000000,100644..100644 --- a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/location/BasicLocationRegistry.java +++ b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/location/BasicLocationRegistry.java @@@ -1,0 -1,506 +1,511 @@@ + /* + * 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.checkNotNull; + + import java.util.ArrayList; + import java.util.Collections; + import java.util.LinkedHashMap; + import java.util.LinkedHashSet; + import java.util.List; + import java.util.Map; + import java.util.NoSuchElementException; + import java.util.ServiceLoader; + import java.util.Set; + + import org.apache.brooklyn.api.catalog.BrooklynCatalog; + import org.apache.brooklyn.api.catalog.CatalogItem; + import org.apache.brooklyn.api.location.Location; + import org.apache.brooklyn.api.location.LocationDefinition; + import org.apache.brooklyn.api.location.LocationRegistry; + import org.apache.brooklyn.api.location.LocationResolver; + import org.apache.brooklyn.api.location.LocationSpec; + import org.apache.brooklyn.api.mgmt.ManagementContext; + import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry; + import org.apache.brooklyn.api.typereg.RegisteredType; + import org.apache.brooklyn.config.ConfigMap; + import org.apache.brooklyn.core.config.ConfigPredicates; + import org.apache.brooklyn.core.config.ConfigUtils; + 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.typereg.RegisteredTypePredicates; + import org.apache.brooklyn.util.collections.MutableList; + import org.apache.brooklyn.util.collections.MutableMap; + import org.apache.brooklyn.util.core.config.ConfigBag; + import org.apache.brooklyn.util.exceptions.Exceptions; + import org.apache.brooklyn.util.guava.Maybe; + import org.apache.brooklyn.util.guava.Maybe.Absent; + import org.apache.brooklyn.util.javalang.JavaClassNames; + import org.apache.brooklyn.util.text.Identifiers; + import org.apache.brooklyn.util.text.StringEscapes.JavaStringEscapes; + import org.apache.brooklyn.util.text.WildcardGlobs; + import org.apache.brooklyn.util.text.WildcardGlobs.PhraseTreatment; + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + + import com.google.common.annotations.VisibleForTesting; + import com.google.common.base.Suppliers; + import com.google.common.collect.ImmutableMap; + import com.google.common.collect.Sets; + + /** + * See {@link LocationRegistry} for general description. + * <p> + * TODO The relationship between the catalog and the location registry is a bit messy. + * For all existing code, the location registry is the definitive way to resolve + * locations. + * <p> + * Any location item added to the catalog must therefore be registered here. + * Whenever an item is added to the catalog, it will automatically call + * {@link #updateDefinedLocation(RegisteredType)}. Similarly, when a location + * is deleted from the catalog it will call {@link #removeDefinedLocation(RegisteredType)}. + * <p> + * However, the location item in the catalog has an unparsed blob of YAML, which contains + * important things like the type and the config of the location. This is only parsed when + * {@link BrooklynCatalog#createSpec(CatalogItem)} is called. We therefore jump through + * some hoops to wire together the catalog and the registry. + * <p> + * To add a location to the catalog, and then to resolve a location that is in the catalog, + * it goes through the following steps: + * + * <ol> + * <li>Call {@link BrooklynCatalog#addItems(String)} + * <ol> + * <li>This automatically calls {@link #updateDefinedLocation(RegisteredType)} + * <li>A LocationDefinition is creating, using as its id the {@link RegisteredType#getSymbolicName()}. + * The definition's spec is {@code brooklyn.catalog:<symbolicName>:<version>}, + * </ol> + * <li>A blueprint can reference the catalog item using its symbolic name, + * such as the YAML {@code location: my-new-location}. + * (this feels similar to the "named locations"). + * <ol> + * <li>This automatically calls {@link #resolve(String)}. + * <li>The LocationDefinition is found by lookig up this name. + * <li>The {@link LocationDefiniton.getSpec()} is retrieved; the right {@link LocationResolver} is + * found for it. + * <li>This uses the {@link CatalogLocationResolver}, because the spec starts with {@code brooklyn.catalog:}. + * <li>This resolver extracts from the spec the <symobolicName>:<version>, and looks up the + * location item using the {@link BrooklynTypeRegistry}. + * <li>It then creates a {@link LocationSpec} by calling {@link BrooklynTypeRegistry#createSpec(RegisteredType)}. + * <ol> + * <li>This first tries to use the type (that is in the YAML) as a simple Java class. + * <li>If that fails, it will resolve the type using {@link #resolve(String, Boolean, Map)}, which + * returns an actual location object. + * <li>It extracts from that location object the appropriate metadata to create a {@link LocationSpec}, + * returns the spec and discards the location object. + * </ol> + * <li>The resolver creates the {@link Location} from the {@link LocationSpec} + * </ol> + * </ol> + * + * TODO There is no concept of a location version in this registry. The version + * in the catalog is generally ignored. + */ + @SuppressWarnings({"rawtypes","unchecked"}) + public class BasicLocationRegistry implements LocationRegistry { + + // TODO save / serialize + // (we persist live locations, ie those in the LocationManager, but not "catalog" locations, ie those in this Registry) + + public static final Logger log = LoggerFactory.getLogger(BasicLocationRegistry.class); + + /** + * Splits a comma-separated list of locations (names or specs) into an explicit list. + * The splitting is very careful to handle commas embedded within specs, to split correctly. + */ + public static List<String> expandCommaSeparateLocations(String locations) { + return WildcardGlobs.getGlobsAfterBraceExpansion("{"+locations+"}", false, PhraseTreatment.INTERIOR_NOT_EXPANDABLE, PhraseTreatment.INTERIOR_NOT_EXPANDABLE); + // don't do this, it tries to expand commas inside parentheses which is not good! + // QuotedStringTokenizer.builder().addDelimiterChars(",").buildList((String)id); + } + + private final ManagementContext mgmt; + /** map of defined locations by their ID */ + private final Map<String,LocationDefinition> definedLocations = new LinkedHashMap<String, LocationDefinition>(); + + protected final Map<String,LocationResolver> resolvers = new LinkedHashMap<String, LocationResolver>(); + + private final Set<String> specsWarnedOnException = Sets.newConcurrentHashSet(); + + public BasicLocationRegistry(ManagementContext mgmt) { + this.mgmt = checkNotNull(mgmt, "mgmt"); + findServices(); + updateDefinedLocations(); + } + + protected void findServices() { + ServiceLoader<LocationResolver> loader = ServiceLoader.load(LocationResolver.class, mgmt.getCatalogClassLoader()); + MutableList<LocationResolver> loadedResolvers; + try { + loadedResolvers = MutableList.copyOf(loader); + } catch (Throwable e) { + log.warn("Error loading resolvers (rethrowing): "+e); + throw Exceptions.propagate(e); + } + + for (LocationResolver r: loadedResolvers) { + registerResolver(r); + } + if (log.isDebugEnabled()) log.debug("Location resolvers are: "+resolvers); + if (resolvers.isEmpty()) log.warn("No location resolvers detected: is src/main/resources correctly included?"); + } + + /** Registers the given resolver, invoking {@link LocationResolver#init(ManagementContext)} on the argument + * and returning true, unless the argument indicates false for {@link LocationResolver.EnableableLocationResolver#isEnabled()} */ + public boolean registerResolver(LocationResolver r) { + r.init(mgmt); + if (r instanceof LocationResolver.EnableableLocationResolver) { + if (!((LocationResolver.EnableableLocationResolver)r).isEnabled()) { + return false; + } + } + resolvers.put(r.getPrefix(), r); + return true; + } + + @Override + public Map<String,LocationDefinition> getDefinedLocations() { + synchronized (definedLocations) { + return ImmutableMap.<String,LocationDefinition>copyOf(definedLocations); + } + } + + @Override + public LocationDefinition getDefinedLocationById(String id) { + return definedLocations.get(id); + } + + @Override + public LocationDefinition getDefinedLocationByName(String name) { + synchronized (definedLocations) { + for (LocationDefinition l: definedLocations.values()) { + if (l.getName().equals(name)) return l; + } + return null; + } + } + + @Override + public void updateDefinedLocation(LocationDefinition l) { + synchronized (definedLocations) { + definedLocations.put(l.getId(), l); + } + } + + /** + * Converts the given item from the catalog into a LocationDefinition, and adds it + * to the registry (overwriting anything already registered with the id + * {@link CatalogItem#getCatalogItemId()}. + */ + public void updateDefinedLocation(CatalogItem<Location, LocationSpec<?>> item) { + String id = item.getCatalogItemId(); + String symbolicName = item.getSymbolicName(); + String spec = CatalogLocationResolver.NAME + ":" + id; + Map<String, Object> config = ImmutableMap.<String, Object>of(); + BasicLocationDefinition locDefinition = new BasicLocationDefinition(symbolicName, symbolicName, spec, config); + + updateDefinedLocation(locDefinition); + } + + /** + * Converts the given item from the catalog into a LocationDefinition, and adds it + * to the registry (overwriting anything already registered with the id + * {@link RegisteredType#getId()}. + */ + public void updateDefinedLocation(RegisteredType item) { + String id = item.getId(); + String symbolicName = item.getSymbolicName(); + String spec = CatalogLocationResolver.NAME + ":" + id; + Map<String, Object> config = ImmutableMap.<String, Object>of(); + BasicLocationDefinition locDefinition = new BasicLocationDefinition(symbolicName, symbolicName, spec, config); + + updateDefinedLocation(locDefinition); + } + + public void removeDefinedLocation(CatalogItem<Location, LocationSpec<?>> item) { + removeDefinedLocation(item.getSymbolicName()); + } + + @Override + public void removeDefinedLocation(String id) { + LocationDefinition removed; + synchronized (definedLocations) { + removed = definedLocations.remove(id); + } + if (removed == null && log.isDebugEnabled()) { + log.debug("{} was asked to remove location with id {} but no such location was registered", this, id); + } + } + + public void updateDefinedLocations() { + synchronized (definedLocations) { + // first read all properties starting brooklyn.location.named.xxx + // (would be nice to move to a better way, e.g. yaml, then deprecate this approach, but first + // we need ability/format for persisting named locations, and better support for adding+saving via REST/GUI) + int count = 0; + String NAMED_LOCATION_PREFIX = "brooklyn.location.named."; + ConfigMap namedLocationProps = mgmt.getConfig().submap(ConfigPredicates.nameStartsWith(NAMED_LOCATION_PREFIX)); + for (String k: namedLocationProps.asMapWithStringKeys().keySet()) { + String name = k.substring(NAMED_LOCATION_PREFIX.length()); + // If has a dot, then is a sub-property of a named location (e.g. brooklyn.location.named.prod1.user=bob) + if (!name.contains(".")) { + // this is a new named location + String spec = (String) namedLocationProps.asMapWithStringKeys().get(k); + // make up an ID + String id = Identifiers.makeRandomId(8); + Map<String, Object> config = ConfigUtils.filterForPrefixAndStrip(namedLocationProps.asMapWithStringKeys(), k+"."); + definedLocations.put(id, new BasicLocationDefinition(id, name, spec, config)); + count++; + } + } + if (log.isDebugEnabled()) + log.debug("Found "+count+" defined locations from properties (*.named.* syntax): "+definedLocations.values()); + if (getDefinedLocationByName("localhost")==null && !BasicOsDetails.Factory.newLocalhostInstance().isWindows() + && LocationConfigUtils.isEnabled(mgmt, "brooklyn.location.localhost")) { + log.debug("Adding a defined location for localhost"); + // add 'localhost' *first* + ImmutableMap<String, LocationDefinition> oldDefined = ImmutableMap.copyOf(definedLocations); + definedLocations.clear(); + String id = Identifiers.makeRandomId(8); + definedLocations.put(id, localhost(id)); + definedLocations.putAll(oldDefined); + } + - for (RegisteredType item: mgmt.getTypeRegistry().getAll(RegisteredTypePredicates.IS_LOCATION)) { ++ for (RegisteredType item: mgmt.getTypeRegistry().getMatching(RegisteredTypePredicates.IS_LOCATION)) { + updateDefinedLocation(item); + count++; + } + } + } + + @VisibleForTesting + void disablePersistence() { + // persistence isn't enabled yet anyway (have to manually save things, + // defining the format and file etc) + } + + protected static BasicLocationDefinition localhost(String id) { + return new BasicLocationDefinition(id, "localhost", "localhost", null); + } + + /** to catch circular references */ + protected ThreadLocal<Set<String>> specsSeen = new ThreadLocal<Set<String>>(); + + @Override @Deprecated + public boolean canMaybeResolve(String spec) { + return getSpecResolver(spec) != null; + } + + @Override + public final Location resolve(String spec) { + return resolve(spec, true, null).get(); + } + + @Override @Deprecated + public final Location resolveIfPossible(String spec) { + if (!canMaybeResolve(spec)) return null; + return resolve(spec, null, null).orNull(); + } + + @Deprecated /** since 0.7.0 not used */ + public final Maybe<Location> resolve(String spec, boolean manage) { + return resolve(spec, manage, null); + } + + public Maybe<Location> resolve(String spec, Boolean manage, Map locationFlags) { + try { + locationFlags = MutableMap.copyOf(locationFlags); + if (manage!=null) { + locationFlags.put(LocalLocationManager.CREATE_UNMANAGED, !manage); + } + + Set<String> seenSoFar = specsSeen.get(); + if (seenSoFar==null) { + seenSoFar = new LinkedHashSet<String>(); + specsSeen.set(seenSoFar); + } + if (seenSoFar.contains(spec)) + return Maybe.absent(Suppliers.ofInstance(new IllegalStateException("Circular reference in definition of location '"+spec+"' ("+seenSoFar+")"))); + seenSoFar.add(spec); + + LocationResolver resolver = getSpecResolver(spec); + + if (resolver != null) { + try { + return Maybe.of(resolver.newLocationFromString(locationFlags, spec, this)); + } catch (RuntimeException e) { + return Maybe.absent(Suppliers.ofInstance(e)); + } + } + + // problem: but let's ensure that classpath is sane to give better errors in common IDE bogus case; + // and avoid repeated logging + String errmsg; + if (spec == null || specsWarnedOnException.add(spec)) { + if (resolvers.get("id")==null || resolvers.get("named")==null) { + log.error("Standard location resolvers not installed, location resolution will fail shortly. " + + "This usually indicates a classpath problem, such as when running from an IDE which " + + "has not properly copied META-INF/services from src/main/resources. " + + "Known resolvers are: "+resolvers.keySet()); + errmsg = "Unresolvable location '"+spec+"': " + + "Problem detected with location resolver configuration; " + + resolvers.keySet()+" are the only available location resolvers. " + + "More information can be found in the logs."; + } else { + log.debug("Location resolution failed for '"+spec+"' (if this is being loaded it will fail shortly): known resolvers are: "+resolvers.keySet()); + errmsg = "Unknown location '"+spec+"': " + + "either this location is not recognised or there is a problem with location resolver configuration."; + } + } else { + // For helpful log message construction: assumes classpath will not suddenly become wrong; might happen with OSGi though! + if (log.isDebugEnabled()) log.debug("Location resolution failed again for '"+spec+"' (throwing)"); + errmsg = "Unknown location '"+spec+"': " + + "either this location is not recognised or there is a problem with location resolver configuration."; + } + + return Maybe.absent(Suppliers.ofInstance(new NoSuchElementException(errmsg))); + + } finally { + specsSeen.remove(); + } + } + + @Override + public final Location resolve(String spec, Map locationFlags) { + return resolve(spec, null, locationFlags).get(); + } + + protected LocationResolver getSpecResolver(String spec) { + int colonIndex = spec.indexOf(':'); + int bracketIndex = spec.indexOf("("); + int dividerIndex = (colonIndex < 0) ? bracketIndex : (bracketIndex < 0 ? colonIndex : Math.min(bracketIndex, colonIndex)); + String prefix = dividerIndex >= 0 ? spec.substring(0, dividerIndex) : spec; + LocationResolver resolver = resolvers.get(prefix); + + if (resolver == null) + resolver = getSpecDefaultResolver(spec); + + return resolver; + } + + protected LocationResolver getSpecDefaultResolver(String spec) { + return getSpecFirstResolver(spec, "id", "named", "jclouds"); + } + protected LocationResolver getSpecFirstResolver(String spec, String ...resolversToCheck) { + for (String resolverId: resolversToCheck) { + LocationResolver resolver = resolvers.get(resolverId); + if (resolver!=null && resolver.accepts(spec, this)) + return resolver; + } + return null; + } + + /** providers default impl for {@link LocationResolver#accepts(String, LocationRegistry)} */ + public static boolean isResolverPrefixForSpec(LocationResolver resolver, String spec, boolean argumentRequired) { + if (spec==null) return false; + if (spec.startsWith(resolver.getPrefix()+":")) return true; + if (!argumentRequired && spec.equals(resolver.getPrefix())) return true; + return false; + } + + @Override + public List<Location> resolve(Iterable<?> spec) { + List<Location> result = new ArrayList<Location>(); + for (Object id : spec) { + if (id instanceof String) { + result.add(resolve((String) id)); + } else if (id instanceof Location) { + result.add((Location) id); + } else { + if (id instanceof Iterable) + throw new IllegalArgumentException("Cannot resolve '"+id+"' to a location; collections of collections not allowed"); + throw new IllegalArgumentException("Cannot resolve '"+id+"' to a location; unsupported type "+ + (id == null ? "null" : id.getClass().getName())); + } + } + return result; + } + + public List<Location> resolveList(Object l) { + if (l==null) l = Collections.emptyList(); + if (l instanceof String) l = JavaStringEscapes.unwrapJsonishListIfPossible((String)l); + if (l instanceof Iterable) return resolve((Iterable<?>)l); + throw new IllegalArgumentException("Location list must be supplied as a collection or a string, not "+ + JavaClassNames.simpleClassName(l)+"/"+l); + } + + @Override + public Location resolve(LocationDefinition ld) { + return resolve(ld, null, null).get(); + } + + @Override @Deprecated + public Location resolveForPeeking(LocationDefinition ld) { + // TODO should clean up how locations are stored, figuring out whether they are shared or not; + // or maybe better, the API calls to this might just want to get the LocationSpec objects back + + // for now we use a 'CREATE_UNMANGED' flag to prevent management (leaks and logging) + return resolve(ld, ConfigBag.newInstance().configure(LocalLocationManager.CREATE_UNMANAGED, true).getAllConfig()); + } + + @Override @Deprecated + public Location resolve(LocationDefinition ld, Map<?,?> flags) { + return resolveLocationDefinition(ld, flags, null); + } + + /** @deprecated since 0.7.0 not used (and optionalName was ignored anyway) */ + @Deprecated + public Location resolveLocationDefinition(LocationDefinition ld, Map locationFlags, String optionalName) { + return resolve(ld, null, locationFlags).get(); + } + + public Maybe<Location> resolve(LocationDefinition ld, Boolean manage, Map locationFlags) { + ConfigBag newLocationFlags = ConfigBag.newInstance(ld.getConfig()) + .putAll(locationFlags) + .putIfAbsentAndNotNull(LocationInternal.NAMED_SPEC_NAME, ld.getName()) + .putIfAbsentAndNotNull(LocationInternal.ORIGINAL_SPEC, ld.getName()); + Maybe<Location> result = resolve(ld.getSpec(), manage, newLocationFlags.getAllConfigRaw()); + if (result.isPresent()) + return result; + throw new IllegalStateException("Cannot instantiate location '"+ld+"' pointing at "+ld.getSpec()+": "+ + Exceptions.collapseText( ((Absent<?>)result).getException() )); + } + + @Override + public Map getProperties() { + return mgmt.getConfig().asMapWithStringKeys(); + } + + @VisibleForTesting ++ public void putProperties(Map<String, ?> vals) { ++ ((ManagementContextInternal)mgmt).getBrooklynProperties().putAll(vals); ++ } ++ ++ @VisibleForTesting + public static void setupLocationRegistryForTesting(ManagementContext mgmt) { + // ensure localhost is added (even on windows) + LocationDefinition l = mgmt.getLocationRegistry().getDefinedLocationByName("localhost"); + if (l==null) mgmt.getLocationRegistry().updateDefinedLocation( + BasicLocationRegistry.localhost(Identifiers.makeRandomId(8)) ); + + ((BasicLocationRegistry)mgmt.getLocationRegistry()).disablePersistence(); + } - + }
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/AbstractManagementContext.java ---------------------------------------------------------------------- diff --cc brooklyn-server/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/AbstractManagementContext.java index 0000000,cfa1d29..d41e059 mode 000000,100644..100644 --- a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/AbstractManagementContext.java +++ b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/AbstractManagementContext.java @@@ -1,0 -1,517 +1,517 @@@ + /* + * 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.mgmt.internal; + + import static com.google.common.base.Preconditions.checkNotNull; + import static java.lang.String.format; + + import java.net.URI; + import java.net.URL; + import java.util.Collections; + import java.util.List; + import java.util.Map; + import java.util.concurrent.Callable; + import java.util.concurrent.ExecutionException; + import java.util.concurrent.atomic.AtomicLong; + + import javax.annotation.Nullable; + + import org.apache.brooklyn.api.catalog.BrooklynCatalog; + import org.apache.brooklyn.api.effector.Effector; + import org.apache.brooklyn.api.entity.Entity; + import org.apache.brooklyn.api.entity.drivers.EntityDriverManager; + import org.apache.brooklyn.api.entity.drivers.downloads.DownloadResolverManager; + import org.apache.brooklyn.api.location.Location; + import org.apache.brooklyn.api.location.LocationRegistry; + 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.Task; + import org.apache.brooklyn.api.mgmt.classloading.BrooklynClassLoadingContext; + import org.apache.brooklyn.api.mgmt.entitlement.EntitlementManager; + import org.apache.brooklyn.api.mgmt.ha.HighAvailabilityManager; + import org.apache.brooklyn.api.mgmt.rebind.RebindManager; + import org.apache.brooklyn.api.objs.BrooklynObject; + import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry; + import org.apache.brooklyn.api.typereg.RegisteredType; + import org.apache.brooklyn.config.StringConfigMap; + import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog; + import org.apache.brooklyn.core.catalog.internal.CatalogInitialization; + import org.apache.brooklyn.core.catalog.internal.CatalogUtils; + import org.apache.brooklyn.core.entity.AbstractEntity; + import org.apache.brooklyn.core.entity.EntityInternal; + import org.apache.brooklyn.core.entity.drivers.BasicEntityDriverManager; + import org.apache.brooklyn.core.entity.drivers.downloads.BasicDownloadsManager; + import org.apache.brooklyn.core.internal.BrooklynProperties; + import org.apache.brooklyn.core.internal.storage.BrooklynStorage; + import org.apache.brooklyn.core.internal.storage.DataGrid; + import org.apache.brooklyn.core.internal.storage.DataGridFactory; + import org.apache.brooklyn.core.internal.storage.impl.BrooklynStorageImpl; + import org.apache.brooklyn.core.internal.storage.impl.inmemory.InMemoryDataGridFactory; + import org.apache.brooklyn.core.location.BasicLocationRegistry; + import org.apache.brooklyn.core.mgmt.BrooklynTaskTags; + import org.apache.brooklyn.core.mgmt.classloading.JavaBrooklynClassLoadingContext; + import org.apache.brooklyn.core.mgmt.entitlement.Entitlements; + import org.apache.brooklyn.core.mgmt.ha.HighAvailabilityManagerImpl; + import org.apache.brooklyn.core.mgmt.rebind.RebindManagerImpl; + import org.apache.brooklyn.core.typereg.BasicBrooklynTypeRegistry; + import org.apache.brooklyn.util.collections.MutableList; + import org.apache.brooklyn.util.collections.MutableMap; + import org.apache.brooklyn.util.core.ResourceUtils; + import org.apache.brooklyn.util.core.config.ConfigBag; + import org.apache.brooklyn.util.core.task.BasicExecutionContext; + import org.apache.brooklyn.util.core.task.Tasks; + import org.apache.brooklyn.util.groovy.GroovyJavaMethods; + import org.apache.brooklyn.util.guava.Maybe; + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + + import com.google.common.base.Function; + import com.google.common.base.Objects; + import com.google.common.base.Preconditions; + import com.google.common.collect.ImmutableSet; + + public abstract class AbstractManagementContext implements ManagementContextInternal { + private static final Logger log = LoggerFactory.getLogger(AbstractManagementContext.class); + + private static DataGridFactory loadDataGridFactory(BrooklynProperties properties) { + String clazzName = properties.getFirst(DataGridFactory.class.getName()); + if(clazzName == null){ + clazzName = InMemoryDataGridFactory.class.getName(); + } + + Class<?> clazz; + try{ + //todo: which classloader should we use? + clazz = LocalManagementContext.class.getClassLoader().loadClass(clazzName); + }catch(ClassNotFoundException e){ + throw new IllegalStateException(format("Could not load class [%s]",clazzName),e); + } + + Object instance; + try { + instance = clazz.newInstance(); + } catch (InstantiationException e) { + throw new IllegalStateException(format("Could not instantiate class [%s]",clazzName),e); + } catch (IllegalAccessException e) { + throw new IllegalStateException(format("Could not instantiate class [%s]",clazzName),e); + } + + if(!(instance instanceof DataGridFactory)){ + throw new IllegalStateException(format("Class [%s] not an instantiate of class [%s]",clazzName, DataGridFactory.class.getName())); + } + + return (DataGridFactory)instance; + } + + static { + ResourceUtils.addClassLoaderProvider(new Function<Object, BrooklynClassLoadingContext>() { + @Override + public BrooklynClassLoadingContext apply(@Nullable Object input) { + if (input instanceof EntityInternal) { + EntityInternal internal = (EntityInternal)input; + if (internal.getCatalogItemId() != null) { + RegisteredType item = internal.getManagementContext().getTypeRegistry().get(internal.getCatalogItemId()); + + if (item != null) { + return CatalogUtils.newClassLoadingContext(internal.getManagementContext(), item); + } else { + log.error("Can't find catalog item " + internal.getCatalogItemId() + + " used for instantiating entity " + internal + + ". Falling back to application classpath."); + } + } + return apply(internal.getManagementSupport()); + } + + if (input instanceof EntityManagementSupport) + return apply(((EntityManagementSupport)input).getManagementContext()); + if (input instanceof ManagementContext) + return JavaBrooklynClassLoadingContext.create((ManagementContext) input); + return null; + } + }); + } + + private final AtomicLong totalEffectorInvocationCount = new AtomicLong(); + - protected BrooklynProperties configMap; ++ protected DeferredBrooklynProperties configMap; + protected BasicLocationRegistry locationRegistry; + protected final BasicBrooklynCatalog catalog; + protected final BrooklynTypeRegistry typeRegistry; + protected ClassLoader baseClassLoader; + protected Iterable<URL> baseClassPathForScanning; + + private final RebindManager rebindManager; + private final HighAvailabilityManager highAvailabilityManager; + + protected volatile BrooklynGarbageCollector gc; + + private final EntityDriverManager entityDriverManager; + protected DownloadResolverManager downloadsManager; + + protected EntitlementManager entitlementManager; + + private final BrooklynStorage storage; + + protected final ExternalConfigSupplierRegistry configSupplierRegistry; + + private volatile boolean running = true; + protected boolean startupComplete = false; + protected final List<Throwable> errors = Collections.synchronizedList(MutableList.<Throwable>of()); + + protected Maybe<URI> uri = Maybe.absent(); + protected CatalogInitialization catalogInitialization; + + public AbstractManagementContext(BrooklynProperties brooklynProperties){ + this(brooklynProperties, null); + } + + public AbstractManagementContext(BrooklynProperties brooklynProperties, DataGridFactory datagridFactory) { - this.configMap = brooklynProperties; ++ this.configMap = new DeferredBrooklynProperties(brooklynProperties, this); + this.entityDriverManager = new BasicEntityDriverManager(); + this.downloadsManager = BasicDownloadsManager.newDefault(configMap); + if (datagridFactory == null) { + datagridFactory = loadDataGridFactory(brooklynProperties); + } + DataGrid datagrid = datagridFactory.newDataGrid(this); + + this.catalog = new BasicBrooklynCatalog(this); + this.typeRegistry = new BasicBrooklynTypeRegistry(this); + + this.storage = new BrooklynStorageImpl(datagrid); + this.rebindManager = new RebindManagerImpl(this); // TODO leaking "this" reference; yuck + this.highAvailabilityManager = new HighAvailabilityManagerImpl(this); // TODO leaking "this" reference; yuck + + this.entitlementManager = Entitlements.newManager(this, brooklynProperties); + this.configSupplierRegistry = new BasicExternalConfigSupplierRegistry(this); // TODO leaking "this" reference; yuck + } + + @Override + public void terminate() { + highAvailabilityManager.stop(); + running = false; + rebindManager.stop(); + storage.terminate(); + // Don't unmanage everything; different entities get given their events at different times + // so can cause problems (e.g. a group finds out that a member is unmanaged, before the + // group itself has been told that it is unmanaged). + } + + @Override + public boolean isRunning() { + return running; + } + + @Override + public boolean isStartupComplete() { + return startupComplete; + } + + @Override + public BrooklynStorage getStorage() { + return storage; + } + + @Override + public RebindManager getRebindManager() { + return rebindManager; + } + + @Override + public HighAvailabilityManager getHighAvailabilityManager() { + return highAvailabilityManager; + } + + @Override + public long getTotalEffectorInvocations() { + return totalEffectorInvocationCount.get(); + } + + @Override + public ExecutionContext getExecutionContext(Entity e) { + // BEC is a thin wrapper around EM so fine to create a new one here; but make sure it gets the real entity + if (e instanceof AbstractEntity) { + ImmutableSet<Object> tags = ImmutableSet.<Object>of( + BrooklynTaskTags.tagForContextEntity(e), + this + ); + return new BasicExecutionContext(MutableMap.of("tags", tags), getExecutionManager()); + } else { + return ((EntityInternal)e).getManagementSupport().getExecutionContext(); + } + } + + @Override + public ExecutionContext getServerExecutionContext() { + // BEC is a thin wrapper around EM so fine to create a new one here + ImmutableSet<Object> tags = ImmutableSet.<Object>of( + this, + BrooklynTaskTags.BROOKLYN_SERVER_TASK_TAG + ); + return new BasicExecutionContext(MutableMap.of("tags", tags), getExecutionManager()); + } + + @Override + public SubscriptionContext getSubscriptionContext(Entity e) { + // BSC is a thin wrapper around SM so fine to create a new one here + return new BasicSubscriptionContext(getSubscriptionManager(), e); + } + + @Override + public SubscriptionContext getSubscriptionContext(Location loc) { + // BSC is a thin wrapper around SM so fine to create a new one here + return new BasicSubscriptionContext(getSubscriptionManager(), loc); + } + + @Override + public EntityDriverManager getEntityDriverManager() { + return entityDriverManager; + } + + @Override + public DownloadResolverManager getEntityDownloadsManager() { + return downloadsManager; + } + + @Override + public EntitlementManager getEntitlementManager() { + return entitlementManager; + } + + protected abstract void manageIfNecessary(Entity entity, Object context); + + @Override + public <T> Task<T> invokeEffector(final Entity entity, final Effector<T> eff, @SuppressWarnings("rawtypes") final Map parameters) { + return runAtEntity(entity, eff, parameters); + } + + protected <T> T invokeEffectorMethodLocal(Entity entity, Effector<T> eff, Object args) { + assert isManagedLocally(entity) : "cannot invoke effector method at "+this+" because it is not managed here"; + totalEffectorInvocationCount.incrementAndGet(); + Object[] transformedArgs = EffectorUtils.prepareArgsForEffector(eff, args); + return GroovyJavaMethods.invokeMethodOnMetaClass(entity, eff.getName(), transformedArgs); + } + + /** + * Method for entity to make effector happen with correct semantics (right place, right task context), + * when a method is called on that entity. + * @throws ExecutionException + */ + @Override + public <T> T invokeEffectorMethodSync(final Entity entity, final Effector<T> eff, final Object args) throws ExecutionException { + try { + Task<?> current = Tasks.current(); + if (current == null || !entity.equals(BrooklynTaskTags.getContextEntity(current)) || !isManagedLocally(entity)) { + manageIfNecessary(entity, eff.getName()); + // Wrap in a task if we aren't already in a task that is tagged with this entity + Task<T> task = runAtEntity( EffectorUtils.getTaskFlagsForEffectorInvocation(entity, eff, + ConfigBag.newInstance().configureStringKey("args", args)), + entity, + new Callable<T>() { + public T call() { + return invokeEffectorMethodLocal(entity, eff, args); + }}); + return task.get(); + } else { + return invokeEffectorMethodLocal(entity, eff, args); + } + } catch (Exception e) { + // don't need to attach any message or warning because the Effector impl hierarchy does that (see calls to EffectorUtils.handleException) + throw new ExecutionException(e); + } + } + + /** + * Whether the master entity record is local, and sensors and effectors can be properly accessed locally. + */ + public abstract boolean isManagedLocally(Entity e); + + /** + * Causes the indicated runnable to be run at the right location for the given entity. + * + * Returns the actual task (if it is local) or a proxy task (if it is remote); + * if management for the entity has not yet started this may start it. + * + * @deprecated since 0.6.0 use effectors (or support {@code runAtEntity(Entity, Effector, Map)} if something else is needed); + * (Callable with Map flags is too open-ended, bothersome to support, and not used much) + */ + @Deprecated + public abstract <T> Task<T> runAtEntity(@SuppressWarnings("rawtypes") Map flags, Entity entity, Callable<T> c); + + /** Runs the given effector in the right place for the given entity. + * The task is immediately submitted in the background, but also recorded in the queueing context (if present) + * so it appears as a child, but marked inessential so it does not fail the parent task, who will ordinarily + * call {@link Task#get()} on the object and may do their own failure handling. + */ + protected abstract <T> Task<T> runAtEntity(final Entity entity, final Effector<T> eff, @SuppressWarnings("rawtypes") final Map parameters); + + @Override + public StringConfigMap getConfig() { + return configMap; + } + + @Override + public BrooklynProperties getBrooklynProperties() { + return configMap; + } + + @Override + public synchronized LocationRegistry getLocationRegistry() { + if (locationRegistry==null) locationRegistry = new BasicLocationRegistry(this); + return locationRegistry; + } + + @Override + public BrooklynCatalog getCatalog() { + if (!getCatalogInitialization().hasRunAnyInitialization()) { + // catalog init is needed; normally this will be done from start sequence, + // but if accessed early -- and in tests -- we will load it here + getCatalogInitialization().setManagementContext(this); + getCatalogInitialization().populateUnofficial(catalog); + } + return catalog; + } + + @Override + public BrooklynTypeRegistry getTypeRegistry() { + return typeRegistry; + } + + @Override + public ClassLoader getCatalogClassLoader() { + // catalog does not have to be initialized + return catalog.getRootClassLoader(); + } + + /** + * Optional class-loader that this management context should use as its base, + * as the first-resort in the catalog, and for scanning (if scanning the default in the catalog). + * In most instances the default classloader (ManagementContext.class.getClassLoader(), assuming + * this was in the JARs used at boot time) is fine, and in those cases this method normally returns null. + * (Surefire does some weird stuff, but the default classloader is fine for loading; + * however it requires a custom base classpath to be set for scanning.) + */ + @Override + public ClassLoader getBaseClassLoader() { + return baseClassLoader; + } + + /** See {@link #getBaseClassLoader()}. Only settable once and must be invoked before catalog is loaded. */ + public void setBaseClassLoader(ClassLoader cl) { + if (baseClassLoader==cl) return; + if (baseClassLoader!=null) throw new IllegalStateException("Cannot change base class loader (in "+this+")"); + if (catalog!=null) throw new IllegalStateException("Cannot set base class after catalog has been loaded (in "+this+")"); + this.baseClassLoader = cl; + } + + /** Optional mechanism for setting the classpath which should be scanned by the catalog, if the catalog + * is scanning the default classpath. Usually it infers the right thing, but some classloaders + * (e.g. surefire) do funny things which the underlying org.reflections.Reflections library can't see in to. + * <p> + * This should normally be invoked early in the server startup. Setting it after the catalog is loaded will not + * take effect without an explicit internal call to do so. Once set, it can be changed prior to catalog loading + * but it cannot be <i>changed</i> once the catalog is loaded. + * <p> + * ClasspathHelper.forJavaClassPath() is often a good argument to pass, and is used internally in some places + * when no items are found on the catalog. */ + @Override + public void setBaseClassPathForScanning(Iterable<URL> urls) { + if (Objects.equal(baseClassPathForScanning, urls)) return; + if (baseClassPathForScanning != null) { + if (catalog==null) + log.warn("Changing scan classpath to "+urls+" from "+baseClassPathForScanning); + else + throw new IllegalStateException("Cannot change base class path for scanning (in "+this+")"); + } + this.baseClassPathForScanning = urls; + } + /** + * @see #setBaseClassPathForScanning(Iterable) + */ + @Override + public Iterable<URL> getBaseClassPathForScanning() { + return baseClassPathForScanning; + } + + public BrooklynGarbageCollector getGarbageCollector() { + return gc; + } + + @Override + public void setManagementNodeUri(URI uri) { + this.uri = Maybe.of(checkNotNull(uri, "uri")); + } + + @Override + public Maybe<URI> getManagementNodeUri() { + return uri; + } + + private Object catalogInitMutex = new Object(); + @Override + public CatalogInitialization getCatalogInitialization() { + synchronized (catalogInitMutex) { + if (catalogInitialization!=null) return catalogInitialization; + CatalogInitialization ci = new CatalogInitialization(); + setCatalogInitialization(ci); + return ci; + } + } + + @Override + public void setCatalogInitialization(CatalogInitialization catalogInitialization) { + synchronized (catalogInitMutex) { + Preconditions.checkNotNull(catalogInitialization, "initialization must not be null"); + if (this.catalogInitialization!=null && this.catalogInitialization != catalogInitialization) + throw new IllegalStateException("Changing catalog init from "+this.catalogInitialization+" to "+catalogInitialization+"; changes not permitted"); + catalogInitialization.setManagementContext(this); + this.catalogInitialization = catalogInitialization; + } + } + + public BrooklynObject lookup(String id) { + return lookup(id, BrooklynObject.class); + } + + @SuppressWarnings("unchecked") + public <T extends BrooklynObject> T lookup(String id, Class<T> type) { + Object result; + result = getEntityManager().getEntity(id); + if (result!=null && type.isInstance(result)) return (T)result; + + result = getLocationManager().getLocation(id); + if (result!=null && type.isInstance(result)) return (T)result; + + // TODO policies, enrichers, feeds + return null; + } + + @Override + public List<Throwable> errors() { + return errors; + } + + /** @since 0.8.0 */ + @Override + public ExternalConfigSupplierRegistry getExternalConfigProviderRegistry() { + return configSupplierRegistry; + } + + } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/LocalManagementContext.java ---------------------------------------------------------------------- diff --cc brooklyn-server/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/LocalManagementContext.java index 0000000,f464d3b..d88a500 mode 000000,100644..100644 --- a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/LocalManagementContext.java +++ b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/LocalManagementContext.java @@@ -1,0 -1,420 +1,420 @@@ + /* + * 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.mgmt.internal; + + import static com.google.common.base.Preconditions.checkNotNull; + import static org.apache.brooklyn.util.JavaGroovyEquivalents.elvis; + + import java.util.Arrays; + import java.util.Collection; + import java.util.Collections; + import java.util.List; + import java.util.Map; + import java.util.Set; + import java.util.WeakHashMap; + import java.util.concurrent.Callable; + import java.util.concurrent.CopyOnWriteArrayList; + + import org.apache.brooklyn.api.effector.Effector; + import org.apache.brooklyn.api.entity.Application; + import org.apache.brooklyn.api.entity.Entity; + import org.apache.brooklyn.api.location.Location; + import org.apache.brooklyn.api.mgmt.AccessController; + import org.apache.brooklyn.api.mgmt.ExecutionContext; + import org.apache.brooklyn.api.mgmt.ExecutionManager; + import org.apache.brooklyn.api.mgmt.ManagementContext; + import org.apache.brooklyn.api.mgmt.SubscriptionManager; + import org.apache.brooklyn.api.mgmt.Task; + import org.apache.brooklyn.api.mgmt.TaskAdaptable; + import org.apache.brooklyn.core.BrooklynFeatureEnablement; + import org.apache.brooklyn.core.effector.Effectors; + import org.apache.brooklyn.core.entity.drivers.downloads.BasicDownloadsManager; + import org.apache.brooklyn.core.internal.BrooklynProperties; + import org.apache.brooklyn.core.internal.BrooklynProperties.Factory.Builder; + import org.apache.brooklyn.core.internal.storage.DataGridFactory; + import org.apache.brooklyn.core.mgmt.entitlement.Entitlements; + import org.apache.brooklyn.core.mgmt.ha.OsgiManager; + import org.apache.brooklyn.core.objs.proxy.InternalEntityFactory; + import org.apache.brooklyn.core.objs.proxy.InternalLocationFactory; + import org.apache.brooklyn.core.objs.proxy.InternalPolicyFactory; + import org.apache.brooklyn.util.core.task.BasicExecutionContext; + import org.apache.brooklyn.util.core.task.BasicExecutionManager; + import org.apache.brooklyn.util.core.task.DynamicTasks; + import org.apache.brooklyn.util.core.task.TaskTags; + 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.text.Strings; + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + + import com.google.common.annotations.Beta; + import com.google.common.annotations.VisibleForTesting; + import com.google.common.base.Throwables; + import com.google.common.collect.ImmutableSet; + + /** + * A local (single node) implementation of the {@link ManagementContext} API. + */ + public class LocalManagementContext extends AbstractManagementContext { + + private static final Logger log = LoggerFactory.getLogger(LocalManagementContext.class); + + private static final Set<LocalManagementContext> INSTANCES = Collections.synchronizedSet(Collections.newSetFromMap(new WeakHashMap<LocalManagementContext, Boolean>())); + + private final Builder builder; + + private final List<ManagementContext.PropertiesReloadListener> reloadListeners = new CopyOnWriteArrayList<ManagementContext.PropertiesReloadListener>(); + + @VisibleForTesting + static Set<LocalManagementContext> getInstances() { + synchronized (INSTANCES) { + return ImmutableSet.copyOf(INSTANCES); + } + } + + // Note also called reflectively by BrooklynLeakListener + public static void logAll(Logger logger){ + for (LocalManagementContext context : getInstances()) { + logger.warn("Management Context "+context+" running, creation stacktrace:\n" + Throwables.getStackTraceAsString(context.constructionStackTrace)); + } + } + + /** terminates all (best effort); returns count of sessions closed; if exceptions thrown, returns negative number. + * semantics might change, particular in dealing with interminable mgmt contexts. */ + // Note also called reflectively by BrooklynLeakListener + @Beta + public static int terminateAll() { + int closed=0,dangling=0; + for (LocalManagementContext context : getInstances()) { + try { + context.terminate(); + closed++; + }catch (Throwable t) { + Exceptions.propagateIfFatal(t); + log.warn("Failed to terminate management context", t); + dangling++; + } + } + if (dangling>0) return -dangling; + return closed; + } + + private String managementPlaneId; + private String managementNodeId; + private BasicExecutionManager execution; + private SubscriptionManager subscriptions; + private LocalEntityManager entityManager; + private final LocalLocationManager locationManager; + private final LocalAccessManager accessManager; + private final LocalUsageManager usageManager; + private OsgiManager osgiManager; + + public final Throwable constructionStackTrace = new Throwable("for construction stacktrace").fillInStackTrace(); + + private final Map<String, Object> brooklynAdditionalProperties; + + /** + * Creates a LocalManagement with default BrooklynProperties. + */ + public LocalManagementContext() { + this(BrooklynProperties.Factory.builderDefault()); + } + + public LocalManagementContext(BrooklynProperties brooklynProperties) { + this(brooklynProperties, (DataGridFactory)null); + } + + /** + * Creates a new LocalManagementContext. + * + * @param brooklynProperties the BrooklynProperties. + * @param datagridFactory the DataGridFactory to use. If this instance is null, it means that the system + * is going to use BrooklynProperties to figure out which instance to load or otherwise + * use a default instance. + */ + @VisibleForTesting + public LocalManagementContext(BrooklynProperties brooklynProperties, DataGridFactory datagridFactory) { + this(Builder.fromProperties(brooklynProperties), datagridFactory); + } + + public LocalManagementContext(Builder builder) { + this(builder, null, null); + } + + public LocalManagementContext(Builder builder, DataGridFactory datagridFactory) { + this(builder, null, datagridFactory); + } + + public LocalManagementContext(Builder builder, Map<String, Object> brooklynAdditionalProperties) { + this(builder, brooklynAdditionalProperties, null); + } + + public LocalManagementContext(BrooklynProperties brooklynProperties, Map<String, Object> brooklynAdditionalProperties) { + this(Builder.fromProperties(brooklynProperties), brooklynAdditionalProperties, null); + } + + public LocalManagementContext(Builder builder, Map<String, Object> brooklynAdditionalProperties, DataGridFactory datagridFactory) { + super(builder.build(), datagridFactory); + + checkNotNull(configMap, "brooklynProperties"); + + // TODO in a persisted world the planeId may be injected + this.managementPlaneId = Strings.makeRandomId(8); + this.managementNodeId = Strings.makeRandomId(8); + this.builder = builder; + this.brooklynAdditionalProperties = brooklynAdditionalProperties; + if (brooklynAdditionalProperties != null) + configMap.addFromMap(brooklynAdditionalProperties); + + BrooklynFeatureEnablement.init(configMap); + + this.locationManager = new LocalLocationManager(this); + this.accessManager = new LocalAccessManager(); + this.usageManager = new LocalUsageManager(this); + + if (configMap.getConfig(OsgiManager.USE_OSGI)) { + this.osgiManager = new OsgiManager(this); + osgiManager.start(); + } + + INSTANCES.add(this); + log.debug("Created management context "+this); + } + + @Override + public String getManagementPlaneId() { + return managementPlaneId; + } + + @Override + public String getManagementNodeId() { + return managementNodeId; + } + + @Override + public void prePreManage(Entity entity) { + getEntityManager().prePreManage(entity); + } + + @Override + public void prePreManage(Location location) { + getLocationManager().prePreManage(location); + } + + @Override + public synchronized Collection<Application> getApplications() { + return getEntityManager().getApplications(); + } + + @Override + public void addEntitySetListener(CollectionChangeListener<Entity> listener) { + getEntityManager().addEntitySetListener(listener); + } + + @Override + public void removeEntitySetListener(CollectionChangeListener<Entity> listener) { + getEntityManager().removeEntitySetListener(listener); + } + + @Override + protected void manageIfNecessary(Entity entity, Object context) { + getEntityManager().manageIfNecessary(entity, context); + } + + @Override + public synchronized LocalEntityManager getEntityManager() { + if (!isRunning()) throw new IllegalStateException("Management context no longer running"); + + if (entityManager == null) { + entityManager = new LocalEntityManager(this); + } + return entityManager; + } + + @Override + public InternalEntityFactory getEntityFactory() { + return getEntityManager().getEntityFactory(); + } + + @Override + public InternalLocationFactory getLocationFactory() { + return getLocationManager().getLocationFactory(); + } + + @Override + public InternalPolicyFactory getPolicyFactory() { + return getEntityManager().getPolicyFactory(); + } + + @Override + public synchronized LocalLocationManager getLocationManager() { + if (!isRunning()) throw new IllegalStateException("Management context no longer running"); + return locationManager; + } + + @Override + public synchronized LocalAccessManager getAccessManager() { + if (!isRunning()) throw new IllegalStateException("Management context no longer running"); + return accessManager; + } + + @Override + public synchronized LocalUsageManager getUsageManager() { + if (!isRunning()) throw new IllegalStateException("Management context no longer running"); + return usageManager; + } + + @Override + public synchronized Maybe<OsgiManager> getOsgiManager() { + if (!isRunning()) throw new IllegalStateException("Management context no longer running"); + if (osgiManager==null) return Maybe.absent("OSGi not available in this instance"); + return Maybe.of(osgiManager); + } + + @Override + public synchronized AccessController getAccessController() { + return getAccessManager().getAccessController(); + } + + @Override + public synchronized SubscriptionManager getSubscriptionManager() { + if (!isRunning()) throw new IllegalStateException("Management context no longer running"); + + if (subscriptions == null) { + subscriptions = new LocalSubscriptionManager(getExecutionManager()); + } + return subscriptions; + } + + @Override + public synchronized ExecutionManager getExecutionManager() { + if (!isRunning()) throw new IllegalStateException("Management context no longer running"); + + if (execution == null) { + execution = new BasicExecutionManager(getManagementNodeId()); + gc = new BrooklynGarbageCollector(configMap, execution, getStorage()); + } + return execution; + } + + @Override + public void terminate() { + INSTANCES.remove(this); + super.terminate(); + if (osgiManager!=null) { + osgiManager.stop(); + osgiManager = null; + } + if (usageManager != null) usageManager.terminate(); + if (execution != null) execution.shutdownNow(); + if (gc != null) gc.shutdownNow(); + } + + @Override + protected void finalize() { + terminate(); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public <T> Task<T> runAtEntity(Map flags, Entity entity, Callable<T> c) { + manageIfNecessary(entity, elvis(Arrays.asList(flags.get("displayName"), flags.get("description"), flags, c))); + return runAtEntity(entity, Tasks.<T>builder().dynamic(true).body(c).flags(flags).build()); + } + + protected <T> Task<T> runAtEntity(Entity entity, TaskAdaptable<T> task) { + getExecutionContext(entity).submit(task); + if (DynamicTasks.getTaskQueuingContext()!=null) { + // put it in the queueing context so it appears in the GUI + // mark it inessential as this is being invoked from code, + // the caller will do 'get' to handle errors + TaskTags.markInessential(task); + DynamicTasks.getTaskQueuingContext().queue(task.asTask()); + } + return task.asTask(); + } + + @Override + protected <T> Task<T> runAtEntity(final Entity entity, final Effector<T> eff, @SuppressWarnings("rawtypes") final Map parameters) { + manageIfNecessary(entity, eff); + // prefer to submit this from the current execution context so it sets up correct cross-context chaining + ExecutionContext ec = BasicExecutionContext.getCurrentExecutionContext(); + if (ec == null) { + log.debug("Top-level effector invocation: {} on {}", eff, entity); + ec = getExecutionContext(entity); + } + return runAtEntity(entity, Effectors.invocation(entity, eff, parameters)); + } + + @Override + public boolean isManagedLocally(Entity e) { + return true; + } + + @Override + public String toString() { + return LocalManagementContext.class.getSimpleName()+"["+getManagementPlaneId()+"-"+getManagementNodeId()+"]"; + } + + @Override + public void reloadBrooklynProperties() { + log.info("Reloading brooklyn properties from " + builder); + if (builder.hasDelegateOriginalProperties()) + log.warn("When reloading, mgmt context "+this+" properties are fixed, so reload will be of limited utility"); + + BrooklynProperties properties = builder.build(); - configMap = properties; ++ configMap = new DeferredBrooklynProperties(properties, this); + if (brooklynAdditionalProperties != null) { + log.info("Reloading additional brooklyn properties from " + brooklynAdditionalProperties); + configMap.addFromMap(brooklynAdditionalProperties); + } + this.downloadsManager = BasicDownloadsManager.newDefault(configMap); + this.entitlementManager = Entitlements.newManager(this, configMap); + + clearLocationRegistry(); + + BrooklynFeatureEnablement.init(configMap); + + // Notify listeners that properties have been reloaded + for (PropertiesReloadListener listener : reloadListeners) { + listener.reloaded(); + } + } + + @VisibleForTesting + public void clearLocationRegistry() { + // Force reload of location registry + this.locationRegistry = null; + } + + @Override + public void addPropertiesReloadListener(PropertiesReloadListener listener) { + reloadListeners.add(checkNotNull(listener, "listener")); + } + + @Override + public void removePropertiesReloadListener(PropertiesReloadListener listener) { + reloadListeners.remove(listener); + } + + public void noteStartupComplete() { + startupComplete = true; + } + } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/sensor/AttributeMap.java ---------------------------------------------------------------------- diff --cc brooklyn-server/core/src/main/java/org/apache/brooklyn/core/sensor/AttributeMap.java index 0000000,72d6d23..75f087e mode 000000,100644..100644 --- a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/sensor/AttributeMap.java +++ b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/sensor/AttributeMap.java @@@ -1,0 -1,199 +1,217 @@@ + /* + * 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.sensor; + + import static com.google.common.base.Preconditions.checkNotNull; + + import java.util.Collection; ++import java.util.Collections; + import java.util.Map; + + import org.apache.brooklyn.api.entity.Entity; + import org.apache.brooklyn.api.sensor.AttributeSensor; + import org.apache.brooklyn.core.BrooklynLogging; + import org.apache.brooklyn.core.entity.AbstractEntity; + import org.apache.brooklyn.util.core.flags.TypeCoercions; + import org.apache.brooklyn.util.guava.Maybe; + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + + import com.google.common.base.Function; + import com.google.common.base.Joiner; + import com.google.common.base.Objects; + import com.google.common.base.Preconditions; + import com.google.common.collect.ImmutableMap; + import com.google.common.collect.Maps; + + /** + * A {@link Map} of {@link Entity} attribute values. + */ + public final class AttributeMap { + + static final Logger log = LoggerFactory.getLogger(AttributeMap.class); + + private static enum Marker { + NULL; + } + + private final AbstractEntity entity; + + // Assumed to be something like a ConcurrentMap passed in. + private final Map<Collection<String>, Object> values; + + /** + * Creates a new AttributeMap. + * + * @param entity the EntityLocal this AttributeMap belongs to. - * @throws IllegalArgumentException if entity is null ++ * @throws NullPointerException if entity is null ++ */ ++ public AttributeMap(AbstractEntity entity) { ++ // Not using ConcurrentMap, because want to (continue to) allow null values. ++ // Could use ConcurrentMapAcceptingNullVals (with the associated performance hit on entrySet() etc). ++ this(entity, Collections.synchronizedMap(Maps.<Collection<String>, Object>newLinkedHashMap())); ++ } ++ ++ /** ++ * Creates a new AttributeMap. ++ * ++ * @param entity the EntityLocal this AttributeMap belongs to. ++ * @param storage the Map in which to store the values - should be concurrent or synchronized. ++ * @throws NullPointerException if entity is null + */ + public AttributeMap(AbstractEntity entity, Map<Collection<String>, Object> storage) { + this.entity = checkNotNull(entity, "entity must be specified"); + this.values = checkNotNull(storage, "storage map must not be null"); + } + + public Map<Collection<String>, Object> asRawMap() { - return ImmutableMap.copyOf(values); ++ synchronized (values) { ++ return ImmutableMap.copyOf(values); ++ } + } + + public Map<String, Object> asMap() { + Map<String, Object> result = Maps.newLinkedHashMap(); - for (Map.Entry<Collection<String>, Object> entry : values.entrySet()) { - String sensorName = Joiner.on('.').join(entry.getKey()); - Object val = (isNull(entry.getValue())) ? null : entry.getValue(); - result.put(sensorName, val); ++ synchronized (values) { ++ for (Map.Entry<Collection<String>, Object> entry : values.entrySet()) { ++ String sensorName = Joiner.on('.').join(entry.getKey()); ++ Object val = (isNull(entry.getValue())) ? null : entry.getValue(); ++ result.put(sensorName, val); ++ } + } + return result; + } + + /** + * Updates the value. + * + * @param path the path to the value. + * @param newValue the new value + * @return the old value. + * @throws IllegalArgumentException if path is null or empty + */ + // TODO path must be ordered(and legal to contain duplicates like "a.b.a"; list would be better + public <T> T update(Collection<String> path, T newValue) { + checkPath(path); + + if (newValue == null) { + newValue = typedNull(); + } + + if (log.isTraceEnabled()) { + log.trace("setting sensor {}={} for {}", new Object[] {path, newValue, entity}); + } + + @SuppressWarnings("unchecked") + T oldValue = (T) values.put(path, newValue); + return (isNull(oldValue)) ? null : oldValue; + } + + private void checkPath(Collection<String> path) { + Preconditions.checkNotNull(path, "path can't be null"); + Preconditions.checkArgument(!path.isEmpty(), "path can't be empty"); + } + + public <T> T update(AttributeSensor<T> attribute, T newValue) { + T oldValue = updateWithoutPublishing(attribute, newValue); + entity.emitInternal(attribute, newValue); + return oldValue; + } + + public <T> T updateWithoutPublishing(AttributeSensor<T> attribute, T newValue) { + if (log.isTraceEnabled()) { + Object oldValue = getValue(attribute); + if (!Objects.equal(oldValue, newValue != null)) { + log.trace("setting attribute {} to {} (was {}) on {}", new Object[] {attribute.getName(), newValue, oldValue, entity}); + } else { + log.trace("setting attribute {} to {} (unchanged) on {}", new Object[] {attribute.getName(), newValue, this}); + } + } + + T oldValue = (T) update(attribute.getNameParts(), newValue); + + return (isNull(oldValue)) ? null : oldValue; + } + + /** + * Where atomicity is desired, the methods in this class synchronize on the {@link #values} map. + */ + public <T> T modify(AttributeSensor<T> attribute, Function<? super T, Maybe<T>> modifier) { + synchronized (values) { + T oldValue = getValue(attribute); + Maybe<? extends T> newValue = modifier.apply(oldValue); + + if (newValue.isPresent()) { + if (log.isTraceEnabled()) log.trace("modified attribute {} to {} (was {}) on {}", new Object[] {attribute.getName(), newValue, oldValue, entity}); + return update(attribute, newValue.get()); + } else { + if (log.isTraceEnabled()) log.trace("modified attribute {} unchanged; not emitting on {}", new Object[] {attribute.getName(), newValue, this}); + return oldValue; + } + } + } + + public void remove(AttributeSensor<?> attribute) { + BrooklynLogging.log(log, BrooklynLogging.levelDebugOrTraceIfReadOnly(entity), + "removing attribute {} on {}", attribute.getName(), entity); + + remove(attribute.getNameParts()); + } + + // TODO path must be ordered(and legal to contain duplicates like "a.b.a"; list would be better + public void remove(Collection<String> path) { + checkPath(path); + + if (log.isTraceEnabled()) { + log.trace("removing sensor {} for {}", new Object[] {path, entity}); + } + + values.remove(path); + } + + /** + * Gets the value + * + * @param path the path of the value to get + * @return the value + * @throws IllegalArgumentException path is null or empty. + */ + public Object getValue(Collection<String> path) { + // TODO previously this would return a map of the sub-tree if the path matched a prefix of a group of sensors, + // or the leaf value if only one value. Arguably that is not required - what is/was the use-case? + // + checkPath(path); + Object result = values.get(path); + return (isNull(result)) ? null : result; + } + + @SuppressWarnings("unchecked") + public <T> T getValue(AttributeSensor<T> sensor) { + return (T) TypeCoercions.coerce(getValue(sensor.getNameParts()), sensor.getType()); + } + + @SuppressWarnings("unchecked") + private <T> T typedNull() { + return (T) Marker.NULL; + } + + private boolean isNull(Object t) { + return t == Marker.NULL; + } + }
