http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6602f694/core/src/main/java/org/apache/brooklyn/core/management/internal/LocalSubscriptionManager.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/management/internal/LocalSubscriptionManager.java
 
b/core/src/main/java/org/apache/brooklyn/core/management/internal/LocalSubscriptionManager.java
new file mode 100644
index 0000000..99b79ec
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/management/internal/LocalSubscriptionManager.java
@@ -0,0 +1,292 @@
+/*
+ * 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.management.internal;
+
+import static brooklyn.util.JavaGroovyEquivalents.elvis;
+import static brooklyn.util.JavaGroovyEquivalents.groovyTruth;
+import static brooklyn.util.JavaGroovyEquivalents.join;
+import static brooklyn.util.JavaGroovyEquivalents.mapOf;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.event.Sensor;
+import org.apache.brooklyn.api.event.SensorEvent;
+import org.apache.brooklyn.api.event.SensorEventListener;
+import org.apache.brooklyn.api.management.ExecutionManager;
+import org.apache.brooklyn.api.management.SubscriptionHandle;
+import org.apache.brooklyn.api.management.SubscriptionManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.entity.basic.Entities;
+import brooklyn.util.task.BasicExecutionManager;
+import brooklyn.util.task.SingleThreadedScheduler;
+import brooklyn.util.text.Identifiers;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimaps;
+
+/**
+ * A {@link SubscriptionManager} that stores subscription details locally.
+ */
+public class LocalSubscriptionManager extends AbstractSubscriptionManager {
+    
+    private static final Logger LOG = 
LoggerFactory.getLogger(LocalSubscriptionManager.class);
+
+    protected final ExecutionManager em;
+    
+    private final String tostring = 
"SubscriptionContext("+Identifiers.getBase64IdFromValue(System.identityHashCode(this),
 5)+")";
+
+    private final AtomicLong totalEventsPublishedCount = new AtomicLong();
+    private final AtomicLong totalEventsDeliveredCount = new AtomicLong();
+    
+    @SuppressWarnings("rawtypes")
+    protected final ConcurrentMap<String, Subscription> allSubscriptions = new 
ConcurrentHashMap<String, Subscription>();
+    @SuppressWarnings("rawtypes")
+    protected final ConcurrentMap<Object, Set<Subscription>> 
subscriptionsBySubscriber = new ConcurrentHashMap<Object, Set<Subscription>>();
+    @SuppressWarnings("rawtypes")
+    protected final ConcurrentMap<Object, Set<Subscription>> 
subscriptionsByToken = new ConcurrentHashMap<Object, Set<Subscription>>();
+    
+    public LocalSubscriptionManager(ExecutionManager m) {
+        this.em = m;
+    }
+        
+    public long getNumSubscriptions() {
+        return allSubscriptions.size();
+    }
+
+    public long getTotalEventsPublished() {
+        return totalEventsPublishedCount.get();
+    }
+    
+    public long getTotalEventsDelivered() {
+        return totalEventsDeliveredCount.get();
+    }
+    
+    @SuppressWarnings("unchecked")
+    protected synchronized <T> SubscriptionHandle subscribe(Map<String, 
Object> flags, Subscription<T> s) {
+        Entity producer = s.producer;
+        Sensor<T> sensor= s.sensor;
+        s.subscriber = getSubscriber(flags, s);
+        if (flags.containsKey("subscriberExecutionManagerTag")) {
+            s.subscriberExecutionManagerTag = 
flags.remove("subscriberExecutionManagerTag");
+            s.subscriberExecutionManagerTagSupplied = true;
+        } else {
+            s.subscriberExecutionManagerTag = 
+                s.subscriber instanceof Entity ? 
"subscription-delivery-entity-"+((Entity)s.subscriber).getId()+"["+s.subscriber+"]"
 : 
+                s.subscriber instanceof String ? 
"subscription-delivery-string["+s.subscriber+"]" : 
+                "subscription-delivery-object["+s.subscriber+"]";
+            s.subscriberExecutionManagerTagSupplied = false;
+        }
+        s.eventFilter = (Predicate<SensorEvent<T>>) 
flags.remove("eventFilter");
+        s.flags = flags;
+        
+        if (LOG.isDebugEnabled()) LOG.debug("Creating subscription {} for {} 
on {} {} in {}", new Object[] {s.id, s.subscriber, producer, sensor, this});
+        allSubscriptions.put(s.id, s);
+        addToMapOfSets(subscriptionsByToken, makeEntitySensorToken(s.producer, 
s.sensor), s);
+        if (s.subscriber!=null) {
+            addToMapOfSets(subscriptionsBySubscriber, s.subscriber, s);
+        }
+        if (!s.subscriberExecutionManagerTagSupplied && 
s.subscriberExecutionManagerTag!=null) {
+            ((BasicExecutionManager) 
em).setTaskSchedulerForTag(s.subscriberExecutionManagerTag, 
SingleThreadedScheduler.class);
+        }
+        return s;
+    }
+
+    @SuppressWarnings("unchecked")
+    public Set<SubscriptionHandle> getSubscriptionsForSubscriber(Object 
subscriber) {
+        return (Set<SubscriptionHandle>) ((Set<?>) 
elvis(subscriptionsBySubscriber.get(subscriber), Collections.emptySet()));
+    }
+
+    public synchronized Set<SubscriptionHandle> 
getSubscriptionsForEntitySensor(Entity source, Sensor<?> sensor) {
+        Set<SubscriptionHandle> subscriptions = new 
LinkedHashSet<SubscriptionHandle>();
+        
subscriptions.addAll(elvis(subscriptionsByToken.get(makeEntitySensorToken(source,
 sensor)), Collections.emptySet()));
+        
subscriptions.addAll(elvis(subscriptionsByToken.get(makeEntitySensorToken(null, 
sensor)), Collections.emptySet()));
+        
subscriptions.addAll(elvis(subscriptionsByToken.get(makeEntitySensorToken(source,
 null)), Collections.emptySet()));
+        
subscriptions.addAll(elvis(subscriptionsByToken.get(makeEntitySensorToken(null, 
null)), Collections.emptySet()));
+        return subscriptions;
+    }
+
+    /**
+     * Unsubscribe the given subscription id.
+     *
+     * @see #subscribe(Map, Entity, Sensor, SensorEventListener)
+     */
+    @SuppressWarnings("rawtypes")
+    public synchronized boolean unsubscribe(SubscriptionHandle sh) {
+        if (!(sh instanceof Subscription)) throw new 
IllegalArgumentException("Only subscription handles of type Subscription 
supported: sh="+sh+"; type="+(sh != null ? sh.getClass().getCanonicalName() : 
null));
+        Subscription s = (Subscription) sh;
+        boolean result = allSubscriptions.remove(s.id) != null;
+        boolean b2 = removeFromMapOfCollections(subscriptionsByToken, 
makeEntitySensorToken(s.producer, s.sensor), s);
+        assert result==b2;
+        if (s.subscriber!=null) {
+            boolean b3 = removeFromMapOfCollections(subscriptionsBySubscriber, 
s.subscriber, s);
+            assert b3 == b2;
+        }
+
+        // FIXME ALEX - this seems wrong
+        ((BasicExecutionManager) 
em).setTaskSchedulerForTag(s.subscriberExecutionManagerTag, 
SingleThreadedScheduler.class);
+        return result;
+    }
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    public <T> void publish(final SensorEvent<T> event) {
+        // REVIEW 1459 - execution
+        
+        // delivery in parallel/background, using execution manager
+        
+        // subscriptions, should define SingleThreadedScheduler for any 
subscriber ID tag
+        // in order to ensure callbacks are invoked in the order they are 
submitted
+        // (recommend exactly one per subscription to prevent deadlock)
+        // this is done with:
+        // em.setTaskSchedulerForTag(subscriberId, 
SingleThreadedScheduler.class);
+        
+        //note, generating the notifications must be done in the calling 
thread to preserve order
+        //e.g. emit(A); emit(B); should cause onEvent(A); onEvent(B) in that 
order
+        if (LOG.isTraceEnabled()) LOG.trace("{} got event {}", this, event);
+        totalEventsPublishedCount.incrementAndGet();
+        
+        Set<Subscription> subs = (Set<Subscription>) ((Set<?>) 
getSubscriptionsForEntitySensor(event.getSource(), event.getSensor()));
+        if (groovyTruth(subs)) {
+            if (LOG.isTraceEnabled()) LOG.trace("sending {}, {} to {}", new 
Object[] {event.getSensor().getName(), event, join(subs, ",")});
+            for (Subscription s : subs) {
+                if (s.eventFilter!=null && !s.eventFilter.apply(event))
+                    continue;
+                final Subscription sAtClosureCreation = s;
+                
+//                Set<Object> tags = MutableSet.of();
+//                if (s.subscriberExecutionManagerTag!=null) 
tags.add(s.subscriberExecutionManagerTag);
+//                if (event.getSource()!=null) 
tags.add(BrooklynTaskTags.tagForContextEntity(event.getSource()));
+//                Map<String, Object> tagsMap = mapOf("tags", (Object)tags);
+                // use code above, instead of line below, if we want 
subscription deliveries associated with the entity;
+                // that will cause them to be cancelled when the entity is 
unmanaged
+                // (not sure that is useful, and likely NOT worth the expense, 
but it might be...) -Alex Oct 2014
+                Map<String, Object> tagsMap = mapOf("tag", 
s.subscriberExecutionManagerTag);
+                
+                em.submit(tagsMap, new Runnable() {
+                    @Override
+                    public String toString() {
+                        return "LSM.publish("+event+")";
+                    }
+                    public void run() {
+                        try {
+                            sAtClosureCreation.listener.onEvent(event);
+                        } catch (Throwable t) {
+                            if (event!=null && event.getSource()!=null && 
Entities.isNoLongerManaged(event.getSource())) {
+                                LOG.debug("Error processing subscriptions to 
"+this+", after entity unmanaged: "+t, t);
+                            } else {
+                                LOG.warn("Error processing subscriptions to 
"+this+": "+t, t);
+                            }
+                        }
+                    }});
+                totalEventsDeliveredCount.incrementAndGet();
+            }
+        }
+    }
+    
+    @Override
+    public String toString() {
+        return tostring;
+    }
+    
+    /**
+     * Copied from LanguageUtils.groovy, to remove dependency.
+     * 
+     * Adds the given value to a collection in the map under the key.
+     * 
+     * A collection (as {@link LinkedHashMap}) will be created if necessary,
+     * synchronized on map for map access/change and set for addition there
+     *
+     * @return the updated set (instance, not copy)
+     * 
+     * @deprecated since 0.5; use {@link HashMultimap}, and {@link 
Multimaps#synchronizedSetMultimap(com.google.common.collect.SetMultimap)}
+     */
+    @Deprecated
+    private static <K,V> Set<V> addToMapOfSets(Map<K,Set<V>> map, K key, V 
valueInCollection) {
+        Set<V> coll;
+        synchronized (map) {
+            coll = map.get(key);
+            if (coll==null) {
+                coll = new LinkedHashSet<V>();
+                map.put(key, coll);
+            }
+            if (coll.isEmpty()) {
+                synchronized (coll) {
+                    coll.add(valueInCollection);
+                }
+                //if collection was empty then add to the collection while 
holding the map lock, to prevent removal
+                return coll;
+            }
+        }
+        synchronized (coll) {
+            if (!coll.isEmpty()) {
+                coll.add(valueInCollection);
+                return coll;
+            }
+        }
+        //if was empty, recurse, because someone else might be removing the 
collection
+        return addToMapOfSets(map, key, valueInCollection);
+    }
+
+    /**
+     * Copied from LanguageUtils.groovy, to remove dependency.
+     * 
+     * Removes the given value from a collection in the map under the key.
+     *
+     * @return the updated set (instance, not copy)
+     * 
+     * @deprecated since 0.5; use {@link ArrayListMultimap} or {@link 
HashMultimap}, and {@link 
Multimaps#synchronizedListMultimap(com.google.common.collect.ListMultimap)} etc
+     */
+    @Deprecated
+    private static <K,V> boolean removeFromMapOfCollections(Map<K,? extends 
Collection<V>> map, K key, V valueInCollection) {
+        Collection<V> coll;
+        synchronized (map) {
+            coll = map.get(key);
+            if (coll==null) return false;
+        }
+        boolean result;
+        synchronized (coll) {
+            result = coll.remove(valueInCollection);
+        }
+        if (coll.isEmpty()) {
+            synchronized (map) {
+                synchronized (coll) {
+                    if (coll.isEmpty()) {
+                        //only remove from the map if no one is adding to the 
collection or to the map, and the collection is still in the map
+                        if (map.get(key)==coll) {
+                            map.remove(key);
+                        }
+                    }
+                }
+            }
+        }
+        return result;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6602f694/core/src/main/java/org/apache/brooklyn/core/management/internal/LocalUsageManager.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/management/internal/LocalUsageManager.java
 
b/core/src/main/java/org/apache/brooklyn/core/management/internal/LocalUsageManager.java
new file mode 100644
index 0000000..dc95d11
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/management/internal/LocalUsageManager.java
@@ -0,0 +1,430 @@
+/*
+ * 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.management.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+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.management.entitlement.EntitlementContext;
+import org.apache.brooklyn.core.management.ManagementContextInjectable;
+import org.apache.brooklyn.core.management.entitlement.Entitlements;
+import org.apache.brooklyn.core.management.usage.ApplicationUsage;
+import org.apache.brooklyn.core.management.usage.LocationUsage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.basic.EntityInternal;
+import brooklyn.entity.basic.Lifecycle;
+import brooklyn.internal.storage.BrooklynStorage;
+
+import org.apache.brooklyn.location.basic.AbstractLocation;
+import org.apache.brooklyn.location.basic.LocationConfigKeys;
+import org.apache.brooklyn.location.basic.LocationInternal;
+
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.flags.TypeCoercions;
+import brooklyn.util.javalang.Reflections;
+import brooklyn.util.time.Duration;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+
+public class LocalUsageManager implements UsageManager {
+
+    // TODO Threading model needs revisited.
+    // Synchronizes on updates to storage; but if two Brooklyn nodes were both 
writing to the same
+    // ApplicationUsage or LocationUsage record there'd be a race. That 
currently won't happen
+    // (at least for ApplicationUsage?) because the app is mastered in just 
one node at a time,
+    // and because location events are just manage/unmanage which should be 
happening in just 
+    // one place at a time for a given location.
+    
+    private static final Logger log = 
LoggerFactory.getLogger(LocalUsageManager.class);
+
+    private static class ApplicationMetadataImpl implements 
org.apache.brooklyn.core.management.internal.UsageListener.ApplicationMetadata {
+        private final Application app;
+        private String applicationId;
+        private String applicationName;
+        private String entityType;
+        private String catalogItemId;
+        private Map<String, String> metadata;
+
+        ApplicationMetadataImpl(Application app) {
+            this.app = checkNotNull(app, "app");
+            applicationId = app.getId();
+            applicationName = app.getDisplayName();
+            entityType = app.getEntityType().getName();
+            catalogItemId = app.getCatalogItemId();
+            metadata = ((EntityInternal)app).toMetadataRecord();
+        }
+        @Override public Application getApplication() {
+            return app;
+        }
+        @Override public String getApplicationId() {
+            return applicationId;
+        }
+        @Override public String getApplicationName() {
+            return applicationName;
+        }
+        @Override public String getEntityType() {
+            return entityType;
+        }
+        @Override public String getCatalogItemId() {
+            return catalogItemId;
+        }
+        @Override public Map<String, String> getMetadata() {
+            return metadata;
+        }
+    }
+    
+    private static class LocationMetadataImpl implements 
org.apache.brooklyn.core.management.internal.UsageListener.LocationMetadata {
+        private final Location loc;
+        private String locationId;
+        private Map<String, String> metadata;
+
+        LocationMetadataImpl(Location loc) {
+            this.loc = checkNotNull(loc, "loc");
+            locationId = loc.getId();
+            metadata = ((LocationInternal)loc).toMetadataRecord();
+        }
+        @Override public Location getLocation() {
+            return loc;
+        }
+        @Override public String getLocationId() {
+            return locationId;
+        }
+        @Override public Map<String, String> getMetadata() {
+            return metadata;
+        }
+    }
+    
+    // Register a coercion from String->UsageListener, so that USAGE_LISTENERS 
defined in brooklyn.properties
+    // will be instantiated, given their class names.
+    static {
+        TypeCoercions.registerAdapter(String.class, 
org.apache.brooklyn.core.management.internal.UsageListener.class, new 
Function<String, org.apache.brooklyn.core.management.internal.UsageListener>() {
+            @Override public 
org.apache.brooklyn.core.management.internal.UsageListener apply(String input) {
+                // TODO Want to use classLoader = 
mgmt.getCatalog().getRootClassLoader();
+                ClassLoader classLoader = 
LocalUsageManager.class.getClassLoader();
+                Optional<Object> result = 
Reflections.invokeConstructorWithArgs(classLoader, input);
+                if (result.isPresent()) {
+                    if (result.get() instanceof 
org.apache.brooklyn.core.management.internal.UsageManager.UsageListener) {
+                        return new 
org.apache.brooklyn.core.management.internal.UsageManager.UsageListener.UsageListenerAdapter((org.apache.brooklyn.core.management.internal.UsageManager.UsageListener)
 result.get());
+                    } else {
+                        return 
(org.apache.brooklyn.core.management.internal.UsageListener) result.get();
+                    }
+                } else {
+                    throw new IllegalStateException("Failed to create 
UsageListener from class name '"+input+"' using no-arg constructor");
+                }
+            }
+        });
+    }
+    
+    @VisibleForTesting
+    public static final String APPLICATION_USAGE_KEY = "usage-application";
+    
+    @VisibleForTesting
+    public static final String LOCATION_USAGE_KEY = "usage-location";
+
+    private final LocalManagementContext managementContext;
+    
+    private final Object mutex = new Object();
+
+    private final 
List<org.apache.brooklyn.core.management.internal.UsageListener> listeners = 
Lists.newCopyOnWriteArrayList();
+    
+    private final AtomicInteger listenerQueueSize = new AtomicInteger();
+    
+    private ListeningExecutorService listenerExecutor = 
MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor(new 
ThreadFactoryBuilder()
+            .setNameFormat("brooklyn-usagemanager-listener-%d")
+            .build()));
+
+    public LocalUsageManager(LocalManagementContext managementContext) {
+        this.managementContext = checkNotNull(managementContext, 
"managementContext");
+        
+        // TODO Once 
org.apache.brooklyn.core.management.internal.UsageManager.UsageListener is 
deleted, restore this
+        // to normal generics!
+        Collection<?> listeners = 
managementContext.getBrooklynProperties().getConfig(UsageManager.USAGE_LISTENERS);
+        if (listeners != null) {
+            for (Object listener : listeners) {
+                if (listener instanceof ManagementContextInjectable) {
+                    
((ManagementContextInjectable)listener).injectManagementContext(managementContext);
+                }
+                if (listener instanceof 
org.apache.brooklyn.core.management.internal.UsageManager.UsageListener) {
+                    
addUsageListener((org.apache.brooklyn.core.management.internal.UsageManager.UsageListener)listener);
+                } else if (listener instanceof 
org.apache.brooklyn.core.management.internal.UsageListener) {
+                    
addUsageListener((org.apache.brooklyn.core.management.internal.UsageListener)listener);
+                } else if (listener == null) {
+                    throw new NullPointerException("null listener in config 
"+UsageManager.USAGE_LISTENERS);
+                } else {
+                    throw new ClassCastException("listener "+listener+" of 
type "+listener.getClass()+" is not of type 
"+org.apache.brooklyn.core.management.internal.UsageListener.class.getName());
+                }
+            }
+        }
+    }
+
+    public void terminate() {
+        // Wait for the listeners to finish + close the listeners
+        Duration timeout = 
managementContext.getBrooklynProperties().getConfig(UsageManager.USAGE_LISTENER_TERMINATION_TIMEOUT);
+        if (listenerQueueSize.get() > 0) {
+            log.info("Usage manager waiting for "+listenerQueueSize+" listener 
events for up to "+timeout);
+        }
+        List<ListenableFuture<?>> futures = Lists.newArrayList();
+        for (final org.apache.brooklyn.core.management.internal.UsageListener 
listener : listeners) {
+            ListenableFuture<?> future = listenerExecutor.submit(new 
Runnable() {
+                public void run() {
+                    if (listener instanceof Closeable) {
+                        try {
+                            ((Closeable)listener).close();
+                        } catch (IOException e) {
+                            log.warn("Problem closing usage listener 
"+listener+" (continuing)", e);
+                        }
+                    }
+                }});
+            futures.add(future);
+        }
+        try {
+            Futures.successfulAsList(futures).get(timeout.toMilliseconds(), 
TimeUnit.MILLISECONDS);
+        } catch (Exception e) {
+            Exceptions.propagateIfFatal(e);
+            log.warn("Problem terminiating usage listeners (continuing)", e);
+        } finally {
+            listenerExecutor.shutdownNow();
+        }
+    }
+
+    private void execOnListeners(final 
Function<org.apache.brooklyn.core.management.internal.UsageListener, Void> job) 
{
+        for (final org.apache.brooklyn.core.management.internal.UsageListener 
listener : listeners) {
+            listenerQueueSize.incrementAndGet();
+            listenerExecutor.execute(new Runnable() {
+                public void run() {
+                    try {
+                        job.apply(listener);
+                    } catch (RuntimeException e) {
+                        log.error("Problem notifying listener "+listener+" of 
"+job, e);
+                        Exceptions.propagateIfFatal(e);
+                    } finally {
+                        listenerQueueSize.decrementAndGet();
+                    }
+                }});
+        }
+    }
+    
+    @Override
+    public void recordApplicationEvent(final Application app, final Lifecycle 
state) {
+        log.debug("Storing application lifecycle usage event: application {} 
in state {}", new Object[] {app, state});
+        ConcurrentMap<String, ApplicationUsage> eventMap = 
managementContext.getStorage().getMap(APPLICATION_USAGE_KEY);
+        synchronized (mutex) {
+            ApplicationUsage usage = eventMap.get(app.getId());
+            if (usage == null) {
+                usage = new ApplicationUsage(app.getId(), 
app.getDisplayName(), app.getEntityType().getName(), 
((EntityInternal)app).toMetadataRecord());
+            }
+            final ApplicationUsage.ApplicationEvent event = new 
ApplicationUsage.ApplicationEvent(state, getUser());
+            usage.addEvent(event);        
+            eventMap.put(app.getId(), usage);
+
+            execOnListeners(new 
Function<org.apache.brooklyn.core.management.internal.UsageListener, Void>() {
+                    public Void 
apply(org.apache.brooklyn.core.management.internal.UsageListener listener) {
+                        listener.onApplicationEvent(new 
ApplicationMetadataImpl(Entities.proxy(app)), event);
+                        return null;
+                    }
+                    public String toString() {
+                        return "applicationEvent("+app+", "+state+")";
+                    }});
+        }
+    }
+    
+    /**
+     * Adds this location event to the usage record for the given location 
(creating the usage 
+     * record if one does not already exist).
+     */
+    @Override
+    public void recordLocationEvent(final Location loc, final Lifecycle state) 
{
+        // TODO This approach (i.e. recording events on manage/unmanage would 
not work for
+        // locations that are reused. For example, in a 
FixedListMachineProvisioningLocation
+        // the ssh machine location is returned to the pool and handed back 
out again.
+        // But maybe the solution there is to hand out different instances so 
that one user
+        // can't change the config of the SshMachineLocation to subsequently 
affect the next 
+        // user.
+        //
+        // TODO Should perhaps extract the location storage methods into their 
own class,
+        // but no strong enough feelings yet...
+        
+        checkNotNull(loc, "location");
+        if (loc.getConfig(AbstractLocation.TEMPORARY_LOCATION)) {
+            log.info("Ignoring location lifecycle usage event for {} (state 
{}), because location is a temporary location", loc, state);
+            return;
+        }
+        checkNotNull(state, "state of location %s", loc);
+        if (loc.getId() == null) {
+            log.error("Ignoring location lifecycle usage event for {} (state 
{}), because location has no id", loc, state);
+            return;
+        }
+        if (managementContext.getStorage() == null) {
+            log.warn("Cannot store location lifecycle usage event for {} 
(state {}), because storage not available", loc, state);
+            return;
+        }
+        
+        Object callerContext = 
loc.getConfig(LocationConfigKeys.CALLER_CONTEXT);
+        
+        if (callerContext != null && callerContext instanceof Entity) {
+            log.debug("Storing location lifecycle usage event: location {} in 
state {}; caller context {}", new Object[] {loc, state, callerContext});
+            
+            Entity caller = (Entity) callerContext;
+            String entityTypeName = caller.getEntityType().getName();
+            String appId = caller.getApplicationId();
+
+            final LocationUsage.LocationEvent event = new 
LocationUsage.LocationEvent(state, caller.getId(), entityTypeName, appId, 
getUser());
+            
+            ConcurrentMap<String, LocationUsage> usageMap = 
managementContext.getStorage().<String, 
LocationUsage>getMap(LOCATION_USAGE_KEY);
+            synchronized (mutex) {
+                LocationUsage usage = usageMap.get(loc.getId());
+                if (usage == null) {
+                    usage = new LocationUsage(loc.getId(), 
((LocationInternal)loc).toMetadataRecord());
+                }
+                usage.addEvent(event);
+                usageMap.put(loc.getId(), usage);
+                
+                execOnListeners(new 
Function<org.apache.brooklyn.core.management.internal.UsageListener, Void>() {
+                        public Void 
apply(org.apache.brooklyn.core.management.internal.UsageListener listener) {
+                            listener.onLocationEvent(new 
LocationMetadataImpl(loc), event);
+                            return null;
+                        }
+                        public String toString() {
+                            return "locationEvent("+loc+", "+state+")";
+                        }});
+            }
+        } else {
+            // normal for high-level locations
+            log.trace("Not recording location lifecycle usage event for {} in 
state {}, because no caller context", new Object[] {loc, state});
+        }
+    }
+
+    /**
+     * Returns the usage info for the location with the given id, or null if 
unknown.
+     */
+    @Override
+    public LocationUsage getLocationUsage(String locationId) {
+        BrooklynStorage storage = managementContext.getStorage();
+
+        Map<String, LocationUsage> usageMap = 
storage.getMap(LOCATION_USAGE_KEY);
+        return usageMap.get(locationId);
+    }
+    
+    /**
+     * Returns the usage info that matches the given predicate.
+     * For example, could be used to find locations used within a given time 
period.
+     */
+    @Override
+    public Set<LocationUsage> getLocationUsage(Predicate<? super 
LocationUsage> filter) {
+        // TODO could do more efficient indexing, to more easily find 
locations in use during a given period.
+        // But this is good enough for first-pass.
+
+        Map<String, LocationUsage> usageMap = 
managementContext.getStorage().getMap(LOCATION_USAGE_KEY);
+        Set<LocationUsage> result = Sets.newLinkedHashSet();
+        
+        for (LocationUsage usage : usageMap.values()) {
+            if (filter.apply(usage)) {
+                result.add(usage);
+            }
+        }
+        return result;
+    }
+    
+    /**
+     * Returns the usage info for the location with the given id, or null if 
unknown.
+     */
+    @Override
+    public ApplicationUsage getApplicationUsage(String appId) {
+        BrooklynStorage storage = managementContext.getStorage();
+
+        Map<String, ApplicationUsage> usageMap = 
storage.getMap(APPLICATION_USAGE_KEY);
+        return usageMap.get(appId);
+    }
+    
+    /**
+     * Returns the usage info that matches the given predicate.
+     * For example, could be used to find applications used within a given 
time period.
+     */
+    @Override
+    public Set<ApplicationUsage> getApplicationUsage(Predicate<? super 
ApplicationUsage> filter) {
+        // TODO could do more efficient indexing, to more easily find 
locations in use during a given period.
+        // But this is good enough for first-pass.
+
+        Map<String, ApplicationUsage> usageMap = 
managementContext.getStorage().getMap(APPLICATION_USAGE_KEY);
+        Set<ApplicationUsage> result = Sets.newLinkedHashSet();
+        
+        for (ApplicationUsage usage : usageMap.values()) {
+            if (filter.apply(usage)) {
+                result.add(usage);
+            }
+        }
+        return result;
+    }
+
+    @Override
+    @Deprecated
+    public void 
addUsageListener(org.apache.brooklyn.core.management.internal.UsageManager.UsageListener
 listener) {
+        addUsageListener(new 
org.apache.brooklyn.core.management.internal.UsageManager.UsageListener.UsageListenerAdapter(listener));
+    }
+
+    @Override
+    @Deprecated
+    public void 
removeUsageListener(org.apache.brooklyn.core.management.internal.UsageManager.UsageListener
 listener) {
+        removeUsageListener(new 
org.apache.brooklyn.core.management.internal.UsageManager.UsageListener.UsageListenerAdapter(listener));
+    }
+    
+    @Override
+    public void 
addUsageListener(org.apache.brooklyn.core.management.internal.UsageListener 
listener) {
+        listeners.add(listener);
+    }
+
+    @Override
+    public void 
removeUsageListener(org.apache.brooklyn.core.management.internal.UsageListener 
listener) {
+        listeners.remove(listener);
+    }
+
+    private String getUser() {
+        EntitlementContext entitlementContext = 
Entitlements.getEntitlementContext();
+        if (entitlementContext != null) {
+            return entitlementContext.user();
+        }
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6602f694/core/src/main/java/org/apache/brooklyn/core/management/internal/LocationManagerInternal.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/management/internal/LocationManagerInternal.java
 
b/core/src/main/java/org/apache/brooklyn/core/management/internal/LocationManagerInternal.java
new file mode 100644
index 0000000..ba426ec
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/management/internal/LocationManagerInternal.java
@@ -0,0 +1,28 @@
+/*
+ * 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.management.internal;
+
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.management.LocationManager;
+
+public interface LocationManagerInternal extends LocationManager, 
BrooklynObjectManagerInternal<Location> {
+
+    public Iterable<String> getLocationIds();
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6602f694/core/src/main/java/org/apache/brooklyn/core/management/internal/ManagementContextInternal.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/management/internal/ManagementContextInternal.java
 
b/core/src/main/java/org/apache/brooklyn/core/management/internal/ManagementContextInternal.java
new file mode 100644
index 0000000..97a3d73
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/management/internal/ManagementContextInternal.java
@@ -0,0 +1,122 @@
+/*
+ * 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.management.internal;
+
+import java.net.URI;
+import java.net.URL;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+
+import org.apache.brooklyn.api.entity.Effector;
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.management.ManagementContext;
+import org.apache.brooklyn.api.management.Task;
+import org.apache.brooklyn.core.management.ha.OsgiManager;
+
+import brooklyn.catalog.internal.CatalogInitialization;
+import brooklyn.config.BrooklynProperties;
+import brooklyn.entity.basic.BrooklynTaskTags;
+import brooklyn.entity.proxying.InternalEntityFactory;
+import brooklyn.entity.proxying.InternalLocationFactory;
+import brooklyn.entity.proxying.InternalPolicyFactory;
+import brooklyn.internal.storage.BrooklynStorage;
+import brooklyn.util.guava.Maybe;
+import brooklyn.util.task.TaskTags;
+
+import com.google.common.annotations.Beta;
+
+public interface ManagementContextInternal extends ManagementContext {
+
+    public static final String SUB_TASK_TAG = TaskTags.SUB_TASK_TAG;
+    
+    public static final String EFFECTOR_TAG = BrooklynTaskTags.EFFECTOR_TAG;
+    public static final String NON_TRANSIENT_TASK_TAG = 
BrooklynTaskTags.NON_TRANSIENT_TASK_TAG;
+    public static final String TRANSIENT_TASK_TAG = 
BrooklynTaskTags.TRANSIENT_TASK_TAG;
+
+    public static final String EMPTY_CATALOG_URL = 
"classpath://brooklyn/empty.catalog.bom";
+    
+    ClassLoader getBaseClassLoader();
+
+    Iterable<URL> getBaseClassPathForScanning();
+
+    void setBaseClassPathForScanning(Iterable<URL> urls);
+
+    void setManagementNodeUri(URI uri);
+
+    void addEntitySetListener(CollectionChangeListener<Entity> listener);
+
+    void removeEntitySetListener(CollectionChangeListener<Entity> listener);
+
+    void terminate();
+    
+    long getTotalEffectorInvocations();
+
+    <T> T invokeEffectorMethodSync(final Entity entity, final Effector<T> eff, 
final Object args) throws ExecutionException;
+    
+    <T> Task<T> invokeEffector(final Entity entity, final Effector<T> eff, 
@SuppressWarnings("rawtypes") final Map parameters);
+
+    BrooklynStorage getStorage();
+    
+    BrooklynProperties getBrooklynProperties();
+    
+    AccessManager getAccessManager();
+
+    UsageManager getUsageManager();
+    
+    /**
+     * @return The OSGi manager, if available; may be absent if OSGi is not 
supported,
+     * e.g. in test contexts (but will be supported in all major contexts).
+     */
+    Maybe<OsgiManager> getOsgiManager();
+
+    InternalEntityFactory getEntityFactory();
+    
+    InternalLocationFactory getLocationFactory();
+    
+    InternalPolicyFactory getPolicyFactory();
+    
+    /**
+     * Registers an entity that has been created, but that has not yet begun 
to be managed.
+     * <p>
+     * This differs from the idea of "preManaged" where the entities are in 
the process of being
+     * managed, but where management is not yet complete.
+     */
+    // TODO would benefit from better naming! The name has percolated up from 
LocalEntityManager.
+    //      should we just rename here as register or preManage?
+    void prePreManage(Entity entity);
+
+    /**
+     * Registers a location that has been created, but that has not yet begun 
to be managed.
+     */
+    void prePreManage(Location location);
+
+    /** Object which allows adding, removing, and clearing errors.
+     * TODO In future this will change to a custom interface with a unique 
identifier for each error. */
+    @Beta
+    List<Throwable> errors();
+
+    @Beta
+    CatalogInitialization getCatalogInitialization();
+
+    @Beta
+    void setCatalogInitialization(CatalogInitialization catalogInitialization);
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6602f694/core/src/main/java/org/apache/brooklyn/core/management/internal/ManagementTransitionInfo.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/management/internal/ManagementTransitionInfo.java
 
b/core/src/main/java/org/apache/brooklyn/core/management/internal/ManagementTransitionInfo.java
new file mode 100644
index 0000000..2d944d9
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/management/internal/ManagementTransitionInfo.java
@@ -0,0 +1,48 @@
+/*
+ * 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.management.internal;
+
+import org.apache.brooklyn.api.management.ManagementContext;
+
+/** Stores a management transition mode, and the management context. */
+// TODO does this class really pull its weight?
+public class ManagementTransitionInfo {
+
+    final ManagementContext mgmtContext;
+    final ManagementTransitionMode mode;
+    
+    public ManagementTransitionInfo(ManagementContext mgmtContext, 
ManagementTransitionMode mode) {
+        this.mgmtContext = mgmtContext;
+        this.mode = mode;
+    }
+    
+    
+    public ManagementContext getManagementContext() {
+        return mgmtContext;
+    }
+
+    public ManagementTransitionMode getMode() {
+        return mode;
+    }
+    
+    @Override
+    public String toString() {
+        return super.toString()+"["+mgmtContext+";"+mode+"]";
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6602f694/core/src/main/java/org/apache/brooklyn/core/management/internal/ManagementTransitionMode.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/management/internal/ManagementTransitionMode.java
 
b/core/src/main/java/org/apache/brooklyn/core/management/internal/ManagementTransitionMode.java
new file mode 100644
index 0000000..3adc625
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/management/internal/ManagementTransitionMode.java
@@ -0,0 +1,127 @@
+/*
+ * 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.management.internal;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Preconditions;
+
+/**
+ * Records details of a management transition, specifically the {@link 
BrooklynObjectManagementMode} before and after,
+ * and allows easy checking of various aspects of that.
+ * <p>
+ * This helps make code readable and keep correct logic if we expand/change 
the management modes.
+ */
+public class ManagementTransitionMode {
+
+    private static final Logger log = 
LoggerFactory.getLogger(ManagementTransitionMode.class);
+    
+    private final BrooklynObjectManagementMode modeBefore, modeAfter;
+
+    private ManagementTransitionMode(BrooklynObjectManagementMode modeBefore, 
BrooklynObjectManagementMode modeAfter) {
+        this.modeBefore = modeBefore;
+        this.modeAfter = modeAfter;
+    }
+    
+    public static ManagementTransitionMode 
transitioning(BrooklynObjectManagementMode modeBefore, 
BrooklynObjectManagementMode modeAfter) {
+        return new 
ManagementTransitionMode(Preconditions.checkNotNull(modeBefore, "modeBefore"), 
Preconditions.checkNotNull(modeAfter, "modeAfter"));
+    }
+
+    @Deprecated /** @deprecated marking places where we aren't sure */
+    public static ManagementTransitionMode 
guessing(BrooklynObjectManagementMode modeBefore, BrooklynObjectManagementMode 
modeAfter) {
+        return transitioning(modeBefore, modeAfter);
+    }
+
+    /** @return the mode this object was previously managed as */
+    public BrooklynObjectManagementMode getModeBefore() {
+        return modeBefore;
+    }
+    
+    /** @return the mode this object is now being managed as */
+    public BrooklynObjectManagementMode getModeAfter() {
+        return modeAfter;
+    }
+    
+    /** This management node was previously not loaded here, 
+     * either it did not exist (and is just being created) or it was in 
persisted state but
+     * not loaded at this node. */
+    public boolean wasNotLoaded() {
+        return getModeBefore()==BrooklynObjectManagementMode.NONEXISTENT || 
getModeBefore()==BrooklynObjectManagementMode.UNMANAGED_PERSISTED;
+    }
+
+    /** This management node is now not going to be loaded here, either it is 
being destroyed
+     * (not known anywhere, not even persisted) or simply forgotten here */
+    public boolean isNoLongerLoaded() {
+        return getModeAfter()==BrooklynObjectManagementMode.NONEXISTENT || 
getModeAfter()==BrooklynObjectManagementMode.UNMANAGED_PERSISTED;
+    }
+
+    /** This management node was the master for the given object */
+    public boolean wasPrimary() {
+        return getModeBefore()==BrooklynObjectManagementMode.MANAGED_PRIMARY;
+    }
+
+    /** This management node is now the master for the given object */
+    public boolean isPrimary() {
+        return getModeAfter()==BrooklynObjectManagementMode.MANAGED_PRIMARY;
+    }
+
+    /** Object was previously loaded as read-only at this management node;
+     * active management was occurring elsewhere (or not at all)
+     */
+    public boolean wasReadOnly() {
+        return getModeBefore()==BrooklynObjectManagementMode.LOADED_READ_ONLY;
+    }
+    
+    /** Object is now being loaded as read-only at this management node;
+     * expect active management to be occurring elsewhere
+     */
+    public boolean isReadOnly() {
+        return getModeAfter()==BrooklynObjectManagementMode.LOADED_READ_ONLY;
+    }
+    
+    /** Object is being created:
+     * previously did not exist (not even in persisted state);
+     * implies that we are the active manager creating it,
+     * i.e. {@link #getModeAfter()} should indicate {@link 
BrooklynObjectManagementMode#MANAGED_PRIMARY}.
+     * (if we're read-only and the manager has just created it, 
+     * {@link #getModeBefore()} should indicate {@link 
BrooklynObjectManagementMode#UNMANAGED_PERSISTED})
+     */
+    public boolean isCreating() {
+        if (getModeBefore()!=BrooklynObjectManagementMode.NONEXISTENT)
+            return false;
+        
+        if (getModeAfter()==BrooklynObjectManagementMode.LOADED_READ_ONLY) {
+            log.warn("isCreating set on RO object; highly irregular!");
+        }
+        return true;
+    }
+
+    /** Object is being destroyed:
+     * either destroyed elsewhere and we're catching up (in read-only mode),
+     * or we've been the active manager and are destroying it */
+    public boolean isDestroying() {
+        return getModeAfter()==BrooklynObjectManagementMode.NONEXISTENT;
+    }
+    
+    @Override
+    public String toString() {
+        return 
ManagementTransitionMode.class.getSimpleName()+"["+getModeBefore()+"->"+getModeAfter()+"]";
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6602f694/core/src/main/java/org/apache/brooklyn/core/management/internal/NonDeploymentAccessManager.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/management/internal/NonDeploymentAccessManager.java
 
b/core/src/main/java/org/apache/brooklyn/core/management/internal/NonDeploymentAccessManager.java
new file mode 100644
index 0000000..1d94f02
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/management/internal/NonDeploymentAccessManager.java
@@ -0,0 +1,98 @@
+/*
+ * 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.management.internal;
+
+import org.apache.brooklyn.api.management.AccessController;
+
+
+public class NonDeploymentAccessManager implements AccessManager {
+
+    private final ManagementContextInternal initialManagementContext;
+    
+    public NonDeploymentAccessManager(ManagementContextInternal 
initialManagementContext) {
+        this.initialManagementContext = initialManagementContext;
+    }
+    
+    private boolean isInitialManagementContextReal() {
+        return (initialManagementContext != null && !(initialManagementContext 
instanceof NonDeploymentManagementContext));
+    }
+
+    @Override
+    public AccessController getAccessController() {
+        if (isInitialManagementContextReal()) {
+            return 
initialManagementContext.getAccessManager().getAccessController();
+        } else {
+            throw new IllegalStateException("Non-deployment context "+this+" 
is not valid for this operation");
+        }
+    }
+
+    @Override
+    public boolean isLocationProvisioningAllowed() {
+        if (isInitialManagementContextReal()) {
+            return 
initialManagementContext.getAccessManager().isLocationProvisioningAllowed();
+        } else {
+            throw new IllegalStateException("Non-deployment context "+this+" 
is not valid for this operation");
+        }
+    }
+
+    @Override
+    public boolean isLocationManagementAllowed() {
+        if (isInitialManagementContextReal()) {
+            return 
initialManagementContext.getAccessManager().isLocationManagementAllowed();
+        } else {
+            throw new IllegalStateException("Non-deployment context "+this+" 
is not valid for this operation");
+        }
+    }
+
+    @Override
+    public boolean isEntityManagementAllowed() {
+        if (isInitialManagementContextReal()) {
+            return 
initialManagementContext.getAccessManager().isEntityManagementAllowed();
+        } else {
+            throw new IllegalStateException("Non-deployment context "+this+" 
is not valid for this operation");
+        }
+    }
+
+    @Override
+    public void setLocationProvisioningAllowed(boolean allowed) {
+        if (isInitialManagementContextReal()) {
+            
initialManagementContext.getAccessManager().setLocationProvisioningAllowed(allowed);
+        } else {
+            throw new IllegalStateException("Non-deployment context "+this+" 
is not valid for this operation");
+        }
+    }
+
+    @Override
+    public void setLocationManagementAllowed(boolean allowed) {
+        if (isInitialManagementContextReal()) {
+            
initialManagementContext.getAccessManager().setLocationManagementAllowed(allowed);
+        } else {
+            throw new IllegalStateException("Non-deployment context "+this+" 
is not valid for this operation");
+        }
+    }
+
+    @Override
+    public void setEntityManagementAllowed(boolean allowed) {
+        if (isInitialManagementContextReal()) {
+            
initialManagementContext.getAccessManager().setEntityManagementAllowed(allowed);
+        } else {
+            throw new IllegalStateException("Non-deployment context "+this+" 
is not valid for this operation");
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6602f694/core/src/main/java/org/apache/brooklyn/core/management/internal/NonDeploymentEntityManager.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/management/internal/NonDeploymentEntityManager.java
 
b/core/src/main/java/org/apache/brooklyn/core/management/internal/NonDeploymentEntityManager.java
new file mode 100644
index 0000000..658bbf7
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/management/internal/NonDeploymentEntityManager.java
@@ -0,0 +1,196 @@
+/*
+ * 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.management.internal;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+
+import org.apache.brooklyn.api.entity.Application;
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.proxying.EntitySpec;
+import org.apache.brooklyn.api.entity.proxying.EntityTypeRegistry;
+import org.apache.brooklyn.api.management.ManagementContext;
+import org.apache.brooklyn.api.policy.Enricher;
+import org.apache.brooklyn.api.policy.EnricherSpec;
+import org.apache.brooklyn.api.policy.Policy;
+import org.apache.brooklyn.api.policy.PolicySpec;
+
+import com.google.common.base.Predicate;
+
+public class NonDeploymentEntityManager implements EntityManagerInternal {
+
+    private final ManagementContext initialManagementContext;
+    
+    public NonDeploymentEntityManager(ManagementContext 
initialManagementContext) {
+        this.initialManagementContext = initialManagementContext;
+    }
+    
+    @Override
+    public EntityTypeRegistry getEntityTypeRegistry() {
+        if (isInitialManagementContextReal()) {
+            return 
initialManagementContext.getEntityManager().getEntityTypeRegistry();
+        } else {
+            throw new IllegalStateException("Non-deployment context "+this+" 
(with no initial management context supplied) is not valid for this 
operation.");
+        }
+    }
+    
+    @Override
+    public <T extends Entity> T createEntity(EntitySpec<T> spec) {
+        if (isInitialManagementContextReal()) {
+            return 
initialManagementContext.getEntityManager().createEntity(spec);
+        } else {
+            throw new IllegalStateException("Non-deployment context "+this+" 
(with no initial management context supplied) is not valid for this 
operation.");
+        }
+    }
+    
+    @Override
+    public <T extends Entity> T createEntity(Map<?,?> config, Class<T> type) {
+        return createEntity(EntitySpec.create(type).configure(config));
+    }
+
+    @Override
+    public <T extends Policy> T createPolicy(PolicySpec<T> spec) {
+        if (isInitialManagementContextReal()) {
+            return 
initialManagementContext.getEntityManager().createPolicy(spec);
+        } else {
+            throw new IllegalStateException("Non-deployment context "+this+" 
(with no initial management context supplied) is not valid for this 
operation.");
+        }
+    }
+    
+    @Override
+    public <T extends Enricher> T createEnricher(EnricherSpec<T> spec) {
+        if (isInitialManagementContextReal()) {
+            return 
initialManagementContext.getEntityManager().createEnricher(spec);
+        } else {
+            throw new IllegalStateException("Non-deployment context "+this+" 
(with no initial management context supplied) is not valid for this 
operation.");
+        }
+    }
+    
+    @Override
+    public Collection<Entity> getEntities() {
+        if (isInitialManagementContextReal()) {
+            return initialManagementContext.getEntityManager().getEntities();
+        } else {
+            return Collections.emptyList();
+        }
+    }
+
+    @Override
+    public Collection<Entity> getEntitiesInApplication(Application 
application) {
+        if (isInitialManagementContextReal()) {
+            return 
initialManagementContext.getEntityManager().getEntitiesInApplication(application);
+        } else {
+            return Collections.emptyList();
+        }
+    }
+
+    @Override
+    public Collection<Entity> findEntities(Predicate<? super Entity> filter) {
+        if (isInitialManagementContextReal()) {
+            return 
initialManagementContext.getEntityManager().findEntities(filter);
+        } else {
+            return Collections.emptyList();
+        }
+    }
+
+    @Override
+    public Collection<Entity> findEntitiesInApplication(Application 
application, Predicate<? super Entity> filter) {
+        if (isInitialManagementContextReal()) {
+            return 
initialManagementContext.getEntityManager().findEntitiesInApplication(application,
 filter);
+        } else {
+            return Collections.emptyList();
+        }
+    }
+
+    @Override
+    public Entity getEntity(String id) {
+        if (isInitialManagementContextReal()) {
+            return initialManagementContext.getEntityManager().getEntity(id);
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public Iterable<String> getEntityIds() {
+        if (isInitialManagementContextReal()) {
+            return 
((EntityManagerInternal)initialManagementContext.getEntityManager()).getEntityIds();
+        } else {
+            return Collections.emptyList();
+        }
+    }
+
+    @Override
+    public ManagementTransitionMode getLastManagementTransitionMode(String 
itemId) {
+        if (isInitialManagementContextReal()) {
+            return 
((EntityManagerInternal)initialManagementContext.getEntityManager()).getLastManagementTransitionMode(itemId);
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public void setManagementTransitionMode(Entity item, 
ManagementTransitionMode mode) {
+        if (isInitialManagementContextReal()) {
+            
((EntityManagerInternal)initialManagementContext.getEntityManager()).setManagementTransitionMode(item,
 mode);
+        } else {
+            throw new IllegalStateException("Non-deployment context "+this+" 
is not valid for this operation");
+        }
+    }
+    
+    @Override
+    public boolean isManaged(Entity entity) {
+        return false;
+    }
+
+    @Override
+    public void manage(Entity e) {
+        throw new IllegalStateException("Non-deployment context "+this+" is 
not valid for this operation: cannot manage "+e);
+    }
+
+    @Override
+    public void unmanage(Entity e, ManagementTransitionMode info) {
+        throw new IllegalStateException("Non-deployment context "+this+" is 
not valid for this operation");
+    }
+    
+    @Override
+    public void manageRebindedRoot(Entity item) {
+        throw new IllegalStateException("Non-deployment context "+this+" is 
not valid for this operation");
+    }
+
+    @Override
+    public void unmanage(Entity e) {
+        throw new IllegalStateException("Non-deployment context "+this+" is 
not valid for this operation: cannot unmanage "+e);
+    }
+    
+    private boolean isInitialManagementContextReal() {
+        return (initialManagementContext != null && !(initialManagementContext 
instanceof NonDeploymentManagementContext));
+    }
+
+    @Override
+    public Iterable<Entity> getAllEntitiesInApplication(Application 
application) {
+        if (isInitialManagementContextReal()) {
+            return 
((EntityManagerInternal)initialManagementContext.getEntityManager()).getAllEntitiesInApplication(application);
+        } else {
+            throw new IllegalStateException("Non-deployment context "+this+" 
(with no initial management context supplied) is not valid for this 
operation.");
+        }
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6602f694/core/src/main/java/org/apache/brooklyn/core/management/internal/NonDeploymentLocationManager.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/management/internal/NonDeploymentLocationManager.java
 
b/core/src/main/java/org/apache/brooklyn/core/management/internal/NonDeploymentLocationManager.java
new file mode 100644
index 0000000..5c81648
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/management/internal/NonDeploymentLocationManager.java
@@ -0,0 +1,146 @@
+/*
+ * 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.management.internal;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.location.LocationSpec;
+import org.apache.brooklyn.api.management.ManagementContext;
+
+public class NonDeploymentLocationManager implements LocationManagerInternal {
+
+    private final ManagementContext initialManagementContext;
+    
+    public NonDeploymentLocationManager(ManagementContext 
initialManagementContext) {
+        this.initialManagementContext = initialManagementContext;
+    }
+    
+    @Override
+    public <T extends Location> T createLocation(LocationSpec<T> spec) {
+        if (isInitialManagementContextReal()) {
+            return 
initialManagementContext.getLocationManager().createLocation(spec);
+        } else {
+            throw new IllegalStateException("Non-deployment context "+this+" 
is not valid for this operation: cannot create "+spec);
+        }
+    }
+
+    @Override
+    public <T extends Location> T createLocation(Map<?, ?> config, Class<T> 
type) {
+        if (isInitialManagementContextReal()) {
+            return 
initialManagementContext.getLocationManager().createLocation(config, type);
+        } else {
+            throw new IllegalStateException("Non-deployment context "+this+" 
is not valid for this operation: cannot create "+type);
+        }
+    }
+    
+    @Override
+    public Collection<Location> getLocations() {
+        if (isInitialManagementContextReal()) {
+            return 
initialManagementContext.getLocationManager().getLocations();
+        } else {
+            return Collections.emptyList();
+        }
+    }
+
+    @Override
+    public Location getLocation(String id) {
+        if (isInitialManagementContextReal()) {
+            return 
initialManagementContext.getLocationManager().getLocation(id);
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public Iterable<String> getLocationIds() {
+        if (isInitialManagementContextReal()) {
+            return 
((LocationManagerInternal)initialManagementContext.getLocationManager()).getLocationIds();
+        } else {
+            return Collections.emptyList();
+        }
+    }
+    
+    @Override
+    public boolean isManaged(Location loc) {
+        return false;
+    }
+
+    @Override
+    public void manageRebindedRoot(Location loc) {
+        if (isInitialManagementContextReal()) {
+            
((LocationManagerInternal)initialManagementContext.getLocationManager()).manageRebindedRoot(loc);
+        } else {
+            throw new IllegalStateException("Non-deployment context "+this+" 
is not valid for this operation: cannot manage "+loc);
+        }
+    }
+
+    @Override
+    @Deprecated
+    public Location manage(Location loc) {
+        if (isInitialManagementContextReal()) {
+            return initialManagementContext.getLocationManager().manage(loc);
+        } else {
+            throw new IllegalStateException("Non-deployment context "+this+" 
is not valid for this operation: cannot manage "+loc);
+        }
+    }
+
+
+    @Override
+    public ManagementTransitionMode getLastManagementTransitionMode(String 
itemId) {
+        if (isInitialManagementContextReal()) {
+            return 
((LocationManagerInternal)initialManagementContext.getLocationManager()).getLastManagementTransitionMode(itemId);
+        } else {
+            throw new IllegalStateException("Non-deployment context "+this+" 
is not valid for this operation");
+        }
+    }
+
+    @Override
+    public void setManagementTransitionMode(Location item, 
ManagementTransitionMode mode) {
+        if (isInitialManagementContextReal()) {
+            
((LocationManagerInternal)initialManagementContext.getLocationManager()).setManagementTransitionMode(item,
 mode);
+        } else {
+            throw new IllegalStateException("Non-deployment context "+this+" 
is not valid for this operation");
+        }
+    }
+
+    @Override
+    public void unmanage(Location item, ManagementTransitionMode info) {
+        if (isInitialManagementContextReal()) {
+            
((LocationManagerInternal)initialManagementContext.getLocationManager()).unmanage(item,
 info);
+        } else {
+            throw new IllegalStateException("Non-deployment context "+this+" 
is not valid for this operation");
+        }
+    }
+
+    @Override
+    public void unmanage(Location loc) {
+        if (isInitialManagementContextReal()) {
+            initialManagementContext.getLocationManager().unmanage(loc);
+        } else {
+            throw new IllegalStateException("Non-deployment context "+this+" 
is not valid for this operation: cannot unmanage "+loc);
+        }
+    }
+    
+    private boolean isInitialManagementContextReal() {
+        return (initialManagementContext != null && !(initialManagementContext 
instanceof NonDeploymentManagementContext));
+    }
+}

Reply via email to