brooklyn-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From hadr...@apache.org
Subject incubator-brooklyn git commit: Illustration and test for PlanToSpecTransformer which uses XML, and tidies encountered in the process
Date Fri, 21 Aug 2015 01:54:34 GMT
Repository: incubator-brooklyn
Updated Branches:
  refs/heads/master ee9b59a74 -> 2a3603ba1


Illustration and test for PlanToSpecTransformer which uses XML, and tidies encountered in
the process


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/2a3603ba
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/2a3603ba
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/2a3603ba

Branch: refs/heads/master
Commit: 2a3603ba19cc5551bfead2f04158e5f666431de7
Parents: ee9b59a
Author: Alex Heneveld <alex.heneveld@cloudsoftcorp.com>
Authored: Thu Aug 20 21:31:35 2015 +0100
Committer: Alex Heneveld <alex.heneveld@cloudsoftcorp.com>
Committed: Thu Aug 20 21:33:02 2015 +0100

----------------------------------------------------------------------
 .../core/mgmt/EntityManagementUtils.java        | 116 ++++++++++------
 .../brooklyn/core/plan/PlanToSpecFactory.java   |  93 +++++++++++--
 .../core/plan/PlanToSpecTransformer.java        |  32 +++--
 .../core/plan/XmlPlanToSpecTransformer.java     | 132 +++++++++++++++++++
 .../core/plan/XmlPlanToSpecTransformerTest.java |  65 +++++++++
 .../util/core/internal/TypeCoercionsTest.java   |   7 +
 .../api/AssemblyTemplateSpecInstantiator.java   |   4 +-
 .../BrooklynAssemblyTemplateInstantiator.java   |  28 +---
 .../spi/creation/CampToSpecTransformer.java     |  10 +-
 .../test/lite/TestAppAssemblyInstantiator.java  |   3 +-
 .../rest/resources/ApplicationResource.java     |   4 +-
 .../brooklyn/util/exceptions/Exceptions.java    |   4 +-
 12 files changed, 403 insertions(+), 95 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2a3603ba/core/src/main/java/org/apache/brooklyn/core/mgmt/EntityManagementUtils.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/EntityManagementUtils.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/EntityManagementUtils.java
index 1391dc4..b86a0a8 100644
--- a/core/src/main/java/org/apache/brooklyn/core/mgmt/EntityManagementUtils.java
+++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/EntityManagementUtils.java
@@ -18,8 +18,6 @@
  */
 package org.apache.brooklyn.core.mgmt;
 
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Callable;
@@ -40,9 +38,9 @@ import org.apache.brooklyn.core.effector.Effectors;
 import org.apache.brooklyn.core.entity.Entities;
 import org.apache.brooklyn.core.entity.EntityFunctions;
 import org.apache.brooklyn.core.entity.trait.Startable;
-import org.apache.brooklyn.core.plan.PlanNotRecognizedException;
 import org.apache.brooklyn.core.plan.PlanToSpecFactory;
 import org.apache.brooklyn.core.plan.PlanToSpecTransformer;
+import org.apache.brooklyn.entity.stock.BasicApplication;
 import org.apache.brooklyn.util.collections.MutableList;
 import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.core.task.TaskBuilder;
@@ -53,6 +51,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.google.common.annotations.Beta;
+import com.google.common.base.Function;
 import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -65,8 +64,13 @@ public class EntityManagementUtils {
     private static final Logger log = LoggerFactory.getLogger(EntityManagementUtils.class);
 
     /**
-     * A marker config value which indicates that an application was created automatically
-     * to allow the management of a non-app entity.
+     * 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.
+     * <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)}.
      */
     public static final ConfigKey<Boolean> WRAPPER_APP_MARKER = ConfigKeys.newBooleanConfigKey("brooklyn.wrapper_app");
 
@@ -77,35 +81,29 @@ public class EntityManagementUtils {
         return app;
     }
 
-    /** as {@link #createUnstarted(ManagementContext, EntitySpec)} but for a YAML spec */
-    public static <T extends Application> T createUnstarted(ManagementContext mgmt,
String yaml) {
-        EntitySpec<T> spec = createEntitySpec(mgmt, yaml);
+    /** as {@link #createUnstarted(ManagementContext, EntitySpec)} but for a string plan
(e.g. camp yaml) */
+    public static Application createUnstarted(ManagementContext mgmt, String plan) {
+        EntitySpec<? extends Application> spec = createEntitySpecForApplication(mgmt,
plan);
         return createUnstarted(mgmt, spec);
     }
     
-    public static <T extends Application> EntitySpec<T> createEntitySpec(ManagementContext
mgmt, String yaml) {
-        Collection<String> types = new ArrayList<String>();
-        for (PlanToSpecTransformer c : PlanToSpecFactory.all(mgmt)) {
-            try {
-                return c.createApplicationSpec(yaml);
-            } catch (PlanNotRecognizedException e) {
-                types.add(c.getName());
+    public static EntitySpec<? extends Application> createEntitySpecForApplication(ManagementContext
mgmt, final String plan) {
+        return PlanToSpecFactory.attemptWithLoaders(mgmt, new Function<PlanToSpecTransformer,
EntitySpec<? extends Application>>() {
+            @Override
+            public EntitySpec<? extends Application> apply(PlanToSpecTransformer input)
{
+                return input.createApplicationSpec(plan);
             }
-        }
-        throw new PlanNotRecognizedException("Invalid plan, tried parsing with " + types);
+        }).get();
     }
 
     @SuppressWarnings({ "unchecked", "rawtypes" })
-    public static AbstractBrooklynObjectSpec<?, ?> createCatalogSpec(ManagementContext
mgmt, CatalogItem<?, ?> item) {
-        Collection<String> types = new ArrayList<String>();
-        for (PlanToSpecTransformer c : PlanToSpecFactory.all(mgmt)) {
-            try {
-                return c.createCatalogSpec((CatalogItem)item);
-            } catch (PlanNotRecognizedException e) {
-                types.add(c.getName());
+    public static AbstractBrooklynObjectSpec<?, ?> createCatalogSpec(ManagementContext
mgmt, final CatalogItem<?, ?> item) {
+        return PlanToSpecFactory.attemptWithLoaders(mgmt, new Function<PlanToSpecTransformer,
AbstractBrooklynObjectSpec<?, ?>>() {
+            @Override
+            public AbstractBrooklynObjectSpec<?, ?> apply(PlanToSpecTransformer input)
{
+                return input.createCatalogSpec((CatalogItem)item);
             }
-        }
-        throw new PlanNotRecognizedException("Invalid plan, tried parsing with " + types);
+        }).get();
     }
 
     /** container for operation which creates something and which wants to return both
@@ -159,14 +157,14 @@ public class EntityManagementUtils {
 
         ManagementContext mgmt = parent.getApplication().getManagementContext();
 
-        EntitySpec<?> specA = createEntitySpec(mgmt, yaml);
+        EntitySpec<? extends Application> specA = createEntitySpecForApplication(mgmt,
yaml);
 
         // see whether we can promote children
         List<EntitySpec<?>> specs = MutableList.of();
-        if (canPromote(specA)) {
+        if (canPromoteChildrenInWrappedApplication(specA)) {
             // we can promote
             for (EntitySpec<?> specC: specA.getChildren()) {
-                collapseSpec(specA, specC);
+                mergeWrapperParentSpecToChildEntity(specA, specC);
                 specs.add(specC);
             }
         } else {
@@ -225,26 +223,64 @@ public class EntityManagementUtils {
         return CreationResult.of(children, task);
     }
 
-    /** worker method to combine specs */
+    /** 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;
+        }
+        return wrapperApplication;
+    }
+    
+    /** 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 collapseSpec(EntitySpec<?> sourceToBeCollapsed, EntitySpec<?>
targetToBeExpanded) {
-        if (Strings.isEmpty(targetToBeExpanded.getDisplayName()))
-            targetToBeExpanded.displayName(sourceToBeCollapsed.getDisplayName());
-        if (!sourceToBeCollapsed.getLocations().isEmpty())
-            targetToBeExpanded.locations(sourceToBeCollapsed.getLocations());
+    public static void mergeWrapperParentSpecToChildEntity(EntitySpec<? extends Application>
wrapperParent, EntitySpec<?> wrappedChild) {
+        if (Strings.isEmpty(wrappedChild.getDisplayName()))
+            wrappedChild.displayName(wrapperParent.getDisplayName());
+        if (!wrapperParent.getLocations().isEmpty())
+            wrappedChild.locations(wrapperParent.getLocations());
 
         // NB: this clobbers child config; might prefer to deeply merge maps etc
         // (but this should not be surprising, as unwrapping is often parameterising the
nested blueprint, so outer config should dominate) 
-        Map<ConfigKey<?>, Object> configWithoutWrapperMarker = Maps.filterKeys(sourceToBeCollapsed.getConfig(),
Predicates.not(Predicates.<ConfigKey<?>>equalTo(EntityManagementUtils.WRAPPER_APP_MARKER)));
-        targetToBeExpanded.configure(configWithoutWrapperMarker);
-        targetToBeExpanded.configure(sourceToBeCollapsed.getFlags());
+        Map<ConfigKey<?>, Object> configWithoutWrapperMarker = Maps.filterKeys(wrapperParent.getConfig(),
Predicates.not(Predicates.<ConfigKey<?>>equalTo(EntityManagementUtils.WRAPPER_APP_MARKER)));
+        wrappedChild.configure(configWithoutWrapperMarker);
+        wrappedChild.configure(wrapperParent.getFlags());
         
         // TODO copying tags to all entities is not ideal;
         // in particular the BrooklynTags.YAML_SPEC tag will show all entities if the root
has multiple
-        targetToBeExpanded.tags(sourceToBeCollapsed.getTags());
+        wrappedChild.tags(wrapperParent.getTags());
+    }
+
+    public static EntitySpec<? extends Application> newWrapperApp() {
+        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.
+     * @see #WRAPPER_APP_MARKER */
+    public static boolean canPromoteWrappedApplication(EntitySpec<? extends Application>
app) {
+        if (app.getChildren().size()!=1)
+            return false;
 
-    public static boolean canPromote(EntitySpec<?> spec) {
+        EntitySpec<?> childSpec = Iterables.getOnlyElement(app.getChildren());
+        if (childSpec.getType()==null || !Application.class.isAssignableFrom(childSpec.getType()))
+            return false;
+
+        return canPromoteChildrenInWrappedApplication(app);
+    }
+    
+    /** returns true if the spec is for an empty-ish wrapper app, 
+     * 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<?> spec)
{
         return canPromoteBasedOnName(spec) &&
                 isWrapperApp(spec) &&
                 //equivalent to no keys starting with "brooklyn."

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2a3603ba/core/src/main/java/org/apache/brooklyn/core/plan/PlanToSpecFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/plan/PlanToSpecFactory.java b/core/src/main/java/org/apache/brooklyn/core/plan/PlanToSpecFactory.java
index 5d21420..6f74855 100644
--- a/core/src/main/java/org/apache/brooklyn/core/plan/PlanToSpecFactory.java
+++ b/core/src/main/java/org/apache/brooklyn/core/plan/PlanToSpecFactory.java
@@ -18,29 +18,102 @@
  */
 package org.apache.brooklyn.core.plan;
 
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.ServiceLoader;
 
 import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.guava.Maybe;
+import org.apache.brooklyn.util.text.Strings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
-import com.google.common.collect.Lists;
+import com.google.common.annotations.Beta;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
 
 public class PlanToSpecFactory {
-    public static PlanToSpecTransformer forMime(ManagementContext mgmt, String mime) {
+    
+    private static final Logger log = LoggerFactory.getLogger(PlanToSpecFactory.class);
+
+    private static Collection<PlanToSpecTransformer> getAll() {
+        return ImmutableList.copyOf(ServiceLoader.load(PlanToSpecTransformer.class));
+    }
+
+    private static Collection<Class<? extends PlanToSpecTransformer>> OVERRIDE;
+    @SafeVarargs
+    @VisibleForTesting
+    public synchronized static void forceAvailable(Class<? extends PlanToSpecTransformer>
...classes) {
+        OVERRIDE = Arrays.asList(classes);
+    }
+    public synchronized static void clearForced() {
+        OVERRIDE = null;
+    }
+
+    public static Collection<PlanToSpecTransformer> all(ManagementContext mgmt) {
+        Collection<Class<? extends PlanToSpecTransformer>> override = OVERRIDE;
+        Collection<PlanToSpecTransformer> result = new ArrayList<PlanToSpecTransformer>();
+        if (override!=null) {
+            for (Class<? extends PlanToSpecTransformer> o1: override) {
+                try {
+                    result.add(o1.newInstance());
+                } catch (Exception e) {
+                    Exceptions.propagate(e);
+                }
+            }
+        } else {
+            result.addAll(getAll());
+        }
+        for(PlanToSpecTransformer t : result) {
+            t.injectManagementContext(mgmt);
+        }
+        return result;
+    }
+
+    @Beta
+    public static PlanToSpecTransformer forPlanType(ManagementContext mgmt, String planType)
{
         Collection<PlanToSpecTransformer> transformers = all(mgmt);
         for (PlanToSpecTransformer transformer : transformers) {
-            if (transformer.accepts(mime)) {
+            if (transformer.accepts(planType)) {
                 return transformer;
             }
         }
-        throw new IllegalStateException("PlanToSpecTransformer for type " + mime + " not
found. Registered transformers are: " + transformers);
+        throw new IllegalStateException("PlanToSpecTransformer for plan type " + planType
+ " not found. Registered transformers are: " + transformers);
     }
-
-    public static Collection<PlanToSpecTransformer> all(ManagementContext mgmt) {
-        Collection<PlanToSpecTransformer> transformers = Lists.newArrayList(ServiceLoader.load(PlanToSpecTransformer.class));
-        for(PlanToSpecTransformer t : transformers) {
-            t.injectManagementContext(mgmt);
+    
+    // TODO primitive loading mechanism, just tries all in order; we'll want something better
as we get more plan transformers 
+    @Beta
+    public static <T> Maybe<T> attemptWithLoaders(ManagementContext mgmt, Function<PlanToSpecTransformer,T>
f) {
+        return attemptWithLoaders(all(mgmt), f);
+    }
+    
+    public static <T> Maybe<T> attemptWithLoaders(Iterable<PlanToSpecTransformer>
transformers, Function<PlanToSpecTransformer,T> f) {
+        Collection<String> transformersWhoDontSupport = new ArrayList<String>();
+        Collection<Exception> otherProblemsFromTransformers = new ArrayList<Exception>();
+        for (PlanToSpecTransformer t: transformers) {
+            try {
+                return Maybe.of(f.apply(t));
+            } catch (PlanNotRecognizedException e) {
+                transformersWhoDontSupport.add(t.getShortDescription() +
+                    (Strings.isNonBlank(e.getMessage()) ? " ("+e.getMessage()+")" : ""));
+            } catch (Exception e) {
+                Exceptions.propagateIfFatal(e);
+                otherProblemsFromTransformers.add(new IllegalArgumentException("Transformer
for "+t.getShortDescription()+" gave an error creating this plan", e));
+            }
+        }
+        // failed
+        Exception result;
+        if (!otherProblemsFromTransformers.isEmpty()) {
+            // at least one thought he could do it
+            log.debug("Plan could not be transformed; failure will be propagated (other transformers
tried = "+transformersWhoDontSupport+"): "+otherProblemsFromTransformers);
+            result = Exceptions.create(null, otherProblemsFromTransformers);
+        } else {
+            result = new PlanNotRecognizedException("Invalid plan; format could not be recognized,
trying with: "+transformersWhoDontSupport);
         }
-        return transformers;
+        return Maybe.absent(result);
     }
+    
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2a3603ba/core/src/main/java/org/apache/brooklyn/core/plan/PlanToSpecTransformer.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/plan/PlanToSpecTransformer.java b/core/src/main/java/org/apache/brooklyn/core/plan/PlanToSpecTransformer.java
index 66f1b62..63d5c1f 100644
--- a/core/src/main/java/org/apache/brooklyn/core/plan/PlanToSpecTransformer.java
+++ b/core/src/main/java/org/apache/brooklyn/core/plan/PlanToSpecTransformer.java
@@ -28,22 +28,34 @@ import org.apache.brooklyn.core.mgmt.ManagementContextInjectable;
 
 import com.google.common.annotations.Beta;
 
-/** Pluggable {@link ServiceLoader} interface for defferent plan-interpreters.
- * Implementations should take a plan and return an {@link EntitySpec}.
+/** Pluggable {@link ServiceLoader} interface for different plan-interpreters,
+ * that is, different ways of taking an application plan and returning an {@link EntitySpec},
+ * and a {@link CatalogItem} and returning an {@link AbstractBrooklynObjectSpec}.
  */
 @Beta
 public interface PlanToSpecTransformer extends ManagementContextInjectable {
     
-    /** Human-readable name for this transformer */
-    String getName();
+    /** A short, human-readable name for this transformer */
+    String getShortDescription();
     
-    /** whether this accepts the given MIME format */
-    boolean accepts(String mime);
+    /** whether this accepts the given plan type */
+    // TODO determine semantics of plan type; for now, we try all using PlanToSpecFactory
methods,
+    // that's okay when there's just a very few, but we'll want something better if that
grows
+    @Beta
+    boolean accepts(String planType);
     
-    /** creates an {@link EntitySpec} given a plan, according to the transformation rules
this understands */
-    <T extends Application> EntitySpec<T> createApplicationSpec(String plan);
+    /** creates an {@link EntitySpec} given a complete plan textual description for a top-level
application, 
+     * according to the transformation rules this understands.
+     * <p>
+     * should throw {@link PlanNotRecognizedException} if not supported. */
+    EntitySpec<? extends Application> createApplicationSpec(String plan) throws PlanNotRecognizedException;
     
-    /** creates an object spec given a catalog item, according to the transformation rules
this understands */
-    <T,SpecT extends AbstractBrooklynObjectSpec<T, SpecT>> AbstractBrooklynObjectSpec<T,
SpecT> createCatalogSpec(CatalogItem<T, SpecT> item);
+    /** creates an object spec given a catalog item.
+     * <p>
+     * the catalog item might be known by type, or its source plan fragment text might be
inspected and transformed.
+     * implementations will typically look at the {@link CatalogItem#getCatalogItemType()}
first.
+     * <p>
+     * should throw {@link PlanNotRecognizedException} if this transformer does not know
what to do with the plan. */
+    <T,SpecT extends AbstractBrooklynObjectSpec<T, SpecT>> AbstractBrooklynObjectSpec<T,
SpecT> createCatalogSpec(CatalogItem<T, SpecT> item) throws PlanNotRecognizedException;
     
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2a3603ba/core/src/test/java/org/apache/brooklyn/core/plan/XmlPlanToSpecTransformer.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/plan/XmlPlanToSpecTransformer.java
b/core/src/test/java/org/apache/brooklyn/core/plan/XmlPlanToSpecTransformer.java
new file mode 100644
index 0000000..5fc103d
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/core/plan/XmlPlanToSpecTransformer.java
@@ -0,0 +1,132 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.core.plan;
+
+import java.io.StringReader;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.apache.brooklyn.api.catalog.CatalogItem;
+import org.apache.brooklyn.api.catalog.CatalogItem.CatalogItemType;
+import org.apache.brooklyn.api.entity.Application;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.internal.AbstractBrooklynObjectSpec;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.core.mgmt.EntityManagementUtils;
+import org.apache.brooklyn.entity.stock.BasicApplication;
+import org.apache.brooklyn.entity.stock.BasicEntity;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.stream.ReaderInputStream;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+
+/** Example implementation of {@link PlanToSpecTransformer} showing 
+ * how implementations are meant to be written. */
+public class XmlPlanToSpecTransformer implements PlanToSpecTransformer {
+
+    @SuppressWarnings("unused")
+    private ManagementContext mgmt;
+
+    @Override
+    public void injectManagementContext(ManagementContext managementContext) {
+        mgmt = managementContext;
+    }
+
+    @Override
+    public String getShortDescription() {
+        return "Dummy app structure created from the XML tree";
+    }
+
+    @Override
+    public boolean accepts(String mime) {
+        if ("test-xml".equals(mime)) return true;
+        return false;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public EntitySpec<? extends Application> createApplicationSpec(String plan) {
+        Document dom = parseXml(plan);
+        EntitySpec<?> result = toEntitySpec(dom, 0);
+        if (Application.class.isAssignableFrom(result.getType())) {
+            return (EntitySpec<Application>) result;
+        } else {
+            return EntityManagementUtils.newWrapperApp().child(result);
+        }
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Override
+    public <T, SpecT extends AbstractBrooklynObjectSpec<T, SpecT>> AbstractBrooklynObjectSpec<T,
SpecT> createCatalogSpec(CatalogItem<T, SpecT> item) {
+        if (item.getPlanYaml()==null) throw new PlanNotRecognizedException("Plan is null");
+        if (item.getCatalogItemType()==CatalogItemType.ENTITY) {
+            return (EntitySpec)toEntitySpec(parseXml(item.getPlanYaml()), 1);
+        }
+        if (item.getCatalogItemType()==CatalogItemType.TEMPLATE) {
+            return (EntitySpec)toEntitySpec(parseXml(item.getPlanYaml()), 0);
+        }
+        throw new PlanNotRecognizedException("Type "+item.getCatalogItemType()+" not supported");
+    }
+
+    private Document parseXml(String plan) {
+        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+        Document dom;
+        
+        try {
+            //Using factory get an instance of document builder
+            DocumentBuilder db = dbf.newDocumentBuilder();
+
+            //parse using builder to get DOM representation of the XML file
+            dom = db.parse(new ReaderInputStream(new StringReader(plan)));
+            
+        } catch (Exception e) {
+            Exceptions.propagateIfFatal(e);
+            throw new PlanNotRecognizedException(e);
+        }
+        return dom;
+    }
+
+    private EntitySpec<?> toEntitySpec(Node dom, int depth) {
+        if (dom.getNodeType()==Node.DOCUMENT_NODE) {
+            if (dom.getChildNodes().getLength()!=1) {
+                // NB: <?...?>  entity preamble might break this
+                throw new IllegalStateException("Document for "+dom+" has "+dom.getChildNodes().getLength()+"
nodes; 1 expected.");
+            }
+            return toEntitySpec(dom.getChildNodes().item(0), depth);
+        }
+        
+        EntitySpec<?> result = depth == 0 ? EntitySpec.create(BasicApplication.class)
: EntitySpec.create(BasicEntity.class);
+        result.displayName(dom.getNodeName());
+        if (dom.getAttributes()!=null) {
+            for (int i=0; i<dom.getAttributes().getLength(); i++)
+                result.configure(dom.getAttributes().item(i).getNodeName(), dom.getAttributes().item(i).getTextContent());
+        }
+        if (dom.getChildNodes()!=null) {
+            for (int i=0; i<dom.getChildNodes().getLength(); i++) {
+                Node item = dom.getChildNodes().item(i);
+                if (item.getNodeType()==Node.ELEMENT_NODE) {
+                    result.child(toEntitySpec(item, depth+1));
+                }
+            }
+        }
+        return result;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2a3603ba/core/src/test/java/org/apache/brooklyn/core/plan/XmlPlanToSpecTransformerTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/plan/XmlPlanToSpecTransformerTest.java
b/core/src/test/java/org/apache/brooklyn/core/plan/XmlPlanToSpecTransformerTest.java
new file mode 100644
index 0000000..d6528df
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/core/plan/XmlPlanToSpecTransformerTest.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.core.plan;
+
+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.mgmt.ManagementContext;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.mgmt.EntityManagementUtils;
+import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
+import org.python.google.common.collect.Iterables;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+/** Tests the sample {@link XmlPlanToSpecTransformer}
+ * which illustrates how the {@link PlanToSpecTransformer} can be used. */
+public class XmlPlanToSpecTransformerTest {
+
+    private ManagementContext mgmt;
+
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        PlanToSpecFactory.forceAvailable(XmlPlanToSpecTransformer.class);
+        mgmt = LocalManagementContextForTests.newInstance();
+    }
+    
+    @AfterMethod(alwaysRun = true)
+    public void tearDown() {
+        PlanToSpecFactory.clearForced();
+        if (mgmt!=null) Entities.destroyAll(mgmt);
+    }
+
+    @Test
+    public void testSimpleXmlPlanParse() {
+        EntitySpec<? extends Application> appSpec = EntityManagementUtils.createEntitySpecForApplication(mgmt,

+            "<root><a_kid foo=\"bar\"/></root>");
+        Application app = EntityManagementUtils.createStarting(mgmt, appSpec).get();
+        Entities.dumpInfo(app);
+        Assert.assertEquals(app.getDisplayName(), "root");
+        Entity child = Iterables.getOnlyElement(app.getChildren());
+        Assert.assertEquals(child.getDisplayName(), "a_kid");
+        Assert.assertEquals(child.config().get(ConfigKeys.newStringConfigKey("foo")), "bar");
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2a3603ba/core/src/test/java/org/apache/brooklyn/util/core/internal/TypeCoercionsTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/util/core/internal/TypeCoercionsTest.java
b/core/src/test/java/org/apache/brooklyn/util/core/internal/TypeCoercionsTest.java
index 8ce718b..ec89ebf 100644
--- a/core/src/test/java/org/apache/brooklyn/util/core/internal/TypeCoercionsTest.java
+++ b/core/src/test/java/org/apache/brooklyn/util/core/internal/TypeCoercionsTest.java
@@ -19,6 +19,7 @@
 package org.apache.brooklyn.util.core.internal;
 
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
 
 import java.math.BigDecimal;
 import java.math.BigInteger;
@@ -67,6 +68,7 @@ public class TypeCoercionsTest {
         assertEquals(TypeCoercions.coerce("true", Boolean.class), (Boolean)true);
         assertEquals(TypeCoercions.coerce("False", Boolean.class), (Boolean)false);
         assertEquals(TypeCoercions.coerce("true ", Boolean.class), (Boolean)true);
+        assertNull(TypeCoercions.coerce(null, Boolean.class), null);
 
         assertEquals(TypeCoercions.coerce("1", char.class), (Character)'1');
         assertEquals(TypeCoercions.coerce("1", short.class), (Short)((short)1));
@@ -209,30 +211,35 @@ public class TypeCoercionsTest {
 
     @Test
     public void testListEntryCoercion() {
+        @SuppressWarnings("serial")
         List<?> s = TypeCoercions.coerce(ImmutableList.of("java.lang.Integer", "java.lang.Double"),
new TypeToken<List<Class<?>>>() { });
         Assert.assertEquals(s, ImmutableList.of(Integer.class, Double.class));
     }
     
     @Test
     public void testListEntryToSetCoercion() {
+        @SuppressWarnings("serial")
         Set<?> s = TypeCoercions.coerce(ImmutableList.of("java.lang.Integer", "java.lang.Double"),
new TypeToken<Set<Class<?>>>() { });
         Assert.assertEquals(s, ImmutableSet.of(Integer.class, Double.class));
     }
     
     @Test
     public void testListEntryToCollectionCoercion() {
+        @SuppressWarnings("serial")
         Collection<?> s = TypeCoercions.coerce(ImmutableList.of("java.lang.Integer",
"java.lang.Double"), new TypeToken<Collection<Class<?>>>() { });
         Assert.assertEquals(s, ImmutableList.of(Integer.class, Double.class));
     }
 
     @Test
     public void testMapValueCoercion() {
+        @SuppressWarnings("serial")
         Map<?,?> s = TypeCoercions.coerce(ImmutableMap.of("int", "java.lang.Integer",
"double", "java.lang.Double"), new TypeToken<Map<String, Class<?>>>() {
});
         Assert.assertEquals(s, ImmutableMap.of("int", Integer.class, "double", Double.class));
     }
     
     @Test
     public void testMapKeyCoercion() {
+        @SuppressWarnings("serial")
         Map<?,?> s = TypeCoercions.coerce(ImmutableMap.of("java.lang.Integer", "int",
"java.lang.Double", "double"), new TypeToken<Map<Class<?>, String>>() {
});
         Assert.assertEquals(s, ImmutableMap.of(Integer.class, "int", Double.class, "double"));
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2a3603ba/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/api/AssemblyTemplateSpecInstantiator.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/api/AssemblyTemplateSpecInstantiator.java
b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/api/AssemblyTemplateSpecInstantiator.java
index 88417e3..df7227b 100644
--- a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/api/AssemblyTemplateSpecInstantiator.java
+++ b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/api/AssemblyTemplateSpecInstantiator.java
@@ -20,6 +20,7 @@ package org.apache.brooklyn.camp.brooklyn.api;
 
 import java.util.Set;
 
+import org.apache.brooklyn.api.entity.Application;
 import org.apache.brooklyn.api.entity.EntitySpec;
 import org.apache.brooklyn.camp.CampPlatform;
 import org.apache.brooklyn.camp.spi.AssemblyTemplate;
@@ -28,8 +29,7 @@ import org.apache.brooklyn.core.mgmt.classloading.BrooklynClassLoadingContext;
 
 public interface AssemblyTemplateSpecInstantiator extends AssemblyTemplateInstantiator {
 
-    EntitySpec<?> createSpec(AssemblyTemplate template, CampPlatform platform, BrooklynClassLoadingContext
loader, boolean autoUnwrapIfAppropriate);
+    EntitySpec<? extends Application> createSpec(AssemblyTemplate template, CampPlatform
platform, BrooklynClassLoadingContext loader, boolean autoUnwrapIfAppropriate);
     EntitySpec<?> createNestedSpec(AssemblyTemplate template, CampPlatform platform,
BrooklynClassLoadingContext itemLoader, Set<String> encounteredCatalogTypes);
-
     
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2a3603ba/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynAssemblyTemplateInstantiator.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynAssemblyTemplateInstantiator.java
b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynAssemblyTemplateInstantiator.java
index 63fa664..d582c45 100644
--- a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynAssemblyTemplateInstantiator.java
+++ b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynAssemblyTemplateInstantiator.java
@@ -41,11 +41,11 @@ import org.apache.brooklyn.camp.spi.PlatformComponentTemplate;
 import org.apache.brooklyn.camp.spi.collection.ResolvableLink;
 import org.apache.brooklyn.camp.spi.instantiate.AssemblyTemplateInstantiator;
 import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog.BrooklynLoaderTracker;
-import org.apache.brooklyn.core.entity.Entities;
 import org.apache.brooklyn.core.catalog.internal.CatalogUtils;
+import org.apache.brooklyn.core.entity.Entities;
 import org.apache.brooklyn.core.mgmt.EntityManagementUtils;
-import org.apache.brooklyn.core.mgmt.HasBrooklynManagementContext;
 import org.apache.brooklyn.core.mgmt.EntityManagementUtils.CreationResult;
+import org.apache.brooklyn.core.mgmt.HasBrooklynManagementContext;
 import org.apache.brooklyn.core.mgmt.classloading.BrooklynClassLoadingContext;
 import org.apache.brooklyn.core.mgmt.classloading.JavaBrooklynClassLoadingContext;
 import org.apache.brooklyn.entity.stock.BasicApplicationImpl;
@@ -56,7 +56,6 @@ import org.apache.brooklyn.util.net.Urls;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 
@@ -88,7 +87,6 @@ public class BrooklynAssemblyTemplateInstantiator implements AssemblyTemplateSpe
         return ((HasBrooklynManagementContext)platform).getBrooklynManagementContext();
     }
 
-    @SuppressWarnings("unchecked")
     public EntitySpec<? extends Application> createSpec(AssemblyTemplate template,
CampPlatform platform, BrooklynClassLoadingContext loader, boolean autoUnwrapIfPossible) {
         log.debug("CAMP creating application instance for {} ({})", template.getId(), template);
 
@@ -106,12 +104,7 @@ public class BrooklynAssemblyTemplateInstantiator implements AssemblyTemplateSpe
         }
 
         if (autoUnwrapIfPossible && shouldUnwrap(template, app)) {
-            EntitySpec<? extends Application> oldApp = app;
-            app = (EntitySpec<? extends Application>) Iterables.getOnlyElement( app.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.collapseSpec(oldApp, app);
+            app = EntityManagementUtils.unwrapApplication(app);
         }
 
         return app;
@@ -132,20 +125,9 @@ public class BrooklynAssemblyTemplateInstantiator implements AssemblyTemplateSpe
     }
 
     protected boolean shouldUnwrap(AssemblyTemplate template, EntitySpec<? extends Application>
app) {
-        Object leaveWrapped = template.getCustomAttributes().get(NEVER_UNWRAP_APPS_PROPERTY);
-        if (leaveWrapped!=null) {
-            if (TypeCoercions.coerce(leaveWrapped, Boolean.class))
-                return false;
-        }
-
-        if (app.getChildren().size()!=1)
+        if (Boolean.TRUE.equals(TypeCoercions.coerce(template.getCustomAttributes().get(NEVER_UNWRAP_APPS_PROPERTY),
Boolean.class)))
             return false;
-
-        EntitySpec<?> childSpec = Iterables.getOnlyElement(app.getChildren());
-        if (childSpec.getType()==null || !Application.class.isAssignableFrom(childSpec.getType()))
-            return false;
-
-        return EntityManagementUtils.canPromote(app);
+        return EntityManagementUtils.canPromoteWrappedApplication(app);
     }
 
     private List<EntitySpec<?>> buildTemplateServicesAsSpecs(BrooklynClassLoadingContext
loader, AssemblyTemplate template, CampPlatform platform) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2a3603ba/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampToSpecTransformer.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampToSpecTransformer.java
b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampToSpecTransformer.java
index ebafa76..7ab200d 100644
--- a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampToSpecTransformer.java
+++ b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampToSpecTransformer.java
@@ -41,8 +41,8 @@ public class CampToSpecTransformer implements PlanToSpecTransformer {
     private ManagementContext mgmt;
 
     @Override
-    public String getName() {
-        return YAML_CAMP_PLAN_TYPE;
+    public String getShortDescription() {
+        return "Brooklyn OASIS CAMP interpreter";
     }
 
     @Override
@@ -51,7 +51,7 @@ public class CampToSpecTransformer implements PlanToSpecTransformer {
     }
 
     @Override
-    public <T extends Application> EntitySpec<T> createApplicationSpec(String
plan) {
+    public EntitySpec<? extends Application> createApplicationSpec(String plan) {
       CampPlatform camp = CampCatalogUtils.getCampPlatform(mgmt);
       AssemblyTemplate at = camp.pdp().registerDeploymentPlan( new StringReader(plan) );
       AssemblyTemplateInstantiator instantiator;
@@ -62,9 +62,7 @@ public class CampToSpecTransformer implements PlanToSpecTransformer {
       }
       if (instantiator instanceof AssemblyTemplateSpecInstantiator) {
           BrooklynClassLoadingContext loader = JavaBrooklynClassLoadingContext.create(mgmt);
-          @SuppressWarnings("unchecked")
-          EntitySpec<T> createSpec = (EntitySpec<T>) ((AssemblyTemplateSpecInstantiator)
instantiator).createSpec(at, camp, loader, true);
-          return createSpec;
+          return ((AssemblyTemplateSpecInstantiator) instantiator).createSpec(at, camp, loader,
true);
       } else {
           // The unknown instantiator can create the app (Assembly), but not a spec.
           // Currently, all brooklyn plans should produce the above.

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2a3603ba/usage/camp/src/test/java/org/apache/brooklyn/camp/brooklyn/test/lite/TestAppAssemblyInstantiator.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/java/org/apache/brooklyn/camp/brooklyn/test/lite/TestAppAssemblyInstantiator.java
b/usage/camp/src/test/java/org/apache/brooklyn/camp/brooklyn/test/lite/TestAppAssemblyInstantiator.java
index dc0c33d..831805f 100644
--- a/usage/camp/src/test/java/org/apache/brooklyn/camp/brooklyn/test/lite/TestAppAssemblyInstantiator.java
+++ b/usage/camp/src/test/java/org/apache/brooklyn/camp/brooklyn/test/lite/TestAppAssemblyInstantiator.java
@@ -21,6 +21,7 @@ package org.apache.brooklyn.camp.brooklyn.test.lite;
 import java.util.Map;
 import java.util.Set;
 
+import org.apache.brooklyn.api.entity.Application;
 import org.apache.brooklyn.api.entity.EntitySpec;
 import org.apache.brooklyn.api.mgmt.ManagementContext;
 import org.apache.brooklyn.camp.CampPlatform;
@@ -58,7 +59,7 @@ public class TestAppAssemblyInstantiator extends BasicAssemblyTemplateInstantiat
     }
 
     @Override
-    public EntitySpec<?> createSpec(AssemblyTemplate template, CampPlatform platform,
BrooklynClassLoadingContext loader, boolean autoUnwrap) {
+    public EntitySpec<? extends Application> createSpec(AssemblyTemplate template,
CampPlatform platform, BrooklynClassLoadingContext loader, boolean autoUnwrap) {
         EntitySpec<TestApplication> app = EntitySpec.create(TestApplication.class)
             .configure(TestEntity.CONF_NAME, template.getName())
             .configure(TestEntity.CONF_MAP_THING, MutableMap.of("type", template.getType(),
"desc", template.getDescription()));

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2a3603ba/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ApplicationResource.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ApplicationResource.java
b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ApplicationResource.java
index 5bc8df9..25e80e0 100644
--- a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ApplicationResource.java
+++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ApplicationResource.java
@@ -272,7 +272,7 @@ public class ApplicationResource extends AbstractBrooklynRestResource
implements
         }
 
         log.debug("Creating app from yaml:\n{}", yaml);
-        EntitySpec<? extends Application> spec = EntityManagementUtils.createEntitySpec(mgmt(),
yaml);
+        EntitySpec<? extends Application> spec = EntityManagementUtils.createEntitySpecForApplication(mgmt(),
yaml);
         
         if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.DEPLOY_APPLICATION,
spec)) {
             throw WebResourceUtils.unauthorized("User '%s' is not authorized to start application
%s",
@@ -330,7 +330,7 @@ public class ApplicationResource extends AbstractBrooklynRestResource
implements
 
         //TODO infer encoding from request
         String potentialYaml = new String(inputToAutodetectType);
-        EntitySpec<? extends Application> spec = EntityManagementUtils.createEntitySpec(mgmt(),
potentialYaml);
+        EntitySpec<? extends Application> spec = EntityManagementUtils.createEntitySpecForApplication(mgmt(),
potentialYaml);
 
         // TODO not json - try ZIP, etc
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2a3603ba/utils/common/src/main/java/org/apache/brooklyn/util/exceptions/Exceptions.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/exceptions/Exceptions.java
b/utils/common/src/main/java/org/apache/brooklyn/util/exceptions/Exceptions.java
index 347a212..583104b 100644
--- a/utils/common/src/main/java/org/apache/brooklyn/util/exceptions/Exceptions.java
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/exceptions/Exceptions.java
@@ -29,6 +29,8 @@ import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.ExecutionException;
 
+import javax.annotation.Nullable;
+
 import org.apache.brooklyn.util.text.Strings;
 
 import com.google.common.base.Predicate;
@@ -267,7 +269,7 @@ public class Exceptions {
         return create(null, exceptions);
     }
     /** creates the given exception, but without propagating it, for use when caller will
be wrapping */
-    public static RuntimeException create(String prefix, Collection<? extends Throwable>
exceptions) {
+    public static RuntimeException create(@Nullable String prefix, Collection<? extends
Throwable> exceptions) {
         if (exceptions.size()==1) {
             Throwable e = exceptions.iterator().next();
             if (Strings.isBlank(prefix)) return new PropagatedRuntimeException(e);



Mime
View raw message