http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e406d1ad/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynComponentTemplateResolver.java ---------------------------------------------------------------------- diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynComponentTemplateResolver.java b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynComponentTemplateResolver.java new file mode 100644 index 0000000..c413e4d --- /dev/null +++ b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynComponentTemplateResolver.java @@ -0,0 +1,490 @@ +/* + * 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.camp.brooklyn.spi.creation; + +import io.brooklyn.camp.spi.AbstractResource; +import io.brooklyn.camp.spi.ApplicationComponentTemplate; +import io.brooklyn.camp.spi.AssemblyTemplate; +import io.brooklyn.camp.spi.PlatformComponentTemplate; + +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.annotation.Nullable; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.brooklyn.camp.brooklyn.BrooklynCampConstants; +import org.apache.brooklyn.camp.brooklyn.BrooklynCampReservedKeys; +import org.apache.brooklyn.camp.brooklyn.spi.creation.service.BrooklynServiceTypeResolver; +import org.apache.brooklyn.camp.brooklyn.spi.creation.service.ServiceTypeResolver; +import org.apache.brooklyn.catalog.CatalogItem; +import brooklyn.catalog.internal.CatalogUtils; +import brooklyn.config.ConfigKey; +import brooklyn.entity.Application; +import brooklyn.entity.Entity; +import brooklyn.entity.basic.AbstractEntity; +import brooklyn.entity.basic.BrooklynTags; +import brooklyn.entity.basic.BrooklynTaskTags; +import brooklyn.entity.basic.ConfigKeys; +import brooklyn.entity.proxying.EntitySpec; +import brooklyn.entity.proxying.InternalEntityFactory; +import brooklyn.location.Location; +import brooklyn.management.ManagementContext; +import brooklyn.management.ManagementContextInjectable; +import brooklyn.management.classloading.BrooklynClassLoadingContext; +import brooklyn.management.classloading.JavaBrooklynClassLoadingContext; +import brooklyn.management.internal.ManagementContextInternal; +import brooklyn.util.ResourceUtils; +import brooklyn.util.collections.MutableList; +import brooklyn.util.collections.MutableSet; +import brooklyn.util.config.ConfigBag; +import brooklyn.util.flags.FlagUtils; +import brooklyn.util.flags.FlagUtils.FlagConfigKeyAndValueRecord; +import brooklyn.util.guava.Maybe; +import brooklyn.util.javalang.Reflections; +import brooklyn.util.task.Tasks; +import brooklyn.util.text.Strings; + +import com.google.common.base.Function; +import com.google.common.base.Splitter; +import com.google.common.collect.Iterables; +import com.google.common.collect.Maps; + +/** + * This generates instances of a template resolver that use a {@link ServiceTypeResolver} + * to parse the {@code serviceType} line in the template. + */ +public class BrooklynComponentTemplateResolver { + + private static final Logger log = LoggerFactory.getLogger(BrooklynComponentTemplateResolver.class); + + private final BrooklynClassLoadingContext loader; + private final ManagementContext mgmt; + private final ConfigBag attrs; + private final Maybe<AbstractResource> template; + private final BrooklynYamlTypeInstantiator.Factory yamlLoader; + private final String type; + private final ServiceTypeResolver typeResolver; + private final AtomicBoolean alreadyBuilt = new AtomicBoolean(false); + + public BrooklynComponentTemplateResolver(BrooklynClassLoadingContext loader, ConfigBag attrs, AbstractResource optionalTemplate, String type, ServiceTypeResolver typeResolver) { + this.loader = loader; + this.mgmt = loader.getManagementContext(); + this.attrs = ConfigBag.newInstanceCopying(attrs); + this.template = Maybe.fromNullable(optionalTemplate); + this.yamlLoader = new BrooklynYamlTypeInstantiator.Factory(loader, this); + this.type = type; + this.typeResolver = typeResolver; + } + + public BrooklynClassLoadingContext getLoader() { return loader; } + public ManagementContext getManagementContext() { return mgmt; } + public ConfigBag getAttrs() { return attrs; } + public Maybe<AbstractResource> getTemplate() { return template; } + public BrooklynYamlTypeInstantiator.Factory getYamlLoader() { return yamlLoader; } + public ServiceTypeResolver getServiceTypeResolver() { return typeResolver; } + public String getDeclaredType() { return type; } + public Boolean isAlreadyBuilt() { return alreadyBuilt.get(); } + + public static class Factory { + + /** returns resolver type based on the service type, inspecting the arguments in order to determine the service type */ + private static ServiceTypeResolver computeResolverType(BrooklynClassLoadingContext context, String knownServiceType, AbstractResource optionalTemplate, ConfigBag attrs) { + String type = getDeclaredType(knownServiceType, optionalTemplate, attrs); + return findService(context, type); + } + + // TODO This could be extended to support multiple prefixes per resolver and a 'best-match' algorithm + protected static ServiceTypeResolver findService(BrooklynClassLoadingContext context, String type) { + if (type.indexOf(':') != -1) { + String prefix = Splitter.on(":").splitToList(type).get(0); + ServiceLoader<ServiceTypeResolver> loader = ServiceLoader.load(ServiceTypeResolver.class, + context.getManagementContext().getCatalogClassLoader()); + for (ServiceTypeResolver resolver : loader) { + if (prefix.equals(resolver.getTypePrefix())) { + return resolver; + } + } + } + return null; + } + + public static BrooklynComponentTemplateResolver newInstance(BrooklynClassLoadingContext context, Map<String, ?> childAttrs) { + return newInstance(context, ConfigBag.newInstance(childAttrs), null); + } + + public static BrooklynComponentTemplateResolver newInstance(BrooklynClassLoadingContext context, AbstractResource template) { + return newInstance(context, ConfigBag.newInstance(template.getCustomAttributes()), template); + } + + public static BrooklynComponentTemplateResolver newInstance(BrooklynClassLoadingContext context, String serviceType) { + return newInstance(context, ConfigBag.newInstance().configureStringKey("serviceType", serviceType), null); + } + + private static BrooklynComponentTemplateResolver newInstance(BrooklynClassLoadingContext context, ConfigBag attrs, AbstractResource optionalTemplate) { + ServiceTypeResolver typeResolver = computeResolverType(context, null, optionalTemplate, attrs); + String type = getDeclaredType(null, optionalTemplate, attrs); + if (typeResolver == null) // use default + typeResolver = new BrooklynServiceTypeResolver(); + return new BrooklynComponentTemplateResolver(context, attrs, optionalTemplate, type, typeResolver); + } + + public static String getDeclaredType(String knownServiceType, AbstractResource optionalTemplate, @Nullable ConfigBag attrs) { + String type = knownServiceType; + if (type==null && optionalTemplate!=null) { + type = optionalTemplate.getType(); + if (type.equals(AssemblyTemplate.CAMP_TYPE) || type.equals(PlatformComponentTemplate.CAMP_TYPE) || type.equals(ApplicationComponentTemplate.CAMP_TYPE)) + // ignore these values for the type; only subclasses are interesting + type = null; + } + if (type==null) type = extractServiceTypeAttribute(attrs); + return type; + } + + private static String extractServiceTypeAttribute(@Nullable ConfigBag attrs) { + return BrooklynYamlTypeInstantiator.InstantiatorFromKey.extractTypeName("service", attrs).orNull(); + } + + public static boolean supportsType(BrooklynClassLoadingContext context, String serviceType) { + ServiceTypeResolver typeResolver = computeResolverType(context, serviceType, null, null); + if (typeResolver != null) return true; + return newInstance(context, serviceType).canResolve(); + } + } + + protected boolean canResolve() { + if (typeResolver.getCatalogItem(this, type)!=null) + return true; + if (loader.tryLoadClass(getJavaType(), Entity.class).isPresent()) + return true; + return false; + } + + /** returns the entity class, if needed in contexts which scan its statics for example */ + protected Class<? extends Entity> loadEntityClass() { + Maybe<Class<? extends Entity>> result = tryLoadEntityClass(); + if (result.isAbsent()) + throw new IllegalStateException("Could not find "+typeResolver.getBrooklynType(type), ((Maybe.Absent<?>)result).getException()); + return result.get(); + } + + /** tries to load the Java entity class */ + protected Maybe<Class<? extends Entity>> tryLoadEntityClass() { + return loader.tryLoadClass(getJavaType(), Entity.class); + } + + // TODO Generalise to have other prefixes (e.g. explicit "catalog:" etc)? + protected boolean isJavaTypePrefix() { + return type != null && (type.toLowerCase().startsWith("java:") || type.toLowerCase().startsWith("brooklyn:java:")); + } + + protected String getJavaType() { + CatalogItem<Entity, EntitySpec<?>> item = typeResolver.getCatalogItem(this, type); + if (!isJavaTypePrefix() && item != null && item.getJavaType() != null) { + return item.getJavaType(); + } else { + return typeResolver.getBrooklynType(type); + } + } + + /** resolves the spec, updating the loader if a catalog item is loaded */ + protected <T extends Entity> EntitySpec<T> resolveSpec(Set<String> encounteredCatalogTypes) { + if (alreadyBuilt.getAndSet(true)) + throw new IllegalStateException("Spec can only be used once: "+this); + + EntitySpec<T> spec = createSpec(encounteredCatalogTypes); + populateSpec(spec); + + return spec; + } + + @SuppressWarnings({ "unchecked" }) + protected <T extends Entity> EntitySpec<T> createSpec(Set<String> encounteredCatalogTypes) { + CatalogItem<Entity, EntitySpec<?>> item = getServiceTypeResolver().getCatalogItem(this, getDeclaredType()); + if (encounteredCatalogTypes==null) encounteredCatalogTypes = MutableSet.of(); + + //Take the symoblicName part of the catalog item only for recursion detection to prevent + //cross referencing of different versions. Not interested in non-catalog item types. + //Prevent catalog items self-referencing even if explicitly different version. + boolean firstOccurrence = (item == null || encounteredCatalogTypes.add(item.getSymbolicName())); + boolean recursiveButTryJava = !firstOccurrence; + + // Load a java class from current loader if explicit java prefix, or if no item, or if item is legacy / + // old-style catalog item (item != null && item.getJavaType() != null). + // Old-style catalog items (can be defined in catalog.xml only) don't have structure, only a single type, so + // they are loaded as a simple java type, only taking the class name from the catalog item instead of the + // type value in the YAML. Classpath entries in the item are also used (through the catalog root classloader). + if (isJavaTypePrefix() || item == null || item.getJavaType() != null) { + return createSpecFromJavaType(); + + // Same as above case, but this time force java type loading (either as plain class or through an old-style + // catalog item, since we have already loaded a class item with the same name as the type value. + } else if (recursiveButTryJava) { + if (tryLoadEntityClass().isAbsent()) { + throw new IllegalStateException("Recursive reference to " + item + " (and cannot be resolved as a Java type)"); + } + return createSpecFromJavaType(); + + // Only case that's left is a catalog item with YAML content - try to parse it recursively + // including it's OSGi bundles in the loader classpath. + } else { + // TODO perhaps migrate to catalog.createSpec ? + EntitySpec<?> spec = BrooklynAssemblyTemplateInstantiator.resolveCatalogYamlReferenceSpec(mgmt, item, encounteredCatalogTypes); + spec.catalogItemId(item.getId()); + + return (EntitySpec<T>)spec; + } + } + + @SuppressWarnings("unchecked") + protected <T extends Entity> EntitySpec<T> createSpecFromJavaType() { + Class<T> type = (Class<T>) loadEntityClass(); + + EntitySpec<T> spec; + if (type.isInterface()) { + spec = EntitySpec.create(type); + } else { + // If this is a concrete class, particularly for an Application class, we want the proxy + // to expose all interfaces it implements. + @SuppressWarnings("rawtypes") + Class interfaceclazz = (Application.class.isAssignableFrom(type)) ? Application.class : Entity.class; + List<Class<?>> additionalInterfaceClazzes = Reflections.getAllInterfaces(type); + spec = EntitySpec.create(interfaceclazz).impl(type).additionalInterfaces(additionalInterfaceClazzes); + } + spec.catalogItemId(CatalogUtils.getCatalogItemIdFromLoader(loader)); + if (template.isPresent() && template.get().getSourceCode()!=null) + spec.tag(BrooklynTags.newYamlSpecTag(template.get().getSourceCode())); + + return spec; + } + + //called from BrooklynAssemblyTemplateInstantiator as well + @SuppressWarnings("unchecked") + protected <T extends Entity> void populateSpec(EntitySpec<T> spec) { + String name, templateId=null, planId=null; + if (template.isPresent()) { + name = template.get().getName(); + templateId = template.get().getId(); + } else { + name = (String)attrs.getStringKey("name"); + } + planId = (String)attrs.getStringKey("id"); + if (planId==null) + planId = (String) attrs.getStringKey(BrooklynCampConstants.PLAN_ID_FLAG); + + Object childrenObj = attrs.getStringKey(BrooklynCampReservedKeys.BROOKLYN_CHILDREN); + if (childrenObj != null) { + // Creating a new set of encounteredCatalogTypes means that this won't check things recursively; + // but we are looking at children so we probably *should* be resetting the recursive list we've looked at; + // (but see also, a previous comment here which suggested otherwise? - Apr 2015) + Set<String> encounteredCatalogTypes = MutableSet.of(); + + Iterable<Map<String,?>> children = (Iterable<Map<String,?>>)childrenObj; + for (Map<String,?> childAttrs : children) { + BrooklynComponentTemplateResolver entityResolver = BrooklynComponentTemplateResolver.Factory.newInstance(loader, childAttrs); + EntitySpec<? extends Entity> childSpec = BrooklynAssemblyTemplateInstantiator.resolveSpec(ResourceUtils.create(this), entityResolver, encounteredCatalogTypes); + spec.child(childSpec); + } + } + if (!Strings.isBlank(name)) + spec.displayName(name); + if (templateId != null) + spec.configure(BrooklynCampConstants.TEMPLATE_ID, templateId); + if (planId != null) + spec.configure(BrooklynCampConstants.PLAN_ID, planId); + + List<Location> childLocations = new BrooklynYamlLocationResolver(mgmt).resolveLocations(attrs.getAllConfig(), true); + if (childLocations != null) + spec.locations(childLocations); + + typeResolver.decorateSpec(this, spec); + configureEntityConfig(spec); + } + + /** returns new *uninitialised* entity, with just a few of the pieces from the spec; + * initialisation occurs soon after, in {@link #initEntity(ManagementContext, Entity, EntitySpec)}, + * inside an execution context and after entity ID's are recognised + */ + protected <T extends Entity> T newEntity(EntitySpec<T> spec) { + Class<? extends T> entityImpl = (spec.getImplementation() != null) ? spec.getImplementation() : mgmt.getEntityManager().getEntityTypeRegistry().getImplementedBy(spec.getType()); + InternalEntityFactory entityFactory = ((ManagementContextInternal)mgmt).getEntityFactory(); + T entity = entityFactory.constructEntity(entityImpl, spec); + + String planId = (String)spec.getConfig().get(BrooklynCampConstants.PLAN_ID); + if (planId != null) { + entity.config().set(BrooklynCampConstants.PLAN_ID, planId); + } + + if (spec.getLocations().size() > 0) { + ((AbstractEntity)entity).addLocations(spec.getLocations()); + } + + if (spec.getParent() != null) entity.setParent(spec.getParent()); + + return entity; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + protected void configureEntityConfig(EntitySpec<?> spec) { + // first take *recognised* flags and config keys from the top-level, and put them in the bag (of brooklyn.config) + // attrs will contain only brooklyn.xxx properties when coming from BrooklynEntityMatcher. + // Any top-level flags will go into "brooklyn.flags". When resolving a spec from $brooklyn:entitySpec + // top level flags remain in place. Have to support both cases. + + ConfigBag bag = ConfigBag.newInstance((Map<Object, Object>) attrs.getStringKey(BrooklynCampReservedKeys.BROOKLYN_CONFIG)); + ConfigBag bagFlags = ConfigBag.newInstanceCopying(attrs); + if (attrs.containsKey(BrooklynCampReservedKeys.BROOKLYN_FLAGS)) { + bagFlags.putAll((Map<String, Object>) attrs.getStringKey(BrooklynCampReservedKeys.BROOKLYN_FLAGS)); + } + + Collection<FlagConfigKeyAndValueRecord> topLevelApparentConfig = findAllFlagsAndConfigKeys(spec, bagFlags); + for (FlagConfigKeyAndValueRecord r: topLevelApparentConfig) { + if (r.getConfigKeyMaybeValue().isPresent()) + bag.putIfAbsent((ConfigKey)r.getConfigKey(), r.getConfigKeyMaybeValue().get()); + if (r.getFlagMaybeValue().isPresent()) + bag.putAsStringKeyIfAbsent(r.getFlagName(), r.getFlagMaybeValue().get()); + } + + // now set configuration for all the items in the bag + Collection<FlagConfigKeyAndValueRecord> records = findAllFlagsAndConfigKeys(spec, bag); + Set<String> keyNamesUsed = new LinkedHashSet<String>(); + for (FlagConfigKeyAndValueRecord r: records) { + if (r.getFlagMaybeValue().isPresent()) { + Object transformed = new SpecialFlagsTransformer(loader).transformSpecialFlags(r.getFlagMaybeValue().get()); + spec.configure(r.getFlagName(), transformed); + keyNamesUsed.add(r.getFlagName()); + } + if (r.getConfigKeyMaybeValue().isPresent()) { + Object transformed = new SpecialFlagsTransformer(loader).transformSpecialFlags(r.getConfigKeyMaybeValue().get()); + spec.configure((ConfigKey<Object>)r.getConfigKey(), transformed); + keyNamesUsed.add(r.getConfigKey().getName()); + } + } + + // set unused keys as anonymous config keys - + // they aren't flags or known config keys, so must be passed as config keys in order for + // EntitySpec to know what to do with them (as they are passed to the spec as flags) + for (String key: MutableSet.copyOf(bag.getUnusedConfig().keySet())) { + // we don't let a flag with the same name as a config key override the config key + // (that's why we check whether it is used) + if (!keyNamesUsed.contains(key)) { + Object transformed = new SpecialFlagsTransformer(loader).transformSpecialFlags(bag.getStringKey(key)); + spec.configure(ConfigKeys.newConfigKey(Object.class, key.toString()), transformed); + } + } + } + + /** + * Searches for config keys in the type, additional interfaces and the implementation (if specified) + */ + private Collection<FlagConfigKeyAndValueRecord> findAllFlagsAndConfigKeys(EntitySpec<?> spec, ConfigBag bagFlags) { + Set<FlagConfigKeyAndValueRecord> allKeys = MutableSet.of(); + allKeys.addAll(FlagUtils.findAllFlagsAndConfigKeys(null, spec.getType(), bagFlags)); + if (spec.getImplementation() != null) { + allKeys.addAll(FlagUtils.findAllFlagsAndConfigKeys(null, spec.getImplementation(), bagFlags)); + } + for (Class<?> iface : spec.getAdditionalInterfaces()) { + allKeys.addAll(FlagUtils.findAllFlagsAndConfigKeys(null, iface, bagFlags)); + } + return allKeys; + } + + protected static class SpecialFlagsTransformer implements Function<Object, Object> { + protected final ManagementContext mgmt; + /* TODO find a way to make do without loader here? + * it is not very nice having to serialize it; but serialization of BLCL is now relatively clean. + * + * it is only used to instantiate classes, and now most things should be registered with catalog; + * the notable exception is when one entity in a bundle is creating another in the same bundle, + * it wants to use his bundle CLC to do that. but we can set up some unique reference to the entity + * which can be used to find it from mgmt, rather than pass the loader. + */ + private BrooklynClassLoadingContext loader = null; + + public SpecialFlagsTransformer(BrooklynClassLoadingContext loader) { + this.loader = loader; + mgmt = loader.getManagementContext(); + } + public Object apply(Object input) { + if (input instanceof Map) + return transformSpecialFlags((Map<?, ?>)input); + else if (input instanceof Set<?>) + return MutableSet.of(transformSpecialFlags((Iterable<?>)input)); + else if (input instanceof List<?>) + return MutableList.copyOf(transformSpecialFlags((Iterable<?>)input)); + else if (input instanceof Iterable<?>) + return transformSpecialFlags((Iterable<?>)input); + else + return transformSpecialFlags((Object)input); + } + + protected Map<?, ?> transformSpecialFlags(Map<?, ?> flag) { + return Maps.transformValues(flag, this); + } + + protected Iterable<?> transformSpecialFlags(Iterable<?> flag) { + return Iterables.transform(flag, this); + } + + protected BrooklynClassLoadingContext getLoader() { + if (loader!=null) return loader; + // TODO currently loader will non-null unless someone has messed with the rebind files, + // but we'd like to get rid of it; ideally we'd have a reference to the entity. + // for now, this is a slightly naff way to do it, if we have to set loader=null as a workaround + Entity entity = BrooklynTaskTags.getTargetOrContextEntity(Tasks.current()); + if (entity!=null) return CatalogUtils.getClassLoadingContext(entity); + return JavaBrooklynClassLoadingContext.create(mgmt); + } + + /** + * Makes additional transformations to the given flag with the extra knowledge of the flag's management context. + * @return The modified flag, or the flag unchanged. + */ + protected Object transformSpecialFlags(Object flag) { + if (flag instanceof EntitySpecConfiguration) { + EntitySpecConfiguration specConfig = (EntitySpecConfiguration) flag; + // TODO: This should called from BrooklynAssemblyTemplateInstantiator.configureEntityConfig + // And have transformSpecialFlags(Object flag, ManagementContext mgmt) drill into the Object flag if it's a map or iterable? + @SuppressWarnings("unchecked") + Map<String, Object> resolvedConfig = (Map<String, Object>)transformSpecialFlags(specConfig.getSpecConfiguration()); + specConfig.setSpecConfiguration(resolvedConfig); + return Factory.newInstance(getLoader(), specConfig.getSpecConfiguration()).resolveSpec(null); + } + if (flag instanceof ManagementContextInjectable) { + log.debug("Injecting Brooklyn management context info object: {}", flag); + ((ManagementContextInjectable) flag).injectManagementContext(loader.getManagementContext()); + } + + return flag; + } + } + + @SuppressWarnings("unchecked") + protected List<Map<String, Object>> getChildren(Map<String, Object> attrs) { + if (attrs==null) return null; + return (List<Map<String, Object>>) attrs.get(BrooklynCampReservedKeys.BROOKLYN_CHILDREN); + } + +}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e406d1ad/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynEntityDecorationResolver.java ---------------------------------------------------------------------- diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynEntityDecorationResolver.java b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynEntityDecorationResolver.java new file mode 100644 index 0000000..8af9ba5 --- /dev/null +++ b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynEntityDecorationResolver.java @@ -0,0 +1,180 @@ +/* + * 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.camp.brooklyn.spi.creation; + +import java.util.List; +import java.util.Map; + +import org.apache.brooklyn.camp.brooklyn.BrooklynCampReservedKeys; +import org.apache.brooklyn.camp.brooklyn.spi.creation.BrooklynYamlTypeInstantiator.InstantiatorFromKey; +import org.apache.brooklyn.catalog.BrooklynCatalog; +import org.apache.brooklyn.catalog.CatalogItem; +import brooklyn.catalog.internal.CatalogUtils; +import brooklyn.entity.proxying.EntityInitializer; +import brooklyn.entity.proxying.EntitySpec; +import brooklyn.management.ManagementContext; +import brooklyn.policy.Enricher; +import brooklyn.policy.EnricherSpec; +import brooklyn.policy.Policy; +import brooklyn.policy.PolicySpec; +import brooklyn.util.collections.MutableList; +import brooklyn.util.config.ConfigBag; + +import com.google.common.annotations.Beta; + +/** + * Pattern for resolving "decorations" on service specs / entity specs, such as policies, enrichers, etc. + * @since 0.7.0 + */ +@Beta +public abstract class BrooklynEntityDecorationResolver<DT> { + + public final BrooklynYamlTypeInstantiator.Factory instantiator; + + protected BrooklynEntityDecorationResolver(BrooklynYamlTypeInstantiator.Factory instantiator) { + this.instantiator = instantiator; + } + + public abstract void decorate(EntitySpec<?> entitySpec, ConfigBag attrs); + + protected Iterable<? extends DT> buildListOfTheseDecorationsFromEntityAttributes(ConfigBag attrs) { + Object value = getDecorationAttributeJsonValue(attrs); + List<DT> decorations = MutableList.of(); + if (value==null) return decorations; + if (value instanceof Iterable) { + for (Object decorationJson: (Iterable<?>)value) + addDecorationFromJsonMap(checkIsMap(decorationJson), decorations); + } else { + // in future may support types other than iterables here, + // e.g. a map short form where the key is the type + throw new IllegalArgumentException(getDecorationKind()+" body should be iterable, not " + value.getClass()); + } + return decorations; + } + + protected Map<?,?> checkIsMap(Object decorationJson) { + if (!(decorationJson instanceof Map)) + throw new IllegalArgumentException(getDecorationKind()+" value must be a Map, not " + + (decorationJson==null ? null : decorationJson.getClass()) ); + return (Map<?,?>) decorationJson; + } + + protected abstract String getDecorationKind(); + protected abstract Object getDecorationAttributeJsonValue(ConfigBag attrs); + + /** creates and adds decorations from the given json to the given collection; + * default impl requires a map and calls {@link #addDecorationFromJsonMap(Map, List)} */ + protected void addDecorationFromJson(Object decorationJson, List<DT> decorations) { + addDecorationFromJsonMap(checkIsMap(decorationJson), decorations); + } + protected abstract void addDecorationFromJsonMap(Map<?,?> decorationJson, List<DT> decorations); + + + public static class PolicySpecResolver extends BrooklynEntityDecorationResolver<PolicySpec<?>> { + + public PolicySpecResolver(BrooklynYamlTypeInstantiator.Factory loader) { super(loader); } + @Override protected String getDecorationKind() { return "Policy"; } + + @Override + public void decorate(EntitySpec<?> entitySpec, ConfigBag attrs) { + entitySpec.policySpecs(buildListOfTheseDecorationsFromEntityAttributes(attrs)); + } + + @Override + protected Object getDecorationAttributeJsonValue(ConfigBag attrs) { + return attrs.getStringKey(BrooklynCampReservedKeys.BROOKLYN_POLICIES); + } + + @Override + @SuppressWarnings("unchecked") + protected void addDecorationFromJsonMap(Map<?, ?> decorationJson, List<PolicySpec<?>> decorations) { + InstantiatorFromKey decoLoader = instantiator.from(decorationJson).prefix("policy"); + + String policyType = decoLoader.getTypeName().get(); + ManagementContext mgmt = instantiator.loader.getManagementContext(); + BrooklynCatalog catalog = mgmt.getCatalog(); + CatalogItem<?, ?> item = getPolicyCatalogItem(catalog, policyType); + PolicySpec<? extends Policy> spec; + if (item != null) { + spec = (PolicySpec<? extends Policy>) catalog.createSpec(item); + spec.configure(decoLoader.getConfigMap()); + } else { + // this pattern of creating a spec could be simplified with a "Configurable" superinterface on *Spec + spec = PolicySpec.create(decoLoader.getType(Policy.class)) + .configure( decoLoader.getConfigMap() ); + } + decorations.add(spec); + } + private CatalogItem<?, ?> getPolicyCatalogItem(BrooklynCatalog catalog, String policyType) { + if (CatalogUtils.looksLikeVersionedId(policyType)) { + String id = CatalogUtils.getIdFromVersionedId(policyType); + String version = CatalogUtils.getVersionFromVersionedId(policyType); + return catalog.getCatalogItem(id, version); + } else { + return catalog.getCatalogItem(policyType, BrooklynCatalog.DEFAULT_VERSION); + } + } + } + + public static class EnricherSpecResolver extends BrooklynEntityDecorationResolver<EnricherSpec<?>> { + + public EnricherSpecResolver(BrooklynYamlTypeInstantiator.Factory loader) { super(loader); } + @Override protected String getDecorationKind() { return "Enricher"; } + + @Override + public void decorate(EntitySpec<?> entitySpec, ConfigBag attrs) { + entitySpec.enricherSpecs(buildListOfTheseDecorationsFromEntityAttributes(attrs)); + } + + @Override + protected Object getDecorationAttributeJsonValue(ConfigBag attrs) { + return attrs.getStringKey(BrooklynCampReservedKeys.BROOKLYN_ENRICHERS); + } + + @Override + protected void addDecorationFromJsonMap(Map<?, ?> decorationJson, List<EnricherSpec<?>> decorations) { + InstantiatorFromKey decoLoader = instantiator.from(decorationJson).prefix("enricher"); + decorations.add(EnricherSpec.create(decoLoader.getType(Enricher.class)) + .configure( decoLoader.getConfigMap() )); + } + } + + public static class InitializerResolver extends BrooklynEntityDecorationResolver<EntityInitializer> { + + public InitializerResolver(BrooklynYamlTypeInstantiator.Factory loader) { super(loader); } + @Override protected String getDecorationKind() { return "Entity initializer"; } + + @Override + public void decorate(EntitySpec<?> entitySpec, ConfigBag attrs) { + entitySpec.addInitializers(buildListOfTheseDecorationsFromEntityAttributes(attrs)); + } + + @Override + protected Object getDecorationAttributeJsonValue(ConfigBag attrs) { + return attrs.getStringKey(BrooklynCampReservedKeys.BROOKLYN_INITIALIZERS); + } + + @Override + protected void addDecorationFromJsonMap(Map<?, ?> decorationJson, List<EntityInitializer> decorations) { + decorations.add(instantiator.from(decorationJson).prefix("initializer").newInstance(EntityInitializer.class)); + } + } + + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e406d1ad/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynEntityMatcher.java ---------------------------------------------------------------------- diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynEntityMatcher.java b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynEntityMatcher.java new file mode 100644 index 0000000..27ccae7 --- /dev/null +++ b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynEntityMatcher.java @@ -0,0 +1,193 @@ +/* + * 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.camp.brooklyn.spi.creation; + +import io.brooklyn.camp.spi.PlatformComponentTemplate; +import io.brooklyn.camp.spi.PlatformComponentTemplate.Builder; +import io.brooklyn.camp.spi.pdp.AssemblyTemplateConstructor; +import io.brooklyn.camp.spi.pdp.Service; +import io.brooklyn.camp.spi.resolve.PdpMatcher; + +import java.util.List; +import java.util.Map; + +import org.apache.brooklyn.camp.brooklyn.BrooklynCampConstants; +import org.apache.brooklyn.camp.brooklyn.BrooklynCampReservedKeys; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.catalog.internal.BasicBrooklynCatalog; +import brooklyn.management.ManagementContext; +import brooklyn.management.classloading.BrooklynClassLoadingContext; +import brooklyn.management.classloading.JavaBrooklynClassLoadingContext; +import brooklyn.util.collections.MutableMap; +import brooklyn.util.net.Urls; +import brooklyn.util.text.Strings; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +public class BrooklynEntityMatcher implements PdpMatcher { + + private static final Logger log = LoggerFactory.getLogger(BrooklynEntityMatcher.class); + + protected final ManagementContext mgmt; + + public BrooklynEntityMatcher(ManagementContext bmc) { + this.mgmt = bmc; + } + + @Override + public boolean accepts(Object deploymentPlanItem) { + return lookupType(deploymentPlanItem) != null; + } + + /** returns the type of the given plan item, + * typically whether a Service can be matched to a Brooklyn entity, + * or null if not supported */ + protected String lookupType(Object deploymentPlanItem) { + if (deploymentPlanItem instanceof Service) { + Service service = (Service)deploymentPlanItem; + + String serviceType = service.getServiceType(); + BrooklynClassLoadingContext loader = BasicBrooklynCatalog.BrooklynLoaderTracker.getLoader(); + if (loader == null) loader = JavaBrooklynClassLoadingContext.create(mgmt); + if (BrooklynComponentTemplateResolver.Factory.supportsType(loader, serviceType)) + return serviceType; + + String protocol = Urls.getProtocol(serviceType); + if (protocol != null) { + if (BrooklynCampConstants.YAML_URL_PROTOCOL_WHITELIST.contains(protocol)) { + return serviceType; + } else { + log.debug("The reference '" + serviceType + "' looks like a URL (running the CAMP Brooklyn entity-matcher) but the protocol '" + + protocol + "' isn't white listed " + BrooklynCampConstants.YAML_URL_PROTOCOL_WHITELIST + ". " + + "Not recognized as catalog item or java item as well!"); + } + } + } + return null; + } + + @Override + public boolean apply(Object deploymentPlanItem, AssemblyTemplateConstructor atc) { + if (!(deploymentPlanItem instanceof Service)) return false; + + String type = lookupType(deploymentPlanItem); + if (type==null) return false; + + log.debug("Item "+deploymentPlanItem+" being instantiated with "+type); + + Object old = atc.getInstantiator(); + if (old!=null && !old.equals(BrooklynAssemblyTemplateInstantiator.class)) { + log.warn("Can't mix Brooklyn entities with non-Brooklyn entities (at present): "+old); + return false; + } + + // TODO should we build up a new type, BrooklynEntityComponentTemplate here + // complete w EntitySpec -- ie merge w BrooklynComponentTemplateResolver ? + + Builder<? extends PlatformComponentTemplate> builder = PlatformComponentTemplate.builder(); + builder.type( type.indexOf(':')==-1 ? "brooklyn:"+type : type ); + + // currently instantiator must be brooklyn at the ATC level + // optionally would be nice to support multiple/mixed instantiators, + // ie at the component level, perhaps with the first one responsible for building the app + atc.instantiator(BrooklynAssemblyTemplateInstantiator.class); + + String name = ((Service)deploymentPlanItem).getName(); + if (!Strings.isBlank(name)) builder.name(name); + + // configuration + Map<String, Object> attrs = MutableMap.copyOf( ((Service)deploymentPlanItem).getCustomAttributes() ); + + if (attrs.containsKey("id")) + builder.customAttribute("planId", attrs.remove("id")); + + Object location = attrs.remove("location"); + if (location!=null) + builder.customAttribute("location", location); + Object locations = attrs.remove("locations"); + if (locations!=null) + builder.customAttribute("locations", locations); + + MutableMap<Object, Object> brooklynFlags = MutableMap.of(); + Object origBrooklynFlags = attrs.remove(BrooklynCampReservedKeys.BROOKLYN_FLAGS); + if (origBrooklynFlags!=null) { + if (!(origBrooklynFlags instanceof Map)) + throw new IllegalArgumentException("brooklyn.flags must be a map of brooklyn flags"); + brooklynFlags.putAll((Map<?,?>)origBrooklynFlags); + } + + addCustomMapAttributeIfNonNull(builder, attrs, BrooklynCampReservedKeys.BROOKLYN_CONFIG); + addCustomListAttributeIfNonNull(builder, attrs, BrooklynCampReservedKeys.BROOKLYN_POLICIES); + addCustomListAttributeIfNonNull(builder, attrs, BrooklynCampReservedKeys.BROOKLYN_ENRICHERS); + addCustomListAttributeIfNonNull(builder, attrs, BrooklynCampReservedKeys.BROOKLYN_INITIALIZERS); + addCustomListAttributeIfNonNull(builder, attrs, BrooklynCampReservedKeys.BROOKLYN_CHILDREN); + addCustomMapAttributeIfNonNull(builder, attrs, BrooklynCampReservedKeys.BROOKLYN_CATALOG); + + brooklynFlags.putAll(attrs); + if (!brooklynFlags.isEmpty()) { + builder.customAttribute(BrooklynCampReservedKeys.BROOKLYN_FLAGS, brooklynFlags); + } + + atc.add(builder.build()); + + return true; + } + + /** + * Looks for the given key in the map of attributes and adds it to the given builder + * as a custom attribute with type List. + * @throws java.lang.IllegalArgumentException if map[key] is not an instance of List + */ + private void addCustomListAttributeIfNonNull(Builder<? extends PlatformComponentTemplate> builder, Map<?,?> attrs, String key) { + Object items = attrs.remove(key); + if (items != null) { + if (items instanceof List) { + List<?> itemList = (List<?>) items; + if (!itemList.isEmpty()) { + builder.customAttribute(key, Lists.newArrayList(itemList)); + } + } else { + throw new IllegalArgumentException(key + " must be a list, is: " + items.getClass().getName()); + } + } + } + + /** + * Looks for the given key in the map of attributes and adds it to the given builder + * as a custom attribute with type Map. + * @throws java.lang.IllegalArgumentException if map[key] is not an instance of Map + */ + private void addCustomMapAttributeIfNonNull(Builder<? extends PlatformComponentTemplate> builder, Map<?,?> attrs, String key) { + Object items = attrs.remove(key); + if (items != null) { + if (items instanceof Map) { + Map<?, ?> itemMap = (Map<?, ?>) items; + if (!itemMap.isEmpty()) { + builder.customAttribute(key, Maps.newHashMap(itemMap)); + } + } else { + throw new IllegalArgumentException(key + " must be a map, is: " + items.getClass().getName()); + } + } + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e406d1ad/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynYamlLocationResolver.java ---------------------------------------------------------------------- diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynYamlLocationResolver.java b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynYamlLocationResolver.java new file mode 100644 index 0000000..7c4f734 --- /dev/null +++ b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynYamlLocationResolver.java @@ -0,0 +1,142 @@ +/* + * 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.camp.brooklyn.spi.creation; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import brooklyn.location.Location; +import brooklyn.location.LocationDefinition; +import brooklyn.management.ManagementContext; +import brooklyn.util.collections.MutableList; +import brooklyn.util.collections.MutableMap; +import brooklyn.util.exceptions.Exceptions; +import brooklyn.util.guava.Maybe; +import brooklyn.util.guava.Maybe.Absent; +import brooklyn.util.text.Strings; + +import com.google.common.collect.Iterables; + +public class BrooklynYamlLocationResolver { + + protected final ManagementContext mgmt; + + public BrooklynYamlLocationResolver(ManagementContext bmc) { + this.mgmt = bmc; + } + + /** returns list of locations, if any were supplied, or null if none indicated */ + @SuppressWarnings("unchecked") + public List<Location> resolveLocations(Map<? super String,?> attrs, boolean removeUsedAttributes) { + Object location = attrs.get("location"); + Object locations = attrs.get("locations"); + + if (location==null && locations==null) + return null; + + Location locationFromString = null; + List<Location> locationsFromList = null; + + if (location!=null) { + if (location instanceof String) { + locationFromString = resolveLocationFromString((String)location); + } else if (location instanceof Map) { + locationFromString = resolveLocationFromMap((Map<?,?>)location); + } else { + throw new IllegalStateException("Illegal parameter for 'location'; must be a string or map (but got "+location+")"); + } + } + + if (locations!=null) { + if (!(locations instanceof Iterable)) + throw new IllegalStateException("Illegal parameter for 'locations'; must be an iterable (but got "+locations+")"); + locationsFromList = resolveLocations( (Iterable<Object>)locations ); + } + + if (locationFromString!=null && locationsFromList!=null) { + if (locationsFromList.size() != 1) + throw new IllegalStateException("Conflicting 'location' and 'locations' ("+location+" and "+locations+"); " + + "if both are supplied the list must have exactly one element being the same"); + if (!locationFromString.equals( Iterables.getOnlyElement(locationsFromList) )) + throw new IllegalStateException("Conflicting 'location' and 'locations' ("+location+" and "+locations+"); " + + "different location specified in each"); + } else if (locationFromString!=null) { + locationsFromList = Arrays.asList(locationFromString); + } + + return locationsFromList; + } + + public List<Location> resolveLocations(Iterable<Object> locations) { + List<Location> result = MutableList.of(); + for (Object l: locations) { + Location ll = resolveLocation(l); + if (ll!=null) result.add(ll); + } + return result; + } + + public Location resolveLocation(Object location) { + if (location instanceof String) { + return resolveLocationFromString((String)location); + } else if (location instanceof Map) { + return resolveLocationFromMap((Map<?,?>)location); + } + // could support e.g. location definition + throw new IllegalStateException("Illegal parameter for 'location' ("+location+"); must be a string or map"); + } + + /** resolves the location from the given spec string, either "Named Location", or "named:Named Location" format; + * returns null if input is blank (or null); otherwise guaranteed to resolve or throw error */ + public Location resolveLocationFromString(String location) { + if (Strings.isBlank(location)) return null; + return resolveLocation(location, MutableMap.of()); + } + + public Location resolveLocationFromMap(Map<?,?> location) { + if (location.size() > 1) { + throw new IllegalStateException("Illegal parameter for 'location'; expected a single entry in map ("+location+")"); + } + Object key = Iterables.getOnlyElement(location.keySet()); + Object value = location.get(key); + + if (!(key instanceof String)) { + throw new IllegalStateException("Illegal parameter for 'location'; expected String key ("+location+")"); + } + if (!(value instanceof Map)) { + throw new IllegalStateException("Illegal parameter for 'location'; expected config map ("+location+")"); + } + return resolveLocation((String)key, (Map<?,?>)value); + } + + protected Location resolveLocation(String spec, Map<?,?> flags) { + LocationDefinition ldef = mgmt.getLocationRegistry().getDefinedLocationByName((String)spec); + if (ldef!=null) + // found it as a named location + return mgmt.getLocationRegistry().resolve(ldef, null, flags).get(); + + Maybe<Location> l = mgmt.getLocationRegistry().resolve(spec, null, flags); + if (l.isPresent()) return l.get(); + + RuntimeException exception = ((Absent<?>)l).getException(); + throw new IllegalStateException("Illegal parameter for 'location' ("+spec+"); not resolvable: "+ + Exceptions.collapseText( exception ), exception); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e406d1ad/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynYamlTypeInstantiator.java ---------------------------------------------------------------------- diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynYamlTypeInstantiator.java b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynYamlTypeInstantiator.java new file mode 100644 index 0000000..8c2cb72 --- /dev/null +++ b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynYamlTypeInstantiator.java @@ -0,0 +1,208 @@ +/* + * 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.camp.brooklyn.spi.creation; + +import java.util.Map; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import org.apache.brooklyn.camp.brooklyn.BrooklynCampReservedKeys; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.management.classloading.BrooklynClassLoadingContext; +import brooklyn.util.collections.MutableMap; +import brooklyn.util.config.ConfigBag; +import brooklyn.util.exceptions.Exceptions; +import brooklyn.util.guava.Maybe; +import brooklyn.util.javalang.Reflections; + +import com.google.common.annotations.Beta; +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; + +/** Assists in loading types referenced from YAML; + * mainly as a way to share logic used in very different contexts. */ +public abstract class BrooklynYamlTypeInstantiator { + + private static final Logger log = LoggerFactory.getLogger(BrooklynYamlTypeInstantiator.class); + + protected final Factory factory; + + @Beta + public static class Factory { + final BrooklynClassLoadingContext loader; + final Object contextForLogging; + + public Factory(BrooklynClassLoadingContext loader, Object contextForLogging) { + this.loader = loader; + this.contextForLogging = contextForLogging; + } + + public InstantiatorFromKey from(Map<?,?> data) { + return new InstantiatorFromKey(this, ConfigBag.newInstance(data)); + } + + public InstantiatorFromKey from(ConfigBag data) { + return new InstantiatorFromKey(this, data); + } + + public InstantiatorFromName type(String typeName) { + return new InstantiatorFromName(this, typeName); + } + + } + + public static class InstantiatorFromKey extends BrooklynYamlTypeInstantiator { + protected final ConfigBag data; + protected String typeKeyPrefix = null; + + /** Nullable only permitted for instances which do not do loading, e.g. LoaderFromKey#lookup */ + protected InstantiatorFromKey(@Nullable Factory factory, ConfigBag data) { + super(factory); + this.data = data; + } + + public static Maybe<String> extractTypeName(String prefix, ConfigBag data) { + if (data==null) return Maybe.absent(); + return new InstantiatorFromKey(null, data).prefix(prefix).getTypeName(); + } + + public InstantiatorFromKey prefix(String prefix) { + typeKeyPrefix = prefix; + return this; + } + + public Maybe<String> getTypeName() { + Maybe<Object> result = data.getStringKeyMaybe(getPreferredKeyName()); + if (result.isAbsent() && typeKeyPrefix!=null) { + // try alternatives if a prefix was specified + result = data.getStringKeyMaybe(typeKeyPrefix+"Type"); + if (result.isAbsent()) result = data.getStringKeyMaybe("type"); + } + + if (result.isAbsent() || result.get()==null) + return Maybe.absent("Missing key '"+getPreferredKeyName()+"'"); + + if (result.get() instanceof String) return Maybe.of((String)result.get()); + + throw new IllegalArgumentException("Invalid value "+result.get().getClass()+" for "+getPreferredKeyName()+"; " + + "expected String, got "+result.get()); + } + + protected String getPreferredKeyName() { + if (typeKeyPrefix!=null) return typeKeyPrefix+"_type"; + return "type"; + } + + /** as {@link #newInstance(Class)} but inferring the type */ + public Object newInstance() { + return newInstance(null); + } + + /** creates a new instance of the type referred to by this description, + * as a subtype of the type supplied here, + * inferring a Map from <code>brooklyn.config</code> key. + * TODO in future also picking up recognized flags and config keys (those declared on the type). + * <p> + * constructs the object using: + * <li> a constructor on the class taking a Map + * <li> a no-arg constructor, only if the inferred map is empty + **/ + public <T> T newInstance(@Nullable Class<T> supertype) { + Class<? extends T> type = getType(supertype); + Map<String, ?> cfg = getConfigMap(); + Optional<? extends T> result = Reflections.invokeConstructorWithArgs(type, cfg); + if (result.isPresent()) + return result.get(); + + ConfigBag cfgBag = ConfigBag.newInstance(cfg); + result = Reflections.invokeConstructorWithArgs(type, cfgBag); + if (result.isPresent()) + return result.get(); + + if (cfg.isEmpty()) { + result = Reflections.invokeConstructorWithArgs(type); + if (result.isPresent()) + return result.get(); + } + + throw new IllegalStateException("No known mechanism for constructing type "+type+" in "+factory.contextForLogging); + } + + /** finds the map of config for the type specified; + * currently only gets <code>brooklyn.config</code>, returning empty map if none, + * but TODO in future should support recognized flags and config keys (those declared on the type), + * incorporating code in {@link BrooklynEntityMatcher}. + */ + @SuppressWarnings("unchecked") + @Nonnull + public Map<String,?> getConfigMap() { + MutableMap<String,Object> result = MutableMap.of(); + Object bc = data.getStringKey(BrooklynCampReservedKeys.BROOKLYN_CONFIG); + if (bc!=null) { + if (bc instanceof Map) + result.putAll((Map<? extends String, ?>) bc); + else + throw new IllegalArgumentException("brooklyn.config key in "+factory.contextForLogging+" should be a map, not "+bc.getClass()+" ("+bc+")"); + } + return result; + } + + } + + public static class InstantiatorFromName extends BrooklynYamlTypeInstantiator { + protected final String typeName; + protected InstantiatorFromName(Factory factory, String typeName) { + super(factory); + this.typeName = typeName; + } + + public Maybe<String> getTypeName() { + return Maybe.fromNullable(typeName); + } + } + + protected BrooklynYamlTypeInstantiator(Factory factory) { + this.factory = factory; + } + + public abstract Maybe<String> getTypeName(); + + public BrooklynClassLoadingContext getClassLoadingContext() { + Preconditions.checkNotNull(factory, "No factory set; cannot use this instance for type loading"); + return factory.loader; + } + + public Class<?> getType() { + return getType(Object.class); + } + + public <T> Class<? extends T> getType(@Nonnull Class<T> type) { + try { + return getClassLoadingContext().loadClass(getTypeName().get(), type); + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + log.debug("Unable to resolve " + type + " " + getTypeName().get() + " (rethrowing) in spec " + factory.contextForLogging); + throw Exceptions.propagate(e); + } + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e406d1ad/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/EntitySpecConfiguration.java ---------------------------------------------------------------------- diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/EntitySpecConfiguration.java b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/EntitySpecConfiguration.java new file mode 100644 index 0000000..626529f --- /dev/null +++ b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/EntitySpecConfiguration.java @@ -0,0 +1,58 @@ +/* + * 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.camp.brooklyn.spi.creation; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.entity.proxying.EntitySpec; + +import com.google.common.collect.Maps; + +/** + * Captures the {@link EntitySpec} configuration defined in YAML. + * + * This class does not parse that output; it just stores it. + */ +public class EntitySpecConfiguration { + + @SuppressWarnings("unused") + private static final Logger LOG = LoggerFactory.getLogger(EntitySpecConfiguration.class); + + private Map<String, Object> specConfiguration; + + public EntitySpecConfiguration(Map<String, ?> specConfiguration) { + this.specConfiguration = Maps.newHashMap(checkNotNull(specConfiguration, "specConfiguration")); + } + + public Map<String, Object> getSpecConfiguration() { + return specConfiguration; + } + + /** + * Allows BrooklynComponentTemplateResolver to traverse the configuration and resolve any entity specs + */ + public void setSpecConfiguration(Map<String, Object> specConfiguration) { + this.specConfiguration = specConfiguration; + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e406d1ad/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/BrooklynServiceTypeResolver.java ---------------------------------------------------------------------- diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/BrooklynServiceTypeResolver.java b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/BrooklynServiceTypeResolver.java new file mode 100644 index 0000000..de9f36f --- /dev/null +++ b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/BrooklynServiceTypeResolver.java @@ -0,0 +1,71 @@ +/* + * 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.camp.brooklyn.spi.creation.service; + +import io.brooklyn.camp.spi.PlatformComponentTemplate; + +import javax.annotation.Nullable; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.brooklyn.camp.brooklyn.spi.creation.BrooklynComponentTemplateResolver; +import org.apache.brooklyn.camp.brooklyn.spi.creation.BrooklynEntityDecorationResolver; +import org.apache.brooklyn.catalog.CatalogItem; +import brooklyn.catalog.internal.CatalogUtils; +import brooklyn.entity.Entity; +import brooklyn.entity.proxying.EntitySpec; +import brooklyn.util.text.Strings; + +/** + * This converts {@link PlatformComponentTemplate} instances whose type is prefixed {@code brooklyn:} + * to Brooklyn {@link EntitySpec} instances. + */ +public class BrooklynServiceTypeResolver implements ServiceTypeResolver { + + private static final Logger LOG = LoggerFactory.getLogger(ServiceTypeResolver.class); + + @Override + public String getTypePrefix() { return DEFAULT_TYPE_PREFIX; } + + @Override + public String getBrooklynType(String serviceType) { + String type = Strings.removeFromStart(serviceType, getTypePrefix() + ":").trim(); + if (type == null) return null; + return type; + } + + @Nullable + @Override + public CatalogItem<Entity,EntitySpec<?>> getCatalogItem(BrooklynComponentTemplateResolver resolver, String serviceType) { + String type = getBrooklynType(serviceType); + if (type != null) { + return CatalogUtils.getCatalogItemOptionalVersion(resolver.getManagementContext(), Entity.class, type); + } else { + return null; + } + } + + @Override + public <T extends Entity> void decorateSpec(BrooklynComponentTemplateResolver resolver, EntitySpec<T> spec) { + new BrooklynEntityDecorationResolver.PolicySpecResolver(resolver.getYamlLoader()).decorate(spec, resolver.getAttrs()); + new BrooklynEntityDecorationResolver.EnricherSpecResolver(resolver.getYamlLoader()).decorate(spec, resolver.getAttrs()); + new BrooklynEntityDecorationResolver.InitializerResolver(resolver.getYamlLoader()).decorate(spec, resolver.getAttrs()); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e406d1ad/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/CatalogServiceTypeResolver.java ---------------------------------------------------------------------- diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/CatalogServiceTypeResolver.java b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/CatalogServiceTypeResolver.java new file mode 100644 index 0000000..e569183 --- /dev/null +++ b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/CatalogServiceTypeResolver.java @@ -0,0 +1,78 @@ +/* + * 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.camp.brooklyn.spi.creation.service; + +import io.brooklyn.camp.spi.PlatformComponentTemplate; + +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.entity.basic.VanillaSoftwareProcess; +import brooklyn.entity.brooklynnode.BrooklynNode; +import brooklyn.entity.group.DynamicCluster; +import brooklyn.entity.group.DynamicRegionsFabric; +import brooklyn.entity.java.VanillaJavaApp; +import brooklyn.entity.proxying.EntitySpec; + +import com.google.common.base.CaseFormat; +import com.google.common.base.Converter; +import com.google.common.collect.ImmutableMap; + +/** + * This converts {@link PlatformComponentTemplate} instances whose type is prefixed {@code catalog:} + * to Brooklyn {@link EntitySpec} instances. + */ +public class CatalogServiceTypeResolver extends BrooklynServiceTypeResolver { + + private static final Logger LOG = LoggerFactory.getLogger(ServiceTypeResolver.class); + + // TODO currently a hardcoded list of aliases; would like that to come from mgmt somehow + private static final Map<String, String> CATALOG_TYPES = ImmutableMap.<String, String>builder() + .put("cluster", DynamicCluster.class.getName()) + .put("fabric", DynamicRegionsFabric.class.getName()) + .put("vanilla", VanillaSoftwareProcess.class.getName()) + .put("software-process", VanillaSoftwareProcess.class.getName()) + .put("java-app", VanillaJavaApp.class.getName()) + .put("brooklyn-node", BrooklynNode.class.getName()) + .put("web-app-cluster","brooklyn.entity.webapp.ControlledDynamicWebAppCluster") + .build(); + + // Allow catalog-type or CatalogType as service type string + private static final Converter<String, String> FMT = CaseFormat.LOWER_HYPHEN.converterTo(CaseFormat.UPPER_CAMEL); + + @Override + public String getTypePrefix() { return "catalog"; } + + @Override + public String getBrooklynType(String serviceType) { + String type = super.getBrooklynType(serviceType); + if (type == null) return null; + + for (String check : CATALOG_TYPES.keySet()) { + if (type.equals(check) || type.equals(FMT.convert(check))) { + return CATALOG_TYPES.get(check); + } + } + + return type; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e406d1ad/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/ChefServiceTypeResolver.java ---------------------------------------------------------------------- diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/ChefServiceTypeResolver.java b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/ChefServiceTypeResolver.java new file mode 100644 index 0000000..f5d135f --- /dev/null +++ b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/ChefServiceTypeResolver.java @@ -0,0 +1,61 @@ +/* + * 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.camp.brooklyn.spi.creation.service; + +import io.brooklyn.camp.spi.PlatformComponentTemplate; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.brooklyn.camp.brooklyn.spi.creation.BrooklynComponentTemplateResolver; +import org.apache.brooklyn.catalog.CatalogItem; +import brooklyn.entity.Entity; +import brooklyn.entity.chef.ChefConfig; +import brooklyn.entity.chef.ChefEntity; +import brooklyn.entity.proxying.EntitySpec; +import brooklyn.util.text.Strings; + +/** + * This converts {@link PlatformComponentTemplate} instances whose type is prefixed {@code chef:} + * to Brooklyn {@link EntitySpec} instances. + */ +public class ChefServiceTypeResolver extends BrooklynServiceTypeResolver { + + private static final Logger LOG = LoggerFactory.getLogger(ServiceTypeResolver.class); + + @Override + public String getTypePrefix() { return "chef"; } + + @Override + public String getBrooklynType(String serviceType) { + return ChefEntity.class.getName(); + } + + /** Chef items are not in the catalog. */ + @Override + public CatalogItem<Entity, EntitySpec<?>> getCatalogItem(BrooklynComponentTemplateResolver resolver, String serviceType) { + return null; + } + + @Override + public <T extends Entity> void decorateSpec(BrooklynComponentTemplateResolver resolver, EntitySpec<T> spec) { + spec.configure(ChefConfig.CHEF_COOKBOOK_PRIMARY_NAME, Strings.removeFromStart(resolver.getDeclaredType(), "chef:")); + super.decorateSpec(resolver, spec); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e406d1ad/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/JavaServiceTypeResolver.java ---------------------------------------------------------------------- diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/JavaServiceTypeResolver.java b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/JavaServiceTypeResolver.java new file mode 100644 index 0000000..8670723 --- /dev/null +++ b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/JavaServiceTypeResolver.java @@ -0,0 +1,39 @@ +/* + * 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.camp.brooklyn.spi.creation.service; + +import io.brooklyn.camp.spi.PlatformComponentTemplate; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.entity.proxying.EntitySpec; + +/** + * This converts {@link PlatformComponentTemplate} instances whose type is prefixed {@code java:} + * to Brooklyn {@link EntitySpec} instances. + */ +public class JavaServiceTypeResolver extends BrooklynServiceTypeResolver { + + private static final Logger LOG = LoggerFactory.getLogger(ServiceTypeResolver.class); + + @Override + public String getTypePrefix() { return "java"; } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e406d1ad/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/ServiceTypeResolver.java ---------------------------------------------------------------------- diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/ServiceTypeResolver.java b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/ServiceTypeResolver.java new file mode 100644 index 0000000..734352b --- /dev/null +++ b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/ServiceTypeResolver.java @@ -0,0 +1,73 @@ +/* + * 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.camp.brooklyn.spi.creation.service; + +import java.util.ServiceLoader; + +import org.apache.brooklyn.camp.brooklyn.spi.creation.BrooklynComponentTemplateResolver; +import org.apache.brooklyn.catalog.CatalogItem; +import brooklyn.entity.Entity; +import brooklyn.entity.proxying.EntitySpec; + +/** + * Resolves and decorates {@link EntitySpec entity specifications} based on the {@code serviceType} in a template. + * <p> + * The {@link #getTypePrefix()} method returns a string that should match the beginning of the + * service type. The resolver implementation will use the rest of the service type information + * to create and decorate an approprate {@link EntitySpec entity}. + * <p> + * The resolvers are loaded using the {@link ServiceLoader} mechanism, allowing external libraries + * to add extra service type implementations that will be picked up at runtime. + * + * @see BrooklynServiceTypeResolver + * @see ChefServiceTypeResolver + */ +public interface ServiceTypeResolver { + + String DEFAULT_TYPE_PREFIX = "brooklyn"; + + /** + * The service type prefix the resolver is responsible for. + */ + String getTypePrefix(); + + /** + * The name of the Java type that Brooklyn will instantiate to create the + * service. This can be generated from parts of the service type information + * or may be a fixed value. + */ + String getBrooklynType(String serviceType); + + /** + * Returns the {@link CatalogItem} if there is one for the given type. + * <p> + * If no type, callers should fall back to default classloading. + */ + CatalogItem<Entity, EntitySpec<?>> getCatalogItem(BrooklynComponentTemplateResolver resolver, String serviceType); + + /** + * Takes the provided {@link EntitySpec} and decorates it appropriately for the service type. + * <p> + * This includes setting configuration and adding policies, enrichers and initializers. + * + * @see BrooklynServiceTypeResolver#decorateSpec(BrooklynComponentTemplateResolver, EntitySpec) + */ + <T extends Entity> void decorateSpec(BrooklynComponentTemplateResolver resolver, EntitySpec<T> spec); + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e406d1ad/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/BrooklynDslDeferredSupplier.java ---------------------------------------------------------------------- diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/BrooklynDslDeferredSupplier.java b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/BrooklynDslDeferredSupplier.java new file mode 100644 index 0000000..9b27607 --- /dev/null +++ b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/BrooklynDslDeferredSupplier.java @@ -0,0 +1,99 @@ +/* + * 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.camp.brooklyn.spi.dsl; + +import java.io.Serializable; + +import io.brooklyn.camp.spi.Assembly; +import io.brooklyn.camp.spi.AssemblyTemplate; +import io.brooklyn.camp.spi.resolve.interpret.PlanInterpretationNode; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.config.ConfigKey; +import brooklyn.entity.Entity; +import brooklyn.entity.basic.Entities; +import brooklyn.entity.basic.EntityInternal; +import brooklyn.entity.effector.EffectorTasks; +import brooklyn.management.Task; +import brooklyn.management.TaskFactory; +import brooklyn.util.exceptions.Exceptions; +import brooklyn.util.task.DeferredSupplier; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** provide an object suitable to resolve chained invocations in a parsed YAML / Deployment Plan DSL, + * which also implements {@link DeferredSupplier} so that they can be resolved when needed + * (e.g. when entity-lookup and execution contexts are available). + * <p> + * implementations of this abstract class are expected to be immutable, + * as instances must support usage in multiple {@link Assembly} instances + * created from a single {@link AssemblyTemplate} + * <p> + * subclasses which return a deferred value are typically only + * resolvable in the context of a {@link Task} on an {@link Entity}; + * these should be only used as the value of a {@link ConfigKey} set in the YAML, + * and should not accessed until after the components / entities are created + * and are being started. + * (TODO the precise semantics of this are under development.) + * <p> + **/ +public abstract class BrooklynDslDeferredSupplier<T> implements DeferredSupplier<T>, TaskFactory<Task<T>>, Serializable { + + private static final long serialVersionUID = -8789624905412198233L; + + private static final Logger log = LoggerFactory.getLogger(BrooklynDslDeferredSupplier.class); + + // TODO json of this object should *be* this, not wrapped this ($brooklyn:literal is a bit of a hack, though it might work!) + @JsonInclude + @JsonProperty(value="$brooklyn:literal") + // currently marked transient because it's only needed for logging + private transient Object dsl = "(gone)"; + + public BrooklynDslDeferredSupplier() { + PlanInterpretationNode sourceNode = BrooklynDslInterpreter.currentNode(); + dsl = sourceNode!=null ? sourceNode.getOriginalValue() : null; + } + + /** returns the current entity; for use in implementations of {@link #get()} */ + protected final static EntityInternal entity() { + // rely on implicit ThreadLocal for now + return (EntityInternal) EffectorTasks.findEntity(); + } + + @Override + public final synchronized T get() { + try { + if (log.isDebugEnabled()) + log.debug("Queuing task to resolve "+dsl); + T result = Entities.submit(entity(), newTask()).get(); + if (log.isDebugEnabled()) + log.debug("Resolved "+result+" from "+dsl); + return result; + } catch (Exception e) { + throw Exceptions.propagate(e); + } + } + + @Override + public abstract Task<T> newTask(); + +}
