http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6602f694/core/src/main/java/org/apache/brooklyn/core/management/internal/BrooklynObjectManagerInternal.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/management/internal/BrooklynObjectManagerInternal.java
 
b/core/src/main/java/org/apache/brooklyn/core/management/internal/BrooklynObjectManagerInternal.java
new file mode 100644
index 0000000..d22ffae
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/management/internal/BrooklynObjectManagerInternal.java
@@ -0,0 +1,36 @@
+/*
+ * 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.basic.BrooklynObject;
+
+public interface BrooklynObjectManagerInternal<T extends BrooklynObject> {
+
+    ManagementTransitionMode getLastManagementTransitionMode(String itemId);
+    void setManagementTransitionMode(T item, ManagementTransitionMode mode);
+
+    /** 
+     * Begins management for the given rebinded root, recursively; 
+     * if rebinding as a read-only copy, {@link #setReadOnly(T, boolean)} 
should be called prior to this.
+     */
+    void manageRebindedRoot(T item);
+    
+    void unmanage(final T e, final ManagementTransitionMode info);
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6602f694/core/src/main/java/org/apache/brooklyn/core/management/internal/CollectionChangeListener.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/management/internal/CollectionChangeListener.java
 
b/core/src/main/java/org/apache/brooklyn/core/management/internal/CollectionChangeListener.java
new file mode 100644
index 0000000..45fa765
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/management/internal/CollectionChangeListener.java
@@ -0,0 +1,24 @@
+/*
+ * 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;
+
+public interface CollectionChangeListener<Item> {
+    void onItemAdded(Item item);
+    void onItemRemoved(Item item);
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6602f694/core/src/main/java/org/apache/brooklyn/core/management/internal/EffectorUtils.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/management/internal/EffectorUtils.java
 
b/core/src/main/java/org/apache/brooklyn/core/management/internal/EffectorUtils.java
new file mode 100644
index 0000000..028f2d2
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/management/internal/EffectorUtils.java
@@ -0,0 +1,356 @@
+/*
+ * 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.GroovyJavaMethods.truth;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+import org.apache.brooklyn.api.entity.Effector;
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.ParameterType;
+import org.apache.brooklyn.api.management.Task;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.entity.basic.BasicParameterType;
+import brooklyn.entity.basic.BrooklynTaskTags;
+import brooklyn.entity.basic.EntityInternal;
+import brooklyn.util.collections.MutableList;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.config.ConfigBag;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.exceptions.PropagatedRuntimeException;
+import brooklyn.util.flags.TypeCoercions;
+import brooklyn.util.guava.Maybe;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+/**
+ * Utility methods for invoking effectors.
+ */
+public class EffectorUtils {
+
+    private static final Logger log = 
LoggerFactory.getLogger(EffectorUtils.class);
+
+    /** prepares arguments for an effector either accepting:
+     *  an array, which should contain the arguments in order, optionally 
omitting those which have defaults defined;
+     *  or a map, which should contain the arguments by name, again optionally 
omitting those which have defaults defined,
+     *  and in this case also performing type coercion.
+     */
+    public static Object[] prepareArgsForEffector(Effector<?> eff, Object 
args) {
+        if (args != null && args.getClass().isArray()) {
+            return prepareArgsForEffectorFromArray(eff, (Object[]) args);
+        }
+        if (args instanceof Map) {
+            return prepareArgsForEffectorFromMap(eff, (Map) args);
+        }
+        log.warn("Deprecated effector invocation style for call to "+eff+", 
expecting a map or an array, got: "+args);
+        if (log.isDebugEnabled()) {
+            log.debug("Deprecated effector invocation style for call to 
"+eff+", expecting a map or an array, got: "+args,
+                new Throwable("Trace for deprecated effector invocation 
style"));
+        }
+        return oldPrepareArgsForEffector(eff, args);
+    }
+
+    /** method used for calls such as   entity.effector(arg1, arg2)
+     * get routed here from AbstractEntity.invokeMethod */
+    private static Object[] prepareArgsForEffectorFromArray(Effector<?> eff, 
Object args[]) {
+        int newArgsNeeded = eff.getParameters().size();
+        if (args.length==1 && args[0] instanceof Map) {
+            if (newArgsNeeded!=1 || 
!eff.getParameters().get(0).getParameterClass().isAssignableFrom(args[0].getClass()))
 {
+                // treat a map in an array as a map passed directly (unless 
the method takes a single-arg map)
+                // this is to support   effector(param1: val1)
+                return prepareArgsForEffectorFromMap(eff, (Map) args[0]);
+            }
+        }
+        return prepareArgsForEffectorAsMapFromArray(eff, 
args).values().toArray(new Object[0]);
+    }
+
+    public static Map prepareArgsForEffectorAsMapFromArray(Effector<?> eff, 
Object args[]) {
+        int newArgsNeeded = eff.getParameters().size();
+        List l = Lists.newArrayList();
+        l.addAll(Arrays.asList(args));
+        Map newArgs = new LinkedHashMap();
+
+        for (int index = 0; index < eff.getParameters().size(); index++) {
+            ParameterType<?> it = eff.getParameters().get(index);
+
+            if (l.size() >= newArgsNeeded) {
+                //all supplied (unnamed) arguments must be used; ignore map
+                newArgs.put(it.getName(), l.remove(0));
+                // TODO do we ignore arguments in the same order that groovy 
does?
+            } else if (!l.isEmpty() && 
it.getParameterClass().isInstance(l.get(0))) {
+                //if there are parameters supplied, and type is correct, they 
get applied before default values
+                //(this is akin to groovy)
+                newArgs.put(it.getName(), l.remove(0));
+            } else if (it instanceof BasicParameterType && 
((BasicParameterType)it).hasDefaultValue()) {
+                //finally, default values are used to make up for missing 
parameters
+                newArgs.put(it.getName(), 
((BasicParameterType)it).getDefaultValue());
+            } else {
+                throw new IllegalArgumentException("Invalid arguments (count 
mismatch) for effector "+eff+": "+args);
+            }
+
+            newArgsNeeded--;
+        }
+        if (newArgsNeeded > 0) {
+            throw new IllegalArgumentException("Invalid arguments (missing 
"+newArgsNeeded+") for effector "+eff+": "+args);
+        }
+        if (!l.isEmpty()) {
+            throw new IllegalArgumentException("Invalid arguments 
("+l.size()+" extra) for effector "+eff+": "+args);
+        }
+        return newArgs;
+    }
+
+    private static Object[] prepareArgsForEffectorFromMap(Effector<?> eff, Map 
m) {
+        m = Maps.newLinkedHashMap(m); //make editable copy
+        List newArgs = Lists.newArrayList();
+        int newArgsNeeded = eff.getParameters().size();
+
+        for (int index = 0; index < eff.getParameters().size(); index++) {
+            ParameterType<?> it = eff.getParameters().get(index);
+            Object v;
+            if (truth(it.getName()) && m.containsKey(it.getName())) {
+                // argument is in the map
+                v = m.remove(it.getName());
+            } else if (it instanceof BasicParameterType && 
((BasicParameterType)it).hasDefaultValue()) {
+                //finally, default values are used to make up for missing 
parameters
+                v = ((BasicParameterType)it).getDefaultValue();
+            } else {
+                throw new IllegalArgumentException("Invalid arguments (missing 
argument "+it+") for effector "+eff+": "+m);
+            }
+
+            newArgs.add(TypeCoercions.coerce(v, it.getParameterClass()));
+            newArgsNeeded--;
+        }
+        if (newArgsNeeded>0)
+            throw new IllegalArgumentException("Invalid arguments (missing 
"+newArgsNeeded+") for effector "+eff+": "+m);
+        return newArgs.toArray(new Object[newArgs.size()]);
+    }
+
+    /**
+     * Takes arguments, and returns an array of arguments suitable for use by 
the Effector
+     * according to the ParameterTypes it exposes.
+     * <p>
+     * The args can be:
+     * <ol>
+     * <li>an array of ordered arguments
+     * <li>a collection (which will be automatically converted to an array)
+     * <li>a single argument (which will then be wrapped in an array)
+     * <li>a map containing the (named) arguments
+     * <li>an array or collection single entry of a map (treated same as 5 
above)
+     * <li>a semi-populated array or collection that also containing a map as 
first arg -
+     *     uses ordered args in array, but uses named values from map in 
preference.
+     * <li>semi-populated array or collection, where default values will 
otherwise be used.
+     * </ol>
+     */
+    public static Object[] oldPrepareArgsForEffector(Effector<?> eff, Object 
args) {
+        //attempt to coerce unexpected types
+        Object[] argsArray;
+        if (args==null) {
+            argsArray = new Object[0];
+        } else if (args.getClass().isArray()) {
+            argsArray = (Object[]) args;
+        } else {
+            if (args instanceof Collection) {
+                argsArray = ((Collection) args).toArray(new 
Object[((Collection) args).size()]);
+            } else {
+                argsArray = new Object[] { args };
+            }
+        }
+
+        //if args starts with a map, assume it contains the named arguments
+        //(but only use it when we have insufficient supplied arguments)
+        List l = Lists.newArrayList();
+        l.addAll(Arrays.asList(argsArray));
+        Map m = (argsArray.length > 0 && argsArray[0] instanceof Map ? 
Maps.newLinkedHashMap((Map) l.remove(0)) : null);
+        List newArgs = Lists.newArrayList();
+        int newArgsNeeded = eff.getParameters().size();
+        boolean mapUsed = false;
+
+        for (int index = 0; index < eff.getParameters().size(); index++) {
+            ParameterType<?> it = eff.getParameters().get(index);
+
+            if (l.size() >= newArgsNeeded) {
+                //all supplied (unnamed) arguments must be used; ignore map
+                newArgs.add(l.remove(0));
+            } else if (truth(m) && truth(it.getName()) && 
m.containsKey(it.getName())) {
+                //some arguments were not supplied, and this one is in the map
+                newArgs.add(m.remove(it.getName()));
+            } else if (index == 0 && 
Map.class.isAssignableFrom(it.getParameterClass())) {
+                //if first arg is a map it takes the supplied map
+                newArgs.add(m);
+                mapUsed = true;
+            } else if (!l.isEmpty() && 
it.getParameterClass().isInstance(l.get(0))) {
+                //if there are parameters supplied, and type is correct, they 
get applied before default values
+                //(this is akin to groovy)
+                newArgs.add(l.remove(0));
+            } else if (it instanceof BasicParameterType && 
((BasicParameterType)it).hasDefaultValue()) {
+                //finally, default values are used to make up for missing 
parameters
+                newArgs.add(((BasicParameterType)it).getDefaultValue());
+            } else {
+                throw new IllegalArgumentException("Invalid arguments (count 
mismatch) for effector "+eff+": "+args);
+            }
+
+            newArgsNeeded--;
+        }
+        if (newArgsNeeded > 0) {
+            throw new IllegalArgumentException("Invalid arguments (missing 
"+newArgsNeeded+") for effector "+eff+": "+args);
+        }
+        if (!l.isEmpty()) {
+            throw new IllegalArgumentException("Invalid arguments 
("+l.size()+" extra) for effector "+eff+": "+args);
+        }
+        if (truth(m) && !mapUsed) {
+            throw new IllegalArgumentException("Invalid arguments 
("+m.size()+" extra named) for effector "+eff+": "+args);
+        }
+        return newArgs.toArray(new Object[newArgs.size()]);
+    }
+
+    /**
+     * Invokes a method effector so that its progress is tracked. For internal 
use only, when we know the effector is backed by a method which is local.
+     */
+    public static <T> T invokeMethodEffector(Entity entity, Effector<T> eff, 
Object[] args) {
+        String name = eff.getName();
+
+        try {
+            if (log.isDebugEnabled()) log.debug("Invoking effector {} on {}", 
new Object[] {name, entity});
+            if (log.isTraceEnabled()) log.trace("Invoking effector {} on {} 
with args {}", new Object[] {name, entity, args});
+            EntityManagementSupport mgmtSupport = 
((EntityInternal)entity).getManagementSupport();
+            if (!mgmtSupport.isDeployed()) {
+                mgmtSupport.attemptLegacyAutodeployment(name);
+            }
+            ManagementContextInternal mgmtContext = 
(ManagementContextInternal) ((EntityInternal) entity).getManagementContext();
+
+            mgmtSupport.getEntityChangeListener().onEffectorStarting(eff, 
args);
+            try {
+                return mgmtContext.invokeEffectorMethodSync(entity, eff, args);
+            } finally {
+                mgmtSupport.getEntityChangeListener().onEffectorCompleted(eff);
+            }
+        } catch (Exception e) {
+            handleEffectorException(entity, eff, e);
+            // (won't return below)
+            return null;
+        }
+    }
+
+    public static void handleEffectorException(Entity entity, Effector<?> 
effector, Throwable throwable) {
+        String message = "Error invoking " + effector.getName() + " at " + 
entity;
+        // Avoid throwing a PropagatedRuntimeException that just repeats the 
last PropagatedRuntimeException.
+        if (throwable instanceof PropagatedRuntimeException &&
+                throwable.getMessage() != null &&
+                throwable.getMessage().startsWith(message)) {
+            throw PropagatedRuntimeException.class.cast(throwable);
+        } else {
+            log.warn(message + ": " + Exceptions.collapseText(throwable));
+            throw new PropagatedRuntimeException(message, throwable);
+        }
+    }
+
+    public static <T> Task<T> invokeEffectorAsync(Entity entity, Effector<T> 
eff, Map<String,?> parameters) {
+        String name = eff.getName();
+
+        if (log.isDebugEnabled()) log.debug("Invoking-async effector {} on 
{}", new Object[] { name, entity });
+        if (log.isTraceEnabled()) log.trace("Invoking-async effector {} on {} 
with args {}", new Object[] { name, entity, parameters });
+        EntityManagementSupport mgmtSupport = 
((EntityInternal)entity).getManagementSupport();
+        if (!mgmtSupport.isDeployed()) {
+            mgmtSupport.attemptLegacyAutodeployment(name);
+        }
+        ManagementContextInternal mgmtContext = (ManagementContextInternal) 
((EntityInternal)entity).getManagementContext();
+
+        // FIXME seems brittle to have the listeners in the Utils method; 
better to move into the context.invokeEff
+        // (or whatever the last mile before invoking the effector is - though 
currently there is not such a canonical place!)
+        mgmtSupport.getEntityChangeListener().onEffectorStarting(eff, 
parameters);
+        try {
+            return mgmtContext.invokeEffector(entity, eff, parameters);
+        } finally {
+            // FIXME this is really Effector submitted
+            mgmtSupport.getEntityChangeListener().onEffectorCompleted(eff);
+        }
+    }
+
+    /** @deprecated since 0.7.0, not used */
+    @Deprecated
+    public static Effector<?> findEffectorMatching(Entity entity, Method 
method) {
+        outer: for (Effector<?> effector : 
entity.getEntityType().getEffectors()) {
+            if (!effector.getName().equals(entity)) continue;
+            if (effector.getParameters().size() != 
method.getParameterTypes().length) continue;
+            for (int i = 0; i < effector.getParameters().size(); i++) {
+                if (effector.getParameters().get(i).getParameterClass() != 
method.getParameterTypes()[i]) continue outer;
+            }
+            return effector;
+        }
+        return null;
+    }
+
+    /** @deprecated since 0.7.0, expects parameters but does not use them! */
+    @Deprecated
+    public static Effector<?> findEffectorMatching(Set<Effector<?>> effectors, 
String effectorName, Map<String, ?> parameters) {
+        // TODO Support overloading: check parameters as well
+        for (Effector<?> effector : effectors) {
+            if (effector.getName().equals(effectorName)) {
+                return effector;
+            }
+        }
+        return null;
+    }
+
+    /** matches effectors by name only (not parameters) */
+    public static Maybe<Effector<?>> findEffector(Collection<? extends 
Effector<?>> effectors, String effectorName) {
+        for (Effector<?> effector : effectors) {
+            if (effector.getName().equals(effectorName)) {
+                return Maybe.<Effector<?>>of(effector);
+            }
+        }
+        return Maybe.absent(new NoSuchElementException("No effector with name 
"+effectorName+" (contenders "+effectors+")"));
+    }
+
+    /** matches effectors by name only (not parameters), based on what is 
declared on the entity static type */
+    public static Maybe<Effector<?>> findEffectorDeclared(Entity entity, 
String effectorName) {
+        return findEffector(entity.getEntityType().getEffectors(), 
effectorName);
+    }
+
+    /** @deprecated since 0.7.0 use {@link 
#getTaskFlagsForEffectorInvocation(Entity, Effector, ConfigBag)} */
+    public static Map<Object,Object> getTaskFlagsForEffectorInvocation(Entity 
entity, Effector<?> effector) {
+        return getTaskFlagsForEffectorInvocation(entity, effector, null);
+    }
+    
+    /** returns a (mutable) map of the standard flags which should be placed 
on an effector */
+    public static Map<Object,Object> getTaskFlagsForEffectorInvocation(Entity 
entity, Effector<?> effector, ConfigBag parameters) {
+        return MutableMap.builder()
+                .put("description", "Invoking effector "+effector.getName()
+                    +" on "+entity.getDisplayName()
+                    +(parameters!=null ? " with parameters 
"+parameters.getAllConfig() : ""))
+                .put("displayName", effector.getName())
+                .put("tags", MutableList.of(
+                        BrooklynTaskTags.EFFECTOR_TAG, 
+                        BrooklynTaskTags.tagForEffectorCall(entity, 
effector.getName(), parameters), 
+                        BrooklynTaskTags.tagForTargetEntity(entity)))
+                .build();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6602f694/core/src/main/java/org/apache/brooklyn/core/management/internal/EntityChangeListener.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/management/internal/EntityChangeListener.java
 
b/core/src/main/java/org/apache/brooklyn/core/management/internal/EntityChangeListener.java
new file mode 100644
index 0000000..d6552ff
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/management/internal/EntityChangeListener.java
@@ -0,0 +1,79 @@
+/*
+ * 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.entity.Effector;
+import org.apache.brooklyn.api.entity.Feed;
+import org.apache.brooklyn.api.event.AttributeSensor;
+import org.apache.brooklyn.api.policy.Enricher;
+import org.apache.brooklyn.api.policy.Policy;
+
+import brooklyn.config.ConfigKey;
+
+public interface EntityChangeListener {
+
+    // TODO for testing only!
+    public static final EntityChangeListener NOOP = new EntityChangeListener() 
{
+        @Override public void onChanged() {}
+        @Override public void onAttributeChanged(AttributeSensor<?> attribute) 
{}
+        @Override public void onConfigChanged(ConfigKey<?> key) {}
+        @Override public void onLocationsChanged() {}
+        @Override public void onMembersChanged() {}
+        @Override public void onTagsChanged() {}
+        @Override public void onChildrenChanged() {}
+        @Override public void onPolicyAdded(Policy policy) {}
+        @Override public void onPolicyRemoved(Policy policy) {}
+        @Override public void onEnricherAdded(Enricher enricher) {}
+        @Override public void onEnricherRemoved(Enricher enricher) {}
+        @Override public void onFeedAdded(Feed feed) {}
+        @Override public void onFeedRemoved(Feed feed) {}
+        @Override public void onEffectorStarting(Effector<?> effector, Object 
parameters) {}
+        @Override public void onEffectorCompleted(Effector<?> effector) {}
+    };
+    
+    void onChanged();
+
+    void onAttributeChanged(AttributeSensor<?> attribute);
+
+    void onConfigChanged(ConfigKey<?> key);
+
+    void onLocationsChanged();
+    
+    void onTagsChanged();
+
+    void onMembersChanged();
+
+    void onChildrenChanged();
+
+    void onPolicyAdded(Policy policy);
+
+    void onPolicyRemoved(Policy policy);
+
+    void onEnricherAdded(Enricher enricher);
+
+    void onEnricherRemoved(Enricher enricher);
+
+    void onFeedAdded(Feed feed);
+
+    void onFeedRemoved(Feed feed);
+
+    void onEffectorStarting(Effector<?> effector, Object parameters);
+    
+    void onEffectorCompleted(Effector<?> effector);
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6602f694/core/src/main/java/org/apache/brooklyn/core/management/internal/EntityManagementSupport.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/management/internal/EntityManagementSupport.java
 
b/core/src/main/java/org/apache/brooklyn/core/management/internal/EntityManagementSupport.java
new file mode 100644
index 0000000..dbba29c
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/management/internal/EntityManagementSupport.java
@@ -0,0 +1,478 @@
+/*
+ * 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.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.brooklyn.api.entity.Application;
+import org.apache.brooklyn.api.entity.Effector;
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.Feed;
+import org.apache.brooklyn.api.event.AttributeSensor;
+import org.apache.brooklyn.api.management.ExecutionContext;
+import org.apache.brooklyn.api.management.ManagementContext;
+import org.apache.brooklyn.api.management.SubscriptionContext;
+import org.apache.brooklyn.api.management.entitlement.EntitlementManager;
+import org.apache.brooklyn.api.policy.Enricher;
+import org.apache.brooklyn.api.policy.Policy;
+import org.apache.brooklyn.core.management.entitlement.Entitlements;
+import 
org.apache.brooklyn.core.management.entitlement.Entitlements.EntityAndItem;
+import 
org.apache.brooklyn.core.management.entitlement.Entitlements.StringAndArgument;
+import 
org.apache.brooklyn.core.management.internal.NonDeploymentManagementContext.NonDeploymentManagementContextMode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.basic.AbstractEntity;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.basic.EntityInternal;
+import brooklyn.util.exceptions.Exceptions;
+
+import com.google.common.annotations.Beta;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Stopwatch;
+
+/**
+ * Encapsulates management activities at an entity.
+ * <p>
+ * On entity deployment, ManagementContext.manage(entity) causes
+ * <p>
+ * * onManagementStarting(ManagementContext)
+ * * onManagementStartingSubscriptions()
+ * * onManagementStartingSensorEmissions()
+ * * onManagementStartingExecutions()
+ * * onManagementStarted() - when all the above is said and done
+ * * onManagementStartingHere();
+ * <p>
+ * on unmanage it hits onManagementStoppingHere() then onManagementStopping().
+ * <p>
+ * When an entity's management migrates, it invoked onManagementStoppingHere() 
at the old location,
+ * then onManagementStartingHere() at the new location.
+ */
+public class EntityManagementSupport {
+
+    private static final Logger log = 
LoggerFactory.getLogger(EntityManagementSupport.class);
+    
+    public EntityManagementSupport(AbstractEntity entity) {
+        this.entity = entity;
+        nonDeploymentManagementContext = new 
NonDeploymentManagementContext(entity, 
NonDeploymentManagementContextMode.PRE_MANAGEMENT);
+    }
+
+    protected transient AbstractEntity entity;
+    NonDeploymentManagementContext nonDeploymentManagementContext;
+    
+    protected transient ManagementContext initialManagementContext;
+    protected transient ManagementContext managementContext;
+    protected transient SubscriptionContext subscriptionContext;
+    protected transient ExecutionContext executionContext;
+    
+    // TODO the application
+    // (elaborate or remove ^^^ ? -AH, Sept 2014)
+    
+    protected final AtomicBoolean managementContextUsable = new 
AtomicBoolean(false);
+    protected final AtomicBoolean currentlyDeployed = new AtomicBoolean(false);
+    protected final AtomicBoolean everDeployed = new AtomicBoolean(false);
+    protected Boolean readOnly = null;
+    protected final AtomicBoolean managementFailed = new AtomicBoolean(false);
+    
+    private volatile EntityChangeListener entityChangeListener = 
EntityChangeListener.NOOP;
+
+    /**
+     * Whether this entity is managed (i.e. "onManagementStarting" has been 
called, so the framework knows about it,
+     * and it has not been unmanaged).
+     */
+    public boolean isDeployed() { return currentlyDeployed.get(); }
+    public boolean isNoLongerManaged() {
+        return wasDeployed() && !isDeployed();
+    }
+    /** whether entity has ever been deployed (managed) */
+    public boolean wasDeployed() { return everDeployed.get(); }
+    
+    @Beta
+    public void setReadOnly(boolean isReadOnly) {
+        if (isDeployed())
+            throw new IllegalStateException("Cannot set read only after 
deployment");
+        this.readOnly = isReadOnly;
+    }
+
+    /** Whether the entity and its adjuncts should be treated as read-only;
+     * may be null briefly when initializing if RO status is unknown. */
+    @Beta
+    public Boolean isReadOnlyRaw() {
+        return readOnly;
+    }
+
+    /** Whether the entity and its adjuncts should be treated as read-only;
+     * error if initializing and RO status is unknown. */
+    @Beta
+    public boolean isReadOnly() {
+        Preconditions.checkNotNull(readOnly, "Read-only status of %s not yet 
known", entity);
+        return readOnly;
+    }
+
+    /**
+     * Whether the entity's management lifecycle is complete (i.e. both 
"onManagementStarting" and "onManagementStarted" have
+     * been called, and it is has not been unmanaged). 
+     */
+    public boolean isFullyManaged() {
+        return (nonDeploymentManagementContext == null) && 
currentlyDeployed.get();
+    }
+
+    public synchronized void setManagementContext(ManagementContextInternal 
val) {
+        if (initialManagementContext != null) {
+            throw new IllegalStateException("Initial management context is 
already set for "+entity+"; cannot change");
+        }
+        if (managementContext != null && !managementContext.equals(val)) {
+            throw new IllegalStateException("Management context is already set 
for "+entity+"; cannot change");
+        }
+        
+        this.initialManagementContext = checkNotNull(val, "managementContext");
+        if (nonDeploymentManagementContext != null) {
+            nonDeploymentManagementContext.setManagementContext(val);
+        }
+    }
+    
+    public void onRebind(ManagementTransitionInfo info) {
+        
nonDeploymentManagementContext.setMode(NonDeploymentManagementContextMode.MANAGEMENT_REBINDING);
+    }
+    
+    public void onManagementStarting(ManagementTransitionInfo info) {
+        try {
+            synchronized (this) {
+                boolean alreadyManaging = isDeployed();
+                
+                if (alreadyManaging) {
+                    log.warn("Already managed: "+entity+" 
("+nonDeploymentManagementContext+"); onManagementStarting is no-op");
+                } else if (nonDeploymentManagementContext == null || 
!nonDeploymentManagementContext.getMode().isPreManaged()) {
+                    throw new IllegalStateException("Not in expected 
pre-managed state: "+entity+" ("+nonDeploymentManagementContext+")");
+                }
+                if (managementContext != null && 
!managementContext.equals(info.getManagementContext())) {
+                    throw new IllegalStateException("Already has management 
context: "+managementContext+"; can't set "+info.getManagementContext());
+                }
+                if (initialManagementContext != null && 
!initialManagementContext.equals(info.getManagementContext())) {
+                    throw new IllegalStateException("Already has different 
initial management context: "+initialManagementContext+"; can't set 
"+info.getManagementContext());
+                }
+                if (alreadyManaging) {
+                    return;
+                }
+                
+                this.managementContext = info.getManagementContext();
+                
nonDeploymentManagementContext.setMode(NonDeploymentManagementContextMode.MANAGEMENT_STARTING);
+                
+                if (!isReadOnly()) {
+                    
nonDeploymentManagementContext.getSubscriptionManager().setDelegate((AbstractSubscriptionManager)
 managementContext.getSubscriptionManager());
+                    
nonDeploymentManagementContext.getSubscriptionManager().startDelegatingForSubscribing();
+                }
+    
+                managementContextUsable.set(true);
+                currentlyDeployed.set(true);
+                everDeployed.set(true);
+                
+                entityChangeListener = new EntityChangeListenerImpl();
+            }
+            
+            /*
+             * TODO framework starting events - phase 1, including rebind
+             *  - establish hierarchy (child, groups, etc; construction if 
necessary on rebind)
+             *  - set location
+             *  - set local config values
+             *  - set saved sensor values
+             *  - register subscriptions -- BUT nothing is allowed to execute
+             *  [these operations may be done before we invoke starting also; 
above can happen in any order;
+             *  sensor _publications_ and executor submissions are queued]
+             *  then:  set the management context and the entity is "managed" 
from the perspective of external viewers (ManagementContext.isManaged(entity) 
returns true)
+             */
+            
+            if (!isReadOnly()) {
+                entity.onManagementStarting();
+            }
+        } catch (Throwable t) {
+            managementFailed.set(true);
+            throw Exceptions.propagate(t);
+        }
+    }
+
+    @SuppressWarnings("deprecation")
+    public void onManagementStarted(ManagementTransitionInfo info) {
+        try {
+            synchronized (this) {
+                boolean alreadyManaged = isFullyManaged();
+                
+                if (alreadyManaged) {
+                    log.warn("Already managed: "+entity+" 
("+nonDeploymentManagementContext+"); onManagementStarted is no-op");
+                } else if (nonDeploymentManagementContext == null || 
nonDeploymentManagementContext.getMode() != 
NonDeploymentManagementContextMode.MANAGEMENT_STARTING) {
+                    throw new IllegalStateException("Not in expected 
\"management starting\" state: "+entity+" 
("+nonDeploymentManagementContext+")");
+                }
+                if (managementContext != info.getManagementContext()) {
+                    throw new IllegalStateException("Already has management 
context: "+managementContext+"; can't set "+info.getManagementContext());
+                }
+                if (alreadyManaged) {
+                    return;
+                }
+                
+                
nonDeploymentManagementContext.setMode(NonDeploymentManagementContextMode.MANAGEMENT_STARTED);
+                
+                /*
+                 * - set derived/inherited config values
+                 * - publish all queued sensors
+                 * - start all queued executions (e.g. subscription delivery)
+                 * [above happens in exactly this order, at each entity]
+                 * then: the entity internally knows it fully managed 
(ManagementSupport.isManaged() returns true -- though not sure we need that);
+                 * subsequent sensor events and executions occur directly (no 
queueing)
+                 */
+                
+                if (!isReadOnly()) {
+                    
nonDeploymentManagementContext.getSubscriptionManager().startDelegatingForPublishing();
+                }
+                
+                // TODO more of the above
+                // TODO custom started activities
+                // (elaborate or remove ^^^ ? -AH, Sept 2014)
+            }
+            
+            if (!isReadOnly()) {
+                entity.onManagementBecomingMaster();
+                entity.onManagementStarted();
+            }
+            
+            synchronized (this) {
+                nonDeploymentManagementContext = null;
+            }
+        } catch (Throwable t) {
+            managementFailed.set(true);
+            throw Exceptions.propagate(t);
+        }
+    }
+    
+    @SuppressWarnings("deprecation")
+    public void onManagementStopping(ManagementTransitionInfo info) {
+        synchronized (this) {
+            if (managementContext != info.getManagementContext()) {
+                throw new IllegalStateException("onManagementStopping 
encountered different management context for "+entity+
+                    (!wasDeployed() ? " (wasn't deployed)" : !isDeployed() ? " 
(no longer deployed)" : "")+
+                    ": "+managementContext+"; expected 
"+info.getManagementContext()+" (may be a pre-registered entity which was never 
properly managed)");
+            }
+            Stopwatch startTime = Stopwatch.createStarted();
+            while (!managementFailed.get() && 
nonDeploymentManagementContext!=null && 
+                    
nonDeploymentManagementContext.getMode()==NonDeploymentManagementContextMode.MANAGEMENT_STARTING)
 {
+                // still becoming managed
+                try {
+                    if (startTime.elapsed(TimeUnit.SECONDS) > 30) {
+                        // emergency fix, 30s timeout for management starting
+                        log.error("Management stopping event "+info+" in 
"+this+" timed out waiting for start; proceeding to stopping");
+                        break;
+                    }
+                    wait(100);
+                } catch (InterruptedException e) {
+                    Exceptions.propagate(e);
+                }
+            }
+            if (nonDeploymentManagementContext==null) {
+                nonDeploymentManagementContext = new 
NonDeploymentManagementContext(entity, 
NonDeploymentManagementContextMode.MANAGEMENT_STOPPING);
+            } else {
+                // already stopped? or not started?
+                
nonDeploymentManagementContext.setMode(NonDeploymentManagementContextMode.MANAGEMENT_STOPPING);
+            }
+        }
+        // TODO custom stopping activities
+        // TODO framework stopping events - no more sensors, executions, etc
+        // (elaborate or remove ^^^ ? -AH, Sept 2014)
+        
+        if (!isReadOnly() && info.getMode().isDestroying()) {
+            // if we support remote parent of local child, the following call 
will need to be properly remoted
+            if (entity.getParent()!=null) 
entity.getParent().removeChild(entity.getProxyIfAvailable());
+        }
+        // new subscriptions will be queued / not allowed
+        
nonDeploymentManagementContext.getSubscriptionManager().stopDelegatingForSubscribing();
+        // new publications will be queued / not allowed
+        
nonDeploymentManagementContext.getSubscriptionManager().stopDelegatingForPublishing();
+        
+        if (!isReadOnly()) {
+            entity.onManagementNoLongerMaster();
+            entity.onManagementStopped();
+        }
+    }
+    
+    public void onManagementStopped(ManagementTransitionInfo info) {
+        synchronized (this) {
+            if (managementContext == null && 
nonDeploymentManagementContext.getMode() == 
NonDeploymentManagementContextMode.MANAGEMENT_STOPPED) {
+                return;
+            }
+            if (managementContext != info.getManagementContext()) {
+                throw new IllegalStateException("Has different management 
context: "+managementContext+"; expected "+info.getManagementContext());
+            }
+            getSubscriptionContext().unsubscribeAll();
+            entityChangeListener = EntityChangeListener.NOOP;
+            managementContextUsable.set(false);
+            currentlyDeployed.set(false);
+            executionContext = null;
+            subscriptionContext = null;
+        }
+        
+        // TODO framework stopped activities, e.g. serialize state ?
+        entity.invalidateReferences();
+        
+        synchronized (this) {
+            managementContext = null;
+            
nonDeploymentManagementContext.setMode(NonDeploymentManagementContextMode.MANAGEMENT_STOPPED);
+        }
+    }
+
+    @VisibleForTesting
+    @Beta
+    public boolean isManagementContextReal() {
+        return managementContextUsable.get();
+    }
+    
+    public synchronized ManagementContext getManagementContext() {
+        return (managementContextUsable.get()) ? managementContext : 
nonDeploymentManagementContext;
+    }    
+    
+    public synchronized ExecutionContext getExecutionContext() {
+        if (executionContext!=null) return executionContext;
+        if (managementContextUsable.get()) {
+            executionContext = managementContext.getExecutionContext(entity);
+            return executionContext;
+        }
+        return nonDeploymentManagementContext.getExecutionContext(entity);
+    }
+    public synchronized SubscriptionContext getSubscriptionContext() {
+        if (subscriptionContext!=null) return subscriptionContext;
+        if (managementContextUsable.get()) {
+            subscriptionContext = 
managementContext.getSubscriptionContext(entity);
+            return subscriptionContext;
+        }
+        return nonDeploymentManagementContext.getSubscriptionContext(entity);
+    }
+    public synchronized EntitlementManager getEntitlementManager() {
+        return getManagementContext().getEntitlementManager();
+    }
+
+    public void attemptLegacyAutodeployment(String effectorName) {
+        synchronized (this) {
+            if (managementContext != null) {
+                log.warn("Autodeployment suggested but not required for " + 
entity + "." + effectorName);
+                return;
+            }
+            if (entity instanceof Application) {
+                log.warn("Autodeployment with new management context triggered 
for " + entity + "." + effectorName + " -- will not be supported in future. 
Explicit manage call required.");
+                if (initialManagementContext != null) {
+                    initialManagementContext.getEntityManager().manage(entity);
+                } else {
+                    Entities.startManagement(entity);
+                }
+                return;
+            }
+        }
+        if ("start".equals(effectorName)) {
+            Entity e=entity;
+            if (e.getParent()!=null && 
((EntityInternal)e.getParent()).getManagementSupport().isDeployed()) { 
+                log.warn("Autodeployment in parent's management context 
triggered for "+entity+"."+effectorName+" -- will not be supported in future. 
Explicit manage call required.");
+                
((EntityInternal)e.getParent()).getManagementContext().getEntityManager().manage(entity);
+                return;
+            }
+        }
+        log.warn("Autodeployment not available for "+entity+"."+effectorName);
+    }
+    
+    public EntityChangeListener getEntityChangeListener() {
+        return entityChangeListener;
+    }
+    
+    private class EntityChangeListenerImpl implements EntityChangeListener {
+        @Override
+        public void onChanged() {
+            
getManagementContext().getRebindManager().getChangeListener().onChanged(entity);
+        }
+        @Override
+        public void onChildrenChanged() {
+            
getManagementContext().getRebindManager().getChangeListener().onChanged(entity);
+        }
+        @Override
+        public void onLocationsChanged() {
+            
getManagementContext().getRebindManager().getChangeListener().onChanged(entity);
+        }
+        @Override
+        public void onTagsChanged() {
+            
getManagementContext().getRebindManager().getChangeListener().onChanged(entity);
+        }
+        @Override
+        public void onMembersChanged() {
+            
getManagementContext().getRebindManager().getChangeListener().onChanged(entity);
+        }
+        @Override
+        public void onPolicyAdded(Policy policy) {
+            
getManagementContext().getRebindManager().getChangeListener().onChanged(entity);
+            
getManagementContext().getRebindManager().getChangeListener().onManaged(policy);
+        }
+        @Override
+        public void onEnricherAdded(Enricher enricher) {
+            
getManagementContext().getRebindManager().getChangeListener().onChanged(entity);
+            
getManagementContext().getRebindManager().getChangeListener().onManaged(enricher);
+        }
+        @Override
+        public void onFeedAdded(Feed feed) {
+            
getManagementContext().getRebindManager().getChangeListener().onChanged(entity);
+            
getManagementContext().getRebindManager().getChangeListener().onManaged(feed);
+        }
+        @Override
+        public void onPolicyRemoved(Policy policy) {
+            
getManagementContext().getRebindManager().getChangeListener().onChanged(entity);
+            
getManagementContext().getRebindManager().getChangeListener().onUnmanaged(policy);
+        }
+        @Override
+        public void onEnricherRemoved(Enricher enricher) {
+            
getManagementContext().getRebindManager().getChangeListener().onChanged(entity);
+            
getManagementContext().getRebindManager().getChangeListener().onUnmanaged(enricher);
+        }
+        @Override
+        public void onFeedRemoved(Feed feed) {
+            
getManagementContext().getRebindManager().getChangeListener().onChanged(entity);
+            
getManagementContext().getRebindManager().getChangeListener().onUnmanaged(feed);
+        }
+        @Override
+        public void onAttributeChanged(AttributeSensor<?> attribute) {
+            // TODO Could make this more efficient by inspecting the attribute 
to decide if needs persisted
+            // immediately, or not important, or transient (e.g. do we really 
need to persist 
+            // request-per-second count for rebind purposes?!)
+            
getManagementContext().getRebindManager().getChangeListener().onChanged(entity);
+        }
+        @Override
+        public void onConfigChanged(ConfigKey<?> key) {
+            
getManagementContext().getRebindManager().getChangeListener().onChanged(entity);
+        }
+        @Override
+        public void onEffectorStarting(Effector<?> effector, Object 
parameters) {
+            Entitlements.checkEntitled(getEntitlementManager(), 
Entitlements.INVOKE_EFFECTOR, EntityAndItem.of(entity, 
StringAndArgument.of(effector.getName(), parameters)));
+        }
+        @Override
+        public void onEffectorCompleted(Effector<?> effector) {
+            
getManagementContext().getRebindManager().getChangeListener().onChanged(entity);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return super.toString()+"["+(entity==null ? "null" : 
entity.getId())+"]";
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6602f694/core/src/main/java/org/apache/brooklyn/core/management/internal/EntityManagementUtils.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/management/internal/EntityManagementUtils.java
 
b/core/src/main/java/org/apache/brooklyn/core/management/internal/EntityManagementUtils.java
new file mode 100644
index 0000000..3f59774
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/management/internal/EntityManagementUtils.java
@@ -0,0 +1,326 @@
+/*
+ * 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 io.brooklyn.camp.CampPlatform;
+import io.brooklyn.camp.spi.Assembly;
+import io.brooklyn.camp.spi.AssemblyTemplate;
+import io.brooklyn.camp.spi.instantiate.AssemblyTemplateInstantiator;
+
+import java.io.StringReader;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Callable;
+
+import javax.annotation.Nullable;
+
+import org.apache.brooklyn.api.entity.Application;
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.basic.EntityLocal;
+import org.apache.brooklyn.api.entity.proxying.EntitySpec;
+import org.apache.brooklyn.api.management.ManagementContext;
+import org.apache.brooklyn.api.management.Task;
+import 
org.apache.brooklyn.api.management.classloading.BrooklynClassLoadingContext;
+import 
org.apache.brooklyn.core.management.classloading.JavaBrooklynClassLoadingContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.camp.brooklyn.api.AssemblyTemplateSpecInstantiator;
+import brooklyn.config.BrooklynServerConfig;
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.basic.BrooklynTaskTags;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.basic.EntityFunctions;
+import brooklyn.entity.effector.Effectors;
+import brooklyn.entity.trait.Startable;
+import brooklyn.util.collections.MutableList;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.guava.Maybe;
+import brooklyn.util.task.TaskBuilder;
+import brooklyn.util.task.Tasks;
+import brooklyn.util.text.Strings;
+import brooklyn.util.time.Duration;
+
+import com.google.common.annotations.Beta;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+
+/** Utility methods for working with entities and applications */
+public class EntityManagementUtils {
+
+    private static final Logger log = 
LoggerFactory.getLogger(EntityManagementUtils.class);
+
+    /**
+     * A marker config value which indicates that an application was created 
automatically
+     * to allow the management of a non-app entity.
+     */
+    public static final ConfigKey<Boolean> WRAPPER_APP_MARKER = 
ConfigKeys.newBooleanConfigKey("brooklyn.wrapper_app");
+
+    /** creates an application from the given app spec, managed by the given 
management context */
+    public static <T extends Application> T createUnstarted(ManagementContext 
mgmt, EntitySpec<T> spec) {
+        T app = mgmt.getEntityManager().createEntity(spec);
+        Entities.startManagement(app, mgmt);
+        return app;
+    }
+
+    /** convenience for accessing camp */
+    public static Maybe<CampPlatform> getCampPlatform(ManagementContext mgmt) {
+        return BrooklynServerConfig.getCampPlatform(mgmt);
+    }
+    
+    /** as {@link #createApplication(ManagementContext, EntitySpec)} but for a 
YAML spec */
+    public static <T extends Application> T createUnstarted(ManagementContext 
mgmt, String yaml) {
+        AssemblyTemplate at = 
getCampPlatform(mgmt).get().pdp().registerDeploymentPlan( new 
StringReader(yaml) );
+        return createUnstarted(mgmt, at);
+    }
+    
+    /** as {@link #createApplication(ManagementContext, EntitySpec)} but for 
an assembly template */
+    @SuppressWarnings("unchecked")
+    public static <T extends Application> T createUnstarted(ManagementContext 
mgmt, AssemblyTemplate at) {
+        CampPlatform camp = getCampPlatform(mgmt).get();
+        AssemblyTemplateInstantiator instantiator;
+        try {
+            instantiator = at.getInstantiator().newInstance();
+        } catch (Exception e) {
+            throw Exceptions.propagate(e);
+        }
+        Assembly assembly;
+        if (instantiator instanceof AssemblyTemplateSpecInstantiator) {
+            BrooklynClassLoadingContext loader = 
JavaBrooklynClassLoadingContext.create(mgmt);
+            
+            EntitySpec<?> spec = ((AssemblyTemplateSpecInstantiator) 
instantiator).createSpec(at, camp, loader, true);
+            Entity app = mgmt.getEntityManager().createEntity(spec);
+            Entities.startManagement((Application)app, mgmt);
+            return (T) app;
+        } else {
+            // currently, all brooklyn plans should produce the above; 
currently this will always throw Unsupported  
+            try {
+                assembly = instantiator.instantiate(at, camp);
+                return (T) mgmt.getEntityManager().getEntity(assembly.getId());
+            } catch (UnsupportedOperationException e) {
+                if (at.getPlatformComponentTemplates()==null || 
at.getPlatformComponentTemplates().isEmpty()) {
+                    if 
(at.getCustomAttributes().containsKey("brooklyn.catalog"))
+                        throw new IllegalArgumentException("Unrecognized 
application blueprint format: expected an application, not a brooklyn.catalog");
+                    throw new IllegalArgumentException("Unrecognized 
application blueprint format: no services defined");
+                }
+                // map this (expected) error to a nicer message
+                throw new IllegalArgumentException("Unrecognized application 
blueprint format");
+            } catch (Exception e) {
+                Exceptions.propagateIfFatal(e);
+                throw new IllegalArgumentException("Invalid plan: "+at, e);
+            }
+        }
+    }
+    
+    /** container for operation which creates something and which wants to 
return both
+     * the items created and any pending create/start task */
+    public static class CreationResult<T,U> {
+        private final T thing;
+        @Nullable private final Task<U> task;
+        public CreationResult(T thing, Task<U> task) {
+            super();
+            this.thing = thing;
+            this.task = task;
+        }
+        
+        protected static <T,U> CreationResult<T,U> of(T thing, @Nullable 
Task<U> task) {
+            return new CreationResult<T,U>(thing, task);
+        }
+        
+        /** returns the thing/things created */
+        @Nullable public T get() { return thing; }
+        /** associated task, ie the one doing the creation/starting */
+        public Task<U> task() { return task; }
+        public CreationResult<T,U> blockUntilComplete(Duration timeout) { if 
(task!=null) task.blockUntilEnded(timeout); return this; }
+        public CreationResult<T,U> blockUntilComplete() { if (task!=null) 
task.blockUntilEnded(); return this; }
+    }
+
+    public static <T extends Application> CreationResult<T,Void> 
createStarting(ManagementContext mgmt, EntitySpec<T> appSpec) {
+        return start(createUnstarted(mgmt, appSpec));
+    }
+
+    public static CreationResult<? extends Application,Void> 
createStarting(ManagementContext mgmt, String appSpec) {
+        return start(createUnstarted(mgmt, appSpec));
+    }
+
+    public static CreationResult<? extends Application,Void> 
createStarting(ManagementContext mgmt, AssemblyTemplate at) {
+        return start(createUnstarted(mgmt, at));
+    }
+
+    public static <T extends Application> CreationResult<T,Void> start(T app) {
+        Task<Void> task = Entities.invokeEffector((EntityLocal)app, app, 
Startable.START,
+            // locations already set in the entities themselves;
+            // TODO make it so that this arg does not have to be supplied to 
START !
+            MutableMap.of("locations", MutableList.of()));
+        return CreationResult.of(app, task);
+    }
+    
+    public static CreationResult<List<Entity>, List<String>> addChildren(final 
EntityLocal parent, String yaml, Boolean start) {
+        if (Boolean.FALSE.equals(start))
+            return CreationResult.of(addChildrenUnstarted(parent, yaml), null);
+        return addChildrenStarting(parent, yaml);
+    }
+    
+    /** adds entities from the given yaml, under the given parent; but does 
not start them */
+    public static List<Entity> addChildrenUnstarted(final EntityLocal parent, 
String yaml) {
+        log.debug("Creating child of "+parent+" from yaml:\n{}", yaml);
+        
+        ManagementContext mgmt = 
parent.getApplication().getManagementContext();
+        CampPlatform camp = BrooklynServerConfig.getCampPlatform(mgmt).get();
+        
+        AssemblyTemplate at = camp.pdp().registerDeploymentPlan( new 
StringReader(yaml) );
+
+        AssemblyTemplateInstantiator instantiator;
+        try {
+            instantiator = at.getInstantiator().newInstance();
+        } catch (Exception e) {
+            throw Exceptions.propagate(e);
+        }
+        if (instantiator instanceof AssemblyTemplateSpecInstantiator) {
+            BrooklynClassLoadingContext loader = 
JavaBrooklynClassLoadingContext.create(mgmt);
+            EntitySpec<?> specA = ((AssemblyTemplateSpecInstantiator) 
instantiator).createSpec(at, camp, loader, false);
+
+            // see whether we can promote children
+            List<EntitySpec<?>> specs = MutableList.of();
+            if (hasNoNameOrCustomKeysOrRoot(at, specA)) {
+                // we can promote
+                for (EntitySpec<?> specC: specA.getChildren()) {
+                    collapseSpec(specA, specC);
+                    specs.add(specC);
+                }
+            } else {
+                // if not promoting, set a nice name if needed
+                if (Strings.isEmpty(specA.getDisplayName())) {
+                    int size = specA.getChildren().size();
+                    String childrenCountString = size+" "+(size!=1 ? 
"children" : "child");
+                    specA.displayName("Dynamically added 
"+childrenCountString);
+                }
+                specs.add(specA);
+            }
+
+            final List<Entity> children = MutableList.of();
+            for (EntitySpec<?> spec: specs) {
+                Entity child = (Entity)parent.addChild(spec);
+                Entities.manage(child);
+                children.add(child);
+            }
+            
+            return children;
+        } else {
+            throw new IllegalStateException("Spec could not be parsed to 
supply a compatible instantiator");
+        }
+    }
+
+    public static CreationResult<List<Entity>,List<String>> 
addChildrenStarting(final EntityLocal parent, String yaml) {
+        final List<Entity> children = addChildrenUnstarted(parent, yaml);
+        String childrenCountString;
+
+        int size = children.size();
+        childrenCountString = size+" "+(size!=1 ? "children" : "child"); 
+
+        TaskBuilder<List<String>> taskM = 
Tasks.<List<String>>builder().name("add children")
+            .dynamic(true)
+            .tag(BrooklynTaskTags.NON_TRANSIENT_TASK_TAG)
+            .body(new Callable<List<String>>() {
+                @Override public List<String> call() throws Exception {
+                    return ImmutableList.copyOf(Iterables.transform(children, 
EntityFunctions.id()));
+                }})
+                .description("Add and start "+childrenCountString);
+
+        TaskBuilder<?> taskS = Tasks.builder().parallel(true).name("add 
(parallel)").description("Start each new entity");
+
+        // autostart if requested
+        for (Entity child: children) {
+            if (child instanceof Startable) {
+                taskS.add(Effectors.invocation(child, Startable.START, 
ImmutableMap.of("locations", ImmutableList.of())));
+            } else {
+                // include a task, just to give feedback in the GUI
+                taskS.add(Tasks.builder().name("create").description("Skipping 
start (not a Startable Entity)")
+                    .body(new Runnable() { public void run() {} })
+                    .tag(BrooklynTaskTags.tagForTargetEntity(child))
+                    .build());
+            }
+        }
+        taskM.add(taskS.build());
+        Task<List<String>> task = Entities.submit(parent, taskM.build());
+
+        return CreationResult.of(children, task);
+    }
+
+    /** worker method to combine specs */
+    @Beta //where should this live long-term?
+    public static void collapseSpec(EntitySpec<?> sourceToBeCollapsed, 
EntitySpec<?> targetToBeExpanded) {
+        if (Strings.isEmpty(targetToBeExpanded.getDisplayName()))
+            
targetToBeExpanded.displayName(sourceToBeCollapsed.getDisplayName());
+        if (!sourceToBeCollapsed.getLocations().isEmpty())
+            targetToBeExpanded.locations(sourceToBeCollapsed.getLocations());
+
+        // NB: this clobbers child config; might prefer to deeply merge maps 
etc
+        // (but this should not be surprising, as unwrapping is often 
parameterising the nested blueprint, so outer config should dominate) 
+        targetToBeExpanded.configure(sourceToBeCollapsed.getConfig());
+        targetToBeExpanded.configure(sourceToBeCollapsed.getFlags());
+        
+        // TODO copying tags to all entities is not ideal;
+        // in particular the BrooklynTags.YAML_SPEC tag will show all entities 
if the root has multiple
+        targetToBeExpanded.tags(sourceToBeCollapsed.getTags());
+    }
+
+    /** worker method to help determine whether child/children can be promoted 
*/
+    @Beta //where should this live long-term?
+    public static boolean hasNoNameOrCustomKeysOrRoot(AssemblyTemplate 
template, EntitySpec<?> spec) {
+        if (Strings.isNonEmpty(template.getName())) {
+            if (spec.getChildren().size()==1) {
+                String childName = 
Iterables.getOnlyElement(spec.getChildren()).getDisplayName();
+                if (Strings.isEmpty(childName) || 
childName.equals(template.getName())) {
+                    // if child has no name, or it's the same, could still 
promote
+                } else {
+                    return false;
+                }
+            } else {
+                // if name set at root and promoting children would be 
ambiguous, do not promote 
+                return false;
+            }
+        } else if (spec.getChildren().size()>1) {
+            // don't allow multiple children if a name is specified as a root
+            return false;
+        }
+        
+        Set<String> rootAttrs = template.getCustomAttributes().keySet();
+        for (String rootAttr: rootAttrs) {
+            if (rootAttr.equals("brooklyn.catalog") || 
rootAttr.equals("brooklyn.config")) {
+                // these do not block promotion
+                continue;
+            }
+            if (rootAttr.startsWith("brooklyn.")) {
+                // any others in 'brooklyn' namespace will block promotion
+                return false;
+            }
+            // location is allowed in both, and is copied on promotion
+            // (name also copied)
+            // others are root currently are ignored on promotion; they are 
usually metadata
+            // TODO might be nice to know what we are excluding
+        }
+        
+        return true;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6602f694/core/src/main/java/org/apache/brooklyn/core/management/internal/EntityManagerInternal.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/management/internal/EntityManagerInternal.java
 
b/core/src/main/java/org/apache/brooklyn/core/management/internal/EntityManagerInternal.java
new file mode 100644
index 0000000..08f4069
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/management/internal/EntityManagerInternal.java
@@ -0,0 +1,32 @@
+/*
+ * 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.entity.Application;
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.management.EntityManager;
+
+public interface EntityManagerInternal extends EntityManager, 
BrooklynObjectManagerInternal<Entity> {
+
+    /** gets all entities currently known to the application, including 
entities that are not yet managed */
+    Iterable<Entity> getAllEntitiesInApplication(Application application);
+
+    public Iterable<String> getEntityIds();
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6602f694/core/src/main/java/org/apache/brooklyn/core/management/internal/GroovyObservablesPropertyChangeToCollectionChangeAdapter.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/management/internal/GroovyObservablesPropertyChangeToCollectionChangeAdapter.java
 
b/core/src/main/java/org/apache/brooklyn/core/management/internal/GroovyObservablesPropertyChangeToCollectionChangeAdapter.java
new file mode 100644
index 0000000..4e74904
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/management/internal/GroovyObservablesPropertyChangeToCollectionChangeAdapter.java
@@ -0,0 +1,65 @@
+/*
+ * 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 groovy.util.ObservableList;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+public class GroovyObservablesPropertyChangeToCollectionChangeAdapter 
implements PropertyChangeListener {
+    @SuppressWarnings("rawtypes")
+    private final CollectionChangeListener delegate;
+
+    public 
GroovyObservablesPropertyChangeToCollectionChangeAdapter(@SuppressWarnings("rawtypes")
 CollectionChangeListener delegate) {
+        this.delegate = delegate;
+    }
+
+    @SuppressWarnings("unchecked")
+    public void propertyChange(PropertyChangeEvent evt) {
+        if (evt instanceof ObservableList.ElementAddedEvent) {
+            delegate.onItemAdded(evt.getNewValue());
+        } else if (evt instanceof ObservableList.ElementRemovedEvent) {
+            delegate.onItemRemoved(evt.getOldValue());
+        } else if (evt instanceof ObservableList.ElementUpdatedEvent) {
+            delegate.onItemRemoved(evt.getOldValue());
+            delegate.onItemAdded(evt.getNewValue());
+        } else if (evt instanceof ObservableList.ElementClearedEvent) {
+            for (Object value : ((ObservableList.ElementClearedEvent) 
evt).getValues()) {
+                delegate.onItemAdded(value);
+            }
+        } else if(evt instanceof ObservableList.MultiElementAddedEvent ) {
+            for(Object value: 
((ObservableList.MultiElementAddedEvent)evt).getValues()){
+                delegate.onItemAdded(value);
+            }
+        }
+    }
+
+    public int hashCode() {
+        return delegate.hashCode();
+    }
+
+    public boolean equals(Object other) {
+        if (other instanceof 
GroovyObservablesPropertyChangeToCollectionChangeAdapter)
+            return 
delegate.equals(((GroovyObservablesPropertyChangeToCollectionChangeAdapter) 
other).delegate);
+        if (other instanceof CollectionChangeListener)
+            return delegate.equals(other);
+        return false;
+    }
+} 
\ 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/LocalAccessManager.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/management/internal/LocalAccessManager.java
 
b/core/src/main/java/org/apache/brooklyn/core/management/internal/LocalAccessManager.java
new file mode 100644
index 0000000..2699676
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/management/internal/LocalAccessManager.java
@@ -0,0 +1,111 @@
+/*
+ * 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.concurrent.atomic.AtomicReference;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.management.AccessController;
+
+import com.google.common.annotations.Beta;
+
+@Beta
+public class LocalAccessManager implements AccessManager {
+
+    private volatile boolean locationProvisioningAllowed = true;
+    private volatile boolean locationManagementAllowed = true;
+    private volatile boolean entityManagementAllowed = true;
+
+    private final AtomicReference<AccessControllerImpl> controller = new 
AtomicReference<AccessControllerImpl>();
+
+    public LocalAccessManager() {
+        updateAccessController();
+    }
+
+    @Override
+    public AccessController getAccessController() {
+        return controller.get();
+    }
+    
+    @Override
+    public boolean isLocationProvisioningAllowed() {
+        return locationProvisioningAllowed;
+    }
+
+    @Override
+    public boolean isLocationManagementAllowed() {
+        return locationManagementAllowed;
+    }
+
+    @Override
+    public boolean isEntityManagementAllowed() {
+        return entityManagementAllowed;
+    }
+
+    @Override
+    public void setLocationProvisioningAllowed(boolean allowed) {
+        locationProvisioningAllowed = allowed;
+        updateAccessController();
+    }
+
+    @Override
+    public void setLocationManagementAllowed(boolean allowed) {
+        locationManagementAllowed = allowed;
+        updateAccessController();
+    }
+
+    @Override
+    public void setEntityManagementAllowed(boolean allowed) {
+        entityManagementAllowed = allowed;
+        updateAccessController();
+    }
+
+    private void updateAccessController() {
+        controller.set(new AccessControllerImpl(locationProvisioningAllowed, 
locationManagementAllowed, entityManagementAllowed));
+    }
+    
+    private static class AccessControllerImpl implements AccessController {
+        private final boolean locationProvisioningAllowed;
+        private final boolean locationManagementAllowed;
+        private final boolean entityManagementAllowed;
+        
+        public AccessControllerImpl(boolean locationProvisioningAllowed, 
boolean locationManagementAllowed, 
+                boolean entityManagementAllowed) {
+            this.locationProvisioningAllowed = locationProvisioningAllowed;
+            this.locationManagementAllowed = locationManagementAllowed;
+            this.entityManagementAllowed = entityManagementAllowed;
+        }
+
+        @Override
+        public Response canProvisionLocation(Location provisioner) {
+            return (locationProvisioningAllowed ? Response.allowed() : 
Response.disallowed("location provisioning disabled"));
+        }
+        
+        @Override
+        public Response canManageLocation(Location loc) {
+            return (locationManagementAllowed ? Response.allowed() : 
Response.disallowed("location management disabled"));
+        }
+        
+        @Override
+        public Response canManageEntity(Entity entity) {
+            return (entityManagementAllowed ? Response.allowed() : 
Response.disallowed("entity management disabled"));
+        }
+    }
+}

Reply via email to