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;
+     }
+ }