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; + } + +}
