result, Class> type) {
+ result.addAll(findConfigBagConstructorSerializers(type));
+ result.addAll(findConfigMapConstructorSerializersWithInheritance(type));
+ }
+
+}
diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java
new file mode 100644
index 0000000000..291e27764a
--- /dev/null
+++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java
@@ -0,0 +1,532 @@
+/*
+ * 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.yoml;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.api.mgmt.classloading.BrooklynClassLoadingContext;
+import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry;
+import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry.RegisteredTypeKind;
+import org.apache.brooklyn.api.typereg.RegisteredType;
+import org.apache.brooklyn.api.typereg.RegisteredType.TypeImplementationPlan;
+import org.apache.brooklyn.api.typereg.RegisteredTypeLoadingContext;
+import org.apache.brooklyn.camp.yoml.YomlTypePlanTransformer.YomlTypeImplementationPlan;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.catalog.internal.CatalogUtils;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.mgmt.classloading.JavaBrooklynClassLoadingContext;
+import org.apache.brooklyn.core.typereg.BasicRegisteredType;
+import org.apache.brooklyn.core.typereg.RegisteredTypeLoadingContexts;
+import org.apache.brooklyn.core.typereg.RegisteredTypes;
+import org.apache.brooklyn.util.collections.MutableSet;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.guava.Maybe;
+import org.apache.brooklyn.util.javalang.Boxing;
+import org.apache.brooklyn.util.text.Strings;
+import org.apache.brooklyn.util.yoml.Yoml;
+import org.apache.brooklyn.util.yoml.YomlSerializer;
+import org.apache.brooklyn.util.yoml.YomlTypeRegistry;
+import org.apache.brooklyn.util.yoml.internal.ConstructionInstructions;
+import org.apache.brooklyn.util.yoml.internal.YomlContext;
+import org.apache.brooklyn.util.yoml.internal.YomlUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.reflect.TypeToken;
+
+/**
+ * Provides a bridge so YOML's type registry can find things in the Brooklyn type registry.
+ *
+ * There are several subtleties in the loading strategy this registry should use depending
+ * when YOML is asking for something to be loaded. This is done through various
+ * {@link RegisteredTypeLoadingContext} instances. That class is able to do things including:
+ *
+ * (a) specify a supertype to use for filtering when finding a type
+ * (b) specify a class loading context (eg based on context's catalog item id / definition)
+ * (c) specify a set of encountered types to prevent looping and resolve a duplicate name
+ * as a class if it has already been resolved as a YOML item
+ * (eg yoml item java.pack.Foo declares its type as java.pack.Foo to mean to load it as a class)
+ * or simply bail out if there is a recursive definition (eg require java:java.pack.Foo in the above case)
+ * (d) specify a construction instruction specified by wrapper/subtype definitions
+ *
+ * Which of these should apply depends on the calling context. The following situations apply:
+ *
+ * (1) when YOML makes the first call to resolve the type at "/", we should apply (a) and (b) supplied by the user;
+ * (c) and (d) should be empty but no harm in applying them (and it will make recursive work easier)
+ * (2) if in the course of that call this registry calls to YOML to evaluate a plan definition of a supertype,
+ * it should act like (1) except use the supertype's loader; ie apply everything except (b)
+ * (3) if YOML makes a subsequent call to resolve a type at a different path, it should apply only (b),
+ * not any of the others
+ *
+ * See {@link #getTypeContextFor(YomlContext)} and usages.
+ *
+ *
+ *
+ * More details on library loading. Consider for instance:
+ *
+ * - id: x
+ * item: { type: X }
+ * - id: x2
+ * item: { type: x }
+ * - id: cluster-x
+ * item: { type: cluster, children: [ { type: x }, { type: X } ] }
+ *
+ * And assume these items declare different libraries.
+ *
+ * We *need* libraries to be used when resolving the reference to a parent java type (e.g. x's parent type X).
+ *
+ * We *don't* want libraries to be used transitively, e.g. when x2 or cluster-x refers to x,
+ * only x's libraries apply to loading X, not x2's libraries.
+ *
+ * We *may* want libraries to be used when resolving references to types in a plan besides the parent type;
+ * e.g. cluster-x's libraries will be needed when resolving its reference to it's child of declared type X.
+ * But we might *NOT* want to support that, and could instead require that any type referenced elsewhere
+ * be defined as an explicit YAML type in the registry. This will simplify our lives as we will force all
+ * java objects to be explicitly defined as a type in the registry. But it means we won't be able to parse
+ * current plans so for the moment this is deferred, and we'd want to go through a "warning" cycle when
+ * applying.
+ *
+ * (In that last case we could even be stricter and say that any yaml types
+ * should have a simple single yaml-java bridge eg using a JavaClassNameTypeImplementationPlan
+ * to facilitate reverse lookup.)
+ *
+ * The defaultLoadingContext is used for the first and third cases above.
+ * The second case is handled by calling back to the registry with a limited context.
+ * (Note it will require some work to be able to distinguish between the third case and the first,
+ * as we don't currently have that contextual information when methods here are called.)
+ *
+ *
+ *
+ * One bit of ugly remains:
+ *
+ * If we install v1 then v2, cluster-x:1 will pick up x:2.
+ * We could change this:
+ * - by encouraging explicit `type: x:1` in the definition of cluster-x
+ * - by allowing `type: x:.` version shorthand, where `.` means the same version as the calling type
+ * - by recording locally-preferred types (aka "friends") on a registered type,
+ * and looking at those friends first; this would be nice in that we could allow private friends
+ *
+ * Yoml.read(...) can take a special "top-level type extensions" in order to find friends,
+ * and/or a special "top-level libraries" in order to resolve types in the first instance.
+ * These would *not* be passed when resolving a found type.
+ */
+public class BrooklynYomlTypeRegistry implements YomlTypeRegistry {
+
+ private static final Logger log = LoggerFactory.getLogger(BrooklynYomlTypeRegistry.class);
+
+ private static final String JAVA_PREFIX = "java:";
+
+ @SuppressWarnings("serial")
+ static ConfigKey> CACHED_SERIALIZERS = ConfigKeys.newConfigKey(new TypeToken>() {}, "yoml.type.serializers",
+ "Serializers found for a registered type");
+
+ private ManagementContext mgmt;
+
+ private RegisteredTypeLoadingContext rootLoadingContext;
+
+ public BrooklynYomlTypeRegistry(@Nonnull ManagementContext mgmt, @Nonnull RegisteredTypeLoadingContext rootLoadingContext) {
+ this.mgmt = mgmt;
+ this.rootLoadingContext = rootLoadingContext;
+
+ }
+
+ protected BrooklynTypeRegistry registry() {
+ return mgmt.getTypeRegistry();
+ }
+
+ @Override
+ public Maybe newInstanceMaybe(String typeName, Yoml yoml) {
+ return newInstanceMaybe(typeName, yoml, rootLoadingContext);
+ }
+
+ @Override
+ public Maybe newInstanceMaybe(String typeName, Yoml yoml, @Nonnull YomlContext yomlContextOfThisEvaluation) {
+ RegisteredTypeLoadingContext applicableLoadingContext = getTypeContextFor(yomlContextOfThisEvaluation);
+ return newInstanceMaybe(typeName, yoml, applicableLoadingContext);
+ }
+
+ protected RegisteredTypeLoadingContext getTypeContextFor(YomlContext yomlContextOfThisEvaluation) {
+ RegisteredTypeLoadingContext applicableLoadingContext;
+ if (Strings.isBlank(yomlContextOfThisEvaluation.getJsonPath())) {
+ // at root, use everything
+ applicableLoadingContext = rootLoadingContext;
+ } else {
+ // elsewhere the only thing we apply is the loader
+ applicableLoadingContext = RegisteredTypeLoadingContexts.builder().loader(rootLoadingContext.getLoader()).build();
+ }
+
+ if (yomlContextOfThisEvaluation.getConstructionInstruction()!=null) {
+ // also apply any construction instruction we've been given
+ applicableLoadingContext = RegisteredTypeLoadingContexts.builder(applicableLoadingContext)
+ .constructorInstruction(yomlContextOfThisEvaluation.getConstructionInstruction()).build();
+ }
+ return applicableLoadingContext;
+ }
+
+ public Maybe newInstanceMaybe(String typeName, Yoml yoml, @Nonnull RegisteredTypeLoadingContext typeContext) {
+ // yoml may be null, for java type lookups, but we could potentially get rid of that call path
+
+ RegisteredType typeR = registry().get(typeName, typeContext);
+
+ if (typeR!=null) {
+ boolean seenType = typeContext.getAlreadyEncounteredTypes().contains(typeName);
+
+ if (!seenType) {
+ // instantiate the parent type
+
+ // keep everything (supertype constraint, encountered types, constructor instruction)
+ // apart from loader info -- loader should be from the type found here
+ RegisteredTypeLoadingContexts.Builder nextContext = RegisteredTypeLoadingContexts.builder(typeContext);
+ nextContext.addEncounteredTypes(typeName);
+ // reset the loader (pretty sure this is right -Alex)
+ // the create call will attach the loader of typeR
+ nextContext.loader(null);
+
+ return Maybe.of((Object) registry().create(typeR, nextContext.build(), null));
+
+ } else {
+ // circular reference means load java, below
+ }
+ } else {
+ // type not found means load java, below
+ }
+
+ Maybe> t = null;
+
+ Exception e = null;
+ try {
+ t = getJavaTypeInternal(typeName, typeContext);
+ } catch (Exception ee) {
+ Exceptions.propagateIfFatal(ee);
+ e = ee;
+ }
+ if (t.isAbsent()) {
+ // generally doesn't come here; it gets filtered by getJavaTypeMaybe
+ if (e==null) e = ((Maybe.Absent>)t).getException();
+ return Maybe.absent("Neither the type registry nor the classpath/libraries could load type "+typeName+
+ (e!=null && !Exceptions.isRootBoringClassNotFound(e, typeName) ? ": "+Exceptions.collapseText(e) : ""));
+ }
+
+ try {
+ return ConstructionInstructions.Factory.newDefault(t.get(), typeContext.getConstructorInstruction()).create();
+ } catch (Exception e2) {
+ return Maybe.absent("Error instantiating type "+typeName+": "+Exceptions.collapseText(e2));
+ }
+ }
+
+ @Override
+ public Object newInstance(String typeN, Yoml yoml) {
+ return newInstanceMaybe(typeN, yoml).orNull();
+ }
+
+ static class YomlClassNotFoundException extends RuntimeException {
+ private static final long serialVersionUID = 5946251753146668070L;
+ public YomlClassNotFoundException(String message, Throwable cause) {
+ super(message, cause);
+ }
+ }
+
+ /*
+ * There is also some complexity around the java type and the supertypes.
+ * For context, the latter is needed so callers can filter appropriately.
+ * The former is needed so that serializers can filter appropriately
+ * (the java type is not very extensively used and could be changed to use the supertypes set,
+ * but we have the same problem for both).
+ *
+ * The issue is that when adding to the catalog the caller likely supplies
+ * only the yaml, not a statement of supertypes. So we have to try to figure out
+ * the supertypes. We can do this (1) on-load, when it is added, or (2) lazy, when it is accessed.
+ * We're going to do (1), and persisting the results, which means we instantiate
+ * each item once on addition: this serves as a useful validation, but it does disallow
+ * forward references (ie referenced types must be declared first; for "templates" we could skip this),
+ * but we avoid re-instantiation on rebind (and the ordering issues that can arise there).
+ *
+ * Option (2) while it has some attractions it makes the API more entangled between RegisteredType and the transformer,
+ * and risks odd errors depending when the lazy evaluation occurs vis-a-vis dependent types.
+ */
+ @Override
+ public Maybe> getJavaTypeMaybe(String typeName, YomlContext context) {
+ if (typeName==null) return Maybe.absent("null type");
+ return getJavaTypeInternal(typeName, getTypeContextFor(context));
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ protected static Maybe> maybeClass(Class> clazz) {
+ // restrict unchecked wildcard generic warning/suppression to here
+ return (Maybe) Maybe.of(clazz);
+ }
+
+ protected Maybe> getJavaTypeInternal(String typeName, RegisteredTypeLoadingContext context) {
+ RegisteredType type = registry().get(typeName, context);
+ if (type!=null && context!=null && !context.getAlreadyEncounteredTypes().contains(type.getId())) {
+ return getJavaTypeInternal(type, context);
+ }
+
+ // try to load it wrt context
+ Class> result = null;
+
+ // strip generics for the purposes here
+ if (typeName.indexOf('<')>0) typeName = typeName.substring(0, typeName.indexOf('<'));
+
+ if (result==null) result = Boxing.boxedType(Boxing.getPrimitiveType(typeName).orNull());
+ if (result==null && YomlUtils.TYPE_STRING.equals(typeName)) result = String.class;
+
+ if (result!=null) return maybeClass(result);
+
+ // currently we accept but don't require the 'java:' prefix
+ boolean isJava = typeName.startsWith(JAVA_PREFIX);
+ typeName = Strings.removeFromStart(typeName, JAVA_PREFIX);
+ BrooklynClassLoadingContext loader = null;
+ if (context!=null && context.getLoader()!=null)
+ loader = context.getLoader();
+ else
+ loader = JavaBrooklynClassLoadingContext.create(mgmt);
+
+ Maybe> resultM = loader.tryLoadClass(typeName);
+ if (resultM.isPresent()) return resultM;
+ if (isJava) return resultM; // if java was explicit, give the java load error
+ RuntimeException e = ((Maybe.Absent>)resultM).getException();
+ return Maybe.absent("Neither the type registry nor the classpath/libraries could find type "+typeName+
+ (e!=null && !Exceptions.isRootBoringClassNotFound(e, typeName) ? ": "+Exceptions.collapseText(e) : ""));
+ }
+
+ protected Maybe> getJavaTypeInternal(RegisteredType type, RegisteredTypeLoadingContext context) {
+ {
+ Class> result = RegisteredTypes.peekActualJavaType(type);
+ if (result!=null) return maybeClass(result);
+ }
+
+ String declaredPrimarySuperTypeName = null;
+
+ if (type.getPlan() instanceof YomlTypeImplementationPlan) {
+ declaredPrimarySuperTypeName = ((YomlTypeImplementationPlan)type.getPlan()).javaType;
+ }
+ if (declaredPrimarySuperTypeName==null) {
+ log.warn("Primary java super type not declared for "+type+"; it should have been specified/inferred at definition time; will try to infer it now");
+ // first look at plan, for a `type` block
+ if (type.getPlan() instanceof YomlTypeImplementationPlan) {
+ Maybe> map = RegisteredTypes.getAsYamlMap(type.getPlan().getPlanData());
+ if (map.isPresent()) declaredPrimarySuperTypeName = Strings.toString(map.get().get("type"));
+ }
+ }
+ if (declaredPrimarySuperTypeName==null) {
+ // could look at declared super types, instantiate, and take the lowest common if found
+ // (but the above is recommended and the preloading below sufficient)
+ }
+
+ Maybe> result = Maybe.absent("Unable to find java supertype for "+type);
+
+ if (declaredPrimarySuperTypeName!=null) {
+ // when looking at supertypes we reset the loader to use loaders for the type,
+ // note the traversal in encountered types, and leave the rest (eg supertype restriction) as was
+ RegisteredTypeLoadingContext newContext = RegisteredTypeLoadingContexts.builder(context)
+ .loader( CatalogUtils.newClassLoadingContext(mgmt, type) )
+ .addEncounteredTypes(type.getId())
+ .build();
+
+ result = getJavaTypeInternal(declaredPrimarySuperTypeName, newContext);
+ }
+
+ if (result.isAbsent()) {
+ RegisteredTypeLoadingContext newContext = RegisteredTypeLoadingContexts.alreadyEncountered(context.getAlreadyEncounteredTypes());
+ // failing that, instantiate it (caching it as type object to prevent recursive lookups?)
+ log.warn("Preloading required to determine type for "+type);
+ Maybe m = newInstanceMaybe(type.getId(), null, newContext);
+ log.info("Preloading completed, got "+m+" for "+type);
+ if (m.isPresent()) result = maybeClass(m.get().getClass());
+ else {
+ // if declared java type, prefer that error, otherwise give error from above
+ if (declaredPrimarySuperTypeName==null) {
+ result = Maybe.absent(new IllegalStateException("Unable to find java supertype declared on "+type+" and unable to instantiate",
+ ((Maybe.Absent>)result).getException()));
+ }
+ }
+ }
+
+ if (result.isPresent()) {
+ RegisteredTypes.cacheActualJavaType(type, result.get());
+ }
+ return result;
+ }
+
+ @Override
+ public String getTypeName(Object obj) {
+ return getTypeNameOfClass(obj.getClass());
+ }
+
+ public static Set WARNS = MutableSet.of(
+ // don't warn on this base class
+ JAVA_PREFIX+Object.class.getName() );
+
+ @Override
+ public String getTypeNameOfClass(Class type) {
+ if (type==null) return null;
+
+ String defaultTypeName = getDefaultTypeNameOfClass(type);
+ String cleanedTypeName = Strings.removeFromStart(getDefaultTypeNameOfClass(type), JAVA_PREFIX);
+
+ // the code below may be a bottleneck; if so, we should cache or something more efficient
+
+ Set types = MutableSet.of();
+ // look in catalog for something where plan matches and consists only of type
+ for (RegisteredType rt: mgmt.getTypeRegistry().getAll()) {
+ if (!(rt.getPlan() instanceof YomlTypeImplementationPlan)) continue;
+ if (((YomlTypeImplementationPlan)rt.getPlan()).javaType==null) continue;
+ if (!((YomlTypeImplementationPlan)rt.getPlan()).javaType.equals(cleanedTypeName)) continue;
+ if (rt.getPlan().getPlanData()==null) types.add(rt);
+ // are there ever plans we want to permit, eg just defining serializers?
+ // (if so check the plan here)
+ }
+ if (types.size()==1) return Iterables.getOnlyElement(types).getSymbolicName();
+ if (types.size()>1) {
+ if (WARNS.add(type.getName()))
+ log.warn("Multiple registered types for "+type+"; picking one arbitrarily");
+ return types.iterator().next().getId();
+ }
+
+ boolean isJava = defaultTypeName.startsWith(JAVA_PREFIX);
+ if (isJava && WARNS.add(type.getName()))
+ log.warn("Returning default for type name of "+type+"; catalog entry should be supplied");
+
+ return defaultTypeName;
+ }
+
+ protected String getDefaultTypeNameOfClass(Class type) {
+ Maybe primitive = Boxing.getPrimitiveName(type);
+ if (primitive.isPresent()) return primitive.get();
+ if (String.class.equals(type)) return "string";
+ // map and list handled by those serializers
+ return JAVA_PREFIX+type.getName();
+ }
+
+ @Override
+ public Iterable getSerializersForType(String typeName, YomlContext yomlContext) {
+ Set result = MutableSet.of();
+ // TODO add root loader?
+ collectSerializers(typeName, getTypeContextFor(yomlContext), result, MutableSet.of());
+ return result;
+ }
+
+ protected void collectSerializers(Object type, RegisteredTypeLoadingContext context, Collection result, Set typesVisited) {
+ if (type==null) return;
+ if (type instanceof String) {
+ // convert string to registered type or class
+ Object typeR = registry().get((String)type);
+ if (typeR==null) {
+ typeR = getJavaTypeInternal((String)type, context).orNull();
+ }
+ if (typeR==null) {
+ // will this ever happen in normal operations?
+ log.warn("Could not find '"+type+" when collecting serializers");
+ return;
+ }
+ type = typeR;
+ }
+ boolean canUpdateCache = typesVisited.isEmpty();
+ if (!typesVisited.add(type)) return; // already seen
+ Set supers = MutableSet.of();
+ if (type instanceof RegisteredType) {
+ List serializers = ((BasicRegisteredType)type).getCache().get(CACHED_SERIALIZERS);
+ if (serializers!=null) {
+ canUpdateCache = false;
+ result.addAll(serializers);
+ } else {
+ // TODO don't cache if it's a snapshot version
+ // (also should invalidate any subtypes, but actually any subtypes of snapshots
+ // should also be snapshots -- that should be enforced!)
+// if (isSnapshot( ((RegisteredType)type).getVersion() )) updateCache = false;
+
+ // apply serializers from this RT
+ TypeImplementationPlan plan = ((RegisteredType)type).getPlan();
+ if (plan instanceof YomlTypeImplementationPlan) {
+ result.addAll( ((YomlTypeImplementationPlan)plan).serializers );
+ }
+
+ // loop over supertypes for serializers declared there (unless we introduce an option to suppress/limit)
+ supers.addAll(((RegisteredType) type).getSuperTypes());
+ }
+ } else if (type instanceof Class) {
+ result.addAll(new BrooklynYomlAnnotations().findSerializerAnnotations((Class>)type, false));
+// // could look up the type? but we should be calling this normally with the RT if we have one so probably not necessary
+// // and could recurse through superclasses and interfaces -- but the above is a better place to do that if needed
+// String name = getTypeNameOfClass((Class>)type);
+// if (name.startsWith(JAVA_PREFIX)) {
+// find...
+//// supers.add(((Class>) type).getSuperclass());
+//// supers.addAll(Arrays.asList(((Class>) type).getInterfaces()));
+// }
+ } else {
+ throw new IllegalStateException("Illegal supertype entry "+type+", visiting "+typesVisited);
+ }
+ for (Object s: supers) {
+ RegisteredTypeLoadingContext unconstrainedSupertypeContext = RegisteredTypeLoadingContexts.builder(context)
+ .expectedSuperType(null).build();
+ collectSerializers(s, unconstrainedSupertypeContext, result, typesVisited);
+ }
+ if (canUpdateCache) {
+ if (type instanceof RegisteredType) {
+ ((BasicRegisteredType)type).getCache().put(CACHED_SERIALIZERS, ImmutableList.copyOf(result));
+ } else {
+ // could use static weak cache map on classes? if so also update above
+ }
+ }
+ }
+
+ public static RegisteredType newYomlRegisteredType(RegisteredTypeKind kind,
+ String symbolicName, String version, String planData,
+ Class> javaConcreteType,
+ Iterable extends Object> addlSuperTypesAsClassOrRegisteredType,
+ Iterable serializers) {
+
+ YomlTypeImplementationPlan plan = new YomlTypeImplementationPlan(planData, javaConcreteType, serializers);
+ RegisteredType result = kind==RegisteredTypeKind.SPEC ? RegisteredTypes.spec(symbolicName, version, plan) : RegisteredTypes.bean(symbolicName, version, plan);
+ RegisteredTypes.addSuperType(result, javaConcreteType);
+ RegisteredTypes.addSuperTypes(result, addlSuperTypesAsClassOrRegisteredType);
+ return result;
+ }
+
+ /** null symbolic name means to take it from annotations or the class name */
+ public static RegisteredType newYomlRegisteredType(RegisteredTypeKind kind, @Nullable String symbolicName, String version, Class> clazz) {
+ Set names = new BrooklynYomlAnnotations().findTypeNamesFromAnnotations(clazz, symbolicName, false);
+
+ Set serializers = new BrooklynYomlAnnotations().findSerializerAnnotations(clazz, false);
+
+ RegisteredType type = BrooklynYomlTypeRegistry.newYomlRegisteredType(kind,
+ // symbolicName, version,
+ names.iterator().next(), version,
+ // planData - null means just use the java type (could have done this earlier),
+ null,
+ // javaConcreteType, superTypesAsClassOrRegisteredType, serializers)
+ clazz, Arrays.asList(clazz), serializers);
+ type = RegisteredTypes.addAliases(type, names);
+ return type;
+ }
+
+}
diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java
new file mode 100644
index 0000000000..0e4ee33119
--- /dev/null
+++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java
@@ -0,0 +1,235 @@
+/*
+ * 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.yoml;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.apache.brooklyn.api.internal.AbstractBrooklynObjectSpec;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.api.typereg.RegisteredType;
+import org.apache.brooklyn.api.typereg.RegisteredTypeLoadingContext;
+import org.apache.brooklyn.camp.brooklyn.spi.dsl.BrooklynDslInterpreter;
+import org.apache.brooklyn.camp.spi.resolve.PlanInterpreter;
+import org.apache.brooklyn.camp.spi.resolve.interpret.PlanInterpretationContext;
+import org.apache.brooklyn.camp.spi.resolve.interpret.PlanInterpretationNode;
+import org.apache.brooklyn.core.catalog.internal.CatalogUtils;
+import org.apache.brooklyn.core.typereg.AbstractFormatSpecificTypeImplementationPlan;
+import org.apache.brooklyn.core.typereg.AbstractTypePlanTransformer;
+import org.apache.brooklyn.core.typereg.RegisteredTypeInfo;
+import org.apache.brooklyn.core.typereg.RegisteredTypeLoadingContexts;
+import org.apache.brooklyn.core.typereg.RegisteredTypes;
+import org.apache.brooklyn.core.typereg.UnsupportedTypePlanException;
+import org.apache.brooklyn.util.collections.MutableList;
+import org.apache.brooklyn.util.collections.MutableSet;
+import org.apache.brooklyn.util.core.task.ValueResolver;
+import org.apache.brooklyn.util.guava.Maybe;
+import org.apache.brooklyn.util.text.Strings;
+import org.apache.brooklyn.util.yoml.Yoml;
+import org.apache.brooklyn.util.yoml.YomlConfig;
+import org.apache.brooklyn.util.yoml.YomlException;
+import org.apache.brooklyn.util.yoml.YomlSerializer;
+import org.apache.brooklyn.util.yoml.internal.ConstructionInstruction;
+import org.apache.brooklyn.util.yoml.internal.ConstructionInstructions;
+import org.apache.brooklyn.util.yoml.internal.YomlConfigs.Builder;
+import org.yaml.snakeyaml.Yaml;
+
+import com.google.common.annotations.Beta;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * Makes it possible for Brooklyn to resolve YOML items,
+ * both types registered with the system using YOML
+ * and plans in the YOML format (sent here as unregistered types),
+ * and supporting any objects and special handling for "spec" objects.
+ *
+ * DONE
+ * - test programmatic addition and parse (beans) with manual objects
+ * - figure out supertypes and use that to determine java type
+ * - attach custom serializers (on plan)
+ * - test serializers
+ * - support serializers by annotation
+ * - set serializers when adding to catalog and test
+ * - $brooklyn:object-yoml:
+ * - support $brooklyn DSL in yoml
+ *
+ * NEXT
+ * - catalog impl supports format
+ *
+ * (initdish can now be made to work)
+ *
+ * THEN
+ * - update docs for above, including $brooklyn:object-yoml, yoml overview
+ * - support specs from yoml
+ * - type registry api, add arbitrary types via yoml, specifying format
+ * - catalog impl in yoml as test?
+ * - type registry persistence
+ * - REST API for deploy accepts specific format, can call yoml (can we test this earlier?)
+ *
+ * AND THEN
+ * - generate its own documentation
+ * - persistence switches to using yoml, warns if any types are not yoml-ized
+ * - yoml allows `constructor: [list]` and `constructor: { mode: static, type: factory, method: newInstance, args: ... }`
+ * and maybe even `constructor: { mode: chain, steps: [ { mode: constructor, type: Foo.Builder }, { mode: method, method: bar, args: [ true ] }, { mode: method, method: build } ] }`
+ * - type access control -- ie restrict who can see what types
+ * - java instantiation access control - ie permission required to access java in custom types (deployed or added to catalog)
+ */
+public class YomlTypePlanTransformer extends AbstractTypePlanTransformer {
+
+ private static final List FORMATS = ImmutableList.of("yoml");
+
+ public static final String FORMAT = FORMATS.get(0);
+
+ public YomlTypePlanTransformer() {
+ super(FORMAT, "YOML Brooklyn syntax", "Standard YOML adapters for Apache Brooklyn including OASIS CAMP");
+ }
+
+ private final static Set IGNORE_SINGLE_KEYS = ImmutableSet.of("name", "version");
+
+ @Override
+ protected double scoreForNullFormat(Object planData, RegisteredType type, RegisteredTypeLoadingContext context) {
+ int score = 0;
+ Maybe> plan = RegisteredTypes.getAsYamlMap(planData);
+ if (plan.isPresent()) {
+ if (plan.get().size()>1 || (plan.get().size()==1 && !IGNORE_SINGLE_KEYS.contains(plan.get().keySet().iterator().next()))) {
+ // weed out obvious bad plans -- in part so that tests pass
+ // TODO in future we should give a tiny score to anything else (once we want to enable this as a popular auto-detetction target)
+// score += 1;
+ // but for now we require at least one recognised keyword
+ }
+ if (plan.get().containsKey("type")) score += 5;
+ if (plan.get().containsKey("services")) score += 2;
+ }
+ if (type instanceof YomlTypeImplementationPlan) score += 100;
+ return (1.0 - 8.0/(score+8));
+ }
+
+ @Override
+ protected double scoreForNonmatchingNonnullFormat(String planFormat, Object planData, RegisteredType type, RegisteredTypeLoadingContext context) {
+ if (FORMATS.contains(planFormat.toLowerCase())) return 0.9;
+ return 0;
+ }
+
+ @Override
+ protected AbstractBrooklynObjectSpec, ?> createSpec(RegisteredType type, RegisteredTypeLoadingContext context) throws Exception {
+ // TODO
+ throw new UnsupportedTypePlanException("YOML doesn't yet support specs");
+ }
+
+ @Override
+ protected Object createBean(RegisteredType type, RegisteredTypeLoadingContext context) throws Exception {
+ Preconditions.checkNotNull(type);
+ Preconditions.checkNotNull(context);
+
+ // add any loaders for this type
+ context = RegisteredTypeLoadingContexts.builder(context)
+ .loader(
+ CatalogUtils.newClassLoadingContext(mgmt, type, context.getLoader()) )
+ .build();
+
+ Yoml y = Yoml.newInstance(newYomlConfig(mgmt, context).build());
+
+ // TODO could cache the parse, could cache the instantiation instructions
+ Object data = type.getPlan().getPlanData();
+
+ Class> expectedSuperType = context.getExpectedJavaSuperType();
+ String expectedSuperTypeName = y.getConfig().getTypeRegistry().getTypeNameOfClass(expectedSuperType);
+
+ Object parsedInput;
+ if (data==null || (data instanceof String)) {
+ if (Strings.isBlank((String)data)) {
+ // blank plan means to use the java type / construction instruction
+ Maybe> javaType = ((BrooklynYomlTypeRegistry) y.getConfig().getTypeRegistry()).getJavaTypeInternal(type, context);
+ ConstructionInstruction constructor = context.getConstructorInstruction();
+ if (javaType.isAbsent() && constructor==null) {
+ return Maybe.absent(new IllegalStateException("Type "+type+" has no plan YAML and error in type", ((Maybe.Absent>)javaType).getException()));
+ }
+
+ Maybe result = ConstructionInstructions.Factory.newDefault(javaType.get(), constructor).create();
+
+ if (result.isAbsent()) {
+ throw new YomlException("Type '"+type+"' has no plan and its java type cannot be instantiated", ((Maybe.Absent>)result).getException());
+ }
+ return result.get();
+ }
+
+ // else we need to parse to json objects, then translate it (below)
+ parsedInput = new Yaml().load((String) data);
+
+ } else {
+ // we do this (supporint non-string and pre-parsed plans) in order to support the YAML DSL
+ // and other limited use cases (see DslYomlObject);
+ // NB this is only for ad hoc plans (code above) and we may deprecate it altogether as soon as we can
+ // get the underlying string in the $brooklyn DSL context
+
+ if (type.getSymbolicName()!=null) {
+ throw new IllegalArgumentException("The implementation plan for '"+type+"' should be a string in order to process as YOML");
+ }
+ parsedInput = data;
+
+ }
+
+ // in either case, now run interpreters (dsl) if it's a m=ap, then translate
+
+ if (parsedInput instanceof Map) {
+ List interpreters = MutableList.of(new BrooklynDslInterpreter());
+ @SuppressWarnings("unchecked")
+ PlanInterpretationNode interpretation = new PlanInterpretationNode(
+ new PlanInterpretationContext((Map)parsedInput, interpreters));
+ parsedInput = interpretation.getNewValue();
+ }
+
+ return y.readFromYamlObject(parsedInput, expectedSuperTypeName);
+ }
+
+ @Beta @VisibleForTesting
+ public static Builder newYomlConfig(@Nonnull ManagementContext mgmt, @Nullable RegisteredTypeLoadingContext context) {
+ if (context==null) context = RegisteredTypeLoadingContexts.any();
+ BrooklynYomlTypeRegistry tr = new BrooklynYomlTypeRegistry(mgmt, context);
+ return YomlConfig.Builder.builder().typeRegistry(tr).
+ serializersPostAddDefaults().
+ // could add any custom global serializers here; but so far these are all linked to types
+ // and collected by tr.collectSerializers(...)
+ coercer(new ValueResolver.ResolvingTypeCoercer());
+ }
+
+ @Override
+ public RegisteredTypeInfo getTypeInfo(RegisteredType type) {
+ // TODO
+ return RegisteredTypeInfo.create(type, this, null, MutableSet.of());
+ }
+
+ static class YomlTypeImplementationPlan extends AbstractFormatSpecificTypeImplementationPlan {
+ final String javaType;
+ final List serializers;
+
+ public YomlTypeImplementationPlan(String planData, Class> javaType, Iterable serializers) {
+ super(FORMATS.get(0), planData);
+ this.javaType = Preconditions.checkNotNull(javaType).getName();
+ this.serializers = MutableList.copyOf(serializers);
+ }
+ }
+}
diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/ConfigBagConstructionWithArgsInstruction.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/ConfigBagConstructionWithArgsInstruction.java
new file mode 100644
index 0000000000..15f82b8a1d
--- /dev/null
+++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/ConfigBagConstructionWithArgsInstruction.java
@@ -0,0 +1,48 @@
+/*
+ * 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.yoml.serializers;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.util.collections.MutableList;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.yoml.internal.ConstructionInstruction;
+import org.apache.brooklyn.util.yoml.internal.ConstructionInstructions.WrappingConstructionInstruction;
+
+import com.google.common.collect.Iterables;
+
+/** Like {@link ConfigKeyMapConstructionWithArgsInstruction} but passing a {@link ConfigBag} as the single argument
+ * to the constructor. */
+public class ConfigBagConstructionWithArgsInstruction extends ConfigKeyMapConstructionWithArgsInstruction {
+
+ public ConfigBagConstructionWithArgsInstruction(Class> type, Map values,
+ WrappingConstructionInstruction optionalOuter, Map> keysByAlias) {
+ super(type, values, optionalOuter, keysByAlias);
+ }
+
+ @Override
+ protected List> combineArguments(List constructorsSoFarOutermostFirst) {
+ return MutableList.of(
+ ConfigBag.newInstance(
+ (Map,?>)Iterables.getOnlyElement( super.combineArguments(constructorsSoFarOutermostFirst) )));
+ }
+
+}
diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/ConfigKeyConstructionInstructions.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/ConfigKeyConstructionInstructions.java
new file mode 100644
index 0000000000..8800fdaa1c
--- /dev/null
+++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/ConfigKeyConstructionInstructions.java
@@ -0,0 +1,48 @@
+/*
+ * 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.yoml.serializers;
+
+import java.util.Map;
+
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.util.yoml.internal.ConstructionInstruction;
+import org.apache.brooklyn.util.yoml.internal.ConstructionInstructions.WrappingConstructionInstruction;
+
+public class ConfigKeyConstructionInstructions {
+
+ /** As the others, but expecting a one-arg constructor which takes a config map;
+ * this will deduce the appropriate values by looking at
+ * the inheritance declarations at the keys and at the values at the outer and inner constructor instructions. */
+ public static ConstructionInstruction newUsingConfigKeyMapConstructor(Class> type,
+ Map values,
+ ConstructionInstruction optionalOuter,
+ Map> keysByAlias) {
+ return new ConfigKeyMapConstructionWithArgsInstruction(type, values, (WrappingConstructionInstruction) optionalOuter,
+ keysByAlias);
+ }
+
+ public static ConstructionInstruction newUsingConfigBagConstructor(Class> type,
+ Map values,
+ ConstructionInstruction optionalOuter,
+ Map> keysByAlias) {
+ return new ConfigBagConstructionWithArgsInstruction(type, values, (WrappingConstructionInstruction) optionalOuter,
+ keysByAlias);
+ }
+
+}
diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/ConfigKeyMapConstructionWithArgsInstruction.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/ConfigKeyMapConstructionWithArgsInstruction.java
new file mode 100644
index 0000000000..aebae24fcf
--- /dev/null
+++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/ConfigKeyMapConstructionWithArgsInstruction.java
@@ -0,0 +1,170 @@
+/*
+ * 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.yoml.serializers;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.brooklyn.config.ConfigInheritance;
+import org.apache.brooklyn.config.ConfigInheritances;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.config.ConfigValueAtContainer;
+import org.apache.brooklyn.core.config.BasicConfigInheritance;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.config.ConfigKeys.InheritanceContext;
+import org.apache.brooklyn.core.config.internal.AncestorContainerAndKeyValueIterator;
+import org.apache.brooklyn.util.collections.MutableList;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.collections.MutableSet;
+import org.apache.brooklyn.util.exceptions.ReferenceWithError;
+import org.apache.brooklyn.util.guava.Maybe;
+import org.apache.brooklyn.util.yoml.internal.ConstructionInstruction;
+import org.apache.brooklyn.util.yoml.internal.ConstructionInstructions.AbstractConstructionWithArgsInstruction;
+import org.apache.brooklyn.util.yoml.internal.ConstructionInstructions.WrappingConstructionInstruction;
+
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+import com.google.common.collect.Iterables;
+
+/** Instruction for using a constructor which takes a single argument being a map of string,object
+ * config keys. This will look at the actual keys defined on the types and traverse the construction
+ * instruction hierarchy to ensure the key values are inherited correctly from supertype definitions. */
+public class ConfigKeyMapConstructionWithArgsInstruction extends AbstractConstructionWithArgsInstruction {
+ protected final Map> keysByNameOrAlias;
+
+ public ConfigKeyMapConstructionWithArgsInstruction(Class> type,
+ Map values,
+ WrappingConstructionInstruction optionalOuter,
+ Map> keysByNameOrAlias) {
+ super(type, MutableList.of(values), optionalOuter);
+ this.keysByNameOrAlias = keysByNameOrAlias;
+ }
+
+ @Override
+ protected List> combineArguments(final List constructorsSoFarOutermostFirst) {
+ Set innerNames = MutableSet.of();
+ for (ConstructionInstruction i: constructorsSoFarOutermostFirst) {
+ innerNames.addAll(getKeyValuesAt(i).keySet());
+ }
+
+ // collect all keys, real local ones, and anonymous ones for anonymous config from parents
+
+ // TODO if keys are defined at yaml-based type or type parent we don't currently have a way to get them
+ // (the TypeRegistry API needs to return more than just java class for that)
+ final Map> keysByName = MutableMap.of();
+
+ for (ConfigKey> keyDeclaredHere: keysByNameOrAlias.values()) {
+ keysByName.put(keyDeclaredHere.getName(), keyDeclaredHere);
+ }
+
+ MutableMap,Object> localValues = MutableMap.of();
+ for (Map.Entry aliasAndValueHere: getKeyValuesAt(this).entrySet()) {
+ ConfigKey> k = keysByNameOrAlias.get(aliasAndValueHere.getKey());
+ if (k==null) {
+ // don't think it should come here; all keys will be known
+ k = anonymousKey(aliasAndValueHere.getKey());
+ }
+ if (!keysByName.containsKey(k.getName())) {
+ keysByName.put(k.getName(), k);
+ }
+ if (!localValues.containsKey(k.getName())) {
+ localValues.put(k, aliasAndValueHere.getValue());
+ }
+ }
+
+ for (String innerKeyName: innerNames) {
+ if (!keysByName.containsKey(innerKeyName)) {
+ // parent defined a value under a key which doesn't match config keys we know
+ keysByName.put(innerKeyName, anonymousKey(innerKeyName));
+ }
+ }
+
+ Map result = MutableMap.of();
+ for (final ConfigKey> k: keysByName.values()) {
+ // go through all keys defined here recognising aliases,
+ // and anonymous keys for other keys at parents (ignoring aliases)
+ Maybe value = localValues.containsKey(k) ? Maybe.ofAllowingNull(localValues.get(k)) : Maybe.absent();
+ // don't set default values
+// Maybe defaultValue = k.hasDefaultValue() ? Maybe.ofAllowingNull((Object)k.getDefaultValue()) : Maybe.absent();
+
+ Function> keyFn = new Function>() {
+ @SuppressWarnings("unchecked")
+ @Override
+ public ConfigKey apply(ConstructionInstruction input) {
+ // type inheritance so pretty safe to assume outermost key declaration
+ return (ConfigKey) keysByName.get(input);
+ }
+ };
+ Function> lookupFn = new Function>() {
+ @Override
+ public Maybe apply(ConstructionInstruction input) {
+ Map values = getKeyValuesAt(input); // TODO allow aliases?
+ if (values.containsKey(k.getName())) return Maybe.of((Object)values.get(k.getName()));
+ return Maybe.absent();
+ }
+ };
+ Function, Maybe> coerceFn = Functions.identity();
+ Function parentFn = new Function() {
+ @Override
+ public ConstructionInstruction apply(ConstructionInstruction input) {
+ // parent is the one *after* us in the list, *not* input.getOuterInstruction()
+ Iterator ci = constructorsSoFarOutermostFirst.iterator();
+ ConstructionInstruction cc = ConfigKeyMapConstructionWithArgsInstruction.this;
+ while (ci.hasNext()) {
+ if (input.equals(cc)) {
+ return ci.next();
+ }
+ cc = ci.next();
+ }
+ return null;
+ }
+ };
+ Iterator> ancestors = new AncestorContainerAndKeyValueIterator(
+ this, keyFn, lookupFn, coerceFn, parentFn);
+
+ ConfigInheritance inheritance = ConfigInheritances.findInheritance(k, InheritanceContext.TYPE_DEFINITION, BasicConfigInheritance.OVERWRITE);
+
+ @SuppressWarnings("unchecked")
+ ReferenceWithError> newValue =
+ ConfigInheritances.resolveInheriting(this, (ConfigKey)k, value, Maybe.absent(),
+ ancestors, InheritanceContext.TYPE_DEFINITION, inheritance);
+
+ if (newValue.getWithError().isValueExplicitlySet())
+ result.put(k.getName(), newValue.getWithError().get());
+ }
+
+ return MutableList.of(result);
+ }
+
+ protected ConfigKey> anonymousKey(String key) {
+ return ConfigKeys.newConfigKey(Object.class, key);
+ }
+
+ @SuppressWarnings("unchecked")
+ protected Map getKeyValuesAt(ConstructionInstruction i) {
+ if (i.getArgs()==null) return MutableMap.of();
+ if (i.getArgs().size()!=1) throw new IllegalArgumentException("Wrong length of constructor params, expected one: "+i.getArgs());
+ Object arg = Iterables.getOnlyElement(i.getArgs());
+ if (arg==null) return MutableMap.of();
+ if (!(arg instanceof Map)) throw new IllegalArgumentException("Wrong type of constructor param, expected map: "+arg);
+ return (Map)arg;
+ }
+}
diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/InstantiateTypeFromRegistryUsingConfigBag.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/InstantiateTypeFromRegistryUsingConfigBag.java
new file mode 100644
index 0000000000..36ff98c3ec
--- /dev/null
+++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/InstantiateTypeFromRegistryUsingConfigBag.java
@@ -0,0 +1,79 @@
+/*
+ * 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.yoml.serializers;
+
+import java.lang.reflect.Field;
+import java.util.Map;
+
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.guava.Maybe;
+import org.apache.brooklyn.util.javalang.Reflections;
+import org.apache.brooklyn.util.yoml.annotations.Alias;
+import org.apache.brooklyn.util.yoml.internal.ConstructionInstruction;
+import org.apache.brooklyn.util.yoml.serializers.InstantiateTypeFromRegistryUsingConfigMap;
+
+@Alias("config-bag-constructor")
+public class InstantiateTypeFromRegistryUsingConfigBag extends InstantiateTypeFromRegistryUsingConfigMap {
+
+ public static class Factory extends InstantiateTypeFromRegistryUsingConfigMap.Factory {
+ protected InstantiateTypeFromRegistryUsingConfigBag newInstance() {
+ return new InstantiateTypeFromRegistryUsingConfigBag();
+ }
+ }
+
+ protected Maybe> findConstructorMaybe(Class> type) {
+ Maybe> c = findConfigBagConstructor(type);
+ if (c.isPresent()) return c;
+ Maybe> c2 = super.findConstructorMaybe(type);
+ if (c2.isPresent()) return c2;
+
+ return c;
+ }
+ protected Maybe> findConfigBagConstructor(Class> type) {
+ return Reflections.findConstructorExactMaybe(type, ConfigBag.class);
+ }
+
+ protected Maybe findFieldMaybe(Class> type) {
+ Maybe f = Reflections.findFieldMaybe(type, fieldNameForConfigInJava);
+ if (f.isPresent() && !(Map.class.isAssignableFrom(f.get().getType()) || ConfigBag.class.isAssignableFrom(f.get().getType())))
+ f = Maybe.absent();
+ return f;
+ }
+
+ @Override
+ protected Map getRawConfigMap(Field f, Object obj) throws IllegalAccessException {
+ if (ConfigBag.class.isAssignableFrom(f.getType())) {
+ return ((ConfigBag)f.get(obj)).getAllConfig();
+ }
+ return super.getRawConfigMap(f, obj);
+ }
+
+ @Override
+ protected ConstructionInstruction newConstructor(Class> type, Map> keysByAlias,
+ Map fieldsFromReadToConstructJava, ConstructionInstruction optionalOuter) {
+ if (findConfigBagConstructor(type).isPresent()) {
+ return ConfigKeyConstructionInstructions.newUsingConfigBagConstructor(type, fieldsFromReadToConstructJava, optionalOuter,
+ keysByAlias);
+ }
+ return ConfigKeyConstructionInstructions.newUsingConfigKeyMapConstructor(type, fieldsFromReadToConstructJava, optionalOuter,
+ keysByAlias);
+ }
+
+}
diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/InstantiateTypeFromRegistryUsingConfigKeyMap.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/InstantiateTypeFromRegistryUsingConfigKeyMap.java
new file mode 100644
index 0000000000..7750714e33
--- /dev/null
+++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/InstantiateTypeFromRegistryUsingConfigKeyMap.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.camp.yoml.serializers;
+
+import java.util.Map;
+
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.util.yoml.annotations.Alias;
+import org.apache.brooklyn.util.yoml.internal.ConstructionInstruction;
+import org.apache.brooklyn.util.yoml.serializers.InstantiateTypeFromRegistryUsingConfigMap;
+
+@Alias("config-map-constructor")
+public class InstantiateTypeFromRegistryUsingConfigKeyMap extends InstantiateTypeFromRegistryUsingConfigMap {
+
+ public static class Factory extends InstantiateTypeFromRegistryUsingConfigMap.Factory {
+ protected InstantiateTypeFromRegistryUsingConfigKeyMap newInstance() {
+ return new InstantiateTypeFromRegistryUsingConfigKeyMap();
+ }
+ }
+
+ @Override
+ protected ConstructionInstruction newConstructor(Class> type, Map> keysByAlias,
+ Map fieldsFromReadToConstructJava, ConstructionInstruction optionalOuter) {
+ return ConfigKeyConstructionInstructions.newUsingConfigKeyMapConstructor(type, fieldsFromReadToConstructJava, optionalOuter,
+ keysByAlias);
+ }
+
+}
diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/types/YomlInitializers.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/types/YomlInitializers.java
new file mode 100644
index 0000000000..1826ec236a
--- /dev/null
+++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/types/YomlInitializers.java
@@ -0,0 +1,298 @@
+/*
+ * 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.yoml.types;
+
+import org.apache.brooklyn.api.entity.EntityInitializer;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry.RegisteredTypeKind;
+import org.apache.brooklyn.api.typereg.RegisteredType;
+import org.apache.brooklyn.camp.yoml.BrooklynYomlTypeRegistry;
+import org.apache.brooklyn.core.BrooklynVersion;
+import org.apache.brooklyn.core.effector.ssh.SshCommandEffector;
+import org.apache.brooklyn.core.sensor.StaticSensor;
+import org.apache.brooklyn.core.sensor.ssh.SshCommandSensor;
+import org.apache.brooklyn.core.typereg.BasicBrooklynTypeRegistry;
+import org.apache.brooklyn.util.time.Duration;
+import org.apache.brooklyn.util.yoml.YomlSerializer;
+
+import com.google.common.annotations.Beta;
+
+/*
+
+TYPES - effector
+- script / bash / ssh
+- invoke-effector
+- publish-sensor
+- initdish-effector
+- parallel
+- sequence
+
+ - ssh
+ - install-file
+ - set-sensor
+ - set-config
+
+
+SEQ
+* initd
+* specify pre-conditions & post-conditions
+* specify what must run before/after
+
+a
+b after a
+c before b
+
+EXTENSIONS:
+* local task parameters -- could pass params to any subtask
+* acquire-semaphore (cancel holder, timeout
+
+* parallel
+* run over entities -- concurrency: 16
+* conditional
+* jumping?
+* set sensors/config
+* access results of previous tasks
+
+
+STYLE NOTES
+
+ brooklyn.initializers:
+ - type: org.apache.brooklyn.core.effector.ssh.SshCommandEffector
+ brooklyn.config:
+ name: sayHiNetcat
+ description: Echo a small hello string to the netcat entity
+ command: |
+ echo $message | nc $TARGET_HOSTNAME 4321
+ parameters:
+ message:
+ description: The string to pass to netcat
+ defaultValue: hi netcat
+
+effectors:
+ say-hi:
+ type: script
+ env:
+ name: $brooklyn:config("name")
+ name: $brooklyn:${name}
+ script:
+ echo hello ${name:-world}
+
+ publish-name:
+ type: publish-sensor
+ sensor: name
+ value: $brooklyn:formatString("%s (%s)", config("name"), $("baz"))
+ parameters:
+ foo: # nothing
+ bar: { description: The bar, default-value: B }
+ baz: Z # default
+
+ start:
+ type: initdish-effector
+ impl:
+ 8.2-something:
+ type: invoke-effector
+ effector: say-hi
+ parameters:
+ name: Bob
+ 8.2-something:
+ invoke-effector: say-hi
+ 8.2-something:
+ invoke-effector:
+ effector: say-hi
+ parameters:
+ name: Bob
+ 8.2-something:
+ "saying hi":
+ type: invoke-effector
+ effector: say-hi
+ parameters:
+ name: Bob
+
+
+COMPARISON TO OLD
+
+we used to say:
+
+ brooklyn.initializers:
+ - type: org.apache.brooklyn.core.effector.ssh.SshCommandEffector
+ brooklyn.config:
+ name: sayHiNetcat
+ description: Echo a small hello string to the netcat entity
+ command: |
+ echo $message | nc $TARGET_HOSTNAME 4321
+ parameters:
+ message:
+ description: The string to pass to netcat
+ defaultValue: hi netcat
+ - type: org.apache.brooklyn.core.sensor.ssh.SshCommandSensor
+ brooklyn.config:
+ name: output.last
+ period: 1s
+ command: tail -1 server-input
+
+now we say:
+
+ brooklyn.initializers:
+ say-hi-netcat:
+ type: ssh-effector
+ description: Echo a small hello string to the netcat entity
+ script: |
+ echo $message | nc $TARGET_HOSTNAME 4321
+ parameters:
+ message:
+ description: The string to pass to netcat
+ default-value: hi netcat
+ output.last:
+ type: ssh-sensor
+ period: 1s
+ command: tail -1 server-input
+
+benefits:
+ - readable: more concise description and supports aliases, maps (which plays nice with merge)
+ - extensible: *much* easier to define new items, including recursive
+ - introspective: auto-generate docs and support code completion with descriptions
+ - bi-directional: we can persist in the same formal
+
+
+
+OTHER THOUGHTS
+
+effectors:
+ - type: CommandSequenceEffector
+ name: start
+ impl:
+ - sequence_identifier: 0-provision [optional]
+ type: my.ProvisionEffectorTaskFactory
+ - seq_id: 1-install
+ type: script
+ script: |
+ echo foo ${entity.config.foo}
+
+
+ 01-install:
+ parallel:
+ - bash: |
+ echo foo ${entity.config.foo}
+ - copy:
+ from: URL
+ to: ${entity.sensor['run.dir']}/file.txt
+ 02-loop-entities:
+ foreach:
+ expression: $brooklyn:component['x'].descendants
+ var: x
+ command:
+ effector:
+ name: restart
+ parameters:
+ restart_machine: auto
+ 03-condition:
+ conditional:
+ if: $x
+ then:
+ bash: echo yup
+ else:
+ - if: $y
+ then:
+ bash: echo y
+ - bash: echo none
+ 04-jump:
+ goto: 02-install
+
+
+
+
+
+catalog:
+ id: my-basic-1
+services:
+- type: vanilla-software-process
+ effectors:
+ start:
+ 0-provision: get-machine
+ 1-install:
+ - copy:
+ from: classpath://foo.tar
+ to: /tmp/foo/foo.tar
+ - bash:
+ - cd /tmp/foo
+ - unzip foo.tar
+ - bash: foo.sh
+ 2-launch:
+ - cd /tmp/foo ; ./foo.sh
+ policy:
+ - type: Hook1
+
+ triggers:
+ - on: $brooklyn:component("db").sensor("database_url")
+ do:
+ - bash:
+ mysql install table
+ - publish_sensor basic-ready true
+
+----
+
+services:
+- type: my-basic-2
+ effectors:
+ start:
+ 1.5.11-post-install:
+ bash: |
+echo custom step
+
+ 1.7-another-post-install:
+
+ */
+public class YomlInitializers {
+
+ /**
+ * Adds the given type to the registry.
+ * As the registry is not yet persisted this method must be called explicitly to initialize any management context using it.
+ * This method will be deleted when there is a catalog-style init/persistence mechanism. */
+ @Beta
+ public static void addLocalType(ManagementContext mgmt, RegisteredType type) {
+ ((BasicBrooklynTypeRegistry) mgmt.getTypeRegistry()).addToLocalUnpersistedTypeRegistry(type, false);
+ }
+
+ /** As {@link #addLocalType(ManagementContext, RegisteredType)} */ @Beta
+ public static void addLocalBean(ManagementContext mgmt, Class> clazz) {
+ addLocalType(mgmt, BrooklynYomlTypeRegistry.newYomlRegisteredType(RegisteredTypeKind.BEAN, null, BrooklynVersion.get(), clazz));
+ }
+
+ /** As {@link #addLocalType(ManagementContext, RegisteredType)} */ @Beta
+ public static void addLocalBean(ManagementContext mgmt, String symbolicName, String planYaml,
+ Class> javaConcreteType,
+ Iterable extends Object> addlSuperTypesAsClassOrRegisteredType,
+ Iterable serializers) {
+ addLocalType(mgmt, BrooklynYomlTypeRegistry.newYomlRegisteredType(RegisteredTypeKind.BEAN, null, BrooklynVersion.get(),
+ planYaml, javaConcreteType, addlSuperTypesAsClassOrRegisteredType, serializers));
+ }
+
+ /** Put here until there is a better init mechanism */
+ @Beta
+ public static void install(ManagementContext mgmt) {
+
+ addLocalBean(mgmt, Duration.class);
+
+ addLocalBean(mgmt, EntityInitializer.class);
+ addLocalBean(mgmt, StaticSensor.class);
+ addLocalBean(mgmt, SshCommandSensor.class);
+ addLocalBean(mgmt, SshCommandEffector.class);
+ }
+
+}
diff --git a/camp/camp-brooklyn/src/main/resources/META-INF/services/org.apache.brooklyn.core.typereg.BrooklynTypePlanTransformer b/camp/camp-brooklyn/src/main/resources/META-INF/services/org.apache.brooklyn.core.typereg.BrooklynTypePlanTransformer
index 0c6fab3321..122e117c61 100644
--- a/camp/camp-brooklyn/src/main/resources/META-INF/services/org.apache.brooklyn.core.typereg.BrooklynTypePlanTransformer
+++ b/camp/camp-brooklyn/src/main/resources/META-INF/services/org.apache.brooklyn.core.typereg.BrooklynTypePlanTransformer
@@ -16,4 +16,5 @@
# specific language governing permissions and limitations
# under the License.
#
+org.apache.brooklyn.camp.yoml.YomlTypePlanTransformer
org.apache.brooklyn.camp.brooklyn.spi.creation.CampTypePlanTransformer
diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/HttpRequestSensorYamlTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/HttpRequestSensorYamlTest.java
index f50f27f271..8dc9333f21 100644
--- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/HttpRequestSensorYamlTest.java
+++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/HttpRequestSensorYamlTest.java
@@ -42,7 +42,7 @@ public class HttpRequestSensorYamlTest extends AbstractYamlRebindTest {
private static final Logger log = LoggerFactory.getLogger(HttpRequestSensorYamlTest.class);
final static AttributeSensor SENSOR_STRING = Sensors.newStringSensor("aString");
- final static String TARGET_TYPE = "java.lang.String";
+ final static String TARGET_TYPE = String.class.getName();
private TestHttpServer server;
private String serverUrl;
diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/BrooklynDslInYomlStringPlanTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/BrooklynDslInYomlStringPlanTest.java
new file mode 100644
index 0000000000..c4f7fc3080
--- /dev/null
+++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/BrooklynDslInYomlStringPlanTest.java
@@ -0,0 +1,122 @@
+/*
+ * 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.yoml;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry.RegisteredTypeKind;
+import org.apache.brooklyn.api.typereg.RegisteredType;
+import org.apache.brooklyn.camp.brooklyn.AbstractYamlTest;
+import org.apache.brooklyn.camp.yoml.types.YomlInitializers;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.entity.EntityInternal;
+import org.apache.brooklyn.core.sensor.Sensors;
+import org.apache.brooklyn.core.typereg.BasicBrooklynTypeRegistry;
+import org.apache.brooklyn.test.Asserts;
+import org.apache.brooklyn.util.guava.Maybe;
+import org.apache.brooklyn.util.time.Duration;
+import org.apache.brooklyn.util.time.Time;
+import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsTopLevel;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Supplier;
+import com.google.common.collect.Iterables;
+
+public class BrooklynDslInYomlStringPlanTest extends AbstractYamlTest {
+
+ @SuppressWarnings("unused")
+ private static final Logger log = LoggerFactory.getLogger(BrooklynDslInYomlStringPlanTest.class);
+
+ private BasicBrooklynTypeRegistry registry() {
+ return (BasicBrooklynTypeRegistry) mgmt().getTypeRegistry();
+ }
+
+ private void add(RegisteredType type) {
+ add(type, false);
+ }
+ private void add(RegisteredType type, boolean canForce) {
+ registry().addToLocalUnpersistedTypeRegistry(type, canForce);
+ }
+
+ @YomlAllFieldsTopLevel
+ public static class ItemA {
+ String name;
+ @Override public String toString() { return super.toString()+"[name="+name+"]"; }
+ }
+
+ private final static RegisteredType SAMPLE_TYPE_BASE = BrooklynYomlTypeRegistry.newYomlRegisteredType(
+ RegisteredTypeKind.BEAN, "item-base", "1", ItemA.class);
+
+ private final static RegisteredType SAMPLE_TYPE_TEST = BrooklynYomlTypeRegistry.newYomlRegisteredType(
+ RegisteredTypeKind.BEAN, "item-w-dsl", "1", "{ type: item-base, name: '$brooklyn:self().attributeWhenReady(\"test.sensor\")' }",
+ ItemA.class, null, null);
+
+ @Test
+ public void testYomlParserRespectsDsl() throws Exception {
+ add(SAMPLE_TYPE_BASE);
+ add(SAMPLE_TYPE_TEST);
+
+ String yaml = Joiner.on("\n").join(
+ "services:",
+ "- type: org.apache.brooklyn.core.test.entity.TestEntity",
+ " brooklyn.config:",
+ " test.obj:",
+ // with this, the yoml is resolved at retrieval time
+ " $brooklyn:object-yoml: item-w-dsl");
+
+ Entity app = createStartWaitAndLogApplication(yaml);
+ Entity entity = Iterables.getOnlyElement( app.getChildren() );
+
+ entity.sensors().set(Sensors.newStringSensor("test.sensor"), "bob");
+ Maybe raw = ((EntityInternal)entity).config().getRaw(ConfigKeys.newConfigKey(Object.class, "test.obj"));
+ Asserts.assertPresent(raw);
+ Asserts.assertInstanceOf(raw.get(), Supplier.class);
+ Object obj = entity.config().get(ConfigKeys.newConfigKey(Object.class, "test.obj"));
+ Assert.assertEquals(((ItemA)obj).name, "bob");
+ }
+
+ @Test
+ public void testYomlDefersDslEvaluationForConfig() throws Exception {
+ add(SAMPLE_TYPE_BASE);
+ add(SAMPLE_TYPE_TEST);
+ YomlInitializers.install(mgmt());
+
+ String yaml = Joiner.on("\n").join(
+ "services:",
+ "- type: org.apache.brooklyn.core.test.entity.TestEntity",
+ " brooklyn.initializers:",
+ " a-sensor:",
+ " type: static-sensor",
+ " value: '$brooklyn:self().attributeWhenReady(\"test.sensor\")'",
+ " period: 100ms");
+
+ Entity app = createStartWaitAndLogApplication(yaml);
+ Entity entity = Iterables.getOnlyElement( app.getChildren() );
+
+ entity.sensors().set(Sensors.newStringSensor("test.sensor"), "bob");
+// EntityAsserts.assertAttributeEqualsEventually(entity, attribute, expected);
+ System.out.println(entity.getAttribute(Sensors.newStringSensor("a-sensor")));
+ Time.sleep(Duration.ONE_SECOND);
+ System.out.println(entity.getAttribute(Sensors.newStringSensor("a-sensor")));
+ }
+
+}
diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTestFixture.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTestFixture.java
new file mode 100644
index 0000000000..8cb34c6a0f
--- /dev/null
+++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTestFixture.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.camp.yoml;
+
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.util.yoml.YomlConfig;
+import org.apache.brooklyn.util.yoml.annotations.YomlAnnotations;
+import org.apache.brooklyn.util.yoml.tests.YomlTestFixture;
+
+public class BrooklynYomlTestFixture extends YomlTestFixture {
+
+ public static YomlTestFixture newInstance() { return new BrooklynYomlTestFixture(); }
+ public static YomlTestFixture newInstance(YomlConfig config) { return new BrooklynYomlTestFixture(config); }
+ public static YomlTestFixture newInstance(ManagementContext mgmt) {
+ return newInstance(YomlTypePlanTransformer.newYomlConfig(mgmt, null).build());
+ }
+
+ public BrooklynYomlTestFixture() {}
+ public BrooklynYomlTestFixture(YomlConfig config) {
+ super(config);
+ }
+
+ @Override
+ protected YomlAnnotations annotationsProvider() {
+ return new BrooklynYomlAnnotations();
+ }
+
+}
diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/ObjectYomlInBrooklynDslTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/ObjectYomlInBrooklynDslTest.java
new file mode 100644
index 0000000000..e580852c23
--- /dev/null
+++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/ObjectYomlInBrooklynDslTest.java
@@ -0,0 +1,113 @@
+/*
+ * 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.yoml;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry.RegisteredTypeKind;
+import org.apache.brooklyn.api.typereg.RegisteredType;
+import org.apache.brooklyn.camp.brooklyn.AbstractYamlTest;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.sensor.Sensors;
+import org.apache.brooklyn.core.typereg.BasicBrooklynTypeRegistry;
+import org.apache.brooklyn.test.Asserts;
+import org.apache.brooklyn.util.javalang.JavaClassNames;
+import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsTopLevel;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.Iterables;
+
+public class ObjectYomlInBrooklynDslTest extends AbstractYamlTest {
+
+ private static final Logger log = LoggerFactory.getLogger(ObjectYomlInBrooklynDslTest.class);
+
+ private BasicBrooklynTypeRegistry registry() {
+ return (BasicBrooklynTypeRegistry) mgmt().getTypeRegistry();
+ }
+
+ private void add(RegisteredType type) {
+ add(type, false);
+ }
+ private void add(RegisteredType type, boolean canForce) {
+ registry().addToLocalUnpersistedTypeRegistry(type, canForce);
+ }
+
+ @YomlAllFieldsTopLevel
+ public static class ItemA {
+ String name;
+ /* required for 'object.fields' */ public void setName(String name) { this.name = name; }
+ @Override public String toString() { return super.toString()+"[name="+name+"]"; }
+ }
+
+ private final static RegisteredType SAMPLE_TYPE = BrooklynYomlTypeRegistry.newYomlRegisteredType(
+ RegisteredTypeKind.BEAN, null, "1", ItemA.class);
+
+ Entity doDeploy(String ...lines) throws Exception {
+ add(SAMPLE_TYPE);
+ String yaml = Joiner.on("\n").join(
+ "services:",
+ "- type: org.apache.brooklyn.core.test.entity.TestEntity",
+ " brooklyn.config:",
+ " test.obj:");
+ yaml += Joiner.on("\n ").join("", "", (Object[])lines);
+
+ Entity app = createStartWaitAndLogApplication(yaml);
+ return Iterables.getOnlyElement( app.getChildren() );
+ }
+
+ void doTest(String ...lines) throws Exception {
+ Entity entity = doDeploy(lines);
+ Object obj = entity.config().get(ConfigKeys.newConfigKey(Object.class, "test.obj"));
+ log.info("Object for "+JavaClassNames.callerNiceClassAndMethod(1)+" : "+obj);
+ Asserts.assertInstanceOf(obj, ItemA.class);
+ Assert.assertEquals(((ItemA)obj).name, "bob");
+ }
+
+ @Test
+ public void testOldStyle() throws Exception {
+ doTest(
+ "$brooklyn:object:",
+ " type: "+ItemA.class.getName(),
+ " object.fields:",
+ " name: bob");
+ }
+
+ @Test
+ public void testYomlSyntax() throws Exception {
+ doTest(
+ "$brooklyn:object-yoml:",
+ " type: "+ItemA.class.getName(),
+ " name: bob");
+ }
+
+ @Test
+ public void testYomlSyntaxUsindDslAgain() throws Exception {
+ Entity entity = doDeploy(
+ "$brooklyn:object-yoml:",
+ " type: "+ItemA.class.getName(),
+ " name: $brooklyn:self().attributeWhenReady(\"test.sensor\")");
+ entity.sensors().set(Sensors.newStringSensor("test.sensor"), "bobella");
+ Object obj = entity.config().get(ConfigKeys.newConfigKey(Object.class, "test.obj"));
+ Assert.assertEquals(((ItemA)obj).name, "bobella");
+ }
+
+}
diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlConfigKeyInheritanceTests.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlConfigKeyInheritanceTests.java
new file mode 100644
index 0000000000..c49b166185
--- /dev/null
+++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlConfigKeyInheritanceTests.java
@@ -0,0 +1,101 @@
+/*
+ * 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.yoml;
+
+import java.util.Map;
+
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.BasicConfigInheritance;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.test.Asserts;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.yoml.annotations.YomlConfigMapConstructor;
+import org.apache.brooklyn.util.yoml.tests.YomlTestFixture;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.Test;
+
+import com.google.common.reflect.TypeToken;
+
+/** Tests that config key inheritance strategies are obeyed when reading with supertypes.
+ */
+public class YomlConfigKeyInheritanceTests {
+
+ private static final Logger log = LoggerFactory.getLogger(YomlConfigKeyInheritanceTests.class);
+
+ @YomlConfigMapConstructor("conf")
+ static class M0 {
+ Map conf = MutableMap.of();
+ M0(Map keys) { this.conf.putAll(keys); }
+
+ @Override
+ public boolean equals(Object obj) {
+ return (obj instanceof M0) && ((M0)obj).conf.equals(conf);
+ }
+ @Override
+ public int hashCode() {
+ return conf.hashCode();
+ }
+ @Override
+ public String toString() {
+ return super.toString()+conf;
+ }
+ }
+
+ static class M1 extends M0 {
+ @SuppressWarnings("serial")
+ static ConfigKey> KM = ConfigKeys.builder(new TypeToken>() {}, "km")
+ .typeInheritance(BasicConfigInheritance.DEEP_MERGE).build();
+
+ @SuppressWarnings("serial")
+ static ConfigKey> KO = ConfigKeys.builder(new TypeToken>() {}, "ko")
+ .typeInheritance(BasicConfigInheritance.OVERWRITE).build();
+
+ M1(Map keys) { super(keys); }
+ }
+
+ @Test
+ public void testReadMergedMap() {
+ YomlTestFixture y = BrooklynYomlTestFixture.newInstance().addTypeWithAnnotations("m1", M1.class)
+ .addType("m1a", "{ type: m1, km: { a: 1, b: 1 }, ko: { a: 1, b: 1 } }")
+ .addType("m1b", "{ type: m1a, km: { b: 2 }, ko: { b: 2 } }");
+
+ y.read("{ type: m1b }", null);
+
+ M1 m1b = (M1)y.getLastReadResult();
+ Asserts.assertEquals(m1b.conf.get(M1.KM.getName()), MutableMap.of("a", 1, "b", 2));
+ Asserts.assertEquals(m1b.conf.get(M1.KO.getName()), MutableMap.of("b", 2));
+ }
+
+ @Test
+ public void testWrite() {
+ // the write is not smart enough to look at default/inherited KV pairs
+ // (this would be nice to change, but a lot of work and not really worth it)
+
+ YomlTestFixture y = YomlTestFixture.newInstance()
+ .addTypeWithAnnotationsAndConfigFieldsIgnoringInheritance("m1", M1.class, MutableMap.of("keys", "config"));
+
+ M1 m1b = new M1(MutableMap.of("km", MutableMap.of("a", 1, "b", 2), "ko", MutableMap.of("a", 1, "b", 2)));
+ y.write(m1b);
+ log.info("written as "+y.getLastWriteResult());
+ y.assertLastWriteIgnoringQuotes(
+ "{ type=m1, km:{ a: 1, b: 2 }, ko: { a: 1, b: 2 }}", "wrong serialization");
+ }
+
+}
diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryBasicTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryBasicTest.java
new file mode 100644
index 0000000000..488867f680
--- /dev/null
+++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryBasicTest.java
@@ -0,0 +1,195 @@
+/*
+ * 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.yoml;
+
+import java.util.Arrays;
+import java.util.Map;
+
+import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry.RegisteredTypeKind;
+import org.apache.brooklyn.api.typereg.RegisteredType;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.test.BrooklynMgmtUnitTestSupport;
+import org.apache.brooklyn.core.typereg.BasicBrooklynTypeRegistry;
+import org.apache.brooklyn.core.typereg.JavaClassNameTypePlanTransformer.JavaClassNameTypeImplementationPlan;
+import org.apache.brooklyn.core.typereg.RegisteredTypes;
+import org.apache.brooklyn.test.Asserts;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.core.yoml.YomlConfigBagConstructor;
+import org.apache.brooklyn.util.javalang.JavaClassNames;
+import org.apache.brooklyn.util.yoml.annotations.Alias;
+import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsTopLevel;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class YomlTypeRegistryBasicTest extends BrooklynMgmtUnitTestSupport {
+
+ private BasicBrooklynTypeRegistry registry() {
+ return (BasicBrooklynTypeRegistry) mgmt.getTypeRegistry();
+ }
+
+ private void add(RegisteredType type) {
+ add(type, false);
+ }
+ private void add(RegisteredType type, boolean canForce) {
+ registry().addToLocalUnpersistedTypeRegistry(type, canForce);
+ }
+
+ public static class ItemA {
+ String name;
+ }
+
+ private final static RegisteredType SAMPLE_TYPE_JAVA = RegisteredTypes.bean("java.A", "1",
+ new JavaClassNameTypeImplementationPlan(ItemA.class.getName()), ItemA.class);
+
+ @Test
+ public void testInstantiateYomlPlan() {
+ add(SAMPLE_TYPE_JAVA);
+ Object x = registry().createBeanFromPlan("yoml", "{ type: java.A }", null, null);
+ Assert.assertTrue(x instanceof ItemA);
+ }
+
+ @Test
+ public void testInstantiateYomlPlanExplicitField() {
+ add(SAMPLE_TYPE_JAVA);
+ Object x = registry().createBeanFromPlan("yoml", "{ type: java.A, fields: { name: Bob } }", null, null);
+ Assert.assertTrue(x instanceof ItemA);
+ Assert.assertEquals( ((ItemA)x).name, "Bob" );
+ }
+
+ private static RegisteredType sampleTypeYoml(String typeName, String typeDefName) {
+ return BrooklynYomlTypeRegistry.newYomlRegisteredType(RegisteredTypeKind.BEAN,
+ // symbolicName, version,
+ typeName==null ? "yoml.A" : typeName, "1",
+ // planData,
+ "{ type: "+ (typeDefName==null ? ItemA.class.getName() : typeDefName) +" }",
+ // javaConcreteType, superTypesAsClassOrRegisteredType, serializers)
+ ItemA.class, Arrays.asList(ItemA.class), null);
+ }
+ private final static RegisteredType SAMPLE_TYPE_YOML = sampleTypeYoml(null, null);
+
+ @Test
+ public void testInstantiateYomlBaseType() {
+ add(SAMPLE_TYPE_YOML);
+ Object x = registry().createBeanFromPlan("yoml", "{ type: yoml.A }", null, null);
+ Assert.assertTrue(x instanceof ItemA);
+ }
+
+ @Test
+ public void testInstantiateYomlBaseTypeJavaPrefix() {
+ add(sampleTypeYoml(null, "'java:"+ItemA.class.getName()+"'"));
+ Object x = registry().createBeanFromPlan("yoml", "{ type: yoml.A }", null, null);
+ Assert.assertTrue(x instanceof ItemA);
+ }
+
+ @Test
+ public void testInstantiateYomlBaseTypeSameName() {
+ add(sampleTypeYoml(ItemA.class.getName(), null));
+ Object x = registry().createBeanFromPlan("yoml", "{ type: "+ItemA.class.getName()+" }", null, null);
+ Assert.assertTrue(x instanceof ItemA);
+ }
+
+ @Test
+ public void testInstantiateYomlBaseTypeExplicitField() {
+ add(SAMPLE_TYPE_YOML);
+ Object x = registry().createBeanFromPlan("yoml", "{ type: yoml.A, fields: { name: Bob } }", null, null);
+ Assert.assertTrue(x instanceof ItemA);
+ Assert.assertEquals( ((ItemA)x).name, "Bob" );
+ }
+
+ @Test
+ public void testYomlTypeMissingGiveGoodError() {
+ try {
+ Object x = registry().createBeanFromPlan("yoml", "{ type: yoml.A, fields: { name: Bob } }", null, null);
+ Asserts.shouldHaveFailedPreviously("Expected type resolution failure; instead it loaded "+x);
+ } catch (Exception e) {
+ Asserts.expectedFailureContainsIgnoreCase(e, "yoml.A", "neither", "registry", "classpath");
+ Asserts.expectedFailureDoesNotContain(e, JavaClassNames.simpleClassName(ClassNotFoundException.class));
+ }
+ }
+
+ @Test
+ public void testYomlTypeMissingGiveGoodErrorNested() {
+ add(sampleTypeYoml("yoml.B", "yoml.A"));
+ try {
+ Object x = registry().createBeanFromPlan("yoml", "{ type: yoml.B, fields: { name: Bob } }", null, null);
+ Asserts.shouldHaveFailedPreviously("Expected type resolution failure; instead it loaded "+x);
+ } catch (Exception e) {
+ Asserts.expectedFailureContainsIgnoreCase(e, "yoml.B", "yoml.A", "neither", "registry", "classpath");
+ Asserts.expectedFailureDoesNotContain(e, JavaClassNames.simpleClassName(ClassNotFoundException.class));
+ }
+ }
+
+ @YomlAllFieldsTopLevel
+ @Alias("item-annotated")
+ public static class ItemAn {
+ final static RegisteredType YOML = BrooklynYomlTypeRegistry.newYomlRegisteredType(RegisteredTypeKind.BEAN,
+ null, "1", ItemAn.class);
+
+ String name;
+ }
+
+ @Test
+ public void testInstantiateAnnotatedYoml() {
+ add(ItemAn.YOML);
+ Object x = registry().createBeanFromPlan("yoml", "{ type: item-annotated, name: bob }", null, null);
+ Assert.assertTrue(x instanceof ItemAn);
+ Assert.assertEquals( ((ItemAn)x).name, "bob" );
+ }
+
+
+ @YomlConfigBagConstructor(value="config", writeAsKey="extraConfig")
+ static class ConfigurableExampleFromBag {
+ ConfigBag config = ConfigBag.newInstance();
+ ConfigurableExampleFromBag(ConfigBag bag) { config.putAll(bag); }
+ static ConfigKey S = ConfigKeys.newStringConfigKey("s");
+ static ConfigKey CB = ConfigKeys.newConfigKey(ConfigurableExampleFromBag.class, "bag");
+ @Override
+ public boolean equals(Object obj) {
+ return (getClass().equals(obj.getClass())) && config.getAllConfig().equals(((ConfigurableExampleFromBag)obj).config.getAllConfig());
+ }
+ @Override
+ public String toString() {
+ return super.toString()+"["+config+"]";
+ }
+ }
+
+ @Test
+ public void testReadWriteAnnotation() {
+ BrooklynYomlTestFixture.newInstance()
+ .addTypeWithAnnotations("bag-example", ConfigurableExampleFromBag.class)
+ .reading("{ type: bag-example, s: foo }").writing(
+ new ConfigurableExampleFromBag(ConfigBag.newInstance().configure(ConfigurableExampleFromBag.S, "foo")))
+ .doReadWriteAssertingJsonMatch();
+ }
+
+ static class ConfigurableExampleFromMap extends ConfigurableExampleFromBag {
+ ConfigurableExampleFromMap(Map bag) { super(ConfigBag.newInstance(bag)); }
+ }
+
+ @Test
+ public void testReadWriteAnnotationMapConstructorInherited() {
+ BrooklynYomlTestFixture.newInstance()
+ .addTypeWithAnnotations("bag-example", ConfigurableExampleFromMap.class)
+ .reading("{ type: bag-example, s: foo }").writing(
+ new ConfigurableExampleFromMap(ConfigBag.newInstance().configure(ConfigurableExampleFromBag.S, "foo").getAllConfig()))
+ .doReadWriteAssertingJsonMatch();
+ }
+
+}
diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryEntityInitializersTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryEntityInitializersTest.java
new file mode 100644
index 0000000000..2a57797871
--- /dev/null
+++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryEntityInitializersTest.java
@@ -0,0 +1,165 @@
+/*
+ * 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.yoml;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.EntityInitializer;
+import org.apache.brooklyn.camp.brooklyn.AbstractYamlTest;
+import org.apache.brooklyn.camp.yoml.types.YomlInitializers;
+import org.apache.brooklyn.core.sensor.DependentConfiguration;
+import org.apache.brooklyn.core.sensor.Sensors;
+import org.apache.brooklyn.core.sensor.StaticSensor;
+import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.apache.brooklyn.test.Asserts;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.time.Duration;
+import org.apache.brooklyn.util.yaml.Yamls;
+import org.apache.brooklyn.util.yoml.tests.YomlTestFixture;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.Iterables;
+
+public class YomlTypeRegistryEntityInitializersTest extends AbstractYamlTest {
+
+ @BeforeMethod(alwaysRun = true)
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ // TODO logically how should we populate the catalog? see notes on the method called below
+ YomlInitializers.install(mgmt());
+ }
+
+ StaticSensor> SS_42 = new StaticSensor(ConfigBag.newInstance()
+ .configure(StaticSensor.SENSOR_NAME, "the-answer")
+ .configure(StaticSensor.SENSOR_TYPE, "int")
+ .configure(StaticSensor.STATIC_VALUE, 42) );
+
+ // permitted anytime
+ final static String SS_42_YAML_SIMPLE = Joiner.on("\n").join(
+ "name: the-answer",
+ "type: static-sensor",
+ "sensor-type: int",
+ "value: 42");
+
+ // permitted if we know we are reading EntityInitializer instances
+ final String SS_42_YAML_SINGLETON_MAP = Joiner.on("\n").join(
+ "the-answer:",
+ " type: static-sensor",
+ " sensor-type: int",
+ " value: 42");
+
+ @Test
+ public void testYomlReadSensor() throws Exception {
+ String yaml = Joiner.on("\n").join(
+ "name: the-answer",
+ "type: static-sensor",
+ "sensor-type: int",
+ "value: 42");
+
+ Object ss = mgmt().getTypeRegistry().createBeanFromPlan("yoml", Yamls.parseAll(yaml).iterator().next(), null, null);
+ Asserts.assertInstanceOf(ss, StaticSensor.class);
+ // Assert.assertEquals(ss, SS_42); // StaticSensor does not support equals
+ }
+
+ @Test
+ public void testYomlReadSensorWithExpectedSuperType() throws Exception {
+ Object ss = mgmt().getTypeRegistry().createBeanFromPlan("yoml", Yamls.parseAll(SS_42_YAML_SIMPLE).iterator().next(), null, EntityInitializer.class);
+ Asserts.assertInstanceOf(ss, StaticSensor.class);
+ // Assert.assertEquals(ss, SS_42); // StaticSensor does not support equals
+ }
+
+ @Test
+ public void testReadSensorAsMapWithName() throws Exception {
+ Object ss = mgmt().getTypeRegistry().createBeanFromPlan("yoml", Yamls.parseAll(SS_42_YAML_SINGLETON_MAP).iterator().next(), null, EntityInitializer.class);
+ Asserts.assertInstanceOf(ss, StaticSensor.class);
+ // Assert.assertEquals(ss, SS_42); // StaticSensor does not support equals
+ }
+
+ @Test
+ public void testYomlReadSensorSingletonMapWithFixture() throws Exception {
+ YomlTestFixture y = BrooklynYomlTestFixture.newInstance(mgmt());
+ y.read(SS_42_YAML_SINGLETON_MAP, "entity-initializer");
+ Asserts.assertInstanceOf(y.getLastReadResult(), StaticSensor.class);
+ }
+
+ @Test
+ public void testYomlWriteSensorWithFixture() throws Exception {
+ YomlTestFixture y = BrooklynYomlTestFixture.newInstance(mgmt());
+ y.write(SS_42, "entity-initializer").assertLastWriteIgnoringQuotes(
+ "{the-answer: { type: static-sensor, period: 5m, targetType: int, timeout: a very long time, value: 42}}"
+ // ideally it would be simple like below but StaticSensor sets all the values
+ // so it ends up looking like the above; not too bad
+// Jsonya.newInstance().add( Yamls.parseAll(SS_42_YAML_SINGLETON_MAP).iterator().next() ).toString()
+ );
+
+ // and another read/write cycle gives the same thing
+ Object fullOutput = y.getLastWriteResult();
+ y.readLastWrite().writeLastRead();
+ Assert.assertEquals(fullOutput, y.getLastWriteResult());
+ }
+
+ // and test in context
+
+ @Test(enabled=false) // this format (list) still runs old camp parse, does not attempt yaml, included for comparison
+ public void testStaticSensorWorksAsList() throws Exception {
+ String yaml = Joiner.on("\n").join(
+ "services:",
+ "- type: org.apache.brooklyn.core.test.entity.TestEntity",
+ " brooklyn.initializers:",
+ " - name: the-answer",
+ " type: static-sensor",
+ " sensor-type: int",
+ " value: 42");
+
+ checkStaticSensorInApp(yaml);
+ }
+
+ @Test
+ public void testStaticSensorWorksAsSingletonMap() throws Exception {
+ String yaml = Joiner.on("\n").join(
+ "services:",
+ "- type: org.apache.brooklyn.core.test.entity.TestEntity",
+ " brooklyn.initializers:",
+ " the-answer:",
+ " type: static-sensor",
+ " sensor-type: int",
+ " value: 42");
+
+ checkStaticSensorInApp(yaml);
+ }
+
+ protected void checkStaticSensorInApp(String yaml)
+ throws Exception, InterruptedException, ExecutionException, TimeoutException {
+ final Entity app = createStartWaitAndLogApplication(yaml);
+ TestEntity entity = (TestEntity) Iterables.getOnlyElement(app.getChildren());
+
+ Assert.assertEquals(
+ entity.getExecutionContext().submit(
+ DependentConfiguration.attributeWhenReady(entity, Sensors.newIntegerSensor("the-answer")) )
+ .get( Duration.FIVE_SECONDS ), (Integer) 42);
+ }
+
+}
diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/demos/YomlSensorEffectorTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/demos/YomlSensorEffectorTest.java
new file mode 100644
index 0000000000..4bd458f485
--- /dev/null
+++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/demos/YomlSensorEffectorTest.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.camp.yoml.demos;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.mgmt.Task;
+import org.apache.brooklyn.camp.brooklyn.AbstractYamlTest;
+import org.apache.brooklyn.camp.yoml.types.YomlInitializers;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.effector.Effectors;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.entity.EntityAsserts;
+import org.apache.brooklyn.core.sensor.Sensors;
+import org.apache.brooklyn.entity.software.base.EmptySoftwareProcess;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class YomlSensorEffectorTest extends AbstractYamlTest {
+
+ private static final Logger log = LoggerFactory.getLogger(YomlSensorEffectorTest.class);
+
+ protected EmptySoftwareProcess startApp() throws Exception {
+ YomlInitializers.install(mgmt());
+
+ Entity app = createAndStartApplication(loadYaml("ssh-sensor-effector-yoml-demo.yaml"));
+ waitForApplicationTasks(app);
+
+ log.info("App started:");
+ Entities.dumpInfo(app);
+
+ EmptySoftwareProcess entity = (EmptySoftwareProcess) app.getChildren().iterator().next();
+ return entity;
+ }
+
+ @Test(groups="Integration")
+ public void testBasicEffector() throws Exception {
+ EmptySoftwareProcess entity = startApp();
+
+ String name = entity.getConfig(ConfigKeys.newStringConfigKey("name"));
+ Assert.assertEquals(name, "bob");
+
+ Task hi = entity.invoke(Effectors.effector(String.class, "echo-hi").buildAbstract(), MutableMap.of());
+ Assert.assertEquals(hi.get().trim(), "hi");
+ }
+
+ @Test(groups="Integration")
+ public void testConfigEffector() throws Exception {
+ EmptySoftwareProcess entity = startApp();
+
+ String name = entity.getConfig(ConfigKeys.newStringConfigKey("name"));
+ Assert.assertEquals(name, "bob");
+
+ Task hi = entity.invoke(Effectors.effector(String.class, "echo-hi-name-from-config").buildAbstract(), MutableMap.of());
+ Assert.assertEquals(hi.get().trim(), "hi bob");
+ }
+
+ @Test(groups="Integration")
+ public void testSensorAndEffector() throws Exception {
+ EmptySoftwareProcess entity = startApp();
+
+ EntityAsserts.assertAttributeChangesEventually(entity, Sensors.newStringSensor("date"));
+ }
+
+}
\ No newline at end of file
diff --git a/camp/camp-brooklyn/src/test/resources/ssh-sensor-effector-yoml-demo.yaml b/camp/camp-brooklyn/src/test/resources/ssh-sensor-effector-yoml-demo.yaml
new file mode 100644
index 0000000000..1bc996962f
--- /dev/null
+++ b/camp/camp-brooklyn/src/test/resources/ssh-sensor-effector-yoml-demo.yaml
@@ -0,0 +1,41 @@
+#
+# 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.
+#
+
+location: localhost
+
+services:
+
+- type: org.apache.brooklyn.entity.software.base.EmptySoftwareProcess
+
+ brooklyn.initializers:
+ echo-hi:
+ type: ssh-effector
+ command: echo hi
+ echo-hi-name-from-config:
+ type: ssh-effector
+ command: echo hi ${NAME}
+ env:
+ NAME: $brooklyn:config("name")
+ date:
+ type: ssh-sensor
+ command: date
+ period: 100ms
+
+ brooklyn.config:
+ name: bob
diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java
index 636742b3a3..0b01419be3 100644
--- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java
+++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java
@@ -69,8 +69,10 @@
import org.apache.brooklyn.core.typereg.BasicRegisteredType;
import org.apache.brooklyn.core.typereg.BasicTypeImplementationPlan;
import org.apache.brooklyn.core.typereg.BrooklynTypePlanTransformer;
+import org.apache.brooklyn.core.typereg.RegisteredTypeInfo;
import org.apache.brooklyn.core.typereg.RegisteredTypeNaming;
import org.apache.brooklyn.core.typereg.RegisteredTypes;
+import org.apache.brooklyn.core.typereg.TypePlanTransformers;
import org.apache.brooklyn.util.collections.MutableList;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.collections.MutableSet;
@@ -1799,11 +1801,21 @@ public ReferenceWithError resolve(RegisteredType typeToValidate,
}
RegisteredTypes.cacheActualJavaType(resultT, resultS);
- Set newSupers = MutableSet.of();
- // TODO collect registered type name supertypes, as strings
+ MutableSet newSupers = MutableSet.of();
newSupers.add(resultS);
+
+ Maybe typeInfo = TypePlanTransformers.getTypeInfo(mgmt, resultT, constraint);
+ if (typeInfo.isPresent()) {
+ Set supersTI = typeInfo.get().getSupertypes();
+ if (supersTI!=null) {
+ newSupers.addAll(supersTI);
+ }
+ }
+
+ // following should be redundant if the above worked, but the
+ // above might not have worked, and no harm in any case
newSupers.addAll(supers);
- newSupers.add(BrooklynObjectType.of(resultO.getClass()).getInterfaceType());
+ newSupers.addIfNotNull(BrooklynObjectType.of(resultO.getClass()).getInterfaceType());
collectSupers(newSupers);
RegisteredTypes.addSuperTypes(resultT, newSupers);
diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogUtils.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogUtils.java
index a25e19ddab..ae4df3a3b4 100644
--- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogUtils.java
+++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogUtils.java
@@ -87,7 +87,7 @@ public static BrooklynClassLoadingContext newClassLoadingContext(ManagementConte
public static BrooklynClassLoadingContext newClassLoadingContext(ManagementContext mgmt, RegisteredType item) {
return newClassLoadingContext(mgmt, item.getId(), item.getLibraries(), null);
}
-
+
/** made @Beta in 0.9.0 because we're not sure to what extent to support stacking loaders;
* only a couple places currently rely on such stacking, in general the item and the bundles *are* the context,
* and life gets hard if we support complex stacking! */
diff --git a/core/src/main/java/org/apache/brooklyn/core/config/BasicConfigKey.java b/core/src/main/java/org/apache/brooklyn/core/config/BasicConfigKey.java
index b2ec95d748..d87d4def68 100644
--- a/core/src/main/java/org/apache/brooklyn/core/config/BasicConfigKey.java
+++ b/core/src/main/java/org/apache/brooklyn/core/config/BasicConfigKey.java
@@ -360,16 +360,12 @@ public ConfigInheritance getInheritance() {
@Deprecated @Override @Nullable
public ConfigInheritance getTypeInheritance() {
- return typeInheritance;
+ return getInheritanceByContext(InheritanceContext.TYPE_DEFINITION);
}
@Deprecated @Override @Nullable
public ConfigInheritance getParentInheritance() {
- if (parentInheritance == null && inheritance != null) {
- parentInheritance = inheritance;
- inheritance = null;
- }
- return parentInheritance;
+ return getInheritanceByContext(InheritanceContext.RUNTIME_MANAGEMENT);
}
/** @see ConfigKey#getConstraint() */
diff --git a/core/src/main/java/org/apache/brooklyn/core/config/ListConfigKey.java b/core/src/main/java/org/apache/brooklyn/core/config/ListConfigKey.java
index 7a957281c4..78026ac790 100644
--- a/core/src/main/java/org/apache/brooklyn/core/config/ListConfigKey.java
+++ b/core/src/main/java/org/apache/brooklyn/core/config/ListConfigKey.java
@@ -18,6 +18,8 @@
*/
package org.apache.brooklyn.core.config;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.ArrayList;
@@ -66,12 +68,13 @@ public class ListConfigKey extends AbstractCollectionConfigKey,List extends BasicConfigKey.Builder,Builder> {
protected Class subType;
+ @SuppressWarnings("unchecked")
public Builder(TypeToken subType, String name) {
- super(new TypeToken>() {}, name);
+ super(typeTokenListWithSubtype((Class)subType.getRawType()), name);
this.subType = (Class) subType.getRawType();
}
public Builder(Class subType, String name) {
- super(new TypeToken>() {}, name);
+ super(typeTokenListWithSubtype(subType), name);
this.subType = checkNotNull(subType, "subType");
}
public Builder(ListConfigKey key) {
@@ -118,11 +121,31 @@ public ListConfigKey(Class subType, String name, String description) {
this(subType, name, description, null);
}
- @SuppressWarnings({ "unchecked", "rawtypes" })
+ @SuppressWarnings("unchecked")
public ListConfigKey(Class subType, String name, String description, List extends V> defaultValue) {
- super((Class)List.class, subType, name, description, (List)defaultValue);
+ super(typeTokenListWithSubtype(subType), subType, name, description, (List) defaultValue);
}
+ @SuppressWarnings("unchecked")
+ private static TypeToken> typeTokenListWithSubtype(final Class subType) {
+ return (TypeToken>) TypeToken.of(new ParameterizedType() {
+ @Override
+ public Type getRawType() {
+ return List.class;
+ }
+
+ @Override
+ public Type getOwnerType() {
+ return null;
+ }
+
+ @Override
+ public Type[] getActualTypeArguments() {
+ return new Type[] { subType };
+ }
+ });
+ }
+
@Override
public String toString() {
return String.format("%s[ListConfigKey:%s]", name, getTypeName());
@@ -143,7 +166,7 @@ public static class ListModifications extends StructuredModifications {
/** when passed as a value to a ListConfigKey, causes each of these items to be added.
* if you have just one, no need to wrap in a mod. */
// to prevent confusion (e.g. if a list is passed) we require two objects here.
- public static final ListModification add(final T o1, final T o2, final T ...oo) {
+ public static final ListModification add(final T o1, final T o2, @SuppressWarnings("unchecked") final T ...oo) {
List l = new ArrayList();
l.add(o1); l.add(o2);
for (T o: oo) l.add(o);
diff --git a/core/src/main/java/org/apache/brooklyn/core/config/MapConfigKey.java b/core/src/main/java/org/apache/brooklyn/core/config/MapConfigKey.java
index e213bf76e6..2536e8ff25 100644
--- a/core/src/main/java/org/apache/brooklyn/core/config/MapConfigKey.java
+++ b/core/src/main/java/org/apache/brooklyn/core/config/MapConfigKey.java
@@ -20,6 +20,8 @@
import static com.google.common.base.Preconditions.checkNotNull;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -68,12 +70,13 @@ public static Builder builder(MapConfigKey key) {
public static class Builder extends BasicConfigKey.Builder,Builder> {
protected Class subType;
+ @SuppressWarnings("unchecked")
public Builder(TypeToken subType, String name) {
- super(new TypeToken>() {}, name);
+ super(typeTokenMapWithSubtype( (Class) subType.getRawType() ), name);
this.subType = (Class) subType.getRawType();
}
public Builder(Class subType, String name) {
- super(new TypeToken>() {}, name);
+ super(typeTokenMapWithSubtype(subType), name);
this.subType = checkNotNull(subType, "subType");
}
public Builder(MapConfigKey key) {
@@ -112,6 +115,26 @@ protected MapConfigKey(Builder builder) {
super(builder, builder.subType);
}
+ @SuppressWarnings("unchecked")
+ private static TypeToken> typeTokenMapWithSubtype(final Class subType) {
+ return (TypeToken>) TypeToken.of(new ParameterizedType() {
+ @Override
+ public Type getRawType() {
+ return Map.class;
+ }
+
+ @Override
+ public Type getOwnerType() {
+ return null;
+ }
+
+ @Override
+ public Type[] getActualTypeArguments() {
+ return new Type[] { String.class, subType };
+ }
+ });
+ }
+
public MapConfigKey(Class subType, String name) {
this(subType, name, name, null);
}
@@ -123,9 +146,8 @@ public MapConfigKey(Class subType, String name, String description) {
// TODO it isn't clear whether defaultValue is an initialValue, or a value to use when map is empty
// probably the latter, currently ... but maybe better to say that map configs are never null,
// and defaultValue is really an initial value?
- @SuppressWarnings({ "unchecked", "rawtypes" })
public MapConfigKey(Class subType, String name, String description, Map defaultValue) {
- super((Class)Map.class, subType, name, description, defaultValue);
+ super(typeTokenMapWithSubtype(subType), subType, name, description, defaultValue);
}
@Override
diff --git a/core/src/main/java/org/apache/brooklyn/core/config/SetConfigKey.java b/core/src/main/java/org/apache/brooklyn/core/config/SetConfigKey.java
index 495a82c8e6..61f7f3e768 100644
--- a/core/src/main/java/org/apache/brooklyn/core/config/SetConfigKey.java
+++ b/core/src/main/java/org/apache/brooklyn/core/config/SetConfigKey.java
@@ -20,6 +20,8 @@
import static com.google.common.base.Preconditions.checkNotNull;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
@@ -57,12 +59,13 @@ public class SetConfigKey extends AbstractCollectionConfigKey, Set extends BasicConfigKey.Builder,Builder> {
protected Class subType;
+ @SuppressWarnings("unchecked")
public Builder(TypeToken subType, String name) {
- super(new TypeToken>() {}, name);
+ super(typeTokenSetWithSubtype((Class)subType.getRawType()), name);
this.subType = (Class) subType.getRawType();
}
public Builder(Class subType, String name) {
- super(new TypeToken>() {}, name);
+ super(typeTokenSetWithSubtype(subType), name);
this.subType = checkNotNull(subType, "subType");
}
public Builder(SetConfigKey key) {
@@ -109,11 +112,31 @@ public SetConfigKey(Class subType, String name, String description) {
this(subType, name, description, null);
}
- @SuppressWarnings({ "unchecked", "rawtypes" })
+ @SuppressWarnings("unchecked")
public SetConfigKey(Class subType, String name, String description, Set extends V> defaultValue) {
- super((Class)Set.class, subType, name, description, (Set) defaultValue);
+ super(typeTokenSetWithSubtype(subType), subType, name, description, (Set)defaultValue);
}
+ @SuppressWarnings("unchecked")
+ private static TypeToken> typeTokenSetWithSubtype(final Class subType) {
+ return (TypeToken>) TypeToken.of(new ParameterizedType() {
+ @Override
+ public Type getRawType() {
+ return Set.class;
+ }
+
+ @Override
+ public Type getOwnerType() {
+ return null;
+ }
+
+ @Override
+ public Type[] getActualTypeArguments() {
+ return new Type[] { subType };
+ }
+ });
+ }
+
@Override
public String toString() {
return String.format("%s[SetConfigKey:%s]", name, getTypeName());
@@ -134,7 +157,7 @@ public static class SetModifications extends StructuredModifications {
/** when passed as a value to a SetConfigKey, causes each of these items to be added.
* if you have just one, no need to wrap in a mod. */
// to prevent confusion (e.g. if a set is passed) we require two objects here.
- public static final SetModification add(final T o1, final T o2, final T ...oo) {
+ public static final SetModification add(final T o1, final T o2, @SuppressWarnings("unchecked") final T ...oo) {
Set l = new LinkedHashSet();
l.add(o1); l.add(o2);
for (T o: oo) l.add(o);
diff --git a/core/src/main/java/org/apache/brooklyn/core/config/internal/AbstractCollectionConfigKey.java b/core/src/main/java/org/apache/brooklyn/core/config/internal/AbstractCollectionConfigKey.java
index c7f977595b..eb432fc15a 100644
--- a/core/src/main/java/org/apache/brooklyn/core/config/internal/AbstractCollectionConfigKey.java
+++ b/core/src/main/java/org/apache/brooklyn/core/config/internal/AbstractCollectionConfigKey.java
@@ -33,6 +33,7 @@
import org.slf4j.LoggerFactory;
import com.google.common.collect.Iterables;
+import com.google.common.reflect.TypeToken;
public abstract class AbstractCollectionConfigKey, V> extends AbstractStructuredConfigKey {
@@ -47,6 +48,10 @@ protected AbstractCollectionConfigKey(Class type, Class subType, String na
super(type, subType, name, description, defaultValue);
}
+ public AbstractCollectionConfigKey(TypeToken type, Class subType, String name, String description, T defaultValue) {
+ super(type, subType, name, description, defaultValue);
+ }
+
public ConfigKey subKey() {
String subName = Identifiers.makeRandomId(8);
return new SubElementConfigKey(this, subType, getName()+"."+subName, "element of "+getName()+", uid "+subName, null);
diff --git a/core/src/main/java/org/apache/brooklyn/core/config/internal/AbstractStructuredConfigKey.java b/core/src/main/java/org/apache/brooklyn/core/config/internal/AbstractStructuredConfigKey.java
index d834283bed..88aff6489e 100644
--- a/core/src/main/java/org/apache/brooklyn/core/config/internal/AbstractStructuredConfigKey.java
+++ b/core/src/main/java/org/apache/brooklyn/core/config/internal/AbstractStructuredConfigKey.java
@@ -29,6 +29,7 @@
import org.apache.brooklyn.util.exceptions.Exceptions;
import com.google.common.collect.Maps;
+import com.google.common.reflect.TypeToken;
public abstract class AbstractStructuredConfigKey extends BasicConfigKey implements StructuredConfigKey {
@@ -46,6 +47,11 @@ public AbstractStructuredConfigKey(Class type, Class subType, String name,
this.subType = subType;
}
+ public AbstractStructuredConfigKey(TypeToken type, Class subType, String name, String description, T defaultValue) {
+ super(type, name, description, defaultValue);
+ this.subType = subType;
+ }
+
protected ConfigKey subKey(String subName) {
return subKey(subName, "sub-element of " + getName() + ", named " + subName);
}
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/AddEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/AddEffector.java
index 04c136433c..ab089fcdc6 100644
--- a/core/src/main/java/org/apache/brooklyn/core/effector/AddEffector.java
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/AddEffector.java
@@ -31,7 +31,10 @@
import org.apache.brooklyn.core.effector.Effectors.EffectorBuilder;
import org.apache.brooklyn.core.entity.EntityInternal;
import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.core.yoml.YomlConfigBagConstructor;
import org.apache.brooklyn.util.text.Strings;
+import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsTopLevel;
+import org.apache.brooklyn.util.yoml.annotations.YomlRenameKey.YomlRenameDefaultKey;
import com.google.common.annotations.Beta;
import com.google.common.base.Preconditions;
@@ -56,6 +59,9 @@
*
* @since 0.7.0 */
@Beta
+@YomlConfigBagConstructor("")
+@YomlAllFieldsTopLevel
+@YomlRenameDefaultKey("name")
public class AddEffector implements EntityInitializer {
public static final ConfigKey EFFECTOR_NAME = ConfigKeys.newStringConfigKey("name");
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/AddSensor.java b/core/src/main/java/org/apache/brooklyn/core/effector/AddSensor.java
index 92cc4ec140..6312fd9682 100644
--- a/core/src/main/java/org/apache/brooklyn/core/effector/AddSensor.java
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/AddSensor.java
@@ -18,6 +18,7 @@
*/
package org.apache.brooklyn.core.effector;
+import java.lang.reflect.Field;
import java.util.Map;
import org.apache.brooklyn.api.entity.Entity;
@@ -30,9 +31,17 @@
import org.apache.brooklyn.core.sensor.Sensors;
import org.apache.brooklyn.util.core.ClassLoaderUtils;
import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.core.yoml.YomlConfigBagConstructor;
+import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.guava.Maybe;
import org.apache.brooklyn.util.javalang.Boxing;
+import org.apache.brooklyn.util.javalang.Reflections;
import org.apache.brooklyn.util.time.Duration;
+import org.apache.brooklyn.util.yoml.annotations.Alias;
+import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsTopLevel;
+import org.apache.brooklyn.util.yoml.annotations.YomlRenameKey.YomlRenameDefaultKey;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import com.google.common.annotations.Beta;
import com.google.common.base.Preconditions;
@@ -47,18 +56,25 @@
* @since 0.7.0
*/
@Beta
+@YomlConfigBagConstructor("")
+@YomlAllFieldsTopLevel
+@YomlRenameDefaultKey("name")
public class AddSensor implements EntityInitializer {
+ private static final Logger log = LoggerFactory.getLogger(AddSensor.class);
+
public static final ConfigKey SENSOR_NAME = ConfigKeys.newStringConfigKey("name", "The name of the sensor to create");
public static final ConfigKey SENSOR_PERIOD = ConfigKeys.newConfigKey(Duration.class, "period", "Period, including units e.g. 1m or 5s or 200ms; default 5 minutes", Duration.FIVE_MINUTES);
+ @Alias({"sensor-type","value-type"})
public static final ConfigKey SENSOR_TYPE = ConfigKeys.newStringConfigKey("targetType", "Target type for the value; default String", "java.lang.String");
protected final String name;
protected final Duration period;
- protected final String type;
+ protected final String targetType;
protected AttributeSensor sensor;
- protected final ConfigBag params;
-
+
+ private ConfigBag extraParams;
+
public AddSensor(Map params) {
this(ConfigBag.newInstance(params));
}
@@ -66,18 +82,93 @@ public AddSensor(Map params) {
public AddSensor(final ConfigBag params) {
this.name = Preconditions.checkNotNull(params.get(SENSOR_NAME), "Name must be supplied when defining a sensor");
this.period = params.get(SENSOR_PERIOD);
- this.type = params.get(SENSOR_TYPE);
- this.params = params;
+
+ this.targetType = params.get(SENSOR_TYPE);
+ this.type = null;
}
-
+
+ protected void rememberUnusedParams(ConfigBag bag) {
+ saveExtraParams(bag, false);
+ }
+ protected void rememberAllParams(ConfigBag bag) {
+ saveExtraParams(bag, false);
+ }
+ private void saveExtraParams(ConfigBag bag, boolean justUnused) {
+ if (extraParams==null) {
+ extraParams = ConfigBag.newInstance();
+ }
+ if (justUnused) {
+ extraParams.putAll(params.getUnusedConfig());
+ } else {
+ this.extraParams.copy(bag);
+ }
+ }
+ protected ConfigBag getRememberedParams() {
+ if (params!=null) {
+ synchronized (this) {
+ readResolve();
+ }
+ }
+ if (extraParams==null) return ConfigBag.newInstance();
+ return extraParams;
+ }
+
@Override
public void apply(EntityLocal entity) {
sensor = newSensor(entity);
((EntityInternal) entity).getMutableEntityType().addSensor(sensor);
}
+ // old names, for XML deserializaton compatiblity
+ private final String type;
+ /** @deprecated since 0.9.0 and semantics slightly different; accessors should use {@link #getRememberedParams()} */
+ private ConfigBag params;
+ private Object readResolve() {
+ try {
+ if (type!=null) {
+ if (targetType==null) {
+ Field f = Reflections.findField(getClass(), "targetType");
+ f.setAccessible(true);
+ f.set(this, type);
+ } else if (!targetType.equals(type)) {
+ throw new IllegalStateException("Incompatible target types found for "+this+": "+type+" vs "+targetType);
+ }
+
+ Field f = Reflections.findField(getClass(), "type");
+ f.setAccessible(true);
+ f.set(this, null);
+ }
+
+ if (params!=null) {
+ if (extraParams==null) {
+ extraParams = params;
+ } else if (!extraParams.getAllConfigAsConfigKeyMap().equals(params.getAllConfigAsConfigKeyMap())) {
+ throw new IllegalStateException("Incompatible extra params found for "+this+": "+params+" vs "+extraParams);
+ }
+ params = null;
+ }
+ } catch (Exception e) {
+ throw Exceptions.propagate(e);
+ }
+ return this;
+ }
+
+ private Object writeReplace() {
+ try {
+ // make this null if there's nothing
+ if (extraParams!=null && extraParams.isEmpty()) {
+ Field f = Reflections.findField(getClass(), "extraParams");
+ f.setAccessible(true);
+ f.set(this, null);
+ }
+ } catch (Exception e) {
+ throw Exceptions.propagate(e);
+ }
+ return this;
+ }
+
private AttributeSensor newSensor(Entity entity) {
- String className = getFullClassName(type);
+ String className = getFullClassName(targetType);
Class clazz = getType(entity, className);
return Sensors.newSensor(clazz, name);
}
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/ssh/SshCommandEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/ssh/SshCommandEffector.java
index 2df5094fe3..6993ebf29a 100644
--- a/core/src/main/java/org/apache/brooklyn/core/effector/ssh/SshCommandEffector.java
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/ssh/SshCommandEffector.java
@@ -48,16 +48,20 @@
import org.apache.brooklyn.util.core.task.Tasks;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.guava.Maybe;
+import org.apache.brooklyn.util.yoml.annotations.Alias;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicates;
import com.google.common.collect.Maps;
+@Alias(preferred="ssh-effector")
public final class SshCommandEffector extends AddEffector {
+ @Alias({"script", "run"})
public static final ConfigKey EFFECTOR_COMMAND = ConfigKeys.newStringConfigKey("command");
public static final ConfigKey EFFECTOR_EXECUTION_DIR = SshCommandSensor.SENSOR_EXECUTION_DIR;
- public static final MapConfigKey EFFECTOR_SHELL_ENVIRONMENT = BrooklynConfigKeys.SHELL_ENVIRONMENT;
+ @Alias(preferred="env", value={"vars","variables","environment"})
+ public static final MapConfigKey EFFECTOR_SHELL_ENVIRONMENT = BrooklynConfigKeys.SHELL_ENVIRONMENT_STRING_VALUES;
public enum ExecutionTarget {
ENTITY,
@@ -87,7 +91,7 @@ public static EffectorBuilder newEffectorBuilder(ConfigBag params) {
protected static class Body extends EffectorBody {
private final Effector> effector;
private final String command;
- private final Map shellEnv;
+ private final Map shellEnv;
private final String executionDir;
private final ExecutionTarget executionTarget;
@@ -162,7 +166,7 @@ public SshEffectorTaskFactory makePartialTaskFactory(ConfigBag params, E
if (shellEnv != null) env.putAll(shellEnv);
// Add the shell environment entries from our invocation
- Map effectorEnv = params.get(EFFECTOR_SHELL_ENVIRONMENT);
+ Map effectorEnv = params.get(EFFECTOR_SHELL_ENVIRONMENT);
if (effectorEnv != null) env.putAll(effectorEnv);
// Try to resolve the configuration in the env Map
diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/BrooklynConfigKeys.java b/core/src/main/java/org/apache/brooklyn/core/entity/BrooklynConfigKeys.java
index f3dca6b9fd..e3af056a9e 100644
--- a/core/src/main/java/org/apache/brooklyn/core/entity/BrooklynConfigKeys.java
+++ b/core/src/main/java/org/apache/brooklyn/core/entity/BrooklynConfigKeys.java
@@ -138,12 +138,21 @@ public class BrooklynConfigKeys {
.runtimeInheritance(BasicConfigInheritance.NOT_REINHERITED)
.build();
- public static final MapConfigKey SHELL_ENVIRONMENT = new MapConfigKey.Builder(Object.class, "shell.env")
- .description("Map of environment variables to pass to the runtime shell. Non-string values are serialized to json before passed to the shell.")
- .defaultValue(ImmutableMap.of())
- .typeInheritance(BasicConfigInheritance.DEEP_MERGE)
- .runtimeInheritance(BasicConfigInheritance.NOT_REINHERITED_ELSE_DEEP_MERGE)
- .build();
+ public static final MapConfigKey SHELL_ENVIRONMENT_STRING_VALUES = new MapConfigKey.Builder(String.class, "shell.env")
+ .description("Map of environment variables to pass to the runtime shell")
+ .defaultValue(ImmutableMap.of())
+ .typeInheritance(BasicConfigInheritance.DEEP_MERGE)
+ .runtimeInheritance(BasicConfigInheritance.NOT_REINHERITED_ELSE_DEEP_MERGE)
+ .build();
+
+ public static final MapConfigKey SHELL_ENVIRONMENT_OBJECT_VALUE = new MapConfigKey.Builder(Object.class, "shell.env")
+ .description("Map of environment variables to pass to the runtime shell. Non-string values are serialized to json before passed to the shell.")
+ .defaultValue(ImmutableMap.of())
+ .typeInheritance(BasicConfigInheritance.DEEP_MERGE)
+ .runtimeInheritance(BasicConfigInheritance.NOT_REINHERITED_ELSE_DEEP_MERGE)
+ .build();
+
+ public static final MapConfigKey