http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/BasicDownloadsManager.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/BasicDownloadsManager.java
 
b/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/BasicDownloadsManager.java
new file mode 100644
index 0000000..72e8f15
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/BasicDownloadsManager.java
@@ -0,0 +1,161 @@
+/*
+ * 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 java.util.Map;
+import java.util.Set;
+
+import org.apache.brooklyn.api.entity.drivers.EntityDriver;
+import org.apache.brooklyn.api.entity.drivers.downloads.DownloadResolver;
+import 
org.apache.brooklyn.api.entity.drivers.downloads.DownloadResolverManager;
+import org.apache.brooklyn.config.StringConfigMap;
+import org.apache.brooklyn.util.text.Strings;
+
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
+public class BasicDownloadsManager implements DownloadResolverManager {
+
+    private final List<Function<? super DownloadRequirement, ? extends 
DownloadTargets>> producers = Lists.newCopyOnWriteArrayList();
+
+    private final List<Function<? super DownloadRequirement, String>> 
filenameProducers = Lists.newCopyOnWriteArrayList();
+
+    /**
+     * The default is (in-order) to:
+     * <ol>
+     *   <li>Use the local repo, if any (defaulting to 
$HOME/.brooklyn/repository)
+     *   <li>Use brooklyn properties for any download overrides defined there 
(see {@link DownloadProducerFromProperties}
+     *   <li>Use the entity's Attributes.DOWNLOAD_URL
+     *   <li>Use the cloudsoft fallback repo
+     * </ol>
+     * @param config
+     */
+    public static BasicDownloadsManager newDefault(StringConfigMap config) {
+        BasicDownloadsManager result = new BasicDownloadsManager();
+        
+        // In-order, will look up: local repo, overrides defined in the 
properties, and then 
+        // the entity's attribute to get the download URL
+        DownloadProducerFromLocalRepo localRepoProducer = new 
DownloadProducerFromLocalRepo(config);
+        DownloadProducerFromProperties propertiesProducer = new 
DownloadProducerFromProperties(config);
+        DownloadProducerFromUrlAttribute attributeProducer = new 
DownloadProducerFromUrlAttribute();
+        DownloadProducerFromCloudsoftRepo cloudsoftRepoProducer = new 
DownloadProducerFromCloudsoftRepo(config);
+        
+        result.registerProducer(localRepoProducer);
+        result.registerProducer(propertiesProducer);
+        result.registerProducer(attributeProducer);
+        result.registerProducer(cloudsoftRepoProducer);
+        
+        
result.registerFilenameProducer(FilenameProducers.fromFilenameProperty());
+        
result.registerFilenameProducer(FilenameProducers.firstPrimaryTargetOf(propertiesProducer));
+        
result.registerFilenameProducer(FilenameProducers.firstPrimaryTargetOf(attributeProducer));
+        
+        return result;
+    }
+    
+    public static BasicDownloadsManager newEmpty() {
+        return new BasicDownloadsManager();
+    }
+    
+    @Override
+    public void registerPrimaryProducer(Function<? super DownloadRequirement, 
? extends DownloadTargets> producer) {
+        producers.add(0, checkNotNull(producer, "resolver"));
+    }
+
+    @Override
+    public void registerProducer(Function<? super DownloadRequirement, ? 
extends DownloadTargets> producer) {
+        producers.add(checkNotNull(producer, "resolver"));
+    }
+
+    @Override
+    public void registerFilenameProducer(Function<? super DownloadRequirement, 
String> producer) {
+        filenameProducers.add(checkNotNull(producer, "producer"));
+    }
+
+    @Override
+    public DownloadResolver newDownloader(EntityDriver driver) {
+        return newDownloader(new BasicDownloadRequirement(driver));
+    }
+
+    @Override
+    public DownloadResolver newDownloader(EntityDriver driver, Map<String, ?> 
properties) {
+        return newDownloader(new BasicDownloadRequirement(driver, properties));
+    }
+
+    @Override
+    public DownloadResolver newDownloader(EntityDriver driver, String 
addonName, Map<String, ?> addonProperties) {
+        return newDownloader(new BasicDownloadRequirement(driver, addonName, 
addonProperties));
+    }
+
+    private DownloadResolver newDownloader(DownloadRequirement req) {
+        // Infer filename
+        String filename = null;
+        for (Function<? super DownloadRequirement, String> filenameProducer : 
filenameProducers) {
+            filename = filenameProducer.apply(req);
+            if (!Strings.isBlank(filename)) break;
+        }
+        
+        // If a filename-producer has given us the filename, then augment the 
DownloadRequirement with that
+        // (so that local-repo substitutions etc can use that explicit 
filename)
+        DownloadRequirement wrappedReq;
+        if (filename == null) {
+            wrappedReq = req;
+        } else {
+            wrappedReq = BasicDownloadRequirement.copy(req, 
ImmutableMap.of("filename", filename));
+        }
+        
+        // Get ordered download targets to be tried
+        List<String> primaries = Lists.newArrayList();
+        List<String> fallbacks = Lists.newArrayList();
+        for (Function<? super DownloadRequirement, ? extends DownloadTargets> 
producer : producers) {
+            DownloadTargets vals = producer.apply(wrappedReq);
+            primaries.addAll(vals.getPrimaryLocations());
+            fallbacks.addAll(vals.getFallbackLocations());
+            if (!vals.canContinueResolving()) {
+                break;
+            }
+        }
+
+        Set<String> result = Sets.newLinkedHashSet();
+        result.addAll(primaries);
+        result.addAll(fallbacks);
+
+        if (result.isEmpty()) {
+            throw new IllegalArgumentException("No downloads matched for 
"+req);
+        }
+        
+        // If filename-producers didn't give any explicit filename, then infer 
from download results
+        if (filename == null) {
+            for (String target : result) {
+                filename = FilenameProducers.inferFilename(target);
+                if (!Strings.isBlank(filename)) break;
+            }
+        }
+        if (Strings.isBlank(filename)) {
+            throw new IllegalArgumentException("No filenames matched for 
"+req+" (targets "+result+")");
+        }
+        
+        // And return the result
+        return new BasicDownloadResolver(result, filename);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/DownloadProducerFromCloudsoftRepo.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/DownloadProducerFromCloudsoftRepo.java
 
b/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/DownloadProducerFromCloudsoftRepo.java
new file mode 100644
index 0000000..715fe96
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/DownloadProducerFromCloudsoftRepo.java
@@ -0,0 +1,83 @@
+/*
+ * 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 java.util.Map;
+
+import 
org.apache.brooklyn.api.entity.drivers.downloads.DownloadResolverManager.DownloadRequirement;
+import 
org.apache.brooklyn.api.entity.drivers.downloads.DownloadResolverManager.DownloadTargets;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.config.StringConfigMap;
+import org.apache.brooklyn.core.config.BasicConfigKey;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Function;
+
+public class DownloadProducerFromCloudsoftRepo implements 
Function<DownloadRequirement, DownloadTargets> {
+    
+    @SuppressWarnings("unused")
+    private static final Logger LOG = 
LoggerFactory.getLogger(DownloadProducerFromCloudsoftRepo.class);
+
+    public static final ConfigKey<String> CLOUDSOFT_REPO_URL = 
BasicConfigKey.builder(String.class)
+            
.name(DownloadProducerFromProperties.DOWNLOAD_CONF_PREFIX+"repo.cloudsoft.url")
+            .description("Whether to use the cloudsoft repo for downloading 
entities, during installs")
+            
.defaultValue("http://downloads.cloudsoftcorp.com/brooklyn/repository";)
+            .build();
+
+    public static final ConfigKey<Boolean> CLOUDSOFT_REPO_ENABLED = 
BasicConfigKey.builder(Boolean.class)
+            
.name(DownloadProducerFromProperties.DOWNLOAD_CONF_PREFIX+"repo.cloudsoft.enabled")
+            .description("Whether to use the cloudsoft repo for downloading 
entities, during installs")
+            .defaultValue(true)
+            .build();
+    
+    public static final String CLOUDSOFT_REPO_URL_PATTERN = "%s/"+
+            "${simpletype}/${version}/"+
+            "<#if filename??>"+
+                "${filename}" +
+            "<#else>"+
+              "<#if addon??>"+
+                
"${simpletype?lower_case}-${addon?lower_case}-${addonversion?lower_case}.${fileSuffix!\"tar.gz\"}"+
+              "<#else>"+
+                  
"${simpletype?lower_case}-${version?lower_case}.${fileSuffix!\"tar.gz\"}"+
+              "</#if>"+
+            "</#if>";
+
+
+    private final StringConfigMap config;
+
+    public DownloadProducerFromCloudsoftRepo(StringConfigMap config) {
+        this.config = config;
+    }
+    
+    public DownloadTargets apply(DownloadRequirement req) {
+        Boolean enabled = config.getConfig(CLOUDSOFT_REPO_ENABLED);
+        String baseUrl = config.getConfig(CLOUDSOFT_REPO_URL);
+        String url = String.format(CLOUDSOFT_REPO_URL_PATTERN, baseUrl);
+        
+        if (enabled) {
+            Map<String, ?> subs = 
DownloadSubstituters.getBasicSubstitutions(req);
+            String result = DownloadSubstituters.substitute(url, subs);
+            return BasicDownloadTargets.builder().addPrimary(result).build();
+            
+        } else {
+            return BasicDownloadTargets.empty();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/DownloadProducerFromLocalRepo.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/DownloadProducerFromLocalRepo.java
 
b/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/DownloadProducerFromLocalRepo.java
new file mode 100644
index 0000000..8de8ae8
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/DownloadProducerFromLocalRepo.java
@@ -0,0 +1,84 @@
+/*
+ * 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 java.util.Map;
+
+import 
org.apache.brooklyn.api.entity.drivers.downloads.DownloadResolverManager.DownloadRequirement;
+import 
org.apache.brooklyn.api.entity.drivers.downloads.DownloadResolverManager.DownloadTargets;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.config.StringConfigMap;
+import org.apache.brooklyn.core.config.BasicConfigKey;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Function;
+
+public class DownloadProducerFromLocalRepo implements 
Function<DownloadRequirement, DownloadTargets> {
+    
+    @SuppressWarnings("unused")
+    private static final Logger LOG = 
LoggerFactory.getLogger(DownloadProducerFromLocalRepo.class);
+
+    public static final ConfigKey<String> LOCAL_REPO_PATH = 
BasicConfigKey.builder(String.class)
+            
.name(DownloadProducerFromProperties.DOWNLOAD_CONF_PREFIX+"repo.local.path")
+            .description("Fully qualified path of the local repo")
+            .defaultValue("$HOME/.brooklyn/repository")
+            .build();
+
+    public static final ConfigKey<Boolean> LOCAL_REPO_ENABLED = 
BasicConfigKey.builder(Boolean.class)
+            
.name(DownloadProducerFromProperties.DOWNLOAD_CONF_PREFIX+"repo.local.enabled")
+            .description("Whether to use the local repo for downloading 
entities, during installs")
+            .defaultValue(true)
+            .build();
+
+    // TODO explain why this is this in lower_case!  it's surprising
+    public static final String LOCAL_REPO_URL_PATTERN = "file://%s/"+
+            "${simpletype}/${version}/"+
+            "<#if filename??>"+
+                "${filename}" +
+            "<#else>"+
+              "<#if addon??>"+
+                
"${simpletype?lower_case}-${addon?lower_case}-${addonversion?lower_case}.${fileSuffix!\"tar.gz\"}"+
+              "<#else>"+
+                  
"${simpletype?lower_case}-${version?lower_case}.${fileSuffix!\"tar.gz\"}"+
+              "</#if>"+
+            "</#if>";
+
+
+    private final StringConfigMap config;
+
+    public DownloadProducerFromLocalRepo(StringConfigMap config) {
+        this.config = config;
+    }
+    
+    public DownloadTargets apply(DownloadRequirement req) {
+        Boolean enabled = config.getConfig(LOCAL_REPO_ENABLED);
+        String path = config.getConfig(LOCAL_REPO_PATH);
+        String url = String.format(LOCAL_REPO_URL_PATTERN, path);
+        
+        if (enabled) {
+            Map<String, ?> subs = 
DownloadSubstituters.getBasicSubstitutions(req);
+            String result = DownloadSubstituters.substitute(url, subs);
+            return BasicDownloadTargets.builder().addPrimary(result).build();
+            
+        } else {
+            return BasicDownloadTargets.empty();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/DownloadProducerFromProperties.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/DownloadProducerFromProperties.java
 
b/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/DownloadProducerFromProperties.java
new file mode 100644
index 0000000..a5b3204
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/DownloadProducerFromProperties.java
@@ -0,0 +1,344 @@
+/*
+ * 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 java.util.Map;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.drivers.EntityDriver;
+import 
org.apache.brooklyn.api.entity.drivers.downloads.DownloadResolverManager.DownloadRequirement;
+import 
org.apache.brooklyn.api.entity.drivers.downloads.DownloadResolverManager.DownloadTargets;
+import org.apache.brooklyn.config.StringConfigMap;
+import org.apache.brooklyn.core.entity.Attributes;
+import org.apache.brooklyn.util.text.Strings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+/**
+ * Based on the contents of brooklyn properties, sets up rules for resolving 
where to
+ * download artifacts from, for installing entities. 
+ * 
+ * By default, these rules override the DOWNLOAD_URL defined on the entities 
in code.
+ * Global properties can be specified that apply to all entities. 
Entity-specific properties
+ * can also be specified (which override the global properties for that entity 
type).
+ * 
+ * Below is an example of realistic configuration for an enterprise who have 
an in-house 
+ * repository that must be used for everything, rather than going out to the 
public internet. 
+ * <pre>
+ * {@code
+ * // FIXME Check format for including addonname- only if addonname is 
non-null?
+ * // FIXME Use this in a testng test case
+ * 
brooklyn.downloads.all.url=http://downloads.acme.com/brookyn/repository/${simpletype}/${simpletype}-${addon??
 addon-}${version}.${fileSuffix!.tar.gz}
+ * }
+ * </pre>
+ * 
+ * To illustrate the features and variations one can use, below is an example 
of global 
+ * properties that can be specified. The semicolon-separated list of URLs will 
be tried  in-order
+ * until one succeeds. The fallback url says to use that if all other URLs 
fail (or no others are
+ * specified). 
+ * <pre>
+ * {@code
+ * brooklyn.downloads.all.url=http://myurl1/${simpletype}-${version}.tar.gz; 
http://myurl2/${simpletype}-${version}.tar.gz
+ * 
brooklyn.downloads.all.fallbackurl=http://myurl3/${simpletype}-${version}.tar.gz
+ * }
+ * </pre>
+ * 
+ * Similarly, entity-specific properties can be defined. All "global 
properties" will also apply
+ * to this entity type, unless explicitly overridden.
+ * <pre>
+ * {@code
+ * 
brooklyn.downloads.entity.tomcatserver.url=http://mytomcaturl1/tomcat-${version}.tar.gz
+ * 
brooklyn.downloads.entity.tomcatserver.fallbackurl=http://myurl2/tomcat-${version}.tar.gz
+ * }
+ * </pre>
+ * 
+ * Downloads for entity-specific add-ons can also be defined. All "global 
properties" will also apply
+ * to this entity type, unless explicitly overridden.
+ * <pre>
+ * {@code
+ * 
brooklyn.downloads.entity.nginxcontroller.addon.stickymodule.url=http://myurl1/nginx-stickymodule-${version}.tar.gz
+ * 
brooklyn.downloads.entity.nginxcontroller.addon.stickymodule.fallbackurl=http://myurl2/nginx-stickymodule-${version}.tar.gz
+ * }
+ * </pre>
+ * 
+ * If no explicit URLs are supplied, then by default it will use the 
DOWNLOAD_URL attribute
+ * of the entity  (if supplied), followed by the fallbackurl if that fails. 
+ * 
+ * A URL can be a "template", where things of the form ${version} will be 
substituted for the value
+ * of "version" provided for that entity. The freemarker template engine is 
used to convert URLs 
+ * (see <a href="http://freemarker.org";>http://freemarker.org</a>). For 
example, one could use the URL:
+ * <pre>
+ * {@code
+ * http://repo.acme.com/${simpletype}-${version}.${fileSuffix!tar.gz}
+ * }
+ * </pre>
+ * The following substitutions are available automatically for a template:
+ * <ul>
+ *   <li>entity: the {@link Entity} instance
+ *   <li>driver: the {@link EntityDriver} instance being used for the Entity
+ *   <li>simpletype: the unqualified name of the entity type
+ *   <li>type: the fully qualified name of the entity type
+ *   <li>addon: the name of the entity add-on, or null if it's the core entity 
artifact
+ *   <li>version: the version number of the entity to be installed (or of the 
add-on)
+ * </ul>
+ */
+public class DownloadProducerFromProperties implements 
Function<DownloadRequirement, DownloadTargets> {
+    
+    /* FIXME: expose config for canContinueResolving.
+     * ... then it uses only the overrides in the properties file. This, in 
combination with
+     * setting something like {@code 
brooklyn.downloads.all.url=http://acme.com/repo/${simpletype}/${simpletype}-${version}.tar.gz},
+     * allows an enterprise to ensure that entities never go to the public 
internet during installation.
+     * 
+     * But also need to override things like nginx downlaod url for the stick 
module and pcre.
+     */
+
+    @SuppressWarnings("unused")
+    private static final Logger LOG = 
LoggerFactory.getLogger(DownloadProducerFromProperties.class);
+
+    public static final String DOWNLOAD_CONF_PREFIX = "brooklyn.downloads.";
+
+    private final StringConfigMap config;
+
+    public DownloadProducerFromProperties(StringConfigMap config) {
+        this.config = config;
+    }
+    
+    public DownloadTargets apply(DownloadRequirement downloadRequirement) {
+        List<Rule> rules = generateRules();
+        BasicDownloadTargets.Builder result = BasicDownloadTargets.builder();
+        for (Rule rule : rules) {
+            if (rule.matches(downloadRequirement.getEntityDriver(), 
downloadRequirement.getAddonName())) {
+                result.addAll(rule.resolve(downloadRequirement));
+            }
+        }
+        
+        return result.build();
+    }
+    
+    /**
+     * Produces a set of URL-generating rules, based on the brooklyn 
properties. These
+     * rules will be applied in-order until one of them returns a non-empty 
result.
+     */
+    private List<Rule> generateRules() {
+        List<Rule> result = Lists.newArrayList();
+        Map<String, String> subconfig = 
filterAndStripPrefix(config.asMapWithStringKeys(), DOWNLOAD_CONF_PREFIX);
+
+        // If exists, use things like:
+        //   brooklyn.downloads.all.fallbackurl=...
+        //   brooklyn.downloads.all.url=...
+        // But only if not overridden by more entity-specify value
+        Map<String, String> forall = filterAndStripPrefix(subconfig, "all.");
+        String fallbackUrlForAll = forall.get("fallbackurl");
+        String urlForAll = forall.get("url");
+        
+        // If exists, use things like:
+        //   brooklyn.downloads.entity.JBoss7Server.url=...
+        Map<String, String> forSpecificEntities = 
filterAndStripPrefix(subconfig, "entity.");
+        Map<String, Map<String,String>> splitBySpecificEntity = 
splitByPrefix(forSpecificEntities);
+        for (Map.Entry<String, Map<String,String>> entry : 
splitBySpecificEntity.entrySet()) {
+            String entityType = entry.getKey();
+            Map<String, String> forentity = entry.getValue();
+            String urlForEntity = forentity.get("url");
+            if (urlForEntity == null) urlForEntity = urlForAll;
+            String fallbackUrlForEntity = forentity.get("fallbackurl");
+            if (fallbackUrlForEntity == null) fallbackUrlForEntity = 
fallbackUrlForAll;
+            
+            result.add(new EntitySpecificRule(entityType, urlForEntity, 
fallbackUrlForEntity));
+            
+            // If exists, use things like:
+            //   
brooklyn.downloads.entity.nginxcontroller.addon.stickymodule.url=...
+            Map<String, String> forSpecificAddons = 
filterAndStripPrefix(forentity, "addon.");
+            Map<String, Map<String,String>> splitBySpecificAddon = 
splitByPrefix(forSpecificAddons);
+            for (Map.Entry<String, Map<String,String>> entry2 : 
splitBySpecificAddon.entrySet()) {
+                String addonName = entry2.getKey();
+                Map<String, String> foraddon = entry2.getValue();
+                String urlForAddon = foraddon.get("url");
+                if (urlForAddon == null) urlForAddon = urlForEntity;
+                String fallbackUrlForAddon = foraddon.get("fallbackurl");
+                if (fallbackUrlForEntity == null) fallbackUrlForAddon = 
fallbackUrlForEntity;
+                
+                result.add(new EntityAddonSpecificRule(entityType, addonName, 
urlForAddon, fallbackUrlForAddon));
+            }
+        }
+
+        if (!forall.isEmpty()) {
+            result.add(new UniversalRule(urlForAll, fallbackUrlForAll));
+        }
+        
+        return result;
+    }
+
+    /**
+     * Returns a sub-map of config for keys that started with the given 
prefix, but where the returned
+     * map's keys do not include the prefix.
+     */
+    private static Map<String,String> filterAndStripPrefix(Map<String,?> 
config, String prefix) {
+        Map<String,String> result = Maps.newLinkedHashMap();
+        for (Map.Entry<String,?> entry : config.entrySet()) {
+            String key = entry.getKey();
+            if (key.startsWith(prefix)) {
+                Object value = entry.getValue();
+                result.put(key.substring(prefix.length()), (value == null) ? 
null : value.toString());
+            }
+        }
+        return result;
+    }
+    
+    /**
+     * Splits the map up into multiple maps, using the key's prefix up to the 
first dot to 
+     * tell which map to include it in. This prefix is used as the key in the 
map-of-maps, and
+     * is omitted in the contained map.
+     * 
+     * For example, given [a.b:v1, a.c:v2, d.e:v3], it will return [ a:[b:v1, 
c:v2], d:[e:v3] ]
+     */
+    private static Map<String,Map<String,String>> 
splitByPrefix(Map<String,String> config) {
+        Map<String,Map<String,String>> result = Maps.newLinkedHashMap();
+        
+        for (Map.Entry<String,String> entry : config.entrySet()) {
+            String key = entry.getKey();
+            String keysuffix = key.substring(key.indexOf(".")+1);
+            String keyprefix = key.substring(0, 
key.length()-keysuffix.length()-1);
+            String value = entry.getValue();
+            
+            Map<String,String> submap = result.get(keyprefix);
+            if (submap == null) {
+                submap = Maps.newLinkedHashMap();
+                result.put(keyprefix, submap);
+            }
+            submap.put(keysuffix, value);
+        }
+        return result;
+    }
+    
+    /**
+     * Resolves the download url, given an EntityDriver, with the following 
rules:
+     * <ol>
+     *   <li>If url is not null, split and trim it on ";" and use
+     *   <li>If url is null, retrive entity's Attributes.DOWNLOAD_URL and use 
if non-null
+     *   <li>If fallbackUrl is not null, split and trim it on ";" and use
+     * <ol>
+     * 
+     * For each of the resulting Strings, transforms them (using freemarker 
syntax for
+     * substitutions). Returns the list.
+     */
+    private static abstract class Rule {
+        private final String url;
+        private final String fallbackUrl;
+        
+        Rule(String url, String fallbackUrl) {
+            this.url = url;
+            this.fallbackUrl = fallbackUrl;
+        }
+        
+        abstract boolean matches(EntityDriver driver, String addon);
+        
+        DownloadTargets resolve(DownloadRequirement req) {
+            EntityDriver driver = req.getEntityDriver();
+            
+            List<String> primaries = Lists.newArrayList();
+            List<String> fallbacks = Lists.newArrayList();
+            if (Strings.isEmpty(url)) {
+                String defaulturl = 
driver.getEntity().getAttribute(Attributes.DOWNLOAD_URL);
+                if (defaulturl != null) primaries.add(defaulturl);
+            } else {
+                String[] parts = url.split(";");
+                for (String part : parts) {
+                    if (!part.isEmpty()) primaries.add(part.trim());
+                }
+            }
+            if (fallbackUrl != null) {
+                String[] parts = fallbackUrl.split(";");
+                for (String part : parts) {
+                    if (!part.isEmpty()) fallbacks.add(part.trim());
+                }
+            }
+
+            BasicDownloadTargets.Builder result = 
BasicDownloadTargets.builder();
+            for (String baseurl : primaries) {
+                result.addPrimary(DownloadSubstituters.substitute(req, 
baseurl));
+            }
+            for (String baseurl : fallbacks) {
+                result.addFallback(DownloadSubstituters.substitute(req, 
baseurl));
+            }
+            return result.build();
+        }
+    }
+
+    /**
+     * Rule for generating URLs that applies to all entities, if a more 
specific rule 
+     * did not exist or failed to find a match.
+     */
+    private static class UniversalRule extends Rule {
+        UniversalRule(String url, String fallbackUrl) {
+            super(url, fallbackUrl);
+        }
+        
+        @Override
+        boolean matches(EntityDriver driver, String addon) {
+            return true;
+        }
+    }
+    
+    /**
+     * Rule for generating URLs that applies to only the entity of the given 
type.
+     */
+    private static class EntitySpecificRule extends Rule {
+        private final String entityType;
+
+        EntitySpecificRule(String entityType, String url, String fallbackUrl) {
+            super(url, fallbackUrl);
+            this.entityType = checkNotNull(entityType, "entityType");
+        }
+        
+        @Override
+        boolean matches(EntityDriver driver, String addon) {
+            String actualType = driver.getEntity().getEntityType().getName();
+            String actualSimpleType = 
actualType.substring(actualType.lastIndexOf(".")+1);
+            return addon == null && 
entityType.equalsIgnoreCase(actualSimpleType);
+        }
+    }
+    
+    /**
+     * Rule for generating URLs that applies to only the entity of the given 
type.
+     */
+    private static class EntityAddonSpecificRule extends Rule {
+        private final String entityType;
+        private final String addonName;
+
+        EntityAddonSpecificRule(String entityType, String addonName, String 
url, String fallbackUrl) {
+            super(url, fallbackUrl);
+            this.entityType = checkNotNull(entityType, "entityType");
+            this.addonName = checkNotNull(addonName, "addonName");
+        }
+        
+        @Override
+        boolean matches(EntityDriver driver, String addon) {
+            String actualType = driver.getEntity().getEntityType().getName();
+            String actualSimpleType = 
actualType.substring(actualType.lastIndexOf(".")+1);
+            return addonName.equals(addon) && 
entityType.equalsIgnoreCase(actualSimpleType);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/DownloadProducerFromUrlAttribute.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/DownloadProducerFromUrlAttribute.java
 
b/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/DownloadProducerFromUrlAttribute.java
new file mode 100644
index 0000000..aa7b842
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/DownloadProducerFromUrlAttribute.java
@@ -0,0 +1,63 @@
+/*
+ * 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 java.util.Map;
+
+import 
org.apache.brooklyn.api.entity.drivers.downloads.DownloadResolverManager.DownloadRequirement;
+import 
org.apache.brooklyn.api.entity.drivers.downloads.DownloadResolverManager.DownloadTargets;
+import org.apache.brooklyn.core.entity.Attributes;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Maps;
+
+/**
+ * Retrieves the DOWNLOAD_URL or DOWNLOAD_ADDON_URLS attribute of a given 
entity, and performs the
+ * template substitutions to generate the download URL.
+ * 
+ * @author aled
+ */
+public class DownloadProducerFromUrlAttribute extends 
DownloadSubstituters.Substituter implements Function<DownloadRequirement, 
DownloadTargets> {
+    public DownloadProducerFromUrlAttribute() {
+        super(
+            new Function<DownloadRequirement, String>() {
+                @Override public String apply(DownloadRequirement input) {
+                    if (input.getAddonName() == null) {
+                        return 
input.getEntityDriver().getEntity().getAttribute(Attributes.DOWNLOAD_URL);
+                    } else {
+                        String addon = input.getAddonName();
+                        Map<String, String> addonUrls = 
input.getEntityDriver().getEntity().getAttribute(Attributes.DOWNLOAD_ADDON_URLS);
+                        return (addonUrls != null) ? addonUrls.get(addon) : 
null;
+                    }
+                }
+            },
+            new Function<DownloadRequirement, Map<String,?>>() {
+                @Override public Map<String,?> apply(DownloadRequirement 
input) {
+                    Map<String,Object> result = Maps.newLinkedHashMap();
+                    if (input.getAddonName() == null) {
+                        
result.putAll(DownloadSubstituters.getBasicEntitySubstitutions(input.getEntityDriver()));
+                    } else {
+                        
result.putAll(DownloadSubstituters.getBasicAddonSubstitutions(input.getEntityDriver(),
 input.getAddonName()));
+                    }
+                    result.putAll(input.getProperties());
+                    return result;
+                }
+            });
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/DownloadSubstituters.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/DownloadSubstituters.java
 
b/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/DownloadSubstituters.java
new file mode 100644
index 0000000..64a081c
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/DownloadSubstituters.java
@@ -0,0 +1,172 @@
+/*
+ * 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.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.Map;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.drivers.EntityDriver;
+import 
org.apache.brooklyn.api.entity.drivers.downloads.DownloadResolverManager.DownloadRequirement;
+import 
org.apache.brooklyn.api.entity.drivers.downloads.DownloadResolverManager.DownloadTargets;
+import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Function;
+import com.google.common.base.Objects;
+
+import freemarker.cache.StringTemplateLoader;
+import freemarker.template.Configuration;
+import freemarker.template.Template;
+import freemarker.template.TemplateException;
+
+public class DownloadSubstituters {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(DownloadSubstituters.class);
+
+    static {
+        // TODO in Freemarker 2.4 SLF4J may be auto-selected and we can remove 
this;
+        // for now, we need it somewhere, else we get j.u.l logging; 
+        // since this is the main place it is used, let's do it here
+        try {
+            LOG.debug("Configuring Freemarker logging for Brooklyn to use 
SLF4J");
+            
System.setProperty(freemarker.log.Logger.SYSTEM_PROPERTY_NAME_LOGGER_LIBRARY, 
freemarker.log.Logger.LIBRARY_NAME_SLF4J);
+        } catch (Exception e) {
+            LOG.warn("Error setting Freemarker logging: "+e, e);
+        }
+    }
+    
+    private DownloadSubstituters() {}
+    
+    /**
+     * Converts the basevalue by substituting things in the form ${key} for 
values specific
+     * to a given entity driver. The keys used are:
+     * <ul>
+     *   <li>driver: the driver instance (e.g. can do freemarker.org stuff 
like ${driver.osTag} to call {@code driver.getOsTag()})
+     *   <li>entity: the entity instance
+     *   <li>type: the fully qualified type name of the entity
+     *   <li>simpletype: the unqualified type name of the entity
+     *   <li>addon: the name of the add-on, or null if for the entity's main 
artifact
+     *   <li>version: the version for this entity (or of the add-on), or not 
included if null
+     * </ul>
+     * 
+     * Additional substitution keys (and values) can be defined using {@link 
DownloadRequirement#getProperties()}; these
+     * override the default substitutions listed above.
+     */
+    public static String substitute(DownloadRequirement req, String basevalue) 
{
+        return substitute(basevalue, getBasicSubstitutions(req));
+    }
+
+    public static Map<String,Object> getBasicSubstitutions(DownloadRequirement 
req) {
+        EntityDriver driver = req.getEntityDriver();
+        String addon = req.getAddonName();
+        Map<String, ?> props = req.getProperties();
+        
+        if (addon == null) {
+            return MutableMap.<String,Object>builder()
+                    .putAll(getBasicEntitySubstitutions(driver))
+                    .putAll(props)
+                    .build();
+        } else {
+            return MutableMap.<String,Object>builder()
+                    .putAll(getBasicAddonSubstitutions(driver, addon))
+                    .putAll(props)
+                    .build();
+        }
+    }
+    
+    public static Map<String,Object> getBasicEntitySubstitutions(EntityDriver 
driver) {
+        Entity entity = driver.getEntity();
+        String type = entity.getEntityType().getName();
+        String simpleType = type.substring(type.lastIndexOf(".")+1);
+        String version = 
entity.getConfig(BrooklynConfigKeys.SUGGESTED_VERSION);
+        
+        return MutableMap.<String,Object>builder()
+                .put("entity", entity)
+                .put("driver", driver)
+                .put("type", type)
+                .put("simpletype", simpleType)
+                .putIfNotNull("version", version)
+                .build();
+    }
+
+    public static Map<String,Object> getBasicAddonSubstitutions(EntityDriver 
driver, String addon) {
+        return MutableMap.<String,Object>builder()
+                .putAll(getBasicEntitySubstitutions(driver))
+                .put("addon", addon)
+                .build();
+    }
+
+    public static String substitute(String basevalue, Map<String,?> 
substitutions) {
+        try {
+            Configuration cfg = new 
Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
+            StringTemplateLoader templateLoader = new StringTemplateLoader();
+            templateLoader.putTemplate("config", basevalue);
+            cfg.setTemplateLoader(templateLoader);
+            Template template = cfg.getTemplate("config");
+            
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            Writer out = new OutputStreamWriter(baos);
+            template.process(substitutions, out);
+            out.flush();
+            
+            return new String(baos.toByteArray());
+        } catch (IOException e) {
+            LOG.warn("Error processing template '"+basevalue+"'", e);
+            throw Exceptions.propagate(e);
+        } catch (TemplateException e) {
+            throw new IllegalArgumentException("Failed to process driver 
download '"+basevalue+"'", e);
+        }
+    }
+
+    public static Function<DownloadRequirement, DownloadTargets> 
substituter(Function<? super DownloadRequirement, String> basevalueProducer, 
Function<? super DownloadRequirement, ? extends Map<String,?>> subsProducer) {
+        // FIXME Also need default subs (entity, driver, simpletype, etc)
+        return new Substituter(basevalueProducer, subsProducer);
+    }
+
+    protected static class Substituter implements 
Function<DownloadRequirement, DownloadTargets> {
+        private final Function<? super DownloadRequirement, String> 
basevalueProducer;
+        private final Function<? super DownloadRequirement, ? extends 
Map<String,?>> subsProducer;
+        
+        Substituter(Function<? super DownloadRequirement, String> 
baseValueProducer, Function<? super DownloadRequirement, ? extends 
Map<String,?>> subsProducer) {
+            this.basevalueProducer = checkNotNull(baseValueProducer, 
"basevalueProducer");
+            this.subsProducer = checkNotNull(subsProducer, "subsProducer");
+        }
+        
+        @Override
+        public DownloadTargets apply(DownloadRequirement input) {
+            String basevalue = basevalueProducer.apply(input);
+            Map<String, ?> subs = subsProducer.apply(input);
+            String result = (basevalue != null) ? substitute(basevalue, subs) 
: null;
+            return (result != null) ? 
BasicDownloadTargets.builder().addPrimary(result).build() : 
BasicDownloadTargets.empty();
+        }
+        
+        @Override public String toString() {
+            return Objects.toStringHelper(this).add("basevalue", 
basevalueProducer).add("subs", subsProducer).toString();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/FilenameProducers.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/FilenameProducers.java
 
b/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/FilenameProducers.java
new file mode 100644
index 0000000..18240f1
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/entity/drivers/downloads/FilenameProducers.java
@@ -0,0 +1,64 @@
+/*
+ * 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 java.util.List;
+
+import javax.annotation.Nullable;
+
+import 
org.apache.brooklyn.api.entity.drivers.downloads.DownloadResolverManager.DownloadRequirement;
+import 
org.apache.brooklyn.api.entity.drivers.downloads.DownloadResolverManager.DownloadTargets;
+import org.apache.brooklyn.util.text.Strings;
+
+import com.google.common.base.Function;
+
+public class FilenameProducers {
+
+    public static String inferFilename(String target) {
+        String result = target.substring(target.lastIndexOf("/")+1);
+        result = result.contains("?") ? result.substring(0, 
result.indexOf("?")) : result;
+        if (!result.contains("."))
+            // require a full stop, else assume it isn't a filename
+            return null;
+        return result;
+    }
+
+    public static Function<DownloadRequirement, String> fromFilenameProperty() 
{
+        return new Function<DownloadRequirement, String>() {
+            @Override public String apply(@Nullable DownloadRequirement req) {
+                Object filename = req.getProperties().get("filename");
+                return (filename != null) ? filename.toString() : null;
+            }
+        };
+    }
+    
+    public static Function<DownloadRequirement, String> 
firstPrimaryTargetOf(final Function<DownloadRequirement, DownloadTargets> 
producer) {
+        return new Function<DownloadRequirement, String>() {
+            @Override public String apply(@Nullable DownloadRequirement req) {
+                DownloadTargets targets = producer.apply(req);
+                List<String> primaryTargets = targets.getPrimaryLocations();
+                for (String primaryTarget : primaryTargets) {
+                    String result = inferFilename(primaryTarget);
+                    if (!Strings.isBlank(result)) return result;
+                }
+                return null;
+            }
+        };
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/factory/AbstractConfigurableEntityFactory.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/entity/factory/AbstractConfigurableEntityFactory.java
 
b/core/src/main/java/org/apache/brooklyn/core/entity/factory/AbstractConfigurableEntityFactory.java
new file mode 100644
index 0000000..6b41e4b
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/entity/factory/AbstractConfigurableEntityFactory.java
@@ -0,0 +1,82 @@
+/*
+ * 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.factory;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.config.ConfigKey;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class AbstractConfigurableEntityFactory<T extends Entity> 
implements ConfigurableEntityFactory<T>, Serializable {
+    private static final Logger log = 
LoggerFactory.getLogger(AbstractConfigurableEntityFactory.class);
+    
+    protected final Map config = new LinkedHashMap();
+
+    public AbstractConfigurableEntityFactory(){
+        this(new HashMap());
+    }
+
+    public AbstractConfigurableEntityFactory(Map flags) {
+        this.config.putAll(flags);
+
+    }
+    public AbstractConfigurableEntityFactory<T> configure(Map flags) {
+        config.putAll(flags);
+        return this;
+    }
+
+    public AbstractConfigurableEntityFactory<T> configure(ConfigKey key, 
Object value) {
+        config.put(key, value);
+        return this;
+    }
+
+    public AbstractConfigurableEntityFactory<T> 
configure(ConfigKey.HasConfigKey key, Object value) {
+        return setConfig(key.getConfigKey(), value);
+    }
+
+    public AbstractConfigurableEntityFactory<T> setConfig(ConfigKey key, 
Object value) {
+        return configure(key, value);
+    }
+
+    public AbstractConfigurableEntityFactory<T> 
setConfig(ConfigKey.HasConfigKey key, Object value) {
+        return configure(key.getConfigKey(), value);
+    }
+
+    public T newEntity(Entity parent){
+        return newEntity(new HashMap(),parent);
+    }
+
+    public T newEntity(Map flags, Entity parent) {
+        Map flags2 = new HashMap();
+        flags2.putAll(config);
+        flags2.putAll(flags);
+        T result = newEntity2(flags2, parent);
+        // we rely increasingly on init, which factory doesn't call; really 
should remove factories!
+        log.warn("Deprecated legacy compatibility, using factory (init will 
not be invoked): "+result);
+        return result;
+    }
+
+    public abstract T newEntity2(Map flags, Entity parent);
+}
+

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/factory/ApplicationBuilder.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/entity/factory/ApplicationBuilder.java
 
b/core/src/main/java/org/apache/brooklyn/core/entity/factory/ApplicationBuilder.java
new file mode 100644
index 0000000..7e00305
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/entity/factory/ApplicationBuilder.java
@@ -0,0 +1,247 @@
+/*
+ * 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.factory;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.mgmt.EntityManager;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.entity.StartableApplication;
+import org.apache.brooklyn.entity.stock.BasicApplication;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * Experimental mechanism for defining/building applications. In future 
releases, this
+ * API will change. Its concepts will most likely be merged with a TOSCA 
implementation
+ * and with {@link EntitySpec}.
+ *
+ * For building an application. Users can sub-class and override doBuild(), 
putting the logic for  
+ * creating and wiring together entities in there.
+ * 
+ * The builder is mutable; a given instance should be used to build only a 
single application.
+ * Once {@link #manage()} has been called, the application will be built and 
no additional configuration
+ * should be performed through this builder.  
+ * 
+ * Example (simplified) code for sub-classing is:
+ * <pre>
+ * {@code
+ *   app = new ApplicationBuilder() {
+ *       //@Override
+ *       public void doBuild() {
+ *           MySqlNode db = addChild(EntitySpec.create(MySqlNode.class)));
+ *           JBoss7Server as = addChild(EntitySpec.create(JBoss7Server.class)
+ *                   .configure(HTTP_PORT, "8080+")
+ *                   .configure(javaSysProp("brooklyn.example.db.url"), 
attributeWhenReady(db, MySqlNode.MYSQL_URL));
+ *       }
+ *   }.manage();
+ * }
+ * </pre>
+ * 
+ * @author aled
+ */
+@Beta
+public abstract class ApplicationBuilder {
+
+    @SuppressWarnings("unused")
+    private static final Logger LOG = 
LoggerFactory.getLogger(ApplicationBuilder.class);
+
+    @SuppressWarnings("unchecked")
+    @Beta
+    /** @deprecated since 0.7.0 the management context should normally be 
passed in;
+     * for TestApplication also see 
TestApplication.Factory.newManagedInstanceForTests() */ 
+    @Deprecated
+    public static <T extends StartableApplication> T newManagedApp(Class<T> 
type) {
+        if (type.isInterface()) {
+            return (T) newManagedApp(EntitySpec.create(type));
+        } else {
+            return (T) 
newManagedApp(EntitySpec.create(StartableApplication.class, type));
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    @Beta
+    /** @deprecated since 0.7.0 the management context should normally be 
passed in;
+     * for TestApplication also see 
TestApplication.Factory.newManagedInstanceForTests() */ 
+    @Deprecated
+    public static <T extends StartableApplication> T 
newManagedApp(EntitySpec<T> spec) {
+        return (T) new ApplicationBuilder(spec) {
+            @Override protected void doBuild() {
+            }
+        }.manage();
+    }
+
+    @SuppressWarnings("unchecked")
+    @Beta
+    public static <T extends StartableApplication> T newManagedApp(Class<T> 
type, ManagementContext managementContext) {
+        if (type.isInterface()) {
+            return (T) newManagedApp(EntitySpec.create(type), 
managementContext);
+        } else {
+            return (T) 
newManagedApp(EntitySpec.create(StartableApplication.class, type), 
managementContext);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    @Beta
+    public static <T extends StartableApplication> T 
newManagedApp(EntitySpec<T> spec, ManagementContext managementContext) {
+        return (T) new ApplicationBuilder(spec) {
+            @Override protected void doBuild() {
+            }
+        }.manage(managementContext);
+    }
+
+    protected volatile boolean managed = false;
+    protected final AtomicBoolean inManage = new AtomicBoolean(false);
+    private EntitySpec<? extends StartableApplication> appSpec;
+    private ManagementContext managementContext;
+    private StartableApplication app;
+    
+    public ApplicationBuilder() {
+        this.appSpec = EntitySpec.create(BasicApplication.class);
+    }
+
+    public ApplicationBuilder(EntitySpec<? extends StartableApplication> 
appSpec) {
+        this.appSpec = EntitySpec.create(appSpec);
+    }
+
+    public final ApplicationBuilder appDisplayName(String val) {
+        checkPreManage();
+        appSpec.displayName(val);
+        return this;
+    }
+    
+    protected final <T extends Entity> T createEntity(EntitySpec<T> spec) {
+        checkDuringManage();
+        EntityManager entityManager = managementContext.getEntityManager();
+        return entityManager.createEntity(spec);
+    }
+
+    /**
+     * Adds the given entity as a child of the application being built.
+     * To be called during {@link #doBuild()}.
+     */
+    protected final <T extends Entity> T addChild(T entity) {
+        checkDuringManage();
+        return app.addChild(entity);
+    }
+
+    /**
+     * Returns the type of the application being built.
+     */
+    public final Class<? extends StartableApplication> getType() {
+        return appSpec.getType();
+    }
+    
+    /**
+     * Configures the application instance.
+     */
+    public final ApplicationBuilder configure(Map<?,?> config) {
+        checkPreManage();
+        appSpec.configure(config);
+        return this;
+    }
+    
+    /**
+     * Adds the given entity as a child of the application being built.
+     */
+    protected final <T extends Entity> T addChild(EntitySpec<T> spec) {
+        checkDuringManage();
+        return addChild(createEntity(spec));
+    }
+    
+    protected final <T extends Entity> T addChild(Map<?,?> config, Class<T> 
type) {
+        checkDuringManage();
+        EntitySpec<T> spec = EntitySpec.create(type).configure(config);
+        return addChild(createEntity(spec));
+    }
+    
+    protected final ManagementContext getManagementContext() {
+        return checkNotNull(managementContext, "must only be called after 
manage()");
+    }
+
+    protected final StartableApplication getApp() {
+        return checkNotNull(app, "must only be called after manage()");
+    }
+
+    /**
+     * For overriding, to create and wire together entities.
+     */
+    protected abstract void doBuild();
+
+    /**
+     * Creates a new {@link ManagementContext}, and then builds and manages 
the application.
+     * 
+     * @see #manage(ManagementContext)
+     */
+    public final StartableApplication manage() {
+        return manage(Entities.newManagementContext());
+    }
+    
+    /**
+     * Builds and manages the application, calling the user's {@link 
#doBuild()} method.
+     * 
+     * @throws IllegalStateException If already managed, or if called during 
{@link #doBuild()}, or if 
+     *                               multiple concurrent calls
+     */
+    public final StartableApplication manage(ManagementContext 
managementContext) {
+        if (!inManage.compareAndSet(false, true)) {
+            throw new IllegalStateException("Concurrent and re-entrant calls 
to manage() forbidden on "+this);
+        }
+        try {
+            checkNotManaged();
+            this.app = 
managementContext.getEntityManager().createEntity(appSpec);
+            this.managementContext = managementContext;
+            doBuild();
+            Entities.startManagement(app, managementContext);
+            managed = true;
+            return app;
+        } finally {
+            inManage.set(false);
+        }
+    }
+    
+    protected void checkPreManage() {
+        if (inManage.get()) {
+            throw new IllegalStateException("Builder being managed; cannot 
perform operation during call to manage(), or in doBuild()");
+        }
+        if (managed) {
+            throw new IllegalStateException("Builder already managed; cannot 
perform operation after call to manage()");
+        }
+    }
+    
+    protected void checkNotManaged() {
+        if (managed) {
+            throw new IllegalStateException("Builder already managed; cannot 
perform operation after call to manage()");
+        }
+    }
+    
+    protected void checkDuringManage() {
+        if (!inManage.get() || app == null) {
+            throw new IllegalStateException("Operation only permitted during 
manage, e.g. called from doBuild() of "+this);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/factory/BasicConfigurableEntityFactory.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/entity/factory/BasicConfigurableEntityFactory.java
 
b/core/src/main/java/org/apache/brooklyn/core/entity/factory/BasicConfigurableEntityFactory.java
new file mode 100644
index 0000000..3011c9a
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/entity/factory/BasicConfigurableEntityFactory.java
@@ -0,0 +1,75 @@
+/*
+ * 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.factory;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.brooklyn.api.entity.Entity;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Throwables;
+
+/** @deprecated since 0.7.0; use EntitySpec instead, as per {@link 
EntityFactory} javadoc */
+@Deprecated
+public class BasicConfigurableEntityFactory<T extends Entity> extends 
AbstractConfigurableEntityFactory<T> {
+    private transient Class<? extends T> clazz;
+    private final String clazzName;
+
+    public BasicConfigurableEntityFactory(Class<? extends T> clazz) {
+        this(new HashMap(), clazz);
+    }
+
+    public BasicConfigurableEntityFactory(Map flags, Class<? extends T> clazz) 
{
+        super(flags);
+        this.clazz = checkNotNull(clazz, "clazz");
+        this.clazzName = clazz.getName();
+    }
+
+    public T newEntity2(Map flags, Entity parent) {
+        try {
+            Constructor<? extends T> constructor = 
clazz.getConstructor(Map.class, Entity.class);
+            return constructor.newInstance(flags, parent);
+        } catch (InstantiationException e) {
+            throw Throwables.propagate(e);
+        } catch (IllegalAccessException e) {
+            throw Throwables.propagate(e);
+        } catch (InvocationTargetException e) {
+            throw Throwables.propagate(e);
+        } catch (NoSuchMethodException e) {
+            throw Throwables.propagate(e);
+        }
+    }
+    
+    private void readObject(ObjectInputStream s) throws IOException, 
ClassNotFoundException {
+        s.defaultReadObject();
+        clazz = (Class<T>) getClass().getClassLoader().loadClass(clazzName);
+    }
+    
+    @Override
+    public String toString() {
+        return Objects.toStringHelper(this).add("type", clazzName).toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/factory/ClosureEntityFactory.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/entity/factory/ClosureEntityFactory.java
 
b/core/src/main/java/org/apache/brooklyn/core/entity/factory/ClosureEntityFactory.java
new file mode 100644
index 0000000..df0cf26
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/entity/factory/ClosureEntityFactory.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.factory;
+
+import groovy.lang.Closure;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.brooklyn.api.entity.Entity;
+
+public class ClosureEntityFactory<T extends Entity> extends 
AbstractConfigurableEntityFactory<T> {
+    private final Closure<T> closure;
+
+    public ClosureEntityFactory(Closure<T> closure){
+        this(new HashMap(),closure);
+    }
+
+    public ClosureEntityFactory(Map flags, Closure<T> closure) {
+        super(flags);
+        this.closure = closure;
+    }
+
+    public T newEntity2(Map flags, Entity parent) {
+        if (closure.getMaximumNumberOfParameters()>1)
+            return closure.call(flags, parent);
+        else {
+            //leaving out the parent is discouraged
+            T entity = closure.call(flags);
+            if(parent!=null && entity.getParent()==null){
+                entity.setParent(parent);
+            }
+
+            return entity;
+        }
+    }
+}
\ 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/factory/ConfigurableEntityFactory.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/entity/factory/ConfigurableEntityFactory.java
 
b/core/src/main/java/org/apache/brooklyn/core/entity/factory/ConfigurableEntityFactory.java
new file mode 100644
index 0000000..af5fba3
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/entity/factory/ConfigurableEntityFactory.java
@@ -0,0 +1,33 @@
+/*
+ * 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.factory;
+
+import java.util.Map;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.config.ConfigKey;
+
+public interface ConfigurableEntityFactory<T extends Entity> extends 
EntityFactory<T> {
+   ConfigurableEntityFactory<T> configure(Map flags);
+   ConfigurableEntityFactory<T> configure(ConfigKey key, Object value);
+   ConfigurableEntityFactory<T> configure(ConfigKey.HasConfigKey key, Object 
value);
+   
+   ConfigurableEntityFactory<T> setConfig(ConfigKey key, Object value);
+   ConfigurableEntityFactory<T> setConfig(ConfigKey.HasConfigKey key, Object 
value);
+}
\ 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/factory/ConfigurableEntityFactoryFromEntityFactory.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/entity/factory/ConfigurableEntityFactoryFromEntityFactory.java
 
b/core/src/main/java/org/apache/brooklyn/core/entity/factory/ConfigurableEntityFactoryFromEntityFactory.java
new file mode 100644
index 0000000..1fc36c3
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/entity/factory/ConfigurableEntityFactoryFromEntityFactory.java
@@ -0,0 +1,45 @@
+/*
+ * 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.factory;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.brooklyn.api.entity.Entity;
+
+public class ConfigurableEntityFactoryFromEntityFactory<T extends Entity> 
extends AbstractConfigurableEntityFactory<T> {
+
+   private final EntityFactory<? extends T> factory;
+
+    public ConfigurableEntityFactoryFromEntityFactory(EntityFactory<? extends 
T> entityFactory){
+        this(new HashMap(),entityFactory);
+    }
+
+    public ConfigurableEntityFactoryFromEntityFactory(Map flags, 
EntityFactory<? extends T> factory) {
+        super(flags);
+        this.factory = checkNotNull(factory, "factory");
+    }
+
+    @Override
+    public T newEntity2(Map flags, Entity parent) {
+        return factory.newEntity(flags, parent);
+    }
+}
\ 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/factory/EntityFactory.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/entity/factory/EntityFactory.java 
b/core/src/main/java/org/apache/brooklyn/core/entity/factory/EntityFactory.java
new file mode 100644
index 0000000..2f4ede7
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/entity/factory/EntityFactory.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.core.entity.factory;
+
+import java.util.Map;
+
+import org.apache.brooklyn.api.entity.Entity;
+
+/**
+ * A Factory for creating entities.
+ *
+ * @deprecated since 0.7.0; use EntitySpec instead, as the factory does not 
put the entity through the initialization process */
+@Deprecated
+public interface EntityFactory<T extends Entity> {
+    T newEntity(Map flags, Entity parent);
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/factory/EntityFactoryForLocation.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/entity/factory/EntityFactoryForLocation.java
 
b/core/src/main/java/org/apache/brooklyn/core/entity/factory/EntityFactoryForLocation.java
new file mode 100644
index 0000000..79f72d7
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/entity/factory/EntityFactoryForLocation.java
@@ -0,0 +1,30 @@
+/*
+ * 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.factory;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.location.Location;
+
+/**
+ * dispatch interface to allow an EntityFactory to indicate it might be able 
to discover
+ * other factories for specific locations (e.g. if the location implements a 
custom entity-aware interface)
+ */
+public interface EntityFactoryForLocation<T extends Entity> {
+    ConfigurableEntityFactory<T> newFactoryForLocation(Location l);
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/main/java/org/apache/brooklyn/core/entity/internal/ConfigMapViewWithStringKeys.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/entity/internal/ConfigMapViewWithStringKeys.java
 
b/core/src/main/java/org/apache/brooklyn/core/entity/internal/ConfigMapViewWithStringKeys.java
new file mode 100644
index 0000000..7d91af4
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/entity/internal/ConfigMapViewWithStringKeys.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.internal;
+
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.BasicConfigKey;
+
+import com.google.common.annotations.Beta;
+import com.google.common.collect.Sets;
+
+/**
+ * Internal class that presents a view over a ConfigMap, so it looks like a 
Map (with the
+ * keys being the config key names).
+ */
+@Beta
+public class ConfigMapViewWithStringKeys implements Map<String,Object> {
+
+    private org.apache.brooklyn.config.ConfigMap target;
+
+    public ConfigMapViewWithStringKeys(org.apache.brooklyn.config.ConfigMap 
target) {
+        this.target = target;
+    }
+    
+    @Override
+    public int size() {
+        return target.getAllConfig().size();
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return target.getAllConfig().isEmpty();
+    }
+
+    @Override
+    public boolean containsKey(Object key) {
+        return keySet().contains(key);
+    }
+
+    @Override
+    public boolean containsValue(Object value) {
+        return values().contains(value);
+    }
+
+    @Override
+    public Object get(Object key) {
+        return target.getConfig(new BasicConfigKey<Object>(Object.class, 
(String)key));
+    }
+
+    @Override
+    public Object put(String key, Object value) {
+        throw new UnsupportedOperationException("This view is read-only");
+    }
+
+    @Override
+    public Object remove(Object key) {
+        throw new UnsupportedOperationException("This view is read-only");
+    }
+
+    @Override
+    public void putAll(Map<? extends String, ? extends Object> m) {
+        throw new UnsupportedOperationException("This view is read-only");
+    }
+
+    @Override
+    public void clear() {
+        throw new UnsupportedOperationException("This view is read-only");
+    }
+
+    @Override
+    public Set<String> keySet() {
+        LinkedHashSet<String> result = Sets.newLinkedHashSet();
+        Set<Map.Entry<ConfigKey<?>, Object>> set = 
target.getAllConfig().entrySet();
+        for (final Map.Entry<ConfigKey<?>, Object> entry: set) {
+            result.add(entry.getKey().getName());
+        }
+        return result;
+    }
+
+    @Override
+    public Collection<Object> values() {
+        return target.getAllConfig().values();
+    }
+
+    @Override
+    public Set<Map.Entry<String, Object>> entrySet() {
+        LinkedHashSet<Map.Entry<String, Object>> result = 
Sets.newLinkedHashSet();
+        Set<Map.Entry<ConfigKey<?>, Object>> set = 
target.getAllConfig().entrySet();
+        for (final Map.Entry<ConfigKey<?>, Object> entry: set) {
+            result.add(new Map.Entry<String, Object>() {
+                @Override
+                public String getKey() {
+                    return entry.getKey().getName();
+                }
+
+                @Override
+                public Object getValue() {
+                    return entry.getValue();
+                }
+
+                @Override
+                public Object setValue(Object value) {
+                    return entry.setValue(value);
+                }
+            });
+        }
+        return result;
+    }
+    
+}

Reply via email to