Merge branch 'master' into blueprint-for-app-spec
diff --git a/brooklyn-server/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynAssemblyTemplateInstantiator.java b/brooklyn-server/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynAssemblyTemplateInstantiator.java
index 5840440..931c2e7 100644
--- a/brooklyn-server/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynAssemblyTemplateInstantiator.java
+++ b/brooklyn-server/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynAssemblyTemplateInstantiator.java
@@ -89,20 +89,20 @@
// first build the children into an empty shell app
List<EntitySpec<?>> childSpecs = createServiceSpecs(template, platform, loader, encounteredTypeSymbolicNames);
for (EntitySpec<?> childSpec : childSpecs) {
- app.child(childSpec);
+ // children get parsed and unwrapped irrespective of the NEVER_UNWRAP_APPS setting;
+ // we could support a NEVER_UNWRAP_NESTED_ENTITIES item but i don't know if there's a use case
+ app.child(EntityManagementUtils.unwrapEntity(childSpec));
}
- if (shouldUnwrap(template, app)) {
+ if (allowedToUnwrap(template, app)) {
app = EntityManagementUtils.unwrapApplication(app);
}
return app;
}
- private boolean shouldUnwrap(AssemblyTemplate template, EntitySpec<? extends Application> app) {
- if (Boolean.TRUE.equals(TypeCoercions.coerce(template.getCustomAttributes().get(NEVER_UNWRAP_APPS_PROPERTY), Boolean.class)))
- return false;
- return EntityManagementUtils.canPromoteWrappedApplication(app);
+ private boolean allowedToUnwrap(AssemblyTemplate template, EntitySpec<? extends Application> app) {
+ return !(Boolean.TRUE.equals(TypeCoercions.coerce(template.getCustomAttributes().get(NEVER_UNWRAP_APPS_PROPERTY), Boolean.class)));
}
private List<EntitySpec<?>> buildTemplateServicesAsSpecs(BrooklynClassLoadingContext loader, AssemblyTemplate template, CampPlatform platform, Set<String> encounteredRegisteredTypeIds) {
diff --git a/brooklyn-server/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynComponentTemplateResolver.java b/brooklyn-server/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynComponentTemplateResolver.java
index 8f4db88..e930635 100644
--- a/brooklyn-server/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynComponentTemplateResolver.java
+++ b/brooklyn-server/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynComponentTemplateResolver.java
@@ -49,6 +49,7 @@
import org.apache.brooklyn.core.config.ConfigKeys;
import org.apache.brooklyn.core.mgmt.BrooklynTags;
import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
+import org.apache.brooklyn.core.mgmt.EntityManagementUtils;
import org.apache.brooklyn.core.mgmt.ManagementContextInjectable;
import org.apache.brooklyn.core.mgmt.classloading.JavaBrooklynClassLoadingContext;
import org.apache.brooklyn.core.resolve.entity.EntitySpecResolver;
@@ -207,7 +208,7 @@
// encounteredRegisteredTypeIds must contain the items currently being loaded (the dependency chain),
// but not parent items in this type already resolved.
EntitySpec<? extends Entity> childSpec = entityResolver.resolveSpec(encounteredRegisteredTypeIds);
- spec.child(childSpec);
+ spec.child(EntityManagementUtils.unwrapEntity(childSpec));
}
}
@@ -365,7 +366,9 @@
@SuppressWarnings("unchecked")
Map<String, Object> resolvedConfig = (Map<String, Object>)transformSpecialFlags(specConfig.getSpecConfiguration());
specConfig.setSpecConfiguration(resolvedConfig);
- return Factory.newInstance(getLoader(), specConfig.getSpecConfiguration()).resolveSpec(encounteredRegisteredTypeIds);
+ EntitySpec<?> entitySpec = Factory.newInstance(getLoader(), specConfig.getSpecConfiguration()).resolveSpec(encounteredRegisteredTypeIds);
+
+ return EntityManagementUtils.unwrapEntity(entitySpec);
}
if (flag instanceof ManagementContextInjectable) {
log.debug("Injecting Brooklyn management context info object: {}", flag);
diff --git a/brooklyn-server/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampResolver.java b/brooklyn-server/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampResolver.java
index 602baa3..5615592 100644
--- a/brooklyn-server/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampResolver.java
+++ b/brooklyn-server/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampResolver.java
@@ -20,7 +20,6 @@
import java.util.Set;
-import com.google.common.collect.Iterables;
import org.apache.brooklyn.api.entity.Application;
import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.api.entity.EntitySpec;
@@ -131,11 +130,12 @@
if (instantiator instanceof AssemblyTemplateSpecInstantiator) {
EntitySpec<? extends Application> appSpec = ((AssemblyTemplateSpecInstantiator)instantiator).createApplicationSpec(at, camp, loader, encounteredTypes);
- if (!isApplication && EntityManagementUtils.canPromoteChildrenInWrappedApplication(appSpec)) {
- EntitySpec<?> childSpec = Iterables.getOnlyElement(appSpec.getChildren());
- EntityManagementUtils.mergeWrapperParentSpecToChildEntity(appSpec, childSpec);
- return childSpec;
- }
+ // above will unwrap but only if it's an Application (and it's permitted);
+ // but it doesn't know whether we need an App or if an Entity is okay
+ if (!isApplication) return EntityManagementUtils.unwrapEntity(appSpec);
+ // if we need an App then definitely *don't* unwrap here because
+ // the instantiator will have done that, and it knows if the plan
+ // specified a wrapped app explicitly (whereas we don't easily know that here!)
return appSpec;
} else {
diff --git a/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlTemplateTest.java b/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlTemplateTest.java
index c3dbc48..06ac681 100644
--- a/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlTemplateTest.java
+++ b/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlTemplateTest.java
@@ -18,13 +18,30 @@
*/
package org.apache.brooklyn.camp.brooklyn.catalog;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.util.List;
+
+import org.apache.brooklyn.api.entity.Application;
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.EntitySpec;
import org.apache.brooklyn.api.typereg.RegisteredType;
import org.apache.brooklyn.camp.brooklyn.AbstractYamlTest;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.mgmt.BrooklynTags;
+import org.apache.brooklyn.core.mgmt.BrooklynTags.NamedStringTag;
+import org.apache.brooklyn.core.mgmt.EntityManagementUtils;
import org.apache.brooklyn.core.mgmt.osgi.OsgiStandaloneTest;
+import org.apache.brooklyn.core.test.entity.TestEntity;
import org.apache.brooklyn.core.typereg.RegisteredTypePredicates;
import org.apache.brooklyn.core.typereg.RegisteredTypes;
+import org.apache.brooklyn.entity.group.DynamicCluster;
+import org.apache.brooklyn.entity.stock.BasicApplication;
+import org.apache.brooklyn.test.Asserts;
import org.apache.brooklyn.test.support.TestResourceUnavailableException;
import org.apache.brooklyn.util.osgi.OsgiTestResources;
+import org.python.google.common.collect.Iterables;
import org.testng.Assert;
import org.testng.TestListenerAdapter;
import org.testng.TestNG;
@@ -63,6 +80,131 @@
deleteCatalogEntity("t1");
}
+ public void testServiceTypeEntityOfTypeCatalogTemplateNotWrapped() throws Exception {
+ addCatalogItems(
+ "brooklyn.catalog:",
+ " id: t1",
+ " item_type: template",
+ " name: myT1",
+ " item:",
+ " services:",
+ " - type: " + TestEntity.class.getName());
+ addCatalogItems(
+ "brooklyn.catalog:",
+ " id: t2",
+ " item_type: template",
+ " name: myT2",
+ " item:",
+ " services:",
+ " - type: t1",
+ " - type: t1");
+
+ Entity app = createAndStartApplication(
+ "services:",
+ "- type: t2");
+ waitForApplicationTasks(app);
+
+ Entities.dumpInfo(app);
+ Entity t1a = Iterables.get(app.getChildren(), 0);
+ Entity t1b = Iterables.get(app.getChildren(), 1);
+ assertEquals(app.getChildren().size(), 2);
+ assertEquals(t1a.getChildren().size(), 0);
+ assertEquals(t1b.getChildren().size(), 0);
+
+ assertTrue(app instanceof BasicApplication);
+ assertTrue(t1a instanceof TestEntity);
+ assertTrue(t1b instanceof TestEntity);
+ }
+
+ @Test
+ public void testChildEntityOfTypeCatalogTemplateNotWrapped() throws Exception {
+ addCatalogItems(
+ "brooklyn.catalog:",
+ " id: t1",
+ " item_type: template",
+ " name: myT1",
+ " item:",
+ " services:",
+ " - type: " + TestEntity.class.getName());
+ addCatalogItems(
+ "brooklyn.catalog:",
+ " id: t2",
+ " item_type: template",
+ " name: myT2",
+ " item:",
+ " services:",
+ " - type: " + TestEntity.class.getName(),
+ " brooklyn.children:",
+ " - type: t1");
+
+ Entity app = createAndStartApplication(
+ "services:",
+ "- type: t2");
+ waitForApplicationTasks(app);
+
+ Entities.dumpInfo(app);
+ Entity t2 = Iterables.getOnlyElement(app.getChildren());
+ Entity t1 = Iterables.getOnlyElement(t2.getChildren());
+ assertEquals(t1.getChildren().size(), 0);
+
+ assertTrue(app instanceof BasicApplication);
+ assertTrue(t1 instanceof TestEntity);
+ assertTrue(t2 instanceof TestEntity);
+ }
+
+ @Test
+ public void testMemberSpecEntityOfTypeCatalogTemplateNotWrapped() throws Exception {
+ addCatalogItems(
+ "brooklyn.catalog:",
+ " id: t1",
+ " item_type: template",
+ " name: myT1",
+ " item:",
+ " services:",
+ " - type: " + TestEntity.class.getName());
+ addCatalogItems(
+ "brooklyn.catalog:",
+ " id: t2",
+ " item_type: template",
+ " name: myT2",
+ " item:",
+ " services:",
+ " - type: " + DynamicCluster.class.getName(),
+ " brooklyn.config:",
+ " memberSpec:",
+ " $brooklyn:entitySpec:",
+ " type: t1",
+ " cluster.initial.size: 1");
+
+ Entity app = createAndStartApplication(
+ "location: localhost",
+ "services:",
+ "- type: t2");
+ waitForApplicationTasks(app);
+
+ Entities.dumpInfo(app);
+ DynamicCluster t2 = (DynamicCluster) Iterables.getOnlyElement(app.getChildren());
+ Entity t1 = Iterables.getOnlyElement(t2.getMembers());
+ assertEquals(t1.getChildren().size(), 0);
+
+ assertTrue(app instanceof BasicApplication);
+ assertTrue(t2 instanceof DynamicCluster);
+ assertTrue(t1 instanceof TestEntity);
+ }
+
+ // XXX this should be made to work!
+// @Test
+// public void testPlanYamlTagOnSpecFromItem() throws Exception {
+// makeItem();
+// EntitySpec<? extends Application> spec = EntityManagementUtils.createEntitySpecForApplication(mgmt(),
+// "services: [ { type: t1 } ]\n" +
+// "location: localhost");
+// List<NamedStringTag> yamls = BrooklynTags.findAll(BrooklynTags.YAML_SPEC_KIND, spec.getTags());
+// Assert.assertEquals(yamls.size(), 1, "Expected 1 yaml tag; instead had: "+yamls);
+// String yaml = Iterables.getOnlyElement(yamls).getContents();
+// Asserts.assertStringContains(yaml, "services:", "t1", "localhost");
+// }
+
private RegisteredType makeItem() {
TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH);
diff --git a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/mgmt/BrooklynTags.java b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/mgmt/BrooklynTags.java
index e8d7915..ffed4f8 100644
--- a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/mgmt/BrooklynTags.java
+++ b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/mgmt/BrooklynTags.java
@@ -21,6 +21,7 @@
import java.io.Serializable;
import java.util.List;
+import org.apache.brooklyn.util.collections.MutableList;
import org.codehaus.jackson.annotate.JsonIgnore;
import org.codehaus.jackson.annotate.JsonProperty;
@@ -73,7 +74,7 @@
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
- ListTag that = (ListTag) o;
+ ListTag<?> that = (ListTag<?>) o;
return list == null ? that.list == null : list.equals(that.list);
}
@@ -117,5 +118,14 @@
}
return null;
}
-
+
+ public static List<NamedStringTag> findAll(String kind, Iterable<Object> tags) {
+ List<NamedStringTag> result = MutableList.of();
+ for (Object object: tags) {
+ if (object instanceof NamedStringTag && kind.equals(((NamedStringTag)object).kind))
+ result.add( (NamedStringTag) object );
+ }
+ return result;
+ }
+
}
diff --git a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/mgmt/EntityManagementUtils.java b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/mgmt/EntityManagementUtils.java
index ef461db..4b6b8ce 100644
--- a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/mgmt/EntityManagementUtils.java
+++ b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/mgmt/EntityManagementUtils.java
@@ -50,7 +50,6 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.google.common.annotations.Beta;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -67,11 +66,12 @@
/**
* A marker config value which indicates that an {@link Application} entity was created automatically,
* needed because a plan might give multiple top-level entities or a non-Application top-level entity,
- * but brooklyn requires an {@link Application} at the root.
+ * in a context where Brooklyn requires an {@link Application} at the root.
* <p>
* Typically when such a wrapper app wraps another {@link Application}
- * (or when we are adding to an existing entity and it wraps multiple {@link Entity} instances)
- * it will be unwrapped. See {@link #newWrapperApp()} and {@link #unwrapApplication(EntitySpec)}.
+ * (or where we are looking for a single {@link Entity}, or a list to add, and they are so wrapped)
+ * it will be unwrapped.
+ * See {@link #newWrapperApp()} and {@link #unwrapApplication(EntitySpec)}.
*/
public static final ConfigKey<Boolean> WRAPPER_APP_MARKER = ConfigKeys.newBooleanConfigKey("brooklyn.wrapper_app");
@@ -159,21 +159,16 @@
// see whether we can promote children
List<EntitySpec<?>> specs = MutableList.of();
- if (canPromoteChildrenInWrappedApplication(specA)) {
- // we can promote
- for (EntitySpec<?> specC: specA.getChildren()) {
- mergeWrapperParentSpecToChildEntity(specA, specC);
- specs.add(specC);
- }
- } else {
+ if (!canUnwrapEntity(specA)) {
// if not promoting, set a nice name if needed
if (Strings.isEmpty(specA.getDisplayName())) {
int size = specA.getChildren().size();
String childrenCountString = size+" "+(size!=1 ? "children" : "child");
specA.displayName("Dynamically added "+childrenCountString);
}
- specs.add(specA);
}
+
+ specs.add(unwrapEntity(specA));
final List<Entity> children = MutableList.of();
for (EntitySpec<?> spec: specs) {
@@ -219,27 +214,37 @@
return CreationResult.of(children, task);
}
-
- /** if an application should be unwrapped, it does so, returning the child; otherwise returns the argument passed in.
- * use {@link #canPromoteWrappedApplication(EntitySpec)} to test whether it will unwrap. */
- public static EntitySpec<? extends Application> unwrapApplication(EntitySpec<? extends Application> wrapperApplication) {
- if (canPromoteWrappedApplication(wrapperApplication)) {
- @SuppressWarnings("unchecked")
- EntitySpec<? extends Application> wrappedApplication = (EntitySpec<? extends Application>) Iterables.getOnlyElement( wrapperApplication.getChildren() );
-
- // if promoted, apply the transformations done to the app
- // (transformations will be done by the resolveSpec call above, but we are collapsing oldApp so transfer to app=newApp)
- EntityManagementUtils.mergeWrapperParentSpecToChildEntity(wrapperApplication, wrappedApplication);
- return wrappedApplication;
+
+ /** Unwraps a single {@link Entity} if appropriate. See {@link #WRAPPER_APP_MARKER}.
+ * Also see {@link #canUnwrapEntity(EntitySpec)} to test whether it will unwrap. */
+ public static EntitySpec<? extends Entity> unwrapEntity(EntitySpec<? extends Entity> wrapperApplication) {
+ if (!canUnwrapEntity(wrapperApplication)) {
+ return wrapperApplication;
}
- return wrapperApplication;
+ EntitySpec<?> wrappedEntity = Iterables.getOnlyElement(wrapperApplication.getChildren());
+ @SuppressWarnings("unchecked")
+ EntitySpec<? extends Application> wrapperApplicationTyped = (EntitySpec<? extends Application>) wrapperApplication;
+ EntityManagementUtils.mergeWrapperParentSpecToChildEntity(wrapperApplicationTyped, wrappedEntity);
+ return wrappedEntity;
+ }
+
+ /** Unwraps a wrapped {@link Application} if appropriate.
+ * This is like {@link #canUnwrapEntity(EntitySpec)} with an additional check that the wrapped child is an {@link Application}.
+ * See {@link #WRAPPER_APP_MARKER} for an overview.
+ * Also see {@link #canUnwrapApplication(EntitySpec)} to test whether it will unwrap. */
+ public static EntitySpec<? extends Application> unwrapApplication(EntitySpec<? extends Application> wrapperApplication) {
+ if (!canUnwrapApplication(wrapperApplication)) {
+ return wrapperApplication;
+ }
+ @SuppressWarnings("unchecked")
+ EntitySpec<? extends Application> wrappedApplication = (EntitySpec<? extends Application>) unwrapEntity(wrapperApplication);
+ return wrappedApplication;
}
/** Modifies the child so it includes the inessential setup of its parent,
* for use when unwrapping specific children, but a name or other item may have been set on the parent.
* See {@link #WRAPPER_APP_MARKER}. */
- @Beta //where should this live long-term?
- public static void mergeWrapperParentSpecToChildEntity(EntitySpec<? extends Application> wrapperParent, EntitySpec<?> wrappedChild) {
+ private static void mergeWrapperParentSpecToChildEntity(EntitySpec<? extends Application> wrapperParent, EntitySpec<?> wrappedChild) {
if (Strings.isNonEmpty(wrapperParent.getDisplayName()))
wrappedChild.displayName(wrapperParent.getDisplayName());
if (!wrapperParent.getLocations().isEmpty())
@@ -263,31 +268,42 @@
return EntitySpec.create(BasicApplication.class).configure(WRAPPER_APP_MARKER, true);
}
- /** returns true if the spec is for an empty-ish wrapper app contianing an application,
- * for use when adding from a plan specifying an application which was wrapped because it had to be.
+ /** As {@link #canUnwrapEntity(EntitySpec)}
+ * but additionally requiring that the wrapped item is an {@link Application},
+ * for use when the context requires an {@link Application} ie a root of a spec.
* @see #WRAPPER_APP_MARKER */
+ public static boolean canUnwrapApplication(EntitySpec<? extends Application> wrapperApplication) {
+ if (!canUnwrapEntity(wrapperApplication)) return false;
+
+ EntitySpec<?> childSpec = Iterables.getOnlyElement(wrapperApplication.getChildren());
+ return (childSpec.getType()!=null && Application.class.isAssignableFrom(childSpec.getType()));
+ }
+ /** @deprecated since 0.9.0 use {@link #canUnwrapApplication(EntitySpec)} */ @Deprecated
public static boolean canPromoteWrappedApplication(EntitySpec<? extends Application> app) {
- if (!hasSingleChild(app))
- return false;
-
- EntitySpec<?> childSpec = Iterables.getOnlyElement(app.getChildren());
- if (childSpec.getType()==null || !Application.class.isAssignableFrom(childSpec.getType()))
- return false;
-
- return canPromoteChildrenInWrappedApplication(app);
+ return canUnwrapApplication(app);
}
- /** returns true if the spec is for a wrapper app with no important settings, wrapping a single child.
- * for use when adding from a plan specifying multiple entities but nothing significant at the application level.
- * @see #WRAPPER_APP_MARKER */
- public static boolean canPromoteChildrenInWrappedApplication(EntitySpec<? extends Application> spec) {
+ /** Returns true if the spec is for a wrapper app with no important settings, wrapping a single child entity.
+ * for use when adding from a plan specifying multiple entities but there is nothing significant at the application level,
+ * and the context would like to flatten it to remove the wrapper yielding just a single entity.
+ * (but note the result is not necessarily an {@link Application};
+ * see {@link #canUnwrapApplication(EntitySpec)} if that is required).
+ * <p>
+ * Note callers will normally use one of {@link #unwrapEntity(EntitySpec)} or {@link #unwrapApplication(EntitySpec)}.
+ *
+ * @see #WRAPPER_APP_MARKER for an overview */
+ public static boolean canUnwrapEntity(EntitySpec<? extends Entity> spec) {
return isWrapperApp(spec) && hasSingleChild(spec) &&
- //equivalent to no keys starting with "brooklyn."
- spec.getEnrichers().isEmpty() &&
- spec.getEnricherSpecs().isEmpty() &&
- spec.getInitializers().isEmpty() &&
- spec.getPolicies().isEmpty() &&
- spec.getPolicySpecs().isEmpty();
+ //equivalent to no keys starting with "brooklyn."
+ spec.getEnrichers().isEmpty() &&
+ spec.getEnricherSpecs().isEmpty() &&
+ spec.getInitializers().isEmpty() &&
+ spec.getPolicies().isEmpty() &&
+ spec.getPolicySpecs().isEmpty();
+ }
+ /** @deprecated since 0.9.0 use {@link #canUnwrapEntity(EntitySpec)} */ @Deprecated
+ public static boolean canPromoteChildrenInWrappedApplication(EntitySpec<? extends Application> spec) {
+ return canUnwrapEntity(spec);
}
public static boolean isWrapperApp(EntitySpec<?> spec) {