brooklyn.entity.basic.Entities.java Source code

Java tutorial

Introduction

Here is the source code for brooklyn.entity.basic.Entities.java

Source

/*
 * 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 brooklyn.entity.basic;

import java.io.Closeable;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import brooklyn.basic.BrooklynObjectInternal;
import brooklyn.config.BrooklynProperties;
import brooklyn.config.ConfigKey;
import brooklyn.config.ConfigKey.HasConfigKey;
import brooklyn.enricher.basic.AbstractEnricher;
import brooklyn.entity.Application;
import brooklyn.entity.Effector;
import brooklyn.entity.Entity;
import brooklyn.entity.Group;
import brooklyn.entity.drivers.EntityDriver;
import brooklyn.entity.drivers.downloads.DownloadResolver;
import brooklyn.entity.effector.Effectors;
import brooklyn.entity.proxying.EntityProxyImpl;
import brooklyn.entity.trait.Startable;
import brooklyn.entity.trait.StartableMethods;
import brooklyn.event.AttributeSensor;
import brooklyn.event.Sensor;
import brooklyn.event.basic.DependentConfiguration;
import brooklyn.location.Location;
import brooklyn.location.LocationSpec;
import brooklyn.location.basic.LocationInternal;
import brooklyn.location.basic.Locations;
import brooklyn.management.ExecutionContext;
import brooklyn.management.LocationManager;
import brooklyn.management.ManagementContext;
import brooklyn.management.Task;
import brooklyn.management.TaskAdaptable;
import brooklyn.management.TaskFactory;
import brooklyn.management.internal.EffectorUtils;
import brooklyn.management.internal.EntityManagerInternal;
import brooklyn.management.internal.LocalManagementContext;
import brooklyn.management.internal.ManagementContextInternal;
import brooklyn.management.internal.NonDeploymentManagementContext;
import brooklyn.policy.Enricher;
import brooklyn.policy.Policy;
import brooklyn.policy.basic.AbstractPolicy;
import brooklyn.util.ResourceUtils;
import brooklyn.util.collections.MutableMap;
import brooklyn.util.config.ConfigBag;
import brooklyn.util.exceptions.Exceptions;
import brooklyn.util.flags.FlagUtils;
import brooklyn.util.guava.Maybe;
import brooklyn.util.repeat.Repeater;
import brooklyn.util.stream.Streams;
import brooklyn.util.task.DynamicTasks;
import brooklyn.util.task.ParallelTask;
import brooklyn.util.task.TaskTags;
import brooklyn.util.task.Tasks;
import brooklyn.util.task.system.ProcessTaskWrapper;
import brooklyn.util.task.system.SystemTasks;
import brooklyn.util.time.Duration;

import com.google.common.annotations.Beta;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.reflect.TypeToken;
import com.google.common.util.concurrent.Atomics;
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;

/**
 * Convenience methods for working with entities.
 * <p>
 * Also see the various {@code *Methods} classes for traits,
 * such as {@link StartableMethods} for {@link Startable} implementations.
 */
public class Entities {

    private static final Logger log = LoggerFactory.getLogger(Entities.class);

    /**
     * Names that, if they appear anywhere in an attribute/config/field indicates that it
     * may be private, so should not be logged etc.
     */
    public static final List<String> SECRET_NAMES = ImmutableList.of("password", "passwd", "credential", "secret",
            "private", "access.cert", "access.key");

    /**
     * Special object used by some setting methods to indicate that a value should be ignored.
     * <p>
     * See specific usages of this field to confirm where.
     */
    public static final Object UNCHANGED = new Object();

    /**
     * Special object used by some setting methods to indicate that a value should be removed.
     * <p>
     * See specific usages of this field to confirm where.
     */
    public static final Object REMOVE = new Object();

    /**
     * Invokes an {@link Effector} on multiple entities, with the named arguments from the parameters {@link Map}
     * using the context of the provided {@link Entity}.
     * <p>
     * Intended for use only from the callingEntity.
     * <p>
     * Returns a {@link ParallelTask} containing the results from each tasks invocation. Calling
     * {@link java.util.concurrent.Future#get() get()} on this will block until all tasks are complete,
     * and will throw an exception if any task resulted in an error.
     *
     * @return {@link ParallelTask} containing results from each invocation
     */
    public static <T> Task<List<T>> invokeEffectorList(EntityLocal callingEntity,
            Iterable<? extends Entity> entitiesToCall, final Effector<T> effector,
            final Map<String, ?> parameters) {
        // formulation is complicated, but it is building up a list of tasks, without blocking on them initially,
        // but ensuring that when the parallel task is gotten it does block on all of them

        if (entitiesToCall == null) {
            entitiesToCall = ImmutableList.of();
        }

        List<TaskAdaptable<T>> tasks = Lists.newArrayList();

        for (final Entity entity : entitiesToCall) {
            tasks.add(Effectors.invocation(entity, effector, parameters));
        }
        ParallelTask<T> invoke = new ParallelTask<T>(
                MutableMap.of("displayName", effector.getName() + " (parallel)", "description",
                        "Invoking effector \"" + effector.getName() + "\" on " + tasks.size()
                                + (tasks.size() == 1 ? " entity" : " entities"),
                        "tag", BrooklynTaskTags.tagForCallerEntity(callingEntity)),
                tasks);
        TaskTags.markInessential(invoke);
        return DynamicTasks.queueIfPossible(invoke).orSubmitAsync(callingEntity).asTask();
    }

    public static <T> Task<List<T>> invokeEffectorListWithMap(EntityLocal callingEntity,
            Iterable<? extends Entity> entitiesToCall, final Effector<T> effector,
            final Map<String, ?> parameters) {
        return invokeEffectorList(callingEntity, entitiesToCall, effector, parameters);
    }

    @SuppressWarnings("unchecked")
    public static <T> Task<List<T>> invokeEffectorListWithArgs(EntityLocal callingEntity,
            Iterable<? extends Entity> entitiesToCall, final Effector<T> effector, Object... args) {
        return invokeEffectorListWithMap(callingEntity, entitiesToCall, effector,
                // putting into a map, unnecessarily, as it ends up being the array again...
                EffectorUtils.prepareArgsForEffectorAsMapFromArray(effector, args));
    }

    public static <T> Task<List<T>> invokeEffectorList(EntityLocal callingEntity,
            Iterable<? extends Entity> entitiesToCall, final Effector<T> effector) {
        return invokeEffectorList(callingEntity, entitiesToCall, effector, Collections.<String, Object>emptyMap());
    }

    public static <T> Task<T> invokeEffector(EntityLocal callingEntity, Entity entityToCall,
            final Effector<T> effector, final Map<String, ?> parameters) {
        Task<T> t = Effectors.invocation(entityToCall, effector, parameters).asTask();
        TaskTags.markInessential(t);

        // we pass to callingEntity for consistency above, but in exec-context it should be re-dispatched to targetEntity
        // reassign t as the return value may be a wrapper, if it is switching execution contexts; see submitInternal's javadoc
        t = ((EntityInternal) callingEntity).getManagementSupport().getExecutionContext()
                .submit(MutableMap.of("tag", BrooklynTaskTags.tagForCallerEntity(callingEntity)), t);

        if (DynamicTasks.getTaskQueuingContext() != null) {
            // include it as a child (in the gui), marked inessential, because the caller is invoking programmatically
            DynamicTasks.queue(t);
        }

        return t;
    }

    @SuppressWarnings("unchecked")
    public static <T> Task<T> invokeEffectorWithArgs(EntityLocal callingEntity, Entity entityToCall,
            final Effector<T> effector, Object... args) {
        return invokeEffector(callingEntity, entityToCall, effector,
                EffectorUtils.prepareArgsForEffectorAsMapFromArray(effector, args));
    }

    public static <T> Task<T> invokeEffector(EntityLocal callingEntity, Entity entityToCall,
            final Effector<T> effector) {
        return invokeEffector(callingEntity, entityToCall, effector, Collections.<String, Object>emptyMap());
    }

    /** Invokes in parallel if multiple, but otherwise invokes the item directly. */
    public static Task<?> invokeEffector(EntityLocal callingEntity, Iterable<? extends Entity> entitiesToCall,
            final Effector<?> effector, final Map<String, ?> parameters) {
        if (Iterables.size(entitiesToCall) == 1)
            return invokeEffector(callingEntity, entitiesToCall.iterator().next(), effector, parameters);
        else
            return invokeEffectorList(callingEntity, entitiesToCall, effector, parameters);
    }

    /** Invokes in parallel if multiple, but otherwise invokes the item directly. */
    public static Task<?> invokeEffector(EntityLocal callingEntity, Iterable<? extends Entity> entitiesToCall,
            final Effector<?> effector) {
        return invokeEffector(callingEntity, entitiesToCall, effector, Collections.<String, Object>emptyMap());
    }

    public static boolean isSecret(String name) {
        String lowerName = name.toLowerCase();
        for (String secretName : SECRET_NAMES) {
            if (lowerName.contains(secretName))
                return true;
        }
        return false;
    }

    public static boolean isTrivial(Object v) {
        if (v instanceof Maybe) {
            if (!((Maybe<?>) v).isPresent())
                return true;
            v = ((Maybe<?>) v).get();
        }

        return v == null || (v instanceof Map && ((Map<?, ?>) v).isEmpty())
                || (v instanceof Collection && ((Collection<?>) v).isEmpty())
                || (v instanceof CharSequence && ((CharSequence) v).length() == 0);
    }

    public static Map<String, Object> sanitize(ConfigBag input) {
        return sanitize(input.getAllConfig());
    }

    public static <K> Map<K, Object> sanitize(Map<K, ?> input) {
        Map<K, Object> result = Maps.newLinkedHashMap();
        for (Map.Entry<K, ?> e : input.entrySet()) {
            if (isSecret("" + e.getKey()))
                result.put(e.getKey(), "xxxxxxxx");
            else
                result.put(e.getKey(), e.getValue());
        }
        return result;
    }

    public static void dumpInfo(Iterable<? extends Entity> entities) {
        for (Entity e : entities) {
            dumpInfo(e);
        }
    }

    public static void dumpInfo(Entity e) {
        try {
            dumpInfo(e, new PrintWriter(System.out), "", "  ");
        } catch (IOException exc) {
            // system.out throwing an exception is odd, so don't have IOException on signature
            throw new RuntimeException(exc);
        }
    }

    public static void dumpInfo(Entity e, Writer out) throws IOException {
        dumpInfo(e, out, "", "  ");
    }

    public static void dumpInfo(Entity e, String currentIndentation, String tab) throws IOException {
        dumpInfo(e, new PrintWriter(System.out), currentIndentation, tab);
    }

    public static void dumpInfo(Entity e, Writer out, String currentIndentation, String tab) throws IOException {
        out.append(currentIndentation + e.toString() + " " + e.getId() + "\n");

        out.append(currentIndentation + tab + tab + "displayName = " + e.getDisplayName() + "\n");

        out.append(currentIndentation + tab + tab + "locations = " + e.getLocations() + "\n");

        Set<ConfigKey<?>> keys = Sets
                .newLinkedHashSet(((EntityInternal) e).getConfigMap().getLocalConfig().keySet());
        for (ConfigKey<?> it : sortConfigKeys(keys)) {
            // use the official config key declared on the type if available
            // (since the map sometimes contains <object> keys
            ConfigKey<?> realKey = e.getEntityType().getConfigKey(it.getName());
            if (realKey != null)
                it = realKey;

            Maybe<Object> mv = ((EntityInternal) e).config().getLocalRaw(it);
            if (!isTrivial(mv)) {
                Object v = mv.get();
                out.append(currentIndentation + tab + tab + it.getName());
                out.append(" = ");
                if (isSecret(it.getName()))
                    out.append("xxxxxxxx");
                else if ((v instanceof Task) && ((Task<?>) v).isDone()) {
                    if (((Task<?>) v).isError()) {
                        out.append("ERROR in " + v);
                    } else {
                        try {
                            out.append(((Task<?>) v).get() + " (from " + v + ")");
                        } catch (ExecutionException ee) {
                            throw new IllegalStateException(
                                    "task " + v + " done and !isError, but threw exception on get", ee);
                        } catch (InterruptedException ie) {
                            Thread.currentThread().interrupt();
                            return;
                        }
                    }
                } else
                    out.append("" + v);
                out.append("\n");
            }
        }

        for (Sensor<?> it : sortSensors(e.getEntityType().getSensors())) {
            if (it instanceof AttributeSensor) {
                Object v = e.getAttribute((AttributeSensor<?>) it);
                if (!isTrivial(v)) {
                    out.append(currentIndentation + tab + tab + it.getName());
                    out.append(": ");
                    if (isSecret(it.getName()))
                        out.append("xxxxxxxx");
                    else
                        out.append("" + v);
                    out.append("\n");
                }
            }
        }

        if (e instanceof Group) {
            StringBuilder members = new StringBuilder();
            for (Entity it : ((Group) e).getMembers()) {
                if (members.length() > 0)
                    members.append(", ");
                members.append(it.getId());
            }
            out.append(currentIndentation + tab + tab + "Members: " + members.toString() + "\n");
        }

        if (!e.getPolicies().isEmpty()) {
            out.append(currentIndentation + tab + tab + "Policies:\n");
            for (Policy policy : e.getPolicies()) {
                dumpInfo(policy, out, currentIndentation + tab + tab + tab, tab);
            }
        }

        if (!e.getEnrichers().isEmpty()) {
            out.append(currentIndentation + tab + tab + "Enrichers:\n");
            for (Enricher enricher : e.getEnrichers()) {
                dumpInfo(enricher, out, currentIndentation + tab + tab + tab, tab);
            }
        }

        for (Entity it : e.getChildren()) {
            dumpInfo(it, out, currentIndentation + tab, tab);
        }

        out.flush();
    }

    public static void dumpInfo(Location loc) {
        try {
            dumpInfo(loc, new PrintWriter(System.out), "", "  ");
        } catch (IOException exc) {
            // system.out throwing an exception is odd, so don't have IOException on signature
            throw new RuntimeException(exc);
        }
    }

    public static void dumpInfo(Location loc, Writer out) throws IOException {
        dumpInfo(loc, out, "", "  ");
    }

    public static void dumpInfo(Location loc, String currentIndentation, String tab) throws IOException {
        dumpInfo(loc, new PrintWriter(System.out), currentIndentation, tab);
    }

    @SuppressWarnings("rawtypes")
    public static void dumpInfo(Location loc, Writer out, String currentIndentation, String tab)
            throws IOException {
        out.append(currentIndentation + loc.toString() + "\n");

        for (Object entryO : ((LocationInternal) loc).config().getBag().getAllConfig().entrySet()) {
            Map.Entry entry = (Map.Entry) entryO;
            Object keyO = entry.getKey();
            String key = keyO instanceof HasConfigKey ? ((HasConfigKey) keyO).getConfigKey().getName()
                    : keyO instanceof ConfigKey ? ((ConfigKey) keyO).getName()
                            : keyO == null ? null : keyO.toString();
            Object val = entry.getValue();
            if (!isTrivial(val)) {
                out.append(currentIndentation + tab + tab + key);
                out.append(" = ");
                if (isSecret(key))
                    out.append("xxxxxxxx");
                else
                    out.append("" + val);
                out.append("\n");
            }
        }

        for (Map.Entry<String, ?> entry : sortMap(FlagUtils.getFieldsWithFlags(loc)).entrySet()) {
            String key = entry.getKey();
            Object val = entry.getValue();
            if (!isTrivial(val)) {
                out.append(currentIndentation + tab + tab + key);
                out.append(" = ");
                if (isSecret(key))
                    out.append("xxxxxxxx");
                else
                    out.append("" + val);
                out.append("\n");
            }
        }

        for (Location it : loc.getChildren()) {
            dumpInfo(it, out, currentIndentation + tab, tab);
        }

        out.flush();
    }

    public static void dumpInfo(Enricher enr) {
        try {
            dumpInfo(enr, new PrintWriter(System.out), "", "  ");
        } catch (IOException exc) {
            // system.out throwing an exception is odd, so don't have IOException on signature
            throw new RuntimeException(exc);
        }
    }

    public static void dumpInfo(Enricher enr, Writer out) throws IOException {
        dumpInfo(enr, out, "", "  ");
    }

    public static void dumpInfo(Enricher enr, String currentIndentation, String tab) throws IOException {
        dumpInfo(enr, new PrintWriter(System.out), currentIndentation, tab);
    }

    public static void dumpInfo(Enricher enr, Writer out, String currentIndentation, String tab)
            throws IOException {
        out.append(currentIndentation + enr.toString() + "\n");

        for (ConfigKey<?> key : sortConfigKeys(enr.getEnricherType().getConfigKeys())) {
            Maybe<Object> val = ((BrooklynObjectInternal) enr).config().getRaw(key);
            if (!isTrivial(val)) {
                out.append(currentIndentation + tab + tab + key);
                out.append(" = ");
                if (isSecret(key.getName()))
                    out.append("xxxxxxxx");
                else
                    out.append("" + val.get());
                out.append("\n");
            }
        }

        out.flush();
    }

    public static void dumpInfo(Policy pol) {
        try {
            dumpInfo(pol, new PrintWriter(System.out), "", "  ");
        } catch (IOException exc) {
            // system.out throwing an exception is odd, so don't have IOException on signature
            throw new RuntimeException(exc);
        }
    }

    public static void dumpInfo(Policy pol, Writer out) throws IOException {
        dumpInfo(pol, out, "", "  ");
    }

    public static void dumpInfo(Policy pol, String currentIndentation, String tab) throws IOException {
        dumpInfo(pol, new PrintWriter(System.out), currentIndentation, tab);
    }

    public static void dumpInfo(Policy pol, Writer out, String currentIndentation, String tab) throws IOException {
        out.append(currentIndentation + pol.toString() + "\n");

        for (ConfigKey<?> key : sortConfigKeys(pol.getPolicyType().getConfigKeys())) {
            Maybe<Object> val = ((BrooklynObjectInternal) pol).config().getRaw(key);
            if (!isTrivial(val)) {
                out.append(currentIndentation + tab + tab + key);
                out.append(" = ");
                if (isSecret(key.getName()))
                    out.append("xxxxxxxx");
                else
                    out.append("" + val.get());
                out.append("\n");
            }
        }

        out.flush();
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    public static List<Sensor<?>> sortSensors(Set<Sensor<?>> sensors) {
        List result = new ArrayList(sensors);
        Collections.sort(result, new Comparator<Sensor>() {
            @Override
            public int compare(Sensor arg0, Sensor arg1) {
                return arg0.getName().compareTo(arg1.getName());
            }

        });
        return result;
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    public static List<ConfigKey<?>> sortConfigKeys(Set<ConfigKey<?>> configs) {
        List result = new ArrayList(configs);
        Collections.sort(result, new Comparator<ConfigKey>() {
            @Override
            public int compare(ConfigKey arg0, ConfigKey arg1) {
                return arg0.getName().compareTo(arg1.getName());
            }

        });
        return result;
    }

    public static <T> Map<String, T> sortMap(Map<String, T> map) {
        Map<String, T> result = Maps.newLinkedHashMap();
        List<String> order = Lists.newArrayList(map.keySet());
        Collections.sort(order, String.CASE_INSENSITIVE_ORDER);

        for (String key : order) {
            result.put(key, map.get(key));
        }
        return result;
    }

    /**
     * Returns true if the given descendant includes the given ancestor in its chain.
     * Does <i>NOT</i> count a node as its ancestor.
     */
    public static boolean isAncestor(Entity descendant, Entity potentialAncestor) {
        Entity ancestor = descendant.getParent();
        while (ancestor != null) {
            if (ancestor.equals(potentialAncestor))
                return true;
            ancestor = ancestor.getParent();
        }
        return false;
    }

    /**
     * Checks whether the descendants of the given ancestor contains the given potentialDescendant.
     * <p>
     * In this test, unlike in {@link #descendants(Entity)}, an entity is not counted as a descendant.
     * note, it is usually preferred to use isAncestor() and swap the order, it is a cheaper method.
     */
    public static boolean isDescendant(Entity ancestor, Entity potentialDescendant) {
        Set<Entity> inspected = Sets.newLinkedHashSet();
        Stack<Entity> toinspect = new Stack<Entity>();
        toinspect.add(ancestor);

        while (!toinspect.isEmpty()) {
            Entity e = toinspect.pop();
            if (e.getChildren().contains(potentialDescendant)) {
                return true;
            }
            inspected.add(e);
            toinspect.addAll(e.getChildren());
            toinspect.removeAll(inspected);
        }

        return false;
    }

    /**
     * Return all descendants of given entity matching the given predicate and optionally the entity itself.
     *
     * @see {@link EntityPredicates} for useful second arguments.
     */
    public static Iterable<Entity> descendants(Entity root, Predicate<? super Entity> matching,
            boolean includeSelf) {
        Iterable<Entity> descs = Iterables
                .concat(Iterables.transform(root.getChildren(), new Function<Entity, Iterable<Entity>>() {
                    @Override
                    public Iterable<Entity> apply(Entity input) {
                        return descendants(input);
                    }
                }));
        return Iterables.filter(Iterables.concat(descs, Collections.singleton(root)), matching);
    }

    /**
     * Returns the entity  matching the given predicate
     *
     * @see #descendants(Entity, Predicate, boolean)
     */
    public static Iterable<Entity> descendants(Entity root, Predicate<Entity> matching) {
        return descendants(root, matching, true);
    }

    /**
     * Returns the entity, its children, and all its children, and so on.
     *
     * @see #descendants(Entity, Predicate, boolean)
     */
    public static Iterable<Entity> descendants(Entity root) {
        return descendants(root, Predicates.alwaysTrue(), true);
    }

    /**
     * Return all descendants of given entity of the given type, potentially including the given root.
     *
     * @see #descendants(Entity)
     * @see Iterables#filter(Iterable, Class)
     */
    public static <T extends Entity> Iterable<T> descendants(Entity root, Class<T> ofType) {
        return Iterables.filter(descendants(root), ofType);
    }

    /** Returns the entity, its parent, its parent, and so on. */
    public static Iterable<Entity> ancestors(final Entity root) {
        return new Iterable<Entity>() {
            @Override
            public Iterator<Entity> iterator() {
                return new Iterator<Entity>() {
                    Entity next = root;

                    @Override
                    public boolean hasNext() {
                        return next != null;
                    }

                    @Override
                    public Entity next() {
                        Entity result = next;
                        next = next.getParent();
                        return result;
                    }

                    @Override
                    public void remove() {
                        throw new UnsupportedOperationException();
                    }
                };
            }
        };
    }

    /**
     * Registers a {@link BrooklynShutdownHooks#invokeStopOnShutdown(Entity)} to shutdown this entity when the JVM exits.
     * (Convenience method located in this class for easy access.)
     */
    public static void invokeStopOnShutdown(Entity entity) {
        BrooklynShutdownHooks.invokeStopOnShutdown(entity);
    }

    /** convenience for starting an entity, esp a new Startable instance which has been created dynamically
     * (after the application is started) */
    public static void start(Entity e, Collection<? extends Location> locations) {
        if (!isManaged(e) && !manage(e)) {
            log.warn(
                    "Using discouraged mechanism to start management -- Entities.start(Application, Locations) -- caller should create and use the preferred management context");
            startManagement(e);
        }
        if (e instanceof Startable)
            Entities.invokeEffector((EntityLocal) e, e, Startable.START, MutableMap.of("locations", locations))
                    .getUnchecked();
    }

    /**
     * Attempts to stop, destroy, and unmanage the given entity.
     * <p>
     * Actual actions performed will depend on the entity type and its current state.
     */
    public static void destroy(Entity e) {
        if (isManaged(e)) {
            if (isReadOnly(e)) {
                unmanage(e);
                log.debug("destroyed and unmanaged read-only copy of " + e);
            } else {
                if (e instanceof Startable)
                    Entities.invokeEffector((EntityLocal) e, e, Startable.STOP).getUnchecked();

                // if destroying gracefully we might also want to do this (currently gets done by GC after unmanage,
                // which is good enough for leaks, but not sure if that's ideal for subscriptions etc)
                //                ((LocalEntityManager)e.getApplication().getManagementContext().getEntityManager()).stopTasks(e, null);

                if (e instanceof EntityInternal)
                    ((EntityInternal) e).destroy();

                unmanage(e);

                log.debug("destroyed and unmanaged " + e + "; mgmt now "
                        + (e.getApplicationId() == null ? "(no app)" : e.getApplication().getManagementContext())
                        + " - managed? " + isManaged(e));
            }
        } else {
            log.debug("skipping destroy of " + e + ": not managed");
        }
    }

    /** Same as {@link #destroy(Entity)} but catching all errors. */
    public static void destroyCatching(Entity entity) {
        try {
            destroy(entity);
        } catch (Exception e) {
            log.warn("ERROR destroying " + entity + " (ignoring): " + e, e);
            Exceptions.propagateIfFatal(e);
        }
    }

    /** Destroys the given location. */
    public static void destroy(Location loc) {
        // TODO unmanage the location, if possible?
        if (loc instanceof Closeable) {
            Streams.closeQuietly((Closeable) loc);
            log.debug("closed " + loc);
        }
    }

    /** Same as {@link #destroy(Location)} but catching all errors. */
    public static void destroyCatching(Location loc) {
        try {
            destroy(loc);
        } catch (Exception e) {
            log.warn("ERROR destroying " + loc + " (ignoring): " + e, e);
            Exceptions.propagateIfFatal(e);
        }
    }

    /**
     * Stops, destroys, and unmanages all apps in the given context, and then terminates the management context.
     * 
     * Apps will be stopped+destroyed+unmanaged concurrently, waiting for all to complete.
     */
    public static void destroyAll(final ManagementContext mgmt) {
        if (mgmt instanceof NonDeploymentManagementContext) {
            // log here because it is easy for tests to destroyAll(app.getMgmtContext())
            // which will *not* destroy the mgmt context if the app has been stopped!
            log.warn("Entities.destroyAll invoked on non-deployment " + mgmt + " - not likely to have much effect! "
                    + "(This usually means the mgmt context has been taken from an entity that has been destroyed. "
                    + "To destroy other things on the management context ensure you keep a handle to the context "
                    + "before the entity is destroyed, such as by creating the management context first.)");
        }
        if (!mgmt.isRunning())
            return;

        ListeningExecutorService executor = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
        List<ListenableFuture<?>> futures = Lists.newArrayList();
        final AtomicReference<Exception> error = Atomics.newReference();
        try {
            log.debug("destroying all apps in " + mgmt + ": " + mgmt.getApplications());
            for (final Application app : mgmt.getApplications()) {
                futures.add(executor.submit(new Runnable() {
                    public void run() {
                        log.debug("destroying app " + app + " (managed? " + isManaged(app) + "; mgmt is " + mgmt
                                + ")");
                        try {
                            destroy(app);
                            log.debug("destroyed app " + app + "; mgmt now " + mgmt);
                        } catch (Exception e) {
                            log.warn("problems destroying app " + app + " (mgmt now " + mgmt
                                    + ", will rethrow at least one exception): " + e);
                            error.compareAndSet(null, e);
                        }
                    }
                }));
            }
            Futures.allAsList(futures).get();

            for (Location loc : mgmt.getLocationManager().getLocations()) {
                destroyCatching(loc);
            }
            if (mgmt instanceof ManagementContextInternal) {
                ((ManagementContextInternal) mgmt).terminate();
            }
            if (error.get() != null)
                throw Exceptions.propagate(error.get());
        } catch (InterruptedException e) {
            throw Exceptions.propagate(e);
        } catch (ExecutionException e) {
            throw Exceptions.propagate(e);
        } finally {
            executor.shutdownNow();
        }
    }

    /** Same as {@link #destroyAll(ManagementContext)} but catching all errors */
    public static void destroyAllCatching(ManagementContext mgmt) {
        try {
            destroyAll(mgmt);
        } catch (Exception e) {
            log.warn("ERROR destroying " + mgmt + " (ignoring): " + e, e);
            Exceptions.propagateIfFatal(e);
        }
    }

    public static boolean isManaged(Entity e) {
        return ((EntityInternal) e).getManagementSupport().isDeployed()
                && ((EntityInternal) e).getManagementContext().isRunning();
    }

    public static boolean isNoLongerManaged(Entity e) {
        return ((EntityInternal) e).getManagementSupport().isNoLongerManaged();
    }

    /** as {@link EntityManagerInternal#isReadOnly(Entity)} */
    @Beta
    public static Boolean isReadOnly(Entity e) {
        return ((EntityInternal) e).getManagementSupport().isReadOnly();
    }

    /** Unwraps a proxy to retrieve the real item, if available.
     * <p>
     * Only intended for use in tests and occasional internal usage, e.g. persistence.
     * For normal operations, callers should ensure the method is available on an interface and accessed via the proxy. */
    @Beta
    @VisibleForTesting
    public static AbstractEntity deproxy(Entity e) {
        if (!(Proxy.isProxyClass(e.getClass()))) {
            log.warn("Attempt to deproxy non-proxy " + e,
                    new Throwable("Location of attempt to deproxy non-proxy " + e));
            return (AbstractEntity) e;
        }
        return (AbstractEntity) ((EntityProxyImpl) Proxy.getInvocationHandler(e)).getDelegate();
    }

    /**
     * Brings this entity under management only if its ancestor is managed.
     * <p>
     * Returns true if successful, otherwise returns false in the expectation that the ancestor
     * will become managed, or throws exception if it has no parent or a non-application root.
     *
     * @throws IllegalStateException if {@literal e} is an {@link Application}.
     * @see #startManagement(Entity)
     */
    public static boolean manage(Entity e) {
        Entity o = e.getParent();
        Entity eum = e; // Highest unmanaged ancestor
        if (o == null)
            throw new IllegalArgumentException("Can't manage " + e + " because it is an orphan");
        while (o.getParent() != null) {
            if (!isManaged(o))
                eum = o;
            o = o.getParent();
        }
        if (isManaged(o)) {
            ((EntityInternal) o).getManagementContext().getEntityManager().manage(eum);
            return true;
        }
        if (!(o instanceof Application)) {
            throw new IllegalStateException("Can't manage " + e + " because it is not rooted at an application");
        }
        return false;
    }

    /**
     * Brings this entity under management, creating a local management context if necessary,
     * assuming root is an application.
     * <p>
     * Returns existing management context if there is one (non-deployment) or a new local management
     * context if not, or throws an exception if root is not an application. Callers are recommended
     * to use {@link #manage(Entity)} instead unless they know a plain-vanilla non-root management
     * context is sufficient e.g. in tests.
     * <p>
     * <b>NOTE</b> This method may change, but is provided as a stop-gap to prevent ad-hoc things
     * being done in the code which are even more likely to break!
     */
    @Beta
    public static ManagementContext startManagement(Entity e) {
        Entity o = e;
        Entity eum = e; // Highest unmanaged ancestor
        while (o.getParent() != null) {
            if (!isManaged(o))
                eum = o;
            o = o.getParent();
        }
        if (isManaged(o)) {
            ManagementContext mgmt = ((EntityInternal) o).getManagementContext();
            mgmt.getEntityManager().manage(eum);
            return mgmt;
        }
        if (!(o instanceof Application))
            throw new IllegalStateException("Can't manage " + e + " because it is not rooted at an application");

        log.warn("Deprecated invocation of startManagement for " + e + " without a management context present; "
                + "a new local management context is being created! (Not recommended unless you really know what you are doing.)");
        ManagementContext mgmt = new LocalManagementContext();
        mgmt.getEntityManager().manage(o);
        return mgmt;
    }

    /**
     * Starts managing the given (unmanaged) app, using the given management context.
     *
     * @see #startManagement(Entity)
     */
    public static ManagementContext startManagement(Application app, ManagementContext mgmt) {
        if (isManaged(app)) {
            throw new IllegalStateException(
                    "Application " + app + " is already managed, so can't set brooklyn properties");
        }
        mgmt.getEntityManager().manage(app);
        return mgmt;
    }

    /**
     * Starts managing the given (unmanaged) app, setting the given brooklyn properties on the new
     * management context.
     *
     * @see #startManagement(Entity)
     */
    public static ManagementContext startManagement(Application app, BrooklynProperties props) {
        if (isManaged(app)) {
            throw new IllegalStateException(
                    "Application " + app + " is already managed, so can't set brooklyn properties");
        }
        ManagementContext mgmt = new LocalManagementContext(props);
        mgmt.getEntityManager().manage(app);
        return mgmt;
    }

    public static ManagementContext newManagementContext() {
        return new LocalManagementContext();
    }

    public static ManagementContext newManagementContext(BrooklynProperties props) {
        return new LocalManagementContext(props);
    }

    public static ManagementContext newManagementContext(Map<?, ?> props) {
        return new LocalManagementContext(BrooklynProperties.Factory.newEmpty().addFromMap(props));
    }

    public static void unmanage(Entity entity) {
        if (((EntityInternal) entity).getManagementSupport().isDeployed()) {
            ((EntityInternal) entity).getManagementContext().getEntityManager().unmanage(entity);
        }
    }

    public static DownloadResolver newDownloader(EntityDriver driver) {
        return newDownloader(driver, ImmutableMap.<String, Object>of());
    }

    public static DownloadResolver newDownloader(EntityDriver driver, Map<String, ?> properties) {
        EntityInternal internal = (EntityInternal) driver.getEntity();
        return internal.getManagementContext().getEntityDownloadsManager().newDownloader(driver, properties);
    }

    public static DownloadResolver newDownloader(EntityDriver driver, String addon) {
        return newDownloader(driver, addon, ImmutableMap.<String, Object>of());
    }

    public static DownloadResolver newDownloader(EntityDriver driver, String addon, Map<String, ?> properties) {
        EntityInternal internal = (EntityInternal) driver.getEntity();
        return internal.getManagementContext().getEntityDownloadsManager().newDownloader(driver, addon, properties);
    }

    public static <T> Supplier<T> attributeSupplier(Entity entity, AttributeSensor<T> sensor) {
        return EntityAndAttribute.supplier(entity, sensor);
    }

    public static <T> Supplier<T> attributeSupplier(EntityAndAttribute<T> tuple) {
        return tuple;
    }

    public static <T> Supplier<T> attributeSupplierWhenReady(EntityAndAttribute<T> tuple) {
        return attributeSupplierWhenReady(tuple.getEntity(), tuple.getAttribute());
    }

    @SuppressWarnings({ "unchecked", "serial" })
    public static <T> Supplier<T> attributeSupplierWhenReady(final Entity entity, final AttributeSensor<T> sensor) {
        final Task<T> task = DependentConfiguration.attributeWhenReady(entity, sensor);
        return new Supplier<T>() {
            @Override
            public T get() {
                try {
                    TypeToken<T> type = new TypeToken<T>(sensor.getType()) {
                    };
                    return Tasks.resolveValue(task, (Class<T>) type.getRawType(),
                            ((EntityInternal) entity).getExecutionContext(), "attributeSupplierWhenReady");
                } catch (Exception e) {
                    throw Exceptions.propagate(e);
                }
            }
        };
    }

    /**
     * @since 0.6.0 Added only for backwards compatibility, where locations are being created directly.
     * @deprecated in 0.6.0; use {@link LocationManager#createLocation(LocationSpec)} instead
     */
    @Deprecated
    public static void manage(Location loc, ManagementContext managementContext) {
        Locations.manage(loc, managementContext);
    }

    /** Fails-fast if value of the given key is null or unresolveable. */
    public static String getRequiredUrlConfig(Entity entity, ConfigKey<String> urlKey) {
        String url = entity.getConfig(urlKey);
        Preconditions.checkNotNull(url, "Key %s on %s should not be null", urlKey, entity);
        if (!ResourceUtils.create(entity).doesUrlExist(url)) {
            throw new IllegalStateException(
                    String.format("Key %s on %s contains unavailable URL %s", urlKey, entity, url));
        }
        return url;
    }

    /** @see #getRequiredUrlConfig(Entity, ConfigKey) */
    public static String getRequiredUrlConfig(Entity entity, HasConfigKey<String> urlKey) {
        return getRequiredUrlConfig(entity, urlKey.getConfigKey());
    }

    /** Fails-fast if value of the given URL is null or unresolveable. */
    public static String checkRequiredUrl(Entity entity, String url) {
        Preconditions.checkNotNull(url, "url");
        if (!ResourceUtils.create(entity).doesUrlExist(url)) {
            throw new IllegalStateException(String.format("URL %s on %s is unavailable", url, entity));
        }
        return url;
    }

    /**
     * Submits a {@link TaskFactory} to construct its task at the entity (in a precursor task) and then to submit it.
     * <p>
     * Important if task construction relies on an entity being in scope (in tags, via {@link BrooklynTaskTags})
     */
    public static <T extends TaskAdaptable<?>> T submit(final Entity entity, final TaskFactory<T> taskFactory) {
        // TODO it is messy to have to do this, but not sure there is a cleaner way :(
        final Semaphore s = new Semaphore(0);
        final AtomicReference<T> result = new AtomicReference<T>();
        final ExecutionContext executionContext = ((EntityInternal) entity).getManagementSupport()
                .getExecutionContext();
        executionContext.execute(new Runnable() {
            // TODO could give this task a name, like "create task from factory"
            @Override
            public void run() {
                T t = taskFactory.newTask();
                result.set(t);
                s.release();
            }
        });
        try {
            s.acquire();
        } catch (InterruptedException e) {
            throw Exceptions.propagate(e);
        }
        executionContext.submit(result.get().asTask());
        return result.get();
    }

    /**
     * Submits a task to run at the entity.
     * 
     * @return the task passed in, for fluency
     */
    public static <T extends TaskAdaptable<?>> T submit(final Entity entity, final T task) {
        final ExecutionContext executionContext = ((EntityInternal) entity).getManagementSupport()
                .getExecutionContext();
        executionContext.submit(task.asTask());
        return task;
    }

    /** Logs a warning if an entity has a value for a config key. */
    public static void warnOnIgnoringConfig(Entity entity, ConfigKey<?> key) {
        if (entity.getConfigRaw(key, true).isPresentAndNonNull())
            log.warn("Ignoring " + key + " set on " + entity + " (" + entity.getConfig(key) + ")");
    }

    /** Waits until {@link Startable#SERVICE_UP} returns true. */
    public static void waitForServiceUp(final Entity entity, Duration timeout) {
        String description = "Waiting for SERVICE_UP on " + entity;
        Tasks.setBlockingDetails(description);
        try {
            if (!Repeater.create(description).limitTimeTo(timeout).rethrowException().backoffTo(Duration.ONE_SECOND)
                    .until(new Callable<Boolean>() {
                        public Boolean call() {
                            return Boolean.TRUE.equals(entity.getAttribute(Startable.SERVICE_UP));
                        }
                    }).run()) {
                throw new IllegalStateException("Timeout waiting for SERVICE_UP from " + entity);
            }
        } finally {
            Tasks.resetBlockingDetails();
        }
        log.debug("Detected SERVICE_UP for software {}", entity);
    }

    public static void waitForServiceUp(final Entity entity, long duration, TimeUnit units) {
        waitForServiceUp(entity, Duration.of(duration, units));
    }

    public static void waitForServiceUp(final Entity entity) {
        Duration timeout = entity.getConfig(BrooklynConfigKeys.START_TIMEOUT);
        waitForServiceUp(entity, timeout);
    }

    /**
     * Convenience for creating and submitted a given shell command against the given mgmt context,
     * primarily intended for use in the groovy GUI console.
     */
    @Beta
    public static ProcessTaskWrapper<Integer> shell(ManagementContext mgmt, String command) {
        ProcessTaskWrapper<Integer> t = SystemTasks.exec(command).newTask();
        mgmt.getServerExecutionContext().submit(t).getUnchecked();
        System.out.println(t.getStdout());
        System.err.println(t.getStderr());
        return t;
    }

}