You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by al...@apache.org on 2015/08/19 23:21:02 UTC

[28/62] [abbrv] incubator-brooklyn git commit: rename core’s o.a.b.entity to o.a.b.core.entity

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/EntityFunctions.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/EntityFunctions.java b/core/src/main/java/org/apache/brooklyn/core/entity/EntityFunctions.java
new file mode 100644
index 0000000..7ec70a1
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/entity/EntityFunctions.java
@@ -0,0 +1,153 @@
+/*
+ * 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.entity;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Collection;
+import java.util.Map;
+
+import org.apache.brooklyn.api.entity.Application;
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.EntityLocal;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.api.objs.Identifiable;
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic;
+import org.apache.brooklyn.util.core.flags.TypeCoercions;
+import org.apache.brooklyn.util.guava.Functionals;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
+import com.google.common.collect.Iterables;
+
+public class EntityFunctions {
+
+    public static <T> Function<Entity, T> attribute(final AttributeSensor<T> attribute) {
+        class GetEntityAttributeFunction implements Function<Entity, T> {
+            @Override public T apply(Entity input) {
+                return (input == null) ? null : input.getAttribute(attribute);
+            }
+        };
+        return new GetEntityAttributeFunction();
+    }
+    
+    public static <T> Function<Entity, T> config(final ConfigKey<T> key) {
+        class GetEntityConfigFunction implements Function<Entity, T> {
+            @Override public T apply(Entity input) {
+                return (input == null) ? null : input.getConfig(key);
+            }
+        };
+        return new GetEntityConfigFunction();
+    }
+    
+    public static Function<Entity, String> displayName() {
+        class GetEntityDisplayName implements Function<Entity, String> {
+            @Override public String apply(Entity input) {
+                return (input == null) ? null : input.getDisplayName();
+            }
+        };
+        return new GetEntityDisplayName();
+    }
+    
+    public static Function<Identifiable, String> id() {
+        class GetIdFunction implements Function<Identifiable, String> {
+            @Override public String apply(Identifiable input) {
+                return (input == null) ? null : input.getId();
+            }
+        };
+        return new GetIdFunction();
+    }
+
+    /** returns a function which sets the given sensors on the entity passed in,
+     * with {@link Entities#UNCHANGED} and {@link Entities#REMOVE} doing those actions. */
+    public static Function<Entity,Void> settingSensorsConstant(final Map<AttributeSensor<?>,Object> values) {
+        checkNotNull(values, "values");
+        class SettingSensorsConstantFunction implements Function<Entity, Void> {
+            @SuppressWarnings({ "unchecked", "rawtypes" })
+            @Override public Void apply(Entity input) {
+                for (Map.Entry<AttributeSensor<?>,Object> entry : values.entrySet()) {
+                    AttributeSensor sensor = (AttributeSensor)entry.getKey();
+                    Object value = entry.getValue();
+                    if (value==Entities.UNCHANGED) {
+                        // nothing
+                    } else if (value==Entities.REMOVE) {
+                        ((EntityInternal)input).removeAttribute(sensor);
+                    } else {
+                        value = TypeCoercions.coerce(value, sensor.getTypeToken());
+                        ((EntityInternal)input).setAttribute(sensor, value);
+                    }
+                }
+                return null;
+            }
+        }
+        return new SettingSensorsConstantFunction();
+    }
+
+    /** as {@link #settingSensorsConstant(Map)} but as a {@link Runnable} */
+    public static Runnable settingSensorsConstant(final Entity entity, final Map<AttributeSensor<?>,Object> values) {
+        checkNotNull(entity, "entity");
+        checkNotNull(values, "values");
+        return Functionals.runnable(Suppliers.compose(settingSensorsConstant(values), Suppliers.ofInstance(entity)));
+    }
+
+    public static <K,V> Function<Entity, Void> updatingSensorMapEntry(final AttributeSensor<Map<K,V>> mapSensor, final K key, final Supplier<? extends V> valueSupplier) {
+        class UpdatingSensorMapEntryFunction implements Function<Entity, Void> {
+            @Override public Void apply(Entity input) {
+                ServiceStateLogic.updateMapSensorEntry((EntityLocal)input, mapSensor, key, valueSupplier.get());
+                return null;
+            }
+        }
+        return new UpdatingSensorMapEntryFunction();
+    }
+    public static <K,V> Runnable updatingSensorMapEntry(final Entity entity, final AttributeSensor<Map<K,V>> mapSensor, final K key, final Supplier<? extends V> valueSupplier) {
+        return Functionals.runnable(Suppliers.compose(updatingSensorMapEntry(mapSensor, key, valueSupplier), Suppliers.ofInstance(entity)));
+    }
+
+    public static Supplier<Collection<Application>> applications(final ManagementContext mgmt) {
+        class AppsSupplier implements Supplier<Collection<Application>> {
+            @Override
+            public Collection<Application> get() {
+                return mgmt.getApplications();
+            }
+        }
+        return new AppsSupplier();
+    }
+    
+    public static Function<Entity, Location> locationMatching(Predicate<? super Location> filter) {
+        return new LocationMatching(filter);
+    }
+    
+    private static class LocationMatching implements Function<Entity, Location> {
+        private Predicate<? super Location> filter;
+        
+        private LocationMatching() { /* for xstream */
+        }
+        public LocationMatching(Predicate<? super Location> filter) {
+            this.filter = filter;
+        }
+        @Override public Location apply(Entity input) {
+            return Iterables.find(input.getLocations(), filter);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/EntityInitializers.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/EntityInitializers.java b/core/src/main/java/org/apache/brooklyn/core/entity/EntityInitializers.java
new file mode 100644
index 0000000..a258007
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/entity/EntityInitializers.java
@@ -0,0 +1,49 @@
+/*
+ * 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.entity;
+
+import java.util.List;
+
+import org.apache.brooklyn.api.entity.EntityInitializer;
+import org.apache.brooklyn.api.entity.EntityLocal;
+
+import com.google.common.collect.ImmutableList;
+
+public class EntityInitializers {
+
+    public static class AddTags implements EntityInitializer {
+        public final List<Object> tags;
+        
+        public AddTags(Object... tags) {
+            this.tags = ImmutableList.copyOf(tags);
+        }
+        
+        @Override
+        public void apply(EntityLocal entity) {
+            for (Object tag: tags)
+                entity.tags().addTag(tag);
+        }
+    }
+
+    
+    public static EntityInitializer addingTags(Object... tags) {
+        return new AddTags(tags);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/EntityInternal.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/EntityInternal.java b/core/src/main/java/org/apache/brooklyn/core/entity/EntityInternal.java
new file mode 100644
index 0000000..0147170
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/entity/EntityInternal.java
@@ -0,0 +1,201 @@
+/*
+ * 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.entity;
+
+import java.util.Collection;
+import java.util.Map;
+
+import org.apache.brooklyn.api.effector.Effector;
+import org.apache.brooklyn.api.entity.EntityLocal;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.mgmt.ExecutionContext;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.api.mgmt.SubscriptionContext;
+import org.apache.brooklyn.api.mgmt.rebind.RebindSupport;
+import org.apache.brooklyn.api.mgmt.rebind.Rebindable;
+import org.apache.brooklyn.api.mgmt.rebind.mementos.EntityMemento;
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.api.sensor.Feed;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.entity.internal.EntityConfigMap;
+import org.apache.brooklyn.core.mgmt.internal.EntityManagementSupport;
+import org.apache.brooklyn.core.objs.BrooklynObjectInternal;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+
+import com.google.common.annotations.Beta;
+
+/** 
+ * Extended Entity interface with additional functionality that is purely-internal (i.e. intended 
+ * for the brooklyn framework only).
+ */
+@Beta
+public interface EntityInternal extends BrooklynObjectInternal, EntityLocal, Rebindable {
+    
+    void addLocations(Collection<? extends Location> locations);
+
+    void removeLocations(Collection<? extends Location> locations);
+
+    void clearLocations();
+
+    /**
+     * 
+     * Like {@link EntityLocal#setAttribute(AttributeSensor, Object)}, except does not publish an attribute-change event.
+     */
+    <T> T setAttributeWithoutPublishing(AttributeSensor<T> sensor, T val);
+
+    /**
+     * @deprecated since 0.7.0; instead just use methods on {@link ConfigurationSupportInternal} returned by {@link #config()}
+     */
+    @Deprecated
+    EntityConfigMap getConfigMap();
+
+    /**
+     * @return a read-only copy of all the config key/value pairs on this entity.
+     * 
+     * @deprecated since 0.7.0; instead just use methods on {@link ConfigurationSupportInternal} returned by {@link #config()},
+     * e.g. getBag().getAllConfigAsConfigKeyMap().
+     */
+    @Deprecated
+    @Beta
+    Map<ConfigKey<?>,Object> getAllConfig();
+
+    /**
+     * Returns a read-only view of all the config key/value pairs on this entity, backed by a string-based map, 
+     * including config names that did not match anything on this entity.
+     * 
+     * @deprecated since 0.7.0; use {@link #config()}, such as {@code entity.config().getBag()}
+     */
+    @Deprecated
+    ConfigBag getAllConfigBag();
+
+    /**
+     * Returns a read-only view of the local (i.e. not inherited) config key/value pairs on this entity, 
+     * backed by a string-based map, including config names that did not match anything on this entity.
+     * 
+     * @deprecated since 0.7.0; use {@link #config()}, such as {@code entity.config().getLocalBag()}
+     */
+    @Deprecated
+    ConfigBag getLocalConfigBag();
+
+    @Beta
+    Map<AttributeSensor, Object> getAllAttributes();
+
+    @Beta
+    void removeAttribute(AttributeSensor<?> attribute);
+
+    /**
+     * 
+     * @deprecated since 0.7.0; use {@link #config()}, such as {@code entity.config().refreshInheritedConfig()}
+     */
+    @Deprecated
+    void refreshInheritedConfig();
+
+    /**
+     * Must be called before the entity is started.
+     * 
+     * @return this entity (i.e. itself)
+     */
+    @Beta // for internal use only
+    EntityInternal configure(Map flags);
+
+    /** 
+     * @return Routings for accessing and inspecting the management context of the entity
+     */
+    EntityManagementSupport getManagementSupport();
+
+    /**
+     * Should be invoked at end-of-life to clean up the item.
+     */
+    @Beta
+    void destroy();
+    
+    /** 
+     * Returns the management context for the entity. If the entity is not yet managed, some 
+     * operations on the management context will fail. 
+     * 
+     * Do not cache this object; instead call getManagementContext() each time you need to use it.
+     */
+    ManagementContext getManagementContext();
+
+    /** 
+     * Returns the task execution context for the entity. If the entity is not yet managed, some 
+     * operations on the management context will fail.
+     * 
+     * Do not cache this object; instead call getExecutionContext() each time you need to use it.
+     */    
+    ExecutionContext getExecutionContext();
+    
+    SubscriptionContext getSubscriptionContext();
+    
+    /** returns the dynamic type corresponding to the type of this entity instance */
+    @Beta
+    EntityDynamicType getMutableEntityType();
+
+    /** returns the effector registered against a given name */
+    @Beta
+    Effector<?> getEffector(String effectorName);
+    
+    FeedSupport feeds();
+    
+    /**
+     * @since 0.7.0-M2
+     * @deprecated since 0.7.0-M2; use {@link #feeds()}
+     */
+    @Deprecated
+    FeedSupport getFeedSupport();
+
+    Map<String, String> toMetadataRecord();
+    
+    /**
+     * Users are strongly discouraged from calling or overriding this method.
+     * It is for internal calls only, relating to persisting/rebinding entities.
+     * This method may change (or be removed) in a future release without notice.
+     */
+    @Override
+    @Beta
+    RebindSupport<EntityMemento> getRebindSupport();
+
+    /**
+     * Can be called to request that the entity be persisted.
+     * This persistence may happen asynchronously, or may not happen at all if persistence is disabled.
+     */
+    void requestPersist();
+    
+    public interface FeedSupport {
+        Collection<Feed> getFeeds();
+        
+        /**
+         * Adds the given feed to this entity. The feed will automatically be re-added on brooklyn restart.
+         */
+        <T extends Feed> T addFeed(T feed);
+        
+        /**
+         * Removes the given feed from this entity. 
+         * @return True if the feed existed at this entity; false otherwise
+         */
+        boolean removeFeed(Feed feed);
+        
+        /**
+         * Removes all feeds from this entity.
+         * Use with caution as some entities automatically register feeds; this will remove those feeds as well.
+         * @return True if any feeds existed at this entity; false otherwise
+         */
+        boolean removeAllFeeds();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/EntityPredicates.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/EntityPredicates.java b/core/src/main/java/org/apache/brooklyn/core/entity/EntityPredicates.java
new file mode 100644
index 0000000..a618784
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/entity/EntityPredicates.java
@@ -0,0 +1,451 @@
+/*
+ * 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.entity;
+
+import java.util.Collection;
+import java.util.regex.Pattern;
+
+import javax.annotation.Nullable;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.Group;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.config.ConfigKey.HasConfigKey;
+import org.apache.brooklyn.util.collections.CollectionFunctionals;
+import org.apache.brooklyn.util.guava.SerializablePredicate;
+import org.apache.brooklyn.util.javalang.Reflections;
+import org.apache.brooklyn.util.text.StringPredicates;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+
+@SuppressWarnings("serial")
+public class EntityPredicates {
+
+    public static Predicate<Entity> idEqualTo(final String val) {
+        return idSatisfies(Predicates.equalTo(val));
+    }
+    
+    public static Predicate<Entity> idSatisfies(final Predicate<? super String> condition) {
+        return new IdSatisfies(condition);
+    }
+    
+    protected static class IdSatisfies implements SerializablePredicate<Entity> {
+        protected final Predicate<? super String> condition;
+        protected IdSatisfies(Predicate<? super String> condition) {
+            this.condition = condition;
+        }
+        @Override
+        public boolean apply(@Nullable Entity input) {
+            return (input != null) && condition.apply(input.getId());
+        }
+        @Override
+        public String toString() {
+            return "idSatisfies("+condition+")";
+        }
+    }
+
+    /** @deprecated since 0.7.0 kept only to allow conversion of anonymous inner classes */
+    @SuppressWarnings("unused") @Deprecated 
+    private static <T> Predicate<Entity> idEqualToOld(final T val) {
+        return new SerializablePredicate<Entity>() {
+            @Override
+            public boolean apply(@Nullable Entity input) {
+                return (input != null) && Objects.equal(input.getId(), val);
+            }
+        };
+    }
+    
+    // ---------------------------
+    
+    public static Predicate<Entity> displayNameEqualTo(final String val) {
+        return displayNameSatisfies(Predicates.equalTo(val));
+    }
+    
+    public static Predicate<Entity> displayNameSatisfies(final Predicate<? super String> condition) {
+        return new DisplayNameSatisfies(condition);
+    }
+    
+    protected static class DisplayNameSatisfies implements SerializablePredicate<Entity> {
+        protected final Predicate<? super String> condition;
+        protected DisplayNameSatisfies(Predicate<? super String> condition) {
+            this.condition = condition;
+        }
+        @Override
+        public boolean apply(@Nullable Entity input) {
+            return (input != null) && condition.apply(input.getDisplayName());
+        }
+        @Override
+        public String toString() {
+            return "displayNameSatisfies("+condition+")";
+        }
+    }
+
+    /** @deprecated since 0.7.0 kept only to allow conversion of anonymous inner classes */
+    @SuppressWarnings("unused") @Deprecated 
+    private static <T> Predicate<Entity> displayNameEqualToOld(final T val) {
+        return new SerializablePredicate<Entity>() {
+            @Override
+            public boolean apply(@Nullable Entity input) {
+                return (input != null) && Objects.equal(input.getDisplayName(), val);
+            }
+        };
+    }
+    
+    /** @deprecated since 0.7.0 use {@link #displayNameSatisfies(Predicate)} to clarify this is *regex* matching
+     * (passing {@link StringPredicates#matchesRegex(String)} as the predicate) */
+    public static Predicate<Entity> displayNameMatches(final String regex) {
+        return displayNameSatisfies(StringPredicates.matchesRegex(regex));
+    }
+
+    /** @deprecated since 0.7.0 kept only to allow conversion of anonymous inner classes */
+    @SuppressWarnings("unused") @Deprecated 
+    private static class DisplayNameMatches implements SerializablePredicate<Entity> {
+        private final String regex;
+        DisplayNameMatches(String regex) {
+            this.regex = regex;
+        }
+        @Override
+        public boolean apply(@Nullable Entity input) {
+            return (input != null && input.getDisplayName() != null) && input.getDisplayName().matches(regex);
+        }
+        @Override
+        public String toString() {
+            return "DisplayNameMatches("+regex+")";
+        }
+    };
+    
+    // ---------------------------
+
+    public static Predicate<Entity> applicationIdEqualTo(final String val) {
+        return applicationIdSatisfies(Predicates.equalTo(val));
+    }
+
+    public static Predicate<Entity> applicationIdSatisfies(final Predicate<? super String> condition) {
+        return new ApplicationIdSatisfies(condition);
+    }
+
+    protected static class ApplicationIdSatisfies implements SerializablePredicate<Entity> {
+        protected final Predicate<? super String> condition;
+        protected ApplicationIdSatisfies(Predicate<? super String> condition) {
+            this.condition = condition;
+        }
+        @Override
+        public boolean apply(@Nullable Entity input) {
+            return (input != null) && condition.apply(input.getApplicationId());
+        }
+        @Override
+        public String toString() {
+            return "applicationIdSatisfies("+condition+")";
+        }
+    }
+
+    /** @deprecated since 0.7.0 kept only to allow conversion of anonymous inner classes */
+    @SuppressWarnings("unused") @Deprecated 
+    private static Predicate<Entity> applicationIdEqualToOld(final String val) {
+        return new SerializablePredicate<Entity>() {
+            @Override
+            public boolean apply(@Nullable Entity input) {
+                return (input != null) && val.equals(input.getApplicationId());
+            }
+        };
+    }
+
+    // ---------------------------
+    
+    public static <T> Predicate<Entity> attributeEqualTo(final AttributeSensor<T> attribute, final T val) {
+        return attributeSatisfies(attribute, Predicates.equalTo(val));
+    }
+    
+    public static <T> Predicate<Entity> attributeSatisfies(final AttributeSensor<T> attribute, final Predicate<T> condition) {
+        return new AttributeSatisfies<T>(attribute, condition);
+    }
+
+    protected static class AttributeSatisfies<T> implements SerializablePredicate<Entity> {
+        protected final AttributeSensor<T> attribute;
+        protected final Predicate<T> condition;
+        private AttributeSatisfies(AttributeSensor<T> attribute, Predicate<T> condition) {
+            this.attribute = attribute;
+            this.condition = condition;
+        }
+        @Override
+        public boolean apply(@Nullable Entity input) {
+            return (input != null) && condition.apply(input.getAttribute(attribute));
+        }
+        @Override
+        public String toString() {
+            return "attributeSatisfies("+attribute.getName()+","+condition+")";
+        }
+    }
+
+    /** @deprecated since 0.7.0 kept only to allow conversion of anonymous inner classes */
+    @SuppressWarnings("unused") @Deprecated 
+    private static <T> Predicate<Entity> attributeEqualToOld(final AttributeSensor<T> attribute, final T val) {
+        return new SerializablePredicate<Entity>() {
+            @Override
+            public boolean apply(@Nullable Entity input) {
+                return (input != null) && Objects.equal(input.getAttribute(attribute), val);
+            }
+        };
+    }
+    
+    public static <T> Predicate<Entity> attributeNotEqualTo(final AttributeSensor<T> attribute, final T val) {
+        return attributeSatisfies(attribute, Predicates.not(Predicates.equalTo(val)));
+    }
+
+    // ---------------------------
+
+    public static <T> Predicate<Entity> configEqualTo(final ConfigKey<T> configKey, final T val) {
+        return configSatisfies(configKey, Predicates.equalTo(val));
+    }
+
+    public static <T> Predicate<Entity> configSatisfies(final ConfigKey<T> configKey, final Predicate<T> condition) {
+        return new ConfigKeySatisfies<T>(configKey, condition);
+    }
+
+    public static <T> Predicate<Entity> configEqualTo(final HasConfigKey<T> configKey, final T val) {
+        return configEqualTo(configKey.getConfigKey(), val);
+    }
+
+    public static <T> Predicate<Entity> configSatisfies(final HasConfigKey<T> configKey, final Predicate<T> condition) {
+        return new ConfigKeySatisfies<T>(configKey.getConfigKey(), condition);
+    }
+
+    protected static class ConfigKeySatisfies<T> implements SerializablePredicate<Entity> {
+        protected final ConfigKey<T> configKey;
+        protected final Predicate<T> condition;
+        private ConfigKeySatisfies(ConfigKey<T> configKey, Predicate<T> condition) {
+            this.configKey = configKey;
+            this.condition = condition;
+        }
+        @Override
+        public boolean apply(@Nullable Entity input) {
+            return (input != null) && condition.apply(input.getConfig(configKey));
+        }
+        @Override
+        public String toString() {
+            return "configKeySatisfies("+configKey.getName()+","+condition+")";
+        }
+    }
+
+    
+    /** @deprecated since 0.7.0 kept only to allow conversion of anonymous inner classes */
+    @SuppressWarnings("unused") @Deprecated 
+    private static <T> Predicate<Entity> configEqualToOld(final ConfigKey<T> configKey, final T val) {
+        return new SerializablePredicate<Entity>() {
+            @Override
+            public boolean apply(@Nullable Entity input) {
+                return (input != null) && Objects.equal(input.getConfig(configKey), val);
+            }
+        };
+    }
+
+    /** @deprecated since 0.7.0 kept only to allow conversion of anonymous inner classes */
+    @SuppressWarnings("unused") @Deprecated 
+    private static <T> Predicate<Entity> configEqualToOld(final HasConfigKey<T> configKey, final T val) {
+        return new SerializablePredicate<Entity>() {
+            @Override
+            public boolean apply(@Nullable Entity input) {
+                return (input != null) && Objects.equal(input.getConfig(configKey), val);
+            }
+        };
+    }
+
+    // ---------------------------
+
+    /**
+     * @param typeRegex a regular expression
+     * @return true if any of the interfaces implemented by the entity (including those derived) match typeRegex.
+     */
+    public static Predicate<Entity> hasInterfaceMatching(String typeRegex) {
+        return new ImplementsInterface(typeRegex);
+    }
+
+    protected static class ImplementsInterface implements SerializablePredicate<Entity> {
+        protected final Pattern pattern;
+
+        public ImplementsInterface(String typeRegex) {
+            this.pattern = Pattern.compile(typeRegex);
+        }
+
+        @Override
+        public boolean apply(@Nullable Entity input) {
+            if (input == null) return false;
+            for (Class<?> cls : Reflections.getAllInterfaces(input.getClass())) {
+                if (pattern.matcher(cls.getName()).matches()) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    // ---------------------------
+
+    /**
+     * Returns a predicate that determines if a given entity is a direct child of this {@code parent}.
+     */
+    public static Predicate<Entity> isChildOf(final Entity parent) {
+        return new IsChildOf(parent);
+    }
+
+    // if needed, could add parentSatisfies(...)
+    
+    protected static class IsChildOf implements SerializablePredicate<Entity> {
+        protected final Entity parent;
+        protected IsChildOf(Entity parent) {
+            this.parent = parent;
+        }
+        @Override
+        public boolean apply(@Nullable Entity input) {
+            return (input != null) && Objects.equal(input.getParent(), parent);
+        }
+        @Override
+        public String toString() {
+            return "isChildOf("+parent+")";
+        }
+    }
+
+    /** @deprecated since 0.7.0 kept only to allow conversion of anonymous inner classes */
+    @SuppressWarnings("unused") @Deprecated 
+    private static <T> Predicate<Entity> isChildOfOld(final Entity parent) {
+        return new SerializablePredicate<Entity>() {
+            @Override
+            public boolean apply(@Nullable Entity input) {
+                return (input != null) && Objects.equal(input.getParent(), parent);
+            }
+        };
+    }
+
+    // ---------------------------
+    
+    public static Predicate<Entity> isMemberOf(final Group group) {
+        return new IsMemberOf(group);
+    }
+
+    protected static class IsMemberOf implements SerializablePredicate<Entity> {
+        protected final Group group;
+        protected IsMemberOf(Group group) {
+            this.group = group;
+        }
+        @Override
+        public boolean apply(@Nullable Entity input) {
+            return (group != null) && (input != null) && group.hasMember(input);
+        }
+        @Override
+        public String toString() {
+            return "isMemberOf("+group+")";
+        }
+    }
+
+    /** @deprecated since 0.7.0 kept only to allow conversion of anonymous inner classes */
+    @SuppressWarnings("unused") @Deprecated 
+    private static <T> Predicate<Entity> isMemberOfOld(final Group group) {
+        return new SerializablePredicate<Entity>() {
+            @Override
+            public boolean apply(@Nullable Entity input) {
+                return (input != null) && group.hasMember(input);
+            }
+        };
+    }
+
+    // ---------------------------
+
+    /**
+     * Create a predicate that matches any entity who has an exact match for the given location
+     * (i.e. {@code entity.getLocations().contains(location)}).
+     */
+    public static <T> Predicate<Entity> locationsIncludes(Location location) {
+        return locationsSatisfy(CollectionFunctionals.contains(location));
+        
+    }
+    
+    public static <T> Predicate<Entity> locationsSatisfy(final Predicate<Collection<Location>> condition) {
+        return new LocationsSatisfy(condition);
+    }
+
+    protected static class LocationsSatisfy implements SerializablePredicate<Entity> {
+        protected final Predicate<Collection<Location>> condition;
+        protected LocationsSatisfy(Predicate<Collection<Location>> condition) {
+            this.condition = condition;
+        }
+        @Override
+        public boolean apply(@Nullable Entity input) {
+            return (input != null) && condition.apply(input.getLocations());
+        }
+        @Override
+        public String toString() {
+            return "locationsSatisfy("+condition+")";
+        }
+    }
+
+    /** @deprecated since 0.7.0 use {@link #locationsIncludes(Location)} */
+    @Deprecated 
+    public static <T> Predicate<Entity> withLocation(final Location location) {
+        return locationsIncludes(location);
+    }
+    
+    /** @deprecated since 0.7.0 use {@link #locationsIncludes(Location)}, introduced to allow deserialization of anonymous inner class */
+    @SuppressWarnings("unused") @Deprecated 
+    private static <T> Predicate<Entity> withLocationOld(final Location location) {
+        return new SerializablePredicate<Entity>() {
+            @Override
+            public boolean apply(@Nullable Entity input) {
+                return (input != null) && input.getLocations().contains(location);
+            }
+        };
+    }
+    
+    // ---------------------------
+
+    public static <T> Predicate<Entity> isManaged() {
+        return new IsManaged();
+    }
+
+    protected static class IsManaged implements SerializablePredicate<Entity> {
+        @Override
+        public boolean apply(@Nullable Entity input) {
+            return (input != null) && Entities.isManaged(input);
+        }
+        @Override
+        public String toString() {
+            return "isManaged()";
+        }
+    }
+
+    /** @deprecated since 0.7.0 use {@link #isManaged()} */ @Deprecated
+    public static <T> Predicate<Entity> managed() {
+        return isManaged();
+    }
+
+    /** @deprecated since 0.7.0 use {@link #isManaged()}, introduced to allow deserialization of anonymous inner class */
+    @SuppressWarnings("unused") @Deprecated
+    private static <T> Predicate<Entity> managedOld() {
+        return new SerializablePredicate<Entity>() {
+            @Override
+            public boolean apply(@Nullable Entity input) {
+                return (input != null) && Entities.isManaged(input);
+            }
+        };
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/EntitySuppliers.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/EntitySuppliers.java b/core/src/main/java/org/apache/brooklyn/core/entity/EntitySuppliers.java
new file mode 100644
index 0000000..0e99f27
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/entity/EntitySuppliers.java
@@ -0,0 +1,47 @@
+/*
+ * 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.entity;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.location.core.Machines;
+import org.apache.brooklyn.location.ssh.SshMachineLocation;
+
+import com.google.common.base.Supplier;
+
+public class EntitySuppliers {
+
+    public static Supplier<SshMachineLocation> uniqueSshMachineLocation(Entity entity) {
+        return new UniqueSshMchineLocation(entity);
+    }
+    
+    private static class UniqueSshMchineLocation implements Supplier<SshMachineLocation> {
+        private Entity entity;
+
+        private UniqueSshMchineLocation() { /* for xstream */
+        }
+        
+        private UniqueSshMchineLocation(Entity entity) {
+            this.entity = entity;
+        }
+        
+        @Override public SshMachineLocation get() {
+            return Machines.findUniqueSshMachineLocation(entity.getLocations()).get();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/EntityTasks.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/EntityTasks.java b/core/src/main/java/org/apache/brooklyn/core/entity/EntityTasks.java
new file mode 100644
index 0000000..ba2992f
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/entity/EntityTasks.java
@@ -0,0 +1,81 @@
+/*
+ * 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.entity;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.mgmt.Task;
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.sensor.core.DependentConfiguration;
+import org.apache.brooklyn.util.collections.CollectionFunctionals;
+import org.apache.brooklyn.util.time.Duration;
+
+import com.google.common.base.Functions;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+
+/** Generally useful tasks related to entities */
+public class EntityTasks {
+
+    /** creates an (unsubmitted) task which waits for the attribute to satisfy the given predicate,
+     * returning false if it times out or becomes unmanaged */
+    public static <T> Task<Boolean> testingAttributeEventually(Entity entity, AttributeSensor<T> sensor, Predicate<T> condition, Duration timeout) {
+        return DependentConfiguration.builder().attributeWhenReady(entity, sensor)
+            .readiness(condition)
+            .postProcess(Functions.constant(true))
+            .timeout(timeout)
+            .onTimeoutReturn(false)
+            .onUnmanagedReturn(false)
+            .build();
+    }
+
+    /** creates an (unsubmitted) task which waits for the attribute to satisfy the given predicate,
+     * throwing if it times out or becomes unmanaged */
+    public static <T> Task<Boolean> requiringAttributeEventually(Entity entity, AttributeSensor<T> sensor, Predicate<T> condition, Duration timeout) {
+        return DependentConfiguration.builder().attributeWhenReady(entity, sensor)
+            .readiness(condition)
+            .postProcess(Functions.constant(true))
+            .timeout(timeout)
+            .onTimeoutThrow()
+            .onUnmanagedThrow()
+            .build();
+    }
+
+    /** as {@link #testingAttributeEventually(Entity, AttributeSensor, Predicate, Duration) for multiple entities */
+    public static <T> Task<Boolean> testingAttributeEventually(Iterable<Entity> entities, AttributeSensor<T> sensor, Predicate<T> condition, Duration timeout) {
+        return DependentConfiguration.builder().attributeWhenReadyFromMultiple(entities, sensor, condition)
+            .postProcess(Functions.constant(true))
+            .timeout(timeout)
+            .onTimeoutReturn(false)
+            .onUnmanagedReturn(false)
+            .postProcessFromMultiple(CollectionFunctionals.all(Predicates.equalTo(true)))
+            .build();
+    }
+    
+    /** as {@link #requiringAttributeEventually(Entity, AttributeSensor, Predicate, Duration) for multiple entities */
+    public static <T> Task<Boolean> requiringAttributeEventually(Iterable<Entity> entities, AttributeSensor<T> sensor, Predicate<T> condition, Duration timeout) {
+        return DependentConfiguration.builder().attributeWhenReadyFromMultiple(entities, sensor, condition)
+            .postProcess(Functions.constant(true))
+            .timeout(timeout)
+            .onTimeoutThrow()
+            .onUnmanagedThrow()
+            .postProcessFromMultiple(CollectionFunctionals.all(Predicates.equalTo(true)))
+            .build();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/EntityTypeSnapshot.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/EntityTypeSnapshot.java b/core/src/main/java/org/apache/brooklyn/core/entity/EntityTypeSnapshot.java
new file mode 100644
index 0000000..ef5c710
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/entity/EntityTypeSnapshot.java
@@ -0,0 +1,126 @@
+/*
+ * 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.entity;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+import org.apache.brooklyn.api.effector.Effector;
+import org.apache.brooklyn.api.effector.ParameterType;
+import org.apache.brooklyn.api.entity.EntityType;
+import org.apache.brooklyn.api.sensor.Sensor;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.objs.BrooklynTypeSnapshot;
+import org.apache.brooklyn.util.guava.Maybe;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Objects;
+import com.google.common.base.Objects.ToStringHelper;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+public class EntityTypeSnapshot extends BrooklynTypeSnapshot implements EntityType {
+    private static final long serialVersionUID = 4670930188951106009L;
+    
+    private final Map<String, Sensor<?>> sensors;
+    private final Set<Effector<?>> effectors;
+    private final Set<Sensor<?>> sensorsSet;
+
+    EntityTypeSnapshot(String name, Map<String, ConfigKey<?>> configKeys, Map<String, Sensor<?>> sensors, Collection<Effector<?>> effectors) {
+        super(name, configKeys);
+        this.sensors = ImmutableMap.copyOf(sensors);
+        this.effectors = ImmutableSet.copyOf(effectors);
+        this.sensorsSet = ImmutableSet.copyOf(this.sensors.values());
+    }
+
+    @Override
+    public Set<Sensor<?>> getSensors() {
+        return sensorsSet;
+    }
+    
+    @Override
+    public Set<Effector<?>> getEffectors() {
+        return effectors;
+    }
+
+    @Override
+    public Maybe<Effector<?>> getEffectorByName(String name) {
+        for (Effector<?> contender : effectors) {
+            if (name.equals(contender.getName()))
+                return Maybe.<Effector<?>>of(contender);
+        }
+        return Maybe.<Effector<?>>absent("No effector matching '"+name+"'");        
+    }
+    
+    @Override
+    public Effector<?> getEffector(String name, Class<?>... parameterTypes) {
+        // TODO Could index for more efficient lookup (e.g. by name in a MultiMap, or using name+parameterTypes as a key)
+        // TODO Looks for exact match; could go for what would be valid to call (i.e. if parameterType is sub-class of ParameterType.getParameterClass then ok)
+        // TODO Could take into account ParameterType.getDefaultValue() for what can be omitted
+        
+        effectorLoop : for (Effector<?> contender : effectors) {
+            if (name.equals(contender.getName())) {
+                List<ParameterType<?>> contenderParameters = contender.getParameters();
+                if (parameterTypes.length == contenderParameters.size()) {
+                    for (int i = 0; i < parameterTypes.length; i++) {
+                        if (parameterTypes[i] != contenderParameters.get(i).getParameterClass()) {
+                            continue effectorLoop;
+                        }
+                    }
+                    return contender;
+                }
+            }
+        }
+        throw new NoSuchElementException("No matching effector "+name+"("+Joiner.on(", ").join(parameterTypes)+") on entity "+getName());
+    }
+
+    @Override
+    public Sensor<?> getSensor(String name) {
+        return sensors.get(name);
+    }
+
+    @Override
+    public boolean hasSensor(String name) {
+        return sensors.containsKey(name);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(super.hashCode(), sensors, effectors);
+    }
+    
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) return true;
+        if (!(obj instanceof EntityTypeSnapshot)) return false;
+        EntityTypeSnapshot o = (EntityTypeSnapshot) obj;
+        
+        return super.equals(obj) && Objects.equal(sensors, o.sensors) && Objects.equal(effectors, o.effectors);
+    }
+    
+    @Override
+    protected ToStringHelper toStringHelper() {
+        return super.toStringHelper()
+                .add("sensors", sensors)
+                .add("effectors", effectors);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/EntityTypes.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/EntityTypes.java b/core/src/main/java/org/apache/brooklyn/core/entity/EntityTypes.java
new file mode 100644
index 0000000..ebedb65
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/entity/EntityTypes.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.core.entity;
+
+import org.apache.brooklyn.core.objs.BrooklynTypes;
+
+/**
+ * @deprecated since 0.7.0; use {@link BrooklynTypes}
+ */
+@Deprecated
+public class EntityTypes extends BrooklynTypes {
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/StartableApplication.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/StartableApplication.java b/core/src/main/java/org/apache/brooklyn/core/entity/StartableApplication.java
new file mode 100644
index 0000000..c0e27c0
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/entity/StartableApplication.java
@@ -0,0 +1,25 @@
+/*
+ * 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.entity;
+
+import org.apache.brooklyn.api.entity.Application;
+import org.apache.brooklyn.core.entity.trait.Startable;
+
+public interface StartableApplication extends Application, Startable {
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/drivers/BasicEntityDriverManager.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/drivers/BasicEntityDriverManager.java b/core/src/main/java/org/apache/brooklyn/core/entity/drivers/BasicEntityDriverManager.java
new file mode 100644
index 0000000..b7c3bd5
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/entity/drivers/BasicEntityDriverManager.java
@@ -0,0 +1,56 @@
+/*
+ * 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.entity.drivers;
+
+import org.apache.brooklyn.api.entity.drivers.DriverDependentEntity;
+import org.apache.brooklyn.api.entity.drivers.EntityDriver;
+import org.apache.brooklyn.api.entity.drivers.EntityDriverManager;
+import org.apache.brooklyn.api.location.Location;
+
+import com.google.common.annotations.Beta;
+
+public class BasicEntityDriverManager implements EntityDriverManager {
+
+    private final RegistryEntityDriverFactory registry;
+    private final ReflectiveEntityDriverFactory reflective;
+    
+    public BasicEntityDriverManager() {
+        registry = new RegistryEntityDriverFactory();
+        reflective = new ReflectiveEntityDriverFactory();
+    }
+    
+    /** driver override mechanism; experimental @since 0.7.0 */
+    @Beta
+    public ReflectiveEntityDriverFactory getReflectiveDriverFactory() {
+        return reflective;
+    }
+    
+    public <D extends EntityDriver> void registerDriver(Class<D> driverInterface, Class<? extends Location> locationClazz, Class<? extends D> driverClazz) {
+        registry.registerDriver(driverInterface, locationClazz, driverClazz);
+    }
+    
+    @Override
+    public <D extends EntityDriver> D build(DriverDependentEntity<D> entity, Location location){
+        if (registry.hasDriver(entity, location)) {
+            return registry.build(entity, location);
+        } else {
+            return reflective.build(entity, location);
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/drivers/ReflectiveEntityDriverFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/drivers/ReflectiveEntityDriverFactory.java b/core/src/main/java/org/apache/brooklyn/core/entity/drivers/ReflectiveEntityDriverFactory.java
new file mode 100644
index 0000000..19973f2
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/entity/drivers/ReflectiveEntityDriverFactory.java
@@ -0,0 +1,277 @@
+/*
+ * 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.entity.drivers;
+
+import java.lang.reflect.Constructor;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.brooklyn.api.entity.drivers.DriverDependentEntity;
+import org.apache.brooklyn.api.entity.drivers.EntityDriver;
+import org.apache.brooklyn.api.location.Location;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.brooklyn.location.paas.PaasLocation;
+import org.apache.brooklyn.location.ssh.SshMachineLocation;
+import org.apache.brooklyn.location.winrm.WinRmMachineLocation;
+import org.apache.brooklyn.util.collections.MutableList;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.exceptions.ReferenceWithError;
+import org.apache.brooklyn.util.text.Strings;
+
+/**
+ * Follows a class naming convention: the driver interface typically ends in "Driver", and the implementation 
+ * must match the driver interface name but with a suffix like "SshDriver" instead of "Driver".
+ * Other rules can be added using {@link #addRule(String, DriverInferenceRule)} or
+ * {@link #addClassFullNameMapping(String, String)}.
+ * <p>
+ * Reflectively instantiates and returns the driver, based on the location passed in,
+ * in {@link #build(DriverDependentEntity, Location)}.
+ * 
+ * @author Peter Veentjer, Alex Heneveld
+ */
+public class ReflectiveEntityDriverFactory {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ReflectiveEntityDriverFactory.class);
+
+    /** Rules, keyed by a unique identifier.  Executed in order of most-recently added first. */
+    protected final Map<String,DriverInferenceRule> rules = MutableMap.of();
+    
+    public ReflectiveEntityDriverFactory() {
+        addRule(DriverInferenceForSshLocation.DEFAULT_IDENTIFIER, new DriverInferenceForSshLocation());
+        addRule(DriverInferenceForPaasLocation.DEFAULT_IDENTIFIER, new DriverInferenceForPaasLocation());
+        addRule(DriverInferenceForWinRmLocation.DEFAULT_IDENTIFIER, new DriverInferenceForWinRmLocation());
+    }
+    
+    public interface DriverInferenceRule {
+        public <D extends EntityDriver> ReferenceWithError<Class<? extends D>> resolve(DriverDependentEntity<D> entity, Class<D> driverInterface, Location location);
+    }
+
+    public static abstract class AbstractDriverInferenceRule implements DriverInferenceRule {
+
+        @Override
+        public <D extends EntityDriver> ReferenceWithError<Class<? extends D>> resolve(DriverDependentEntity<D> entity, Class<D> driverInterface, Location location) {
+            try {
+                String newName = inferDriverClassName(entity, driverInterface, location);
+                if (newName==null) return null;
+
+                return loadDriverClass(newName, entity, driverInterface);
+                
+            } catch (Exception e) {
+                Exceptions.propagateIfFatal(e);
+                return ReferenceWithError.newInstanceThrowingError(null, e);
+            }
+        }
+
+        public abstract <D extends EntityDriver> String inferDriverClassName(DriverDependentEntity<D> entity, Class<D> driverInterface, Location location);
+
+        protected <D extends EntityDriver> ReferenceWithError<Class<? extends D>> loadDriverClass(String className, DriverDependentEntity<D> entity, Class<D> driverInterface) {
+            ReferenceWithError<Class<? extends D>> r1 = loadClass(className, entity.getClass().getClassLoader());
+            if (!r1.hasError()) return r1;
+            ReferenceWithError<Class<? extends D>> r2 = loadClass(className, driverInterface.getClass().getClassLoader());
+            if (!r2.hasError()) return r2;
+            return r1;
+        }
+
+        @SuppressWarnings({ "unchecked", "rawtypes" })
+        protected <D extends EntityDriver> ReferenceWithError<Class<? extends D>> loadClass(String className, ClassLoader classLoader) {
+            try {
+                return (ReferenceWithError<Class<? extends D>>)(ReferenceWithError) ReferenceWithError.newInstanceWithoutError((Class<? extends EntityDriver>)classLoader.loadClass(className));
+            } catch (Exception e) {
+                Exceptions.propagateIfFatal(e);
+                return ReferenceWithError.newInstanceThrowingError(null, e);
+            }
+        }
+    }
+    
+    public static abstract class AbstractDriverInferenceRenamingInferenceRule extends AbstractDriverInferenceRule {
+
+        protected final String expectedPattern;
+        protected final String replacement;
+
+        public AbstractDriverInferenceRenamingInferenceRule(String expectedPattern, String replacement) {
+            this.expectedPattern = expectedPattern;
+            this.replacement = replacement;
+        }
+        
+        public String getIdentifier() {
+            return getClass().getName()+"["+expectedPattern+"]";
+        }
+        
+        @Override
+        public String toString() {
+            return getClass().getName()+"["+expectedPattern+"->"+replacement+"]";
+        }
+    }
+    
+    public static class DriverInferenceByRenamingClassFullName extends AbstractDriverInferenceRenamingInferenceRule {
+
+        public DriverInferenceByRenamingClassFullName(String expectedClassFullName, String newClassFullName) {
+            super(expectedClassFullName, newClassFullName);
+        }
+        
+        @Override
+        public <D extends EntityDriver> String inferDriverClassName(DriverDependentEntity<D> entity, Class<D> driverInterface, Location location) {
+            if (driverInterface.getName().equals(expectedPattern)) {
+                return replacement;
+            }
+            return null;
+        }
+    }
+    
+    public static class DriverInferenceByRenamingClassSimpleName extends AbstractDriverInferenceRenamingInferenceRule {
+
+        public DriverInferenceByRenamingClassSimpleName(String expectedClassSimpleName, String newClassSimpleName) {
+            super(expectedClassSimpleName, newClassSimpleName);
+        }
+        
+        @Override
+        public <D extends EntityDriver> String inferDriverClassName(DriverDependentEntity<D> entity, Class<D> driverInterface, Location location) {
+            if (driverInterface.getSimpleName().equals(expectedPattern)) { 
+                // i'd like to do away with drivers altogether, but if people *really* need to use this and suppress the warning,
+                // they can use the full class rename
+                LOG.warn("Using discouraged driver simple class rename to find "+replacement+" for "+expectedPattern+"; it is recommended to set getDriverInterface() or newDriver() appropriately");
+                return Strings.removeFromEnd(driverInterface.getName(), expectedPattern)+replacement;
+            }
+            return null;
+        }
+    }
+    
+    public static class DriverInferenceForSshLocation extends AbstractDriverInferenceRule {
+
+        public static final String DEFAULT_IDENTIFIER = "ssh-location-driver-inference-rule";
+
+        @Override
+        public <D extends EntityDriver> String inferDriverClassName(DriverDependentEntity<D> entity, Class<D> driverInterface, Location location) {
+            String driverInterfaceName = driverInterface.getName();
+            if (!(location instanceof SshMachineLocation)) return null;
+            if (!driverInterfaceName.endsWith("Driver")) {
+                throw new IllegalArgumentException(String.format("Driver name [%s] doesn't end with 'Driver'; cannot auto-detect SshDriver class name", driverInterfaceName));
+            }
+            return Strings.removeFromEnd(driverInterfaceName, "Driver")+"SshDriver";
+        }
+    }
+
+    public static class DriverInferenceForPaasLocation extends AbstractDriverInferenceRule {
+
+        public static final String DEFAULT_IDENTIFIER = "paas-location-driver-inference-rule";
+
+        @Override
+        public <D extends EntityDriver> String inferDriverClassName(DriverDependentEntity<D> entity, Class<D> driverInterface, Location location) {
+            String driverInterfaceName = driverInterface.getName();
+            if (!(location instanceof PaasLocation)) return null;
+            if (!driverInterfaceName.endsWith("Driver")) {
+                throw new IllegalArgumentException(String.format("Driver name [%s] doesn't end with 'Driver'; cannot auto-detect PaasDriver class name", driverInterfaceName));
+            }
+            return Strings.removeFromEnd(driverInterfaceName, "Driver") + ((PaasLocation) location).getPaasProviderName() + "Driver";
+        }
+    }
+
+    public static class DriverInferenceForWinRmLocation extends AbstractDriverInferenceRule {
+
+        public static final String DEFAULT_IDENTIFIER = "winrm-location-driver-inference-rule";
+
+        @Override
+        public <D extends EntityDriver> String inferDriverClassName(DriverDependentEntity<D> entity, Class<D> driverInterface, Location location) {
+            String driverInterfaceName = driverInterface.getName();
+            if (!(location instanceof WinRmMachineLocation)) return null;
+            if (!driverInterfaceName.endsWith("Driver")) {
+                throw new IllegalArgumentException(String.format("Driver name [%s] doesn't end with 'Driver'; cannot auto-detect WinRmDriver class name", driverInterfaceName));
+            }
+            return Strings.removeFromEnd(driverInterfaceName, "Driver")+"WinRmDriver";
+        }
+    }
+
+    /** adds a rule; possibly replacing an old one if one exists with the given identifier. the new rule is added after all previous ones.
+     * @return the replaced rule, or null if there was no old rule */
+    public DriverInferenceRule addRule(String identifier, DriverInferenceRule rule) {
+        DriverInferenceRule oldRule = rules.remove(identifier);
+        rules.put(identifier, rule);
+        LOG.debug("Added driver mapping rule "+rule);
+        return oldRule;
+    }
+
+    public DriverInferenceRule addClassFullNameMapping(String expectedClassFullName, String newClassFullName) {
+        DriverInferenceByRenamingClassFullName rule = new DriverInferenceByRenamingClassFullName(expectedClassFullName, newClassFullName);
+        return addRule(rule.getIdentifier(), rule);
+    }
+
+    public DriverInferenceRule addClassSimpleNameMapping(String expectedClassSimpleName, String newClassSimpleName) {
+        DriverInferenceByRenamingClassSimpleName rule = new DriverInferenceByRenamingClassSimpleName(expectedClassSimpleName, newClassSimpleName);
+        return addRule(rule.getIdentifier(), rule);
+    }
+
+    public <D extends EntityDriver> D build(DriverDependentEntity<D> entity, Location location){
+        Class<D> driverInterface = entity.getDriverInterface();
+        Class<? extends D> driverClass = null;
+        List<Throwable> exceptions = MutableList.of();
+        if (driverInterface.isInterface()) {
+            List<DriverInferenceRule> ruleListInExecutionOrder = MutableList.copyOf(rules.values());
+            Collections.reverse(ruleListInExecutionOrder);
+            // above puts rules in order with most recently added first
+            for (DriverInferenceRule rule: ruleListInExecutionOrder) {
+                ReferenceWithError<Class<? extends D>> clazzR = rule.resolve(entity, driverInterface, location);
+                if (clazzR!=null) {
+                    if (!clazzR.hasError()) {
+                        Class<? extends D> clazz = clazzR.get();
+                        if (clazz!=null) {
+                            driverClass = clazz;
+                            break;
+                        }
+                    } else {
+                        exceptions.add(clazzR.getError());
+                    }
+                }
+            }
+        } else {
+            driverClass = driverInterface;
+        }
+        LOG.debug("Driver for "+driverInterface.getName()+" in "+location+" is: "+driverClass);
+
+        if (driverClass==null) {
+            if (exceptions.isEmpty())
+                throw new RuntimeException("No drivers could be found for "+driverInterface.getName()+"; "
+                    + "currently only SshMachineLocation is supported for autodetection (location "+location+")");
+            else throw Exceptions.create("No drivers could be loaded for "+driverInterface.getName()+" in "+location, exceptions);
+        }
+
+        try {
+            Constructor<? extends D> constructor = getConstructor(driverClass);
+            constructor.setAccessible(true);
+            return constructor.newInstance(entity, location);
+        } catch (Exception e) {
+            LOG.warn("Unable to instantiate "+driverClass+" (rethrowing): "+e);
+            throw Exceptions.propagate(e);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private <D extends EntityDriver> Constructor<D> getConstructor(Class<D> driverClass) {
+        for (Constructor<?> constructor : driverClass.getConstructors()) {
+            if (constructor.getParameterTypes().length == 2) {
+                return (Constructor<D>) constructor;
+            }
+        }
+
+        throw new RuntimeException(String.format("Class [%s] has no constructor with 2 arguments", driverClass.getName()));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/drivers/RegistryEntityDriverFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/drivers/RegistryEntityDriverFactory.java b/core/src/main/java/org/apache/brooklyn/core/entity/drivers/RegistryEntityDriverFactory.java
new file mode 100644
index 0000000..d2dd125
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/entity/drivers/RegistryEntityDriverFactory.java
@@ -0,0 +1,127 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.core.entity.drivers;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.drivers.DriverDependentEntity;
+import org.apache.brooklyn.api.entity.drivers.EntityDriver;
+import org.apache.brooklyn.api.entity.drivers.EntityDriverManager;
+import org.apache.brooklyn.api.location.Location;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Throwables;
+
+/**
+ * A registry of driver classes, keyed off the driver-interface + location type it is for.
+ * 
+ * @author Aled Sage
+ */
+public class RegistryEntityDriverFactory implements EntityDriverManager {
+
+    private final Map<DriverLocationTuple, Class<? extends EntityDriver>> registry = new LinkedHashMap<DriverLocationTuple, Class<? extends EntityDriver>>();
+
+    @Override
+    public <D extends EntityDriver> D build(DriverDependentEntity<D> entity, Location location) {
+        Class<? extends D> driverClass = lookupDriver(entity.getDriverInterface(), location);
+        return newDriverInstance(driverClass, entity, location);
+    }
+
+    public boolean hasDriver(DriverDependentEntity<?> entity, Location location) {
+        return lookupDriver(entity.getDriverInterface(), location) != null;
+    }
+
+    public <D extends EntityDriver> void registerDriver(Class<D> driverInterface, Class<? extends Location> locationClazz, Class<? extends D> driverClazz) {
+        synchronized (registry) {
+            registry.put(new DriverLocationTuple(driverInterface, locationClazz), driverClazz);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private <D extends EntityDriver> Class<? extends D> lookupDriver(Class<D> driverInterface, Location location) {
+        synchronized (registry) {
+            for (DriverLocationTuple contender : registry.keySet()) {
+                if (contender.matches(driverInterface, location)) {
+                    return (Class<? extends D>) registry.get(contender);
+                }
+            }
+        }
+        return null;
+    }
+    
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    private <D> Constructor<D> getConstructor(Class<? extends D> driverClass) {
+        for (Constructor constructor : driverClass.getConstructors()) {
+            if (constructor.getParameterTypes().length == 2) {
+                return constructor;
+            }
+        }
+
+        //TODO:
+        throw new RuntimeException(String.format("Class [%s] has no constructor with 2 arguments",driverClass.getName()));
+    }
+
+    private <D> D newDriverInstance(Class<D> driverClass, Entity entity, Location location) {
+        Constructor<D> constructor = getConstructor(driverClass);
+        try {
+            constructor.setAccessible(true);
+            return constructor.newInstance(entity, location);
+        } catch (InstantiationException e) {
+            throw Throwables.propagate(e);
+        } catch (IllegalAccessException e) {
+            throw Throwables.propagate(e);
+        } catch (InvocationTargetException e) {
+            throw Throwables.propagate(e);
+        }
+    }
+
+    private static class DriverLocationTuple {
+        private final Class<? extends EntityDriver> driverInterface;
+        private final Class<? extends Location> locationClazz;
+        
+        public DriverLocationTuple(Class<? extends EntityDriver> driverInterface, Class<? extends Location> locationClazz) {
+            this.driverInterface = checkNotNull(driverInterface, "driver interface");
+            this.locationClazz = checkNotNull(locationClazz, "location class");
+        }
+        
+        public boolean matches(Class<? extends EntityDriver> driver, Location location) {
+            return driverInterface.isAssignableFrom(driver) && locationClazz.isInstance(location);
+        }
+        
+        @Override
+        public int hashCode() {
+            return Objects.hashCode(driverInterface, locationClazz);
+        }
+        
+        @Override
+        public boolean equals(Object other) {
+            if (!(other instanceof DriverLocationTuple)) {
+                return false;
+            }
+            DriverLocationTuple o = (DriverLocationTuple) other;
+            return driverInterface.equals(o.driverInterface) && locationClazz.equals(o.locationClazz);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/BasicDownloadRequirement.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/BasicDownloadRequirement.java b/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/BasicDownloadRequirement.java
new file mode 100644
index 0000000..0a1106b
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/BasicDownloadRequirement.java
@@ -0,0 +1,85 @@
+/*
+ * 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.entity.drivers.downloads;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Map;
+
+import org.apache.brooklyn.api.entity.drivers.EntityDriver;
+import org.apache.brooklyn.api.entity.drivers.downloads.DownloadResolverManager.DownloadRequirement;
+import org.apache.brooklyn.util.collections.MutableMap;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableMap;
+
+public class BasicDownloadRequirement implements DownloadRequirement {
+
+    private final EntityDriver entityDriver;
+    private final String addonName;
+    private final Map<String, ?> properties;
+
+    /**
+     * Copies the given DownloadRequirement, but overriding the original properties with the given additional properties.
+     */
+    public static BasicDownloadRequirement copy(DownloadRequirement req, Map<String,?> additionalProperties) {
+        Map<String,?> props = MutableMap.<String,Object>builder().putAll(req.getProperties()).putAll(additionalProperties).build();
+        if (req.getAddonName() == null) {
+            return new BasicDownloadRequirement(req.getEntityDriver(), props);
+        } else {
+            return new BasicDownloadRequirement(req.getEntityDriver(), req.getAddonName(), props);
+        }
+    }
+
+    public BasicDownloadRequirement(EntityDriver driver) {
+        this(driver, ImmutableMap.<String,Object>of());
+    }
+    
+    public BasicDownloadRequirement(EntityDriver driver, Map<String, ?> properties) {
+        this.entityDriver = checkNotNull(driver, "entityDriver");
+        this.addonName = null;
+        this.properties = checkNotNull(properties, "properties");
+    }
+    
+    public BasicDownloadRequirement(EntityDriver entityDriver, String addonName, Map<String, ?> properties) {
+        this.entityDriver = checkNotNull(entityDriver, "entityDriver");
+        this.addonName = checkNotNull(addonName, "addonName");
+        this.properties = checkNotNull(properties, "properties");
+    }
+
+    @Override
+    public EntityDriver getEntityDriver() {
+        return entityDriver;
+    }
+
+    @Override
+    public String getAddonName() {
+        return addonName;
+    }
+
+    @Override
+    public Map<String, ?> getProperties() {
+        return properties;
+    }
+    
+    @Override
+    public String toString() {
+        return Objects.toStringHelper(this).add("driver", entityDriver).add("addon", addonName).omitNullValues().toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/BasicDownloadResolver.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/BasicDownloadResolver.java b/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/BasicDownloadResolver.java
new file mode 100644
index 0000000..aed8f0b
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/BasicDownloadResolver.java
@@ -0,0 +1,66 @@
+/*
+ * 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.entity.drivers.downloads;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.List;
+
+import org.apache.brooklyn.api.entity.drivers.downloads.DownloadResolver;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableList;
+
+public class BasicDownloadResolver implements DownloadResolver {
+
+    private final List<String> targets;
+    private final String filename;
+    private final String unpackDirectoryName;
+
+    public BasicDownloadResolver(Iterable<String> targets, String filename) {
+        this(targets, filename, null);
+    }
+    
+    public BasicDownloadResolver(Iterable<String> targets, String filename, String unpackDirectoryName) {
+        this.targets = ImmutableList.copyOf(checkNotNull(targets, "targets"));
+        this.filename = checkNotNull(filename, "filename");
+        this.unpackDirectoryName = unpackDirectoryName;
+    }
+    
+    @Override
+    public List<String> getTargets() {
+        return targets;
+    }
+
+    @Override
+    public String getFilename() {
+        return filename;
+    }
+
+    @Override
+    public String getUnpackedDirectoryName(String defaultVal) {
+        return unpackDirectoryName == null ? defaultVal : unpackDirectoryName;
+    }
+    
+    @Override
+    public String toString() {
+        return Objects.toStringHelper(this).add("targets", targets).add("filename", filename)
+                .add("unpackDirName", unpackDirectoryName).omitNullValues().toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/BasicDownloadTargets.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/BasicDownloadTargets.java b/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/BasicDownloadTargets.java
new file mode 100644
index 0000000..d8e6599
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/BasicDownloadTargets.java
@@ -0,0 +1,121 @@
+/*
+ * 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.entity.drivers.downloads;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.List;
+
+import org.apache.brooklyn.api.entity.drivers.downloads.DownloadResolverManager.DownloadTargets;
+import org.apache.brooklyn.util.collections.MutableList;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
+public class BasicDownloadTargets implements DownloadTargets {
+
+    private static final DownloadTargets EMPTY = builder().build();
+    
+    public static DownloadTargets empty() {
+        return EMPTY;
+    }
+    
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    public static class Builder {
+        private List<String> primaries = Lists.newArrayList();
+        private List<String> fallbacks = Lists.newArrayList();
+        private boolean canContinueResolving = true;
+        
+        public Builder addAll(DownloadTargets other) {
+            addPrimaries(other.getPrimaryLocations());
+            addFallbacks(other.getFallbackLocations());
+            return this;
+        }
+        
+        public Builder addPrimary(String val) {
+            checkNotNull(val, "val");
+            if (!primaries.contains(val)) primaries.add(val);
+            return this;
+        }
+
+        public Builder addPrimaries(Iterable<String> vals) {
+            for (String val : checkNotNull(vals, "vals")) {
+                addPrimary(val);
+            }
+            return this;
+        }
+
+        public Builder addFallback(String val) {
+            checkNotNull(val, "val");
+            if (!fallbacks.contains(val)) fallbacks.add(val);
+            return this;
+        }
+
+        public Builder addFallbacks(Iterable<String> vals) {
+            for (String val : checkNotNull(vals, "vals")) {
+                addFallback(val);
+            }
+            return this;
+        }
+
+        public Builder canContinueResolving(boolean val) {
+            canContinueResolving = val;
+            return this;
+        }
+        
+        public BasicDownloadTargets build() {
+            return new BasicDownloadTargets(this);
+        }
+    }
+
+    private final List<String> primaries;
+    private final List<String> fallbacks;
+    private final boolean canContinueResolving;
+    
+    protected BasicDownloadTargets(Builder builder) {
+        primaries = ImmutableList.copyOf(builder.primaries);
+        fallbacks = MutableList.<String>builder().addAll(builder.fallbacks).removeAll(builder.primaries).build().asUnmodifiable();
+        canContinueResolving = builder.canContinueResolving;
+    }
+
+    @Override
+    public List<String> getPrimaryLocations() {
+        return primaries;
+    }
+
+    @Override
+    public List<String> getFallbackLocations() {
+        return fallbacks;
+    }
+
+    @Override
+    public boolean canContinueResolving() {
+        return canContinueResolving;
+    }
+    
+    @Override
+    public String toString() {
+        return Objects.toStringHelper(this).add("primaries", primaries).add("fallbacks", fallbacks)
+                .add("canContinueResolving", canContinueResolving).toString();
+    }
+}