brooklyn-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From aleds...@apache.org
Subject [27/62] [abbrv] incubator-brooklyn git commit: rename core’s o.a.b.entity to o.a.b.core.entity
Date Wed, 19 Aug 2015 21:21:01 GMT
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;
+    }
+    
+}


Mime
View raw message