You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2015/12/23 12:06:38 UTC
[15/71] [abbrv] incubator-brooklyn git commit: Merge commit 'e430723'
into reorg2
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;
+ }
+ }