http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/test/java/org/apache/brooklyn/core/entity/OwnedChildrenTest.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/java/org/apache/brooklyn/core/entity/OwnedChildrenTest.java 
b/core/src/test/java/org/apache/brooklyn/core/entity/OwnedChildrenTest.java
new file mode 100644
index 0000000..dede9aa
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/core/entity/OwnedChildrenTest.java
@@ -0,0 +1,213 @@
+/*
+ * 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 org.apache.brooklyn.test.Asserts.assertEqualsIgnoringOrder;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+import org.apache.brooklyn.api.entity.Application;
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.core.entity.AbstractApplication;
+import org.apache.brooklyn.core.entity.AbstractEntity;
+import org.apache.brooklyn.core.entity.Entities;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+
+public class OwnedChildrenTest {
+
+    private Application app;
+
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() {
+        app = new AbstractApplication() {};
+    }
+
+    @AfterMethod(alwaysRun = true)
+    public void tearDown(){
+        if (app != null) Entities.destroyAll(app.getManagementContext());
+    }
+
+    // Tests that the deprecated "owner" still works
+    @Test
+    public void testSetOwnerInConstructorMap() {
+        Entity e = new AbstractEntity(app) {};
+        
+        assertEquals(e.getParent(), app);
+        assertEqualsIgnoringOrder(app.getChildren(), ImmutableList.of(e));
+        assertEquals(e.getApplication(), app);
+    }
+    
+    @Test
+    public void testSetParentInConstructorMap() {
+        Entity e = new AbstractEntity(app) {};
+        
+        assertEquals(e.getParent(), app);
+        assertEqualsIgnoringOrder(app.getChildren(), ImmutableList.of(e));
+        assertEquals(e.getApplication(), app);
+    }
+    
+    @Test
+    public void testSetParentInConstructorArgument() {
+        Entity e = new AbstractEntity(app) {};
+        
+        assertEquals(e.getParent(), app);
+        assertEqualsIgnoringOrder(app.getChildren(), ImmutableList.of(e));
+        assertEquals(e.getApplication(), app);
+    }
+    
+    @Test
+    public void testSetParentInSetterMethod() {
+        Entity e = new AbstractEntity() {};
+        e.setParent(app);
+        
+        assertEquals(e.getParent(), app);
+        assertEqualsIgnoringOrder(app.getChildren(), ImmutableList.of(e));
+        assertEquals(e.getApplication(), app);
+    }
+
+    @Test
+    public void testAddChild() {
+        Entity e = new AbstractEntity() {};
+        app.addChild(e);
+        
+        assertEquals(e.getParent(), app);
+        assertEqualsIgnoringOrder(app.getChildren(), ImmutableList.of(e));
+        assertEquals(e.getApplication(), app);
+    }
+    
+    @Test
+    public void testSetParentWhenMatchesParentSetInConstructor() {
+        Entity e = new AbstractEntity(app) {};
+        e.setParent(app);
+        
+        assertEquals(e.getParent(), app);
+        assertEqualsIgnoringOrder(app.getChildren(), ImmutableList.of(e));
+    }
+    
+    @Test(expectedExceptions = UnsupportedOperationException.class)
+    public void testSetParentWhenDiffersFromParentSetInConstructor() {
+        Entity e = new AbstractEntity(app) {};
+        Entity e2 = new AbstractEntity() {};
+        e.setParent(e2);
+        fail();
+    }
+    
+    @Test
+    public void testParentCanHaveMultipleChildren() {
+        Entity e = new AbstractEntity(app) {};
+        Entity e2 = new AbstractEntity(app) {};
+        
+        assertEquals(e.getParent(), app);
+        assertEquals(e2.getParent(), app);
+        assertEqualsIgnoringOrder(app.getChildren(), ImmutableList.of(e,e2));
+    }
+    
+    @Test
+    public void testHierarchyOfOwners() {
+        Entity e = new AbstractEntity(app) {};
+        Entity e2 = new AbstractEntity(e) {};
+        Entity e3 = new AbstractEntity(e2) {};
+        
+        assertEquals(app.getParent(), null);
+        assertEquals(e.getParent(), app);
+        assertEquals(e2.getParent(), e);
+        assertEquals(e3.getParent(), e2);
+        
+        assertEqualsIgnoringOrder(app.getChildren(), ImmutableList.of(e));
+        assertEqualsIgnoringOrder(e.getChildren(), ImmutableList.of(e2));
+        assertEqualsIgnoringOrder(e2.getChildren(), ImmutableList.of(e3));
+        assertEqualsIgnoringOrder(e3.getChildren(), ImmutableList.of());
+    }
+    
+    @Test(enabled = false) // FIXME fails currently
+    public void testRemoveChild() {
+        Entity e = new AbstractEntity(app) {};
+        app.removeChild(e);
+        
+        assertEqualsIgnoringOrder(app.getChildren(), ImmutableList.of());
+        assertEquals(e.getParent(), null);
+    }
+    
+    @Test
+    public void testParentalLoopForbiddenViaAddChild() {
+        Entity e = new AbstractEntity() {};
+        Entity e2 = new AbstractEntity(e) {};
+        try {
+            e2.addChild(e);
+            fail();
+        } catch (IllegalStateException ex) {
+            // success
+        }
+        
+        assertEqualsIgnoringOrder(e.getChildren(), ImmutableList.of(e2));
+        assertEqualsIgnoringOrder(e2.getChildren(), ImmutableList.of());
+        assertEquals(e.getParent(), null);
+        assertEquals(e2.getParent(), e);
+    }
+    
+    @Test
+    public void testParentalLoopForbiddenViaSetParent() {
+        Entity e = new AbstractEntity() {};
+        Entity e2 = new AbstractEntity(e) {};
+        try {
+            e.setParent(e2);
+            fail();
+        } catch (IllegalStateException ex) {
+                       ex.printStackTrace();
+            // success
+        }
+        assertEqualsIgnoringOrder(e.getChildren(), ImmutableList.of(e2));
+        assertEqualsIgnoringOrder(e2.getChildren(), ImmutableList.of());
+        assertEquals(e.getParent(), null);
+        assertEquals(e2.getParent(), e);
+    }
+    
+    @Test(expectedExceptions = IllegalStateException.class)
+    public void testParentingOneselfForbidden() {
+        AbstractEntity e = new AbstractEntity() {};
+        e.addChild(e);
+        fail();
+    }
+    
+    @Test
+    public void testIsAncestor() {
+        AbstractEntity e = new AbstractEntity(app) {};
+        AbstractEntity e2 = new AbstractEntity(e) {};
+        
+               assertTrue(Entities.isAncestor(e2, app));
+               assertTrue(Entities.isAncestor(e2, e));
+               assertFalse(Entities.isAncestor(e2, e2));
+    }
+    
+    @Test
+    public void testIsDescendant() {
+        AbstractEntity e = new AbstractEntity(app) {};
+        AbstractEntity e2 = new AbstractEntity(e) {};
+
+               assertTrue(Entities.isDescendant(app, e));
+               assertTrue(Entities.isDescendant(app, e2));
+               assertFalse(Entities.isDescendant(e2, e));
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/test/java/org/apache/brooklyn/core/entity/PolicyRegistrationTest.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/java/org/apache/brooklyn/core/entity/PolicyRegistrationTest.java
 
b/core/src/test/java/org/apache/brooklyn/core/entity/PolicyRegistrationTest.java
new file mode 100644
index 0000000..325168a
--- /dev/null
+++ 
b/core/src/test/java/org/apache/brooklyn/core/entity/PolicyRegistrationTest.java
@@ -0,0 +1,152 @@
+/*
+ * 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 org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.fail;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.policy.Policy;
+import org.apache.brooklyn.api.policy.PolicySpec;
+import org.apache.brooklyn.api.sensor.EnricherSpec;
+import org.apache.brooklyn.api.sensor.SensorEvent;
+import org.apache.brooklyn.api.sensor.SensorEventListener;
+import org.apache.brooklyn.core.entity.AbstractEntity;
+import org.apache.brooklyn.core.entity.lifecycle.PolicyDescriptor;
+import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
+import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.apache.brooklyn.core.test.entity.TestEntityNoEnrichersImpl;
+import org.apache.brooklyn.policy.core.AbstractPolicy;
+import org.apache.brooklyn.test.TestUtils;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+
+public class PolicyRegistrationTest extends BrooklynAppUnitTestSupport {
+
+    private static final int TIMEOUT_MS = 10*1000;
+    
+    private TestEntity entity;
+    private Policy policy1;
+    private Policy policy2;
+
+    private List<PolicyDescriptor> added;
+    private List<PolicyDescriptor> removed;
+
+    @BeforeMethod(alwaysRun=true)
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        entity = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+        policy1 = new AbstractPolicy() {};
+        policy2 = new AbstractPolicy() {};
+        
+        added = Lists.newCopyOnWriteArrayList();
+        removed = Lists.newCopyOnWriteArrayList();
+        
+        app.subscribe(entity, AbstractEntity.POLICY_ADDED, new 
SensorEventListener<PolicyDescriptor>() {
+            @Override public void onEvent(SensorEvent<PolicyDescriptor> event) 
{
+                added.add(event.getValue());
+            }});
+        app.subscribe(entity, AbstractEntity.POLICY_REMOVED, new 
SensorEventListener<PolicyDescriptor>() {
+                @Override public void onEvent(SensorEvent<PolicyDescriptor> 
event) {
+                    removed.add(event.getValue());
+                }});
+    }
+    
+    @Test
+    public void testGetPoliciesIsInitiallyEmpty() {
+        assertEquals(entity.getPolicies(), ImmutableList.of());
+    }
+
+    @Test(expectedExceptions = { UnsupportedOperationException.class })
+    public void testGetPoliciesReturnsImmutableCollection() {
+        entity.getPolicies().add(policy1);
+        fail();
+    }
+
+    @Test
+    public void testAddAndRemovePolicies() {
+        entity.addPolicy(policy1);
+        assertEquals(entity.getPolicies(), ImmutableList.of(policy1));
+        assertEqualsEventually(added, ImmutableList.of(new 
PolicyDescriptor(policy1)));
+        
+        entity.addPolicy(policy2);
+        assertEquals(entity.getPolicies(), ImmutableList.of(policy1, policy2));
+        assertEqualsEventually(added, ImmutableList.of(new 
PolicyDescriptor(policy1), new PolicyDescriptor(policy2)));
+        
+        entity.removePolicy(policy1);
+        assertEquals(entity.getPolicies(), ImmutableList.of(policy2));
+        assertEqualsEventually(removed, ImmutableList.of(new 
PolicyDescriptor(policy1)));
+        
+        entity.removePolicy(policy2);
+        assertEquals(entity.getPolicies(), ImmutableList.of());
+        assertEqualsEventually(removed, ImmutableList.of(new 
PolicyDescriptor(policy1), new PolicyDescriptor(policy2)));
+    }
+
+    @Test
+    public void testAddPolicySpec() {
+        EntitySpecTest.MyPolicy policy = 
entity.addPolicy(PolicySpec.create(EntitySpecTest.MyPolicy.class));
+        assertNotNull(policy);
+        assertEquals(entity.getPolicies(), ImmutableList.of(policy));
+        assertEqualsEventually(added, ImmutableList.of(new 
PolicyDescriptor(policy)));
+    }
+    
+    @Test
+    public void testAddEnricherSpec() {
+        TestEntity entity2 = 
app.createAndManageChild(EntitySpec.create(TestEntity.class, 
TestEntityNoEnrichersImpl.class));
+        EntitySpecTest.MyEnricher enricher = 
entity2.addEnricher(EnricherSpec.create(EntitySpecTest.MyEnricher.class));
+        assertNotNull(enricher);
+        assertEquals(entity2.getEnrichers(), ImmutableList.of(enricher));
+    }
+
+    @Test
+    public void testRemoveAllPolicies() {
+        entity.addPolicy(policy1);
+        entity.addPolicy(policy2);
+        entity.removeAllPolicies();
+        
+        assertEquals(entity.getPolicies(), ImmutableList.of());
+        assertCollectionEqualsEventually(removed, ImmutableSet.of(new 
PolicyDescriptor(policy1), new PolicyDescriptor(policy2)));
+    }
+    
+    private <T> void assertEqualsEventually(final T actual, final T expected) {
+        TestUtils.assertEventually(MutableMap.of("timeout", TIMEOUT_MS), new 
Runnable() {
+                @Override public void run() {
+                    assertEquals(actual, expected, "actual="+actual);
+                }});
+    }
+    
+    // Ignores order of vals in collection, but asserts each same size and 
same elements 
+    private <T> void assertCollectionEqualsEventually(final Collection<? 
extends T> actual, final Collection<? extends T> expected) {
+        TestUtils.assertEventually(MutableMap.of("timeout", TIMEOUT_MS), new 
Runnable() {
+                @Override public void run() {
+                    assertEquals(ImmutableSet.copyOf(actual), 
ImmutableSet.copyOf(expected), "actual="+actual);
+                    assertEquals(actual.size(), expected.size(), 
"actual="+actual);
+                }});
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/test/java/org/apache/brooklyn/core/entity/RecordingSensorEventListener.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/java/org/apache/brooklyn/core/entity/RecordingSensorEventListener.java
 
b/core/src/test/java/org/apache/brooklyn/core/entity/RecordingSensorEventListener.java
new file mode 100644
index 0000000..44920ed
--- /dev/null
+++ 
b/core/src/test/java/org/apache/brooklyn/core/entity/RecordingSensorEventListener.java
@@ -0,0 +1,115 @@
+/*
+ * 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.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+
+import org.apache.brooklyn.api.sensor.SensorEvent;
+import org.apache.brooklyn.api.sensor.SensorEventListener;
+
+import com.google.common.base.Function;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.primitives.Longs;
+
+/**
+ * An event listener that records each event and allows callers to access all 
values and
+ * all values sorted by event timestamp.
+ */
+public class RecordingSensorEventListener<T> implements 
SensorEventListener<T>, Iterable<SensorEvent<T>> {
+
+    private final List<SensorEvent<T>> events = 
Lists.newCopyOnWriteArrayList();
+    private final boolean suppressDuplicates;
+    private T lastValue;
+
+    public RecordingSensorEventListener() {
+        this(false);
+    }
+
+    public RecordingSensorEventListener(boolean suppressDuplicates) {
+        this.suppressDuplicates = suppressDuplicates;
+    }
+
+    @Override
+    public void onEvent(SensorEvent<T> event) {
+        if (!suppressDuplicates || events.isEmpty() || 
!Objects.equals(lastValue, event.getValue())) {
+            events.add(event);
+            lastValue = event.getValue();
+        }
+    }
+
+    /**
+     * @return An immutable iterable of the recorded events.
+     */
+    public List<SensorEvent<T>> getEvents() {
+        return ImmutableList.copyOf(events);
+    }
+
+    /**
+     * @return A live read-only view of recorded events.
+     */
+    public Iterable<T> getEventValues() {
+        return FluentIterable.from(events)
+                .transform(new GetValueFunction<T>());
+    }
+
+    /**
+     * @return A static read-only view of event values sorted by the time at 
which they occurred.
+     */
+    public Iterable<T> getEventValuesSortedByTimestamp() {
+        List<SensorEvent<T>> copy = Lists.newArrayList(events);
+        Collections.sort(copy, new EventTimestampComparator());
+        return FluentIterable.from(copy)
+                .transform(new GetValueFunction<T>());
+    }
+
+    /**
+     * Clears all events recorded by the listener.
+     */
+    public void clearEvents() {
+        this.events.clear();
+        lastValue = null;
+    }
+
+    @Override
+    public Iterator<SensorEvent<T>> iterator() {
+        return getEvents().iterator();
+    }
+
+    private static class GetValueFunction<T> implements 
Function<SensorEvent<T>, T> {
+        @Override
+        public T apply(SensorEvent<T> input) {
+            return input.getValue();
+        }
+    }
+
+    private static class EventTimestampComparator implements 
Comparator<SensorEvent<?>> {
+        @Override
+        public int compare(SensorEvent<?> o1, SensorEvent<?> o2) {
+            return Longs.compare(o1.getTimestamp(), o2.getTimestamp());
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/test/java/org/apache/brooklyn/core/entity/SanitizerTest.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/java/org/apache/brooklyn/core/entity/SanitizerTest.java 
b/core/src/test/java/org/apache/brooklyn/core/entity/SanitizerTest.java
new file mode 100644
index 0000000..79161d4
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/core/entity/SanitizerTest.java
@@ -0,0 +1,38 @@
+/*
+ * 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 org.testng.Assert.assertEquals;
+
+import java.util.Map;
+
+import org.apache.brooklyn.core.config.Sanitizer;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableMap;
+
+public class SanitizerTest {
+
+    @Test
+    public void testSanitize() throws Exception {
+        Map<String, Object> sanitized = 
Sanitizer.sanitize(ConfigBag.newInstance(ImmutableMap.of("password", 
"pa55w0rd", "mykey", "myval")));
+        assertEquals(sanitized, ImmutableMap.of("password", "xxxxxxxx", 
"mykey", "myval"));
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/test/java/org/apache/brooklyn/core/entity/drivers/BasicEntityDriverManagerTest.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/java/org/apache/brooklyn/core/entity/drivers/BasicEntityDriverManagerTest.java
 
b/core/src/test/java/org/apache/brooklyn/core/entity/drivers/BasicEntityDriverManagerTest.java
new file mode 100644
index 0000000..fd0d421
--- /dev/null
+++ 
b/core/src/test/java/org/apache/brooklyn/core/entity/drivers/BasicEntityDriverManagerTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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 org.testng.Assert.assertTrue;
+
+import org.apache.brooklyn.api.entity.drivers.DriverDependentEntity;
+import org.apache.brooklyn.core.entity.drivers.BasicEntityDriverManager;
+import 
org.apache.brooklyn.core.entity.drivers.ReflectiveEntityDriverFactoryTest.MyDriver;
+import 
org.apache.brooklyn.core.entity.drivers.ReflectiveEntityDriverFactoryTest.MyDriverDependentEntity;
+import 
org.apache.brooklyn.core.entity.drivers.ReflectiveEntityDriverFactoryTest.MySshDriver;
+import 
org.apache.brooklyn.core.entity.drivers.RegistryEntityDriverFactoryTest.MyOtherSshDriver;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import org.apache.brooklyn.location.core.SimulatedLocation;
+import org.apache.brooklyn.location.ssh.SshMachineLocation;
+import org.apache.brooklyn.util.collections.MutableMap;
+
+public class BasicEntityDriverManagerTest {
+
+    private BasicEntityDriverManager manager;
+    private SshMachineLocation sshLocation;
+    private SimulatedLocation simulatedLocation;
+
+    @BeforeMethod
+    public void setUp() throws Exception {
+        manager = new BasicEntityDriverManager();
+        sshLocation = new SshMachineLocation(MutableMap.of("address", 
"localhost"));
+        simulatedLocation = new SimulatedLocation();
+    }
+
+    @AfterMethod
+    public void tearDown(){
+        // nothing to tear down; no management context created
+    }
+    
+    @Test
+    public void testPrefersRegisteredDriver() throws Exception {
+        DriverDependentEntity<MyDriver> entity = new 
MyDriverDependentEntity<MyDriver>(MyDriver.class);
+        manager.registerDriver(MyDriver.class, SshMachineLocation.class, 
MyOtherSshDriver.class);
+        assertTrue(manager.build(entity, sshLocation) instanceof 
MyOtherSshDriver);
+    }
+    
+    @Test
+    public void testFallsBackToReflectiveDriver() throws Exception {
+        DriverDependentEntity<MyDriver> entity = new 
MyDriverDependentEntity<MyDriver>(MyDriver.class);
+        assertTrue(manager.build(entity, sshLocation) instanceof MySshDriver);
+    }
+    
+    @Test
+    public void testRespectsLocationWhenDecidingOnDriver() throws Exception {
+        DriverDependentEntity<MyDriver> entity = new 
MyDriverDependentEntity<MyDriver>(MyDriver.class);
+        manager.registerDriver(MyDriver.class, SimulatedLocation.class, 
MyOtherSshDriver.class);
+        assertTrue(manager.build(entity, simulatedLocation) instanceof 
MyOtherSshDriver);
+        assertTrue(manager.build(entity, sshLocation) instanceof MySshDriver);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/test/java/org/apache/brooklyn/core/entity/drivers/EntityDriverRegistryTest.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/java/org/apache/brooklyn/core/entity/drivers/EntityDriverRegistryTest.java
 
b/core/src/test/java/org/apache/brooklyn/core/entity/drivers/EntityDriverRegistryTest.java
new file mode 100644
index 0000000..977d380
--- /dev/null
+++ 
b/core/src/test/java/org/apache/brooklyn/core/entity/drivers/EntityDriverRegistryTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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 org.testng.Assert.assertTrue;
+
+import org.apache.brooklyn.api.entity.drivers.DriverDependentEntity;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.core.entity.Entities;
+import 
org.apache.brooklyn.core.entity.drivers.ReflectiveEntityDriverFactoryTest.MyDriver;
+import 
org.apache.brooklyn.core.entity.drivers.ReflectiveEntityDriverFactoryTest.MyDriverDependentEntity;
+import 
org.apache.brooklyn.core.entity.drivers.RegistryEntityDriverFactoryTest.MyOtherSshDriver;
+import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import org.apache.brooklyn.location.ssh.SshMachineLocation;
+
+public class EntityDriverRegistryTest {
+
+    private ManagementContext managementContext;
+    private SshMachineLocation sshLocation;
+
+    @BeforeMethod
+    public void setUp() throws Exception {
+        managementContext = new LocalManagementContextForTests();
+        sshLocation = new SshMachineLocation(MutableMap.of("address", 
"localhost"));
+    }
+
+    @AfterMethod
+    public void tearDown(){
+        if (managementContext != null) Entities.destroyAll(managementContext);
+    }
+
+    @Test
+    public void testInstantiatesRegisteredDriver() throws Exception {
+        
managementContext.getEntityDriverManager().registerDriver(MyDriver.class, 
SshMachineLocation.class, MyOtherSshDriver.class);
+        DriverDependentEntity<MyDriver> entity = new 
MyDriverDependentEntity<MyDriver>(MyDriver.class);
+        MyDriver driver = 
managementContext.getEntityDriverManager().build(entity, sshLocation);
+        assertTrue(driver instanceof MyOtherSshDriver);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/test/java/org/apache/brooklyn/core/entity/drivers/ReflectiveEntityDriverFactoryTest.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/java/org/apache/brooklyn/core/entity/drivers/ReflectiveEntityDriverFactoryTest.java
 
b/core/src/test/java/org/apache/brooklyn/core/entity/drivers/ReflectiveEntityDriverFactoryTest.java
new file mode 100644
index 0000000..86a2d99
--- /dev/null
+++ 
b/core/src/test/java/org/apache/brooklyn/core/entity/drivers/ReflectiveEntityDriverFactoryTest.java
@@ -0,0 +1,169 @@
+/*
+ * 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.location.paas.PaasLocation;
+import org.apache.brooklyn.location.ssh.SshMachineLocation;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.EntityLocal;
+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.apache.brooklyn.core.entity.AbstractEntity;
+import org.apache.brooklyn.core.entity.drivers.ReflectiveEntityDriverFactory;
+import org.apache.brooklyn.core.test.location.TestPaasLocation;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertTrue;
+
+public class ReflectiveEntityDriverFactoryTest {
+
+    private ReflectiveEntityDriverFactory factory;
+    private SshMachineLocation sshLocation;
+    private PaasLocation paasLocation;
+    private DriverDependentEntity<MyDriver> entity;
+    
+    @BeforeMethod
+    public void setUp() throws Exception {
+        factory = new ReflectiveEntityDriverFactory();
+        sshLocation = new SshMachineLocation(MutableMap.of("address", 
"localhost"));
+        entity = new MyDriverDependentEntity<MyDriver>(MyDriver.class);
+
+        paasLocation = new TestPaasLocation();
+    }
+
+    @AfterMethod
+    public void tearDown() {
+        // nothing to tear down; no management context created
+    }
+    
+    protected void assertDriverIs(Class<?> clazz, Location location) {
+        MyDriver driver = factory.build(entity, location);
+        assertTrue(driver.getClass().equals(clazz), "driver="+driver+"; should 
be "+clazz);
+    }
+    
+    @Test
+    public void testInstantiatesSshDriver() throws Exception {
+        assertDriverIs(MySshDriver.class, sshLocation);
+    }
+
+    @Test
+    public void testInstantiatesPaasDriver() throws Exception {
+        assertDriverIs(MyTestPaasDriver.class, paasLocation);
+    }
+    
+    @Test
+    public void testFullNameMapping() throws Exception {
+        factory.addClassFullNameMapping(MyDriver.class.getName(), 
MyCustomDriver.class.getName());
+        assertDriverIs(MyCustomDriver.class, sshLocation);
+    }
+
+    @Test
+    public void testFullNameMappingMulti() throws Exception {
+        factory.addClassFullNameMapping(MyDriver.class.getName(), "X");
+        factory.addClassFullNameMapping(MyDriver.class.getName(), 
MyCustomDriver.class.getName());
+        assertDriverIs(MyCustomDriver.class, sshLocation);
+    }
+
+
+    @Test
+    public void testFullNameMappingFailure1() throws Exception {
+        factory.addClassFullNameMapping(MyDriver.class.getName()+"X", 
MyCustomDriver.class.getName());
+        assertDriverIs(MySshDriver.class, sshLocation);
+    }
+
+    @Test
+    public void testFullNameMappingFailure2() throws Exception {
+        factory.addClassFullNameMapping(MyDriver.class.getName(), 
MyCustomDriver.class.getName());
+        factory.addClassFullNameMapping(MyDriver.class.getName(), "X");
+        assertDriverIs(MySshDriver.class, sshLocation);
+    }
+
+    @Test
+    public void testSimpleNameMapping() throws Exception {
+        factory.addClassSimpleNameMapping(MyDriver.class.getSimpleName(), 
MyCustomDriver.class.getSimpleName());
+        assertDriverIs(MyCustomDriver.class, sshLocation);
+    }
+
+    @Test
+    public void testSimpleNameMappingFailure() throws Exception {
+        factory.addClassSimpleNameMapping(MyDriver.class.getSimpleName()+"X", 
MyCustomDriver.class.getSimpleName());
+        assertDriverIs(MySshDriver.class, sshLocation);
+    }
+    
+    public static class MyDriverDependentEntity<D extends EntityDriver> 
extends AbstractEntity implements DriverDependentEntity<D> {
+        private final Class<D> clazz;
+
+        public MyDriverDependentEntity(Class<D> clazz) {
+            this.clazz = clazz;
+        }
+        
+        @Override
+        public Class<D> getDriverInterface() {
+            return clazz;
+        }
+        
+        @Override
+        public D getDriver() {
+            throw new UnsupportedOperationException();
+        }
+    }
+    
+    public static interface MyDriver extends EntityDriver {
+    }
+    
+    public static class MySshDriver implements MyDriver {
+        public MySshDriver(Entity entity, SshMachineLocation machine) {
+        }
+
+        @Override
+        public Location getLocation() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public EntityLocal getEntity() {
+            throw new UnsupportedOperationException();
+        }
+    }
+    
+    public static class MyCustomDriver extends MySshDriver {
+        public MyCustomDriver(Entity entity, SshMachineLocation machine) {
+            super(entity, machine);
+        }
+    }
+    
+    public static class MyTestPaasDriver implements MyDriver {
+        public MyTestPaasDriver(Entity entity, PaasLocation location) {
+        }
+
+        @Override
+        public Location getLocation() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public EntityLocal getEntity() {
+            throw new UnsupportedOperationException();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/test/java/org/apache/brooklyn/core/entity/drivers/RegistryEntityDriverFactoryTest.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/java/org/apache/brooklyn/core/entity/drivers/RegistryEntityDriverFactoryTest.java
 
b/core/src/test/java/org/apache/brooklyn/core/entity/drivers/RegistryEntityDriverFactoryTest.java
new file mode 100644
index 0000000..97aa2d2
--- /dev/null
+++ 
b/core/src/test/java/org/apache/brooklyn/core/entity/drivers/RegistryEntityDriverFactoryTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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 org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.EntityLocal;
+import org.apache.brooklyn.api.entity.drivers.DriverDependentEntity;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.core.entity.drivers.RegistryEntityDriverFactory;
+import 
org.apache.brooklyn.core.entity.drivers.ReflectiveEntityDriverFactoryTest.MyDriver;
+import 
org.apache.brooklyn.core.entity.drivers.ReflectiveEntityDriverFactoryTest.MyDriverDependentEntity;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import org.apache.brooklyn.location.core.SimulatedLocation;
+import org.apache.brooklyn.location.ssh.SshMachineLocation;
+import org.apache.brooklyn.util.collections.MutableMap;
+
+public class RegistryEntityDriverFactoryTest {
+
+    private RegistryEntityDriverFactory factory;
+    private SshMachineLocation sshLocation;
+    private SimulatedLocation simulatedLocation;
+
+    @BeforeMethod
+    public void setUp() throws Exception {
+        factory = new RegistryEntityDriverFactory();
+        sshLocation = new SshMachineLocation(MutableMap.of("address", 
"localhost"));
+        simulatedLocation = new SimulatedLocation();
+    }
+
+    @AfterMethod
+    public void tearDown(){
+        // nothing to tear down; no management context created
+    }
+
+    @Test
+    public void testHasDriver() throws Exception {
+        DriverDependentEntity<MyDriver> entity = new 
MyDriverDependentEntity<MyDriver>(MyDriver.class);
+        factory.registerDriver(MyDriver.class, SshMachineLocation.class, 
MyOtherSshDriver.class);
+        assertTrue(factory.hasDriver(entity, sshLocation));
+        assertFalse(factory.hasDriver(entity, simulatedLocation));
+    }
+
+    @Test
+    public void testInstantiatesRegisteredDriver() throws Exception {
+        DriverDependentEntity<MyDriver> entity = new 
MyDriverDependentEntity<MyDriver>(MyDriver.class);
+        factory.registerDriver(MyDriver.class, SshMachineLocation.class, 
MyOtherSshDriver.class);
+        MyDriver driver = factory.build(entity, sshLocation);
+        assertTrue(driver instanceof MyOtherSshDriver);
+    }
+
+    public static class MyOtherSshDriver implements MyDriver {
+        public MyOtherSshDriver(Entity entity, Location machine) {
+        }
+
+        @Override
+        public EntityLocal getEntity() {
+            throw new UnsupportedOperationException();
+        }
+        
+        @Override
+        public Location getLocation() {
+            throw new UnsupportedOperationException();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/test/java/org/apache/brooklyn/core/entity/drivers/downloads/BasicDownloadsRegistryTest.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/java/org/apache/brooklyn/core/entity/drivers/downloads/BasicDownloadsRegistryTest.java
 
b/core/src/test/java/org/apache/brooklyn/core/entity/drivers/downloads/BasicDownloadsRegistryTest.java
new file mode 100644
index 0000000..56c711d
--- /dev/null
+++ 
b/core/src/test/java/org/apache/brooklyn/core/entity/drivers/downloads/BasicDownloadsRegistryTest.java
@@ -0,0 +1,155 @@
+/*
+ * 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 org.testng.Assert.assertEquals;
+import static org.testng.Assert.fail;
+
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.entity.drivers.downloads.DownloadResolver;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.core.entity.Attributes;
+import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.entity.factory.ApplicationBuilder;
+import org.apache.brooklyn.core.internal.BrooklynProperties;
+import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
+import org.apache.brooklyn.core.test.entity.TestApplication;
+import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import org.apache.brooklyn.location.core.SimulatedLocation;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+public class BasicDownloadsRegistryTest {
+
+    private BrooklynProperties brooklynProperties;
+    private LocalManagementContext managementContext;
+    private Location loc;
+    private TestApplication app;
+    private TestEntity entity;
+    private MyEntityDriver driver;
+
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        brooklynProperties = BrooklynProperties.Factory.newEmpty();
+        managementContext = new LocalManagementContext(brooklynProperties);
+        loc = new SimulatedLocation();
+        app = ApplicationBuilder.newManagedApp(TestApplication.class, 
managementContext);
+        entity = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+        driver = new MyEntityDriver(entity, loc);
+        
+    }
+    
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws Exception {
+        if (app != null) Entities.destroyAll(app.getManagementContext());
+    }
+
+    @Test
+    public void testUsesDownloadUrlAttribute() throws Exception {
+        entity.setConfig(BrooklynConfigKeys.SUGGESTED_VERSION, "myversion");
+        entity.setAttribute(Attributes.DOWNLOAD_URL, 
"acme.com/version=${version},type=${type},simpletype=${simpletype}");
+        String expectedFilename = 
String.format("version=%s,type=%s,simpletype=%s", "myversion", 
TestEntity.class.getName(), "TestEntity");
+        
+        String expectedLocalRepo = 
String.format("file://$HOME/.brooklyn/repository/%s/%s/%s", "TestEntity", 
"myversion", expectedFilename);
+        String expectedDownloadUrl = String.format("acme.com/%s", 
expectedFilename);
+        String expectedCloudsoftRepo = 
String.format("http://downloads.cloudsoftcorp.com/brooklyn/repository/%s/%s/%s";,
 "TestEntity", "myversion", expectedFilename);
+        assertResolves(expectedLocalRepo, expectedDownloadUrl, 
expectedCloudsoftRepo);
+    }
+    
+    @Test
+    public void testUsesDownloadAddonUrlsAttribute() throws Exception {
+        entity.setConfig(BrooklynConfigKeys.SUGGESTED_VERSION, 
"myentityversion");
+        entity.setAttribute(Attributes.DOWNLOAD_ADDON_URLS, 
ImmutableMap.of("myaddon", 
"acme.com/addon=${addon},version=${addonversion},type=${type},simpletype=${simpletype}"));
+        String expectedFilename = 
String.format("addon=%s,version=%s,type=%s,simpletype=%s", "myaddon", 
"myaddonversion", TestEntity.class.getName(), "TestEntity");
+        
+        String expectedLocalRepo = 
String.format("file://$HOME/.brooklyn/repository/%s/%s/%s", "TestEntity", 
"myentityversion", expectedFilename);
+        String expectedDownloadUrl = String.format("acme.com/%s", 
expectedFilename);
+        String expectedCloudsoftRepo = 
String.format("http://downloads.cloudsoftcorp.com/brooklyn/repository/%s/%s/%s";,
 "TestEntity", "myentityversion", expectedFilename);
+        DownloadResolver actual = 
managementContext.getEntityDownloadsManager().newDownloader(driver, "myaddon", 
ImmutableMap.of("addonversion", "myaddonversion"));
+        assertEquals(actual.getTargets(), ImmutableList.of(expectedLocalRepo, 
expectedDownloadUrl, expectedCloudsoftRepo), "actual="+actual);
+    }
+    
+    @Test
+    public void 
testDefaultResolverSubstitutesDownloadUrlFailsIfVersionMissing() throws 
Exception {
+        entity.setAttribute(Attributes.DOWNLOAD_URL, "version=${version}");
+        try {
+            DownloadResolver result = 
managementContext.getEntityDownloadsManager().newDownloader(driver);
+            fail("Should have failed, but got "+result);
+        } catch (IllegalArgumentException e) {
+            if (!e.toString().contains("${version}")) throw e;
+        }
+    }
+    
+    @Test
+    public void 
testReturnsLocalRepoThenOverrideThenAttributeValThenCloudsoftUrlThenFallback() 
throws Exception {
+        BrooklynProperties managementProperties = 
managementContext.getBrooklynProperties();
+        managementProperties.put("brooklyn.downloads.all.url", 
"http://fromprops/${version}.allprimary";);
+        managementProperties.put("brooklyn.downloads.all.fallbackurl", 
"http://fromfallback/${version}.allfallback";);
+        entity.setAttribute(Attributes.DOWNLOAD_URL, 
"http://fromattrib/${version}.default";);
+        entity.setConfig(BrooklynConfigKeys.SUGGESTED_VERSION, "myversion");
+        String expectedFilename = "myversion.allprimary";
+
+        String expectedLocalRepo = 
String.format("file://$HOME/.brooklyn/repository/%s/%s/%s", "TestEntity", 
"myversion", expectedFilename);
+        String expectedDownloadUrl = String.format("http://fromattrib/%s";, 
"myversion.default");
+        String expectedCloudsoftRepo = 
String.format("http://downloads.cloudsoftcorp.com/brooklyn/repository/%s/%s/%s";,
 "TestEntity", "myversion", expectedFilename);
+        assertResolves(
+                expectedLocalRepo,
+                "http://fromprops/myversion.allprimary";, 
+                expectedDownloadUrl, 
+                expectedCloudsoftRepo, 
+                "http://fromfallback/myversion.allfallback";);
+    }
+
+    @Test
+    public void testInfersFilenameFromDownloadUrl() throws Exception {
+        entity.setConfig(BrooklynConfigKeys.SUGGESTED_VERSION, "myversion");
+        entity.setAttribute(Attributes.DOWNLOAD_URL, 
"http://myhost.com/myfile-${version}.tar.gz";);
+
+        DownloadResolver actual = 
managementContext.getEntityDownloadsManager().newDownloader(driver);
+        assertEquals(actual.getFilename(), "myfile-myversion.tar.gz");
+    }
+    
+    @Test
+    public void testInfersAddonFilenameFromDownloadUrl() throws Exception {
+        entity.setConfig(BrooklynConfigKeys.SUGGESTED_VERSION, "myversion");
+        entity.setAttribute(Attributes.DOWNLOAD_ADDON_URLS, 
ImmutableMap.of("myaddon", "http://myhost.com/myfile-${addonversion}.tar.gz";));
+
+        DownloadResolver actual = 
managementContext.getEntityDownloadsManager().newDownloader(driver, "myaddon", 
ImmutableMap.of("addonversion", "myaddonversion"));
+        assertEquals(actual.getFilename(), "myfile-myaddonversion.tar.gz");
+    }
+    
+    @Test
+    public void testCanOverrideFilenameFromDownloadUrl() throws Exception {
+        entity.setConfig(BrooklynConfigKeys.SUGGESTED_VERSION, "myversion");
+        entity.setAttribute(Attributes.DOWNLOAD_URL, 
"http://myhost.com/download/";);
+
+        DownloadResolver actual = 
managementContext.getEntityDownloadsManager().newDownloader(driver, 
ImmutableMap.of("filename", "overridden.filename.tar.gz"));
+        assertEquals(actual.getFilename(), "overridden.filename.tar.gz");
+    }
+    
+    private void assertResolves(String... expected) {
+        DownloadResolver actual = 
managementContext.getEntityDownloadsManager().newDownloader(driver);
+        assertEquals(actual.getTargets(), ImmutableList.copyOf(expected), 
"actual="+actual);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/test/java/org/apache/brooklyn/core/entity/drivers/downloads/DownloadProducerFromLocalRepoTest.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/java/org/apache/brooklyn/core/entity/drivers/downloads/DownloadProducerFromLocalRepoTest.java
 
b/core/src/test/java/org/apache/brooklyn/core/entity/drivers/downloads/DownloadProducerFromLocalRepoTest.java
new file mode 100644
index 0000000..393e539
--- /dev/null
+++ 
b/core/src/test/java/org/apache/brooklyn/core/entity/drivers/downloads/DownloadProducerFromLocalRepoTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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 org.testng.Assert.assertEquals;
+
+import java.util.List;
+
+import org.apache.brooklyn.api.entity.EntitySpec;
+import 
org.apache.brooklyn.api.entity.drivers.downloads.DownloadResolverManager.DownloadRequirement;
+import 
org.apache.brooklyn.api.entity.drivers.downloads.DownloadResolverManager.DownloadTargets;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
+import org.apache.brooklyn.core.entity.Entities;
+import 
org.apache.brooklyn.core.entity.drivers.downloads.BasicDownloadRequirement;
+import 
org.apache.brooklyn.core.entity.drivers.downloads.DownloadProducerFromLocalRepo;
+import org.apache.brooklyn.core.entity.factory.ApplicationBuilder;
+import org.apache.brooklyn.core.internal.BrooklynProperties;
+import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
+import org.apache.brooklyn.core.test.entity.TestApplication;
+import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import org.apache.brooklyn.location.core.SimulatedLocation;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+public class DownloadProducerFromLocalRepoTest {
+
+    private BrooklynProperties brooklynProperties;
+    private LocalManagementContext managementContext;
+    private Location loc;
+    private TestApplication app;
+    private TestEntity entity;
+    private MyEntityDriver driver;
+    private String entitySimpleType;
+    private DownloadProducerFromLocalRepo resolver;
+
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        brooklynProperties = BrooklynProperties.Factory.newEmpty();
+        managementContext = new LocalManagementContext(brooklynProperties);
+        
+        loc = new SimulatedLocation();
+        app = ApplicationBuilder.newManagedApp(TestApplication.class, 
managementContext);
+        entity = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+        driver = new MyEntityDriver(entity, loc);
+        entitySimpleType = TestEntity.class.getSimpleName();
+        
+        resolver = new DownloadProducerFromLocalRepo(brooklynProperties);
+    }
+    
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws Exception {
+        if (app != null) Entities.destroyAll(app.getManagementContext());
+    }
+
+    @Test
+    public void testReturnsEmptyWhenDisabled() throws Exception {
+        
brooklynProperties.put(DownloadProducerFromLocalRepo.LOCAL_REPO_ENABLED, false);
+        assertResolves(ImmutableList.<String>of(), ImmutableList.<String>of());
+    }
+    
+    @Test
+    public void testReturnsDefault() throws Exception {
+        // uses default of ${simpletype}-${version}.tar.gz";
+        String entityVersion = "myversion";
+        String downloadFilename = 
(entitySimpleType+"-"+entityVersion+".tar.gz").toLowerCase();
+        entity.setConfig(BrooklynConfigKeys.SUGGESTED_VERSION, entityVersion);
+        
assertResolves(String.format("file://$HOME/.brooklyn/repository/%s/%s/%s", 
entitySimpleType, entityVersion, downloadFilename));
+    }
+    
+    @Test
+    public void testReturnsFilenameFromDriver() throws Exception {
+        String entityVersion = "myversion";
+        String filename = "my.file.name";
+        entity.setConfig(BrooklynConfigKeys.SUGGESTED_VERSION, entityVersion);
+        
+        BasicDownloadRequirement req = new BasicDownloadRequirement(driver, 
ImmutableMap.of("filename", filename));
+        assertResolves(req, 
String.format("file://$HOME/.brooklyn/repository/%s/%s/%s", entitySimpleType, 
entityVersion, filename));
+    }
+    
+    @Test
+    public void testReturnsFileSuffixFromRequirements() throws Exception {
+        // uses ${driver.downloadFileSuffix}
+        String entityVersion = "myversion";
+        String fileSuffix = "mysuffix";
+        String expectedFilename = 
(entitySimpleType+"-"+entityVersion+"."+fileSuffix).toLowerCase();
+        entity.setConfig(BrooklynConfigKeys.SUGGESTED_VERSION, entityVersion);
+        
+        BasicDownloadRequirement req = new BasicDownloadRequirement(driver, 
ImmutableMap.of("fileSuffix", fileSuffix));
+        assertResolves(req, 
String.format("file://$HOME/.brooklyn/repository/%s/%s/%s", entitySimpleType, 
entityVersion, expectedFilename));
+    }
+    
+    private void assertResolves(String... expected) {
+        assertResolves(ImmutableList.copyOf(expected), 
ImmutableList.<String>of());
+    }
+    
+    private void assertResolves(List<String> expectedPrimaries, List<String> 
expectedFallbacks) {
+        assertResolves(new BasicDownloadRequirement(driver), 
expectedPrimaries, expectedFallbacks);
+    }
+    
+    private void assertResolves(DownloadRequirement req, String... expected) {
+        assertResolves(req, ImmutableList.copyOf(expected), 
ImmutableList.<String>of());
+    }
+
+    private void assertResolves(DownloadRequirement req, List<String> 
expectedPrimaries, List<String> expectedFallbacks) {
+        DownloadTargets actual = resolver.apply(req);
+        assertEquals(actual.getPrimaryLocations(), expectedPrimaries);
+        assertEquals(actual.getFallbackLocations(), expectedFallbacks);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/test/java/org/apache/brooklyn/core/entity/drivers/downloads/DownloadProducerFromPropertiesTest.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/java/org/apache/brooklyn/core/entity/drivers/downloads/DownloadProducerFromPropertiesTest.java
 
b/core/src/test/java/org/apache/brooklyn/core/entity/drivers/downloads/DownloadProducerFromPropertiesTest.java
new file mode 100644
index 0000000..e3b8f1c
--- /dev/null
+++ 
b/core/src/test/java/org/apache/brooklyn/core/entity/drivers/downloads/DownloadProducerFromPropertiesTest.java
@@ -0,0 +1,162 @@
+/*
+ * 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 org.testng.Assert.assertEquals;
+
+import java.util.List;
+
+import org.apache.brooklyn.api.entity.EntitySpec;
+import 
org.apache.brooklyn.api.entity.drivers.downloads.DownloadResolverManager.DownloadRequirement;
+import 
org.apache.brooklyn.api.entity.drivers.downloads.DownloadResolverManager.DownloadTargets;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
+import org.apache.brooklyn.core.entity.Entities;
+import 
org.apache.brooklyn.core.entity.drivers.downloads.BasicDownloadRequirement;
+import 
org.apache.brooklyn.core.entity.drivers.downloads.DownloadProducerFromLocalRepo;
+import 
org.apache.brooklyn.core.entity.drivers.downloads.DownloadProducerFromProperties;
+import org.apache.brooklyn.core.entity.factory.ApplicationBuilder;
+import org.apache.brooklyn.core.internal.BrooklynProperties;
+import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
+import org.apache.brooklyn.core.test.entity.TestApplication;
+import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import org.apache.brooklyn.location.core.SimulatedLocation;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+public class DownloadProducerFromPropertiesTest {
+
+    private BrooklynProperties brooklynProperties;
+    private LocalManagementContext managementContext;
+    private Location loc;
+    private TestApplication app;
+    private TestEntity entity;
+    private MyEntityDriver driver;
+    private DownloadProducerFromProperties resolver;
+
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        brooklynProperties = BrooklynProperties.Factory.newEmpty();
+        
brooklynProperties.put(DownloadProducerFromLocalRepo.LOCAL_REPO_ENABLED, false);
+        managementContext = new LocalManagementContext(brooklynProperties);
+        
+        loc = new SimulatedLocation();
+        app = ApplicationBuilder.newManagedApp(TestApplication.class, 
managementContext);
+        entity = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+        driver = new MyEntityDriver(entity, loc);
+        
+        resolver = new DownloadProducerFromProperties(brooklynProperties);
+    }
+    
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws Exception {
+        if (app != null) Entities.destroyAll(app.getManagementContext());
+    }
+
+    @Test
+    public void testReturnsEmptyWhenEmpty() throws Exception {
+        assertResolves(ImmutableList.<String>of(), ImmutableList.<String>of());
+    }
+    
+    @Test
+    public void testReturnsGlobalUrl() throws Exception {
+        brooklynProperties.put("brooklyn.downloads.all.url", "myurl");
+        assertResolves("myurl");
+    }
+    
+    @Test
+    public void testReturnsGlobalUrlsSplitOnSemicolon() throws Exception {
+        brooklynProperties.put("brooklyn.downloads.all.url", "myurl; myurl2");
+        assertResolves("myurl", "myurl2");
+    }
+    
+    @Test
+    public void testReturnsGlobalFallbackUrl() throws Exception {
+        brooklynProperties.put("brooklyn.downloads.all.fallbackurl", "myurl");
+        assertResolves(ImmutableList.<String>of(), ImmutableList.of("myurl"));
+    }
+
+    @Test
+    public void testSubstitutionsAppliedToFallbackUrl() throws Exception {
+        brooklynProperties.put("brooklyn.downloads.all.fallbackurl", 
"version=${version}");
+        entity.setConfig(BrooklynConfigKeys.SUGGESTED_VERSION, "myversion");
+        assertResolves(ImmutableList.<String>of(), 
ImmutableList.of("version=myversion"));
+    }
+
+    @Test
+    public void testReturnsGlobalFallbackUrlAsLast() throws Exception {
+        brooklynProperties.put("brooklyn.downloads.all.url", "myurl");
+        brooklynProperties.put("brooklyn.downloads.all.fallbackurl", "myurl2");
+        assertResolves(ImmutableList.of("myurl"), ImmutableList.of("myurl2"));
+    }
+    
+    @Test
+    public void testReturnsGlobalUrlWithEntitySubstituions() throws Exception {
+        brooklynProperties.put("brooklyn.downloads.all.url", 
"version=${version}");
+        entity.setConfig(BrooklynConfigKeys.SUGGESTED_VERSION, "myversion");
+        assertResolves("version=myversion");
+    }
+    
+    @Test
+    public void testEntitySpecificUrlOverridesGlobalUrl() throws Exception {
+        brooklynProperties.put("brooklyn.downloads.all.url", 
"version=${version}");
+        brooklynProperties.put("brooklyn.downloads.entity.TestEntity.url", 
"overridden,version=${version}");
+        entity.setConfig(BrooklynConfigKeys.SUGGESTED_VERSION, "myversion");
+        assertResolves("overridden,version=myversion", "version=myversion");
+    }
+    
+    @Test
+    public void testEntitySpecificAddonUsesGlobalUrl() throws Exception {
+        brooklynProperties.put("brooklyn.downloads.all.url", 
"version=${version}");
+
+        DownloadRequirement req = new BasicDownloadRequirement(driver, 
"myaddon", ImmutableMap.of("version", "myversion"));
+        assertResolves(req, ImmutableList.of("version=myversion"), 
ImmutableList.<String>of());
+    }
+    
+    @Test
+    public void testEntitySpecificAddonOverridesGlobalUrl() throws Exception {
+        brooklynProperties.put("brooklyn.downloads.all.url", 
"${addon}-${version}");
+        
brooklynProperties.put("brooklyn.downloads.entity.TestEntity.addon.myaddon.url",
 "overridden,${addon}-${version}");
+
+        DownloadRequirement req = new BasicDownloadRequirement(driver, 
"myaddon", ImmutableMap.of("version", "myversion"));
+        assertResolves(req, ImmutableList.of("overridden,myaddon-myversion", 
"myaddon-myversion"), ImmutableList.<String>of());
+    }
+    
+    private void assertResolves(String... expected) {
+        assertResolves(ImmutableList.copyOf(expected), 
ImmutableList.<String>of());
+    }
+    
+    private void assertResolves(List<String> expectedPrimaries, List<String> 
expectedFallbacks) {
+        assertResolves(new BasicDownloadRequirement(driver), 
expectedPrimaries, expectedFallbacks);
+    }
+    
+    private void assertResolves(DownloadRequirement req, String... expected) {
+        assertResolves(req, ImmutableList.copyOf(expected), 
ImmutableList.<String>of());
+    }
+
+    private void assertResolves(DownloadRequirement req, List<String> 
expectedPrimaries, List<String> expectedFallbacks) {
+        DownloadTargets actual = resolver.apply(req);
+        assertEquals(actual.getPrimaryLocations(), expectedPrimaries, 
"actual="+actual);
+        assertEquals(actual.getFallbackLocations(), expectedFallbacks, 
"actual="+actual);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/test/java/org/apache/brooklyn/core/entity/drivers/downloads/DownloadSubstitutersTest.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/java/org/apache/brooklyn/core/entity/drivers/downloads/DownloadSubstitutersTest.java
 
b/core/src/test/java/org/apache/brooklyn/core/entity/drivers/downloads/DownloadSubstitutersTest.java
new file mode 100644
index 0000000..0d2ea21
--- /dev/null
+++ 
b/core/src/test/java/org/apache/brooklyn/core/entity/drivers/downloads/DownloadSubstitutersTest.java
@@ -0,0 +1,131 @@
+/*
+ * 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 org.testng.Assert.assertEquals;
+import static org.testng.Assert.fail;
+
+import java.util.Map;
+
+import org.apache.brooklyn.api.entity.EntitySpec;
+import 
org.apache.brooklyn.api.entity.drivers.downloads.DownloadResolverManager.DownloadTargets;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
+import 
org.apache.brooklyn.core.entity.drivers.downloads.BasicDownloadRequirement;
+import org.apache.brooklyn.core.entity.drivers.downloads.DownloadSubstituters;
+import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
+import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import org.apache.brooklyn.location.core.SimulatedLocation;
+
+import com.google.common.base.Functions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+public class DownloadSubstitutersTest extends BrooklynAppUnitTestSupport {
+
+    private Location loc;
+    private TestEntity entity;
+    private MyEntityDriver driver;
+
+    @BeforeMethod(alwaysRun=true)
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        loc = new SimulatedLocation();
+        entity = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+        driver = new MyEntityDriver(entity, loc);
+    }
+    
+    @Test
+    public void testSimpleSubstitution() throws Exception {
+        entity.setConfig(BrooklynConfigKeys.SUGGESTED_VERSION, "myversion");
+        String pattern = "mykey1=${mykey1},mykey2=${mykey2}";
+        String result = DownloadSubstituters.substitute(pattern, 
ImmutableMap.of("mykey1", "myval1", "mykey2", "myval2"));
+        assertEquals(result, "mykey1=myval1,mykey2=myval2");
+    }
+
+    @Test
+    public void testSubstitutionIncludesDefaultSubs() throws Exception {
+        entity.setConfig(BrooklynConfigKeys.SUGGESTED_VERSION, "myversion");
+        String pattern = 
"version=${version},type=${type},simpletype=${simpletype}";
+        BasicDownloadRequirement req = new BasicDownloadRequirement(driver);
+        String result = DownloadSubstituters.substitute(req, pattern);
+        assertEquals(result, String.format("version=%s,type=%s,simpletype=%s", 
"myversion", TestEntity.class.getName(), TestEntity.class.getSimpleName()));
+    }
+
+    @Test
+    public void testSubstitutionDoesMultipleMatches() throws Exception {
+        String simpleType = TestEntity.class.getSimpleName();
+        String pattern = "simpletype=${simpletype},simpletype=${simpletype}";
+        BasicDownloadRequirement req = new BasicDownloadRequirement(driver);
+        String result = DownloadSubstituters.substitute(req, pattern);
+        assertEquals(result, String.format("simpletype=%s,simpletype=%s", 
simpleType, simpleType));
+    }
+
+    @Test
+    public void testSubstitutionUsesEntityBean() throws Exception {
+        String entityid = entity.getId();
+        String pattern = "id=${entity.id}";
+        BasicDownloadRequirement req = new BasicDownloadRequirement(driver);
+        String result = DownloadSubstituters.substitute(req, pattern);
+        assertEquals(result, String.format("id=%s", entityid));
+    }
+
+    @Test
+    public void testSubstitutionUsesDriverBean() throws Exception {
+        String entityid = entity.getId();
+        String pattern = "id=${driver.entity.id}";
+        BasicDownloadRequirement req = new BasicDownloadRequirement(driver);
+        String result = DownloadSubstituters.substitute(req, pattern);
+        assertEquals(result, String.format("id=%s", entityid));
+    }
+
+    @Test
+    public void testSubstitutionUsesOverrides() throws Exception {
+        entity.setConfig(BrooklynConfigKeys.SUGGESTED_VERSION, "myversion");
+        String pattern = "version=${version},mykey1=${mykey1}";
+        BasicDownloadRequirement req = new BasicDownloadRequirement(driver, 
ImmutableMap.of("version", "overriddenversion", "mykey1", "myval1"));
+        String result = DownloadSubstituters.substitute(req, pattern);
+        assertEquals(result, "version=overriddenversion,mykey1=myval1");
+    }
+
+    @Test
+    public void testThrowsIfUnmatchedSubstitutions() throws Exception {
+        String pattern = "nothere=${nothere}";
+        BasicDownloadRequirement req = new BasicDownloadRequirement(driver);
+        try {
+            String result = DownloadSubstituters.substitute(req, pattern);
+            fail("Should have failed, but got "+result);
+        } catch (IllegalArgumentException e) {
+            if (!e.toString().contains("${nothere}")) throw e;
+        }
+    }
+
+    @Test
+    public void testSubstituter() throws Exception {
+        entity.setConfig(BrooklynConfigKeys.SUGGESTED_VERSION, "myversion");
+        String baseurl = 
"version=${version},type=${type},simpletype=${simpletype}";
+        Map<String,Object> subs = 
DownloadSubstituters.getBasicEntitySubstitutions(driver);
+        DownloadTargets result = 
DownloadSubstituters.substituter(Functions.constant(baseurl), 
Functions.constant(subs)).apply(new BasicDownloadRequirement(driver));
+        String expected = String.format("version=%s,type=%s,simpletype=%s", 
"myversion", TestEntity.class.getName(), TestEntity.class.getSimpleName());
+        assertEquals(result.getPrimaryLocations(), ImmutableList.of(expected));
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/test/java/org/apache/brooklyn/core/entity/drivers/downloads/FilenameProducersTest.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/java/org/apache/brooklyn/core/entity/drivers/downloads/FilenameProducersTest.java
 
b/core/src/test/java/org/apache/brooklyn/core/entity/drivers/downloads/FilenameProducersTest.java
new file mode 100644
index 0000000..0fd155d
--- /dev/null
+++ 
b/core/src/test/java/org/apache/brooklyn/core/entity/drivers/downloads/FilenameProducersTest.java
@@ -0,0 +1,34 @@
+/*
+ * 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 org.testng.Assert.assertEquals;
+
+import org.apache.brooklyn.core.entity.drivers.downloads.FilenameProducers;
+import org.testng.annotations.Test;
+
+public class FilenameProducersTest {
+
+    @Test
+    public void testInferFilename() throws Exception {
+        assertEquals(FilenameProducers.inferFilename("myname.tgz"), 
"myname.tgz");
+        assertEquals(FilenameProducers.inferFilename("a/myname.tgz"), 
"myname.tgz");
+        assertEquals(FilenameProducers.inferFilename("acme.com/download/"), 
null);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/test/java/org/apache/brooklyn/core/entity/drivers/downloads/MyEntityDriver.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/java/org/apache/brooklyn/core/entity/drivers/downloads/MyEntityDriver.java
 
b/core/src/test/java/org/apache/brooklyn/core/entity/drivers/downloads/MyEntityDriver.java
new file mode 100644
index 0000000..504d07c
--- /dev/null
+++ 
b/core/src/test/java/org/apache/brooklyn/core/entity/drivers/downloads/MyEntityDriver.java
@@ -0,0 +1,44 @@
+/*
+ * 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 org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.EntityLocal;
+import org.apache.brooklyn.api.entity.drivers.EntityDriver;
+import org.apache.brooklyn.api.location.Location;
+
+public class MyEntityDriver implements EntityDriver {
+    private final Entity entity;
+    private final Location location;
+
+    MyEntityDriver(Entity entity, Location location) {
+        this.entity = entity;
+        this.location = location;
+    }
+    
+    @Override
+    public EntityLocal getEntity() {
+        return (EntityLocal) entity;
+    }
+
+    @Override
+    public Location getLocation() {
+        return location;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/test/java/org/apache/brooklyn/core/entity/hello/HelloEntity.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/java/org/apache/brooklyn/core/entity/hello/HelloEntity.java 
b/core/src/test/java/org/apache/brooklyn/core/entity/hello/HelloEntity.java
new file mode 100644
index 0000000..9d14868
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/core/entity/hello/HelloEntity.java
@@ -0,0 +1,53 @@
+/*
+ * 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.hello;
+
+import org.apache.brooklyn.api.entity.ImplementedBy;
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.api.sensor.Sensor;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.annotation.Effector;
+import org.apache.brooklyn.core.annotation.EffectorParam;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.effector.core.MethodEffector;
+import org.apache.brooklyn.entity.group.AbstractGroup;
+import org.apache.brooklyn.sensor.core.BasicSensor;
+import org.apache.brooklyn.sensor.core.Sensors;
+
+@ImplementedBy(HelloEntityImpl.class)
+public interface HelloEntity extends AbstractGroup {
+
+    /** records name of the person represented by this entity */
+    public static ConfigKey<String> MY_NAME = 
ConfigKeys.newStringConfigKey("my.name");
+    
+    /** this "person"'s favourite name */
+    public static AttributeSensor<String> FAVOURITE_NAME = 
Sensors.newStringSensor("my.favourite.name");
+    
+    /** records age (in years) of the person represented by this entity */
+    public static AttributeSensor<Integer> AGE = 
Sensors.newIntegerSensor("my.age");
+    
+    /** emits a "birthday" event whenever age is changed (tests non-attribute 
events) */    
+    public static Sensor<Void> ITS_MY_BIRTHDAY = new 
BasicSensor<Void>(Void.TYPE, "my.birthday");
+    
+    /**  */
+    public static MethodEffector<Void> SET_AGE = new 
MethodEffector<Void>(HelloEntity.class, "setAge");
+    
+    @Effector(description="allows setting the age")
+    public void setAge(@EffectorParam(name="age") Integer age);
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/test/java/org/apache/brooklyn/core/entity/hello/HelloEntityImpl.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/java/org/apache/brooklyn/core/entity/hello/HelloEntityImpl.java 
b/core/src/test/java/org/apache/brooklyn/core/entity/hello/HelloEntityImpl.java
new file mode 100644
index 0000000..67ae8a6
--- /dev/null
+++ 
b/core/src/test/java/org/apache/brooklyn/core/entity/hello/HelloEntityImpl.java
@@ -0,0 +1,31 @@
+/*
+ * 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.hello;
+
+import org.apache.brooklyn.entity.group.AbstractGroupImpl;
+
+
+public class HelloEntityImpl extends AbstractGroupImpl implements HelloEntity {
+
+    @Override
+    public void setAge(Integer age) {
+        setAttribute(AGE, age);
+        emit(ITS_MY_BIRTHDAY, null);
+    }
+}

Reply via email to