You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2014/09/10 00:49:20 UTC

[17/19] git commit: support an EntityInitializer which makes an effector which adds a child from a blueprint, with config keys from parameters. refactor addition of children from EntityResource and BrooklynAssemblyTemplateInstantiator to share code, temp

support an EntityInitializer which makes an effector which adds a child from a blueprint, with config keys from parameters.
refactor addition of children from EntityResource and BrooklynAssemblyTemplateInstantiator to share code, temporarily in AddChildrenEffector.
also supply a default `restart` method for apps and improve the exceptions thrown when instantiation fails.


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

Branch: refs/heads/master
Commit: f7ce1a25f1cd8cc07716ef6fe7c6d9e320a7ed8a
Parents: f19ef81
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Tue Sep 9 11:10:46 2014 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Tue Sep 9 12:24:12 2014 +0100

----------------------------------------------------------------------
 .../entity/basic/AbstractApplication.java       |   3 +-
 .../entity/effector/AddChildrenEffector.java    | 310 +++++++++++++++++++
 .../brooklyn/entity/effector/AddEffector.java   |  32 +-
 .../brooklyn/entity/trait/StartableMethods.java |   2 +-
 .../java/brooklyn/util/config/ConfigBag.java    |   4 +-
 .../brooklyn/camp/lite/CampYamlLiteTest.java    |  47 ++-
 .../camp/lite/TestAppAssemblyInstantiator.java  |  41 ++-
 .../entity/software/ssh/SshCommandEffector.java |   6 +-
 .../BrooklynAssemblyTemplateInstantiator.java   |  51 +--
 .../brooklyn/rest/resources/EntityResource.java | 141 +--------
 .../brooklyn/util/javalang/Reflections.java     |  45 +--
 11 files changed, 459 insertions(+), 223 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f7ce1a25/core/src/main/java/brooklyn/entity/basic/AbstractApplication.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/AbstractApplication.java b/core/src/main/java/brooklyn/entity/basic/AbstractApplication.java
index 4cab77c..f4044f7 100644
--- a/core/src/main/java/brooklyn/entity/basic/AbstractApplication.java
+++ b/core/src/main/java/brooklyn/entity/basic/AbstractApplication.java
@@ -228,9 +228,10 @@ public abstract class AbstractApplication extends AbstractEntity implements Star
         StartableMethods.stop(this);
     }
 
+    /** default impl invokes restart on all children simultaneously */
     @Override
     public void restart() {
-        throw new UnsupportedOperationException();
+        StartableMethods.restart(this);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f7ce1a25/core/src/main/java/brooklyn/entity/effector/AddChildrenEffector.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/effector/AddChildrenEffector.java b/core/src/main/java/brooklyn/entity/effector/AddChildrenEffector.java
new file mode 100644
index 0000000..f216a53
--- /dev/null
+++ b/core/src/main/java/brooklyn/entity/effector/AddChildrenEffector.java
@@ -0,0 +1,310 @@
+/*
+ * 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 brooklyn.entity.effector;
+
+import io.brooklyn.camp.CampPlatform;
+import io.brooklyn.camp.spi.AssemblyTemplate;
+import io.brooklyn.camp.spi.instantiate.AssemblyTemplateInstantiator;
+
+import java.io.Reader;
+import java.io.StringReader;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Callable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.camp.brooklyn.api.AssemblyTemplateSpecInstantiator;
+import brooklyn.config.BrooklynServerConfig;
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.Effector;
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.BrooklynTaskTags;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.basic.EntityFunctions;
+import brooklyn.entity.basic.EntityLocal;
+import brooklyn.entity.effector.Effectors.EffectorBuilder;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.entity.trait.Startable;
+import brooklyn.management.ManagementContext;
+import brooklyn.management.Task;
+import brooklyn.management.classloading.BrooklynClassLoadingContext;
+import brooklyn.management.classloading.JavaBrooklynClassLoadingContext;
+import brooklyn.util.collections.MutableList;
+import brooklyn.util.config.ConfigBag;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.task.TaskBuilder;
+import brooklyn.util.task.Tasks;
+import brooklyn.util.text.Strings;
+import brooklyn.util.time.Duration;
+
+import com.google.common.annotations.Beta;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.gson.Gson;
+
+/** Entity initializer which defines an effector which adds a child blueprint to an entity.
+ * <p>
+ * One of the config keys {@link #BLUEPRINT_YAML} (containing a YAML blueprint (map or string)) 
+ * or {@link #BLUEPRINT_TYPE} (containing a string referring to a catalog type) should be supplied, but not both.
+ * Parameters defined here are supplied as config during the entity creation.
+ * 
+ * @since 0.7.0 */
+@Beta
+public class AddChildrenEffector extends AddEffector {
+    
+    private static final Logger log = LoggerFactory.getLogger(AddChildrenEffector.class);
+    
+    public static final ConfigKey<Object> BLUEPRINT_YAML = ConfigKeys.newConfigKey(Object.class, "blueprint_yaml");
+    public static final ConfigKey<String> BLUEPRINT_TYPE = ConfigKeys.newStringConfigKey("blueprint_type");
+    public static final ConfigKey<Boolean> AUTO_START = ConfigKeys.newBooleanConfigKey("auto_start");
+    
+    public AddChildrenEffector(ConfigBag params) {
+        super(newEffectorBuilder(params).build());
+    }
+    
+    public AddChildrenEffector(Map<String,String> params) {
+        this(ConfigBag.newInstance(params));
+    }
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    public static EffectorBuilder<List<String>> newEffectorBuilder(ConfigBag params) {
+        EffectorBuilder<List<String>> eff = (EffectorBuilder) AddEffector.newEffectorBuilder(List.class, params);
+        eff.impl(new Body(eff.buildAbstract(), params));
+        return eff;
+    }
+
+    protected static class Body extends EffectorBody<List<String>> {
+
+        private final Effector<?> effector;
+        private final String blueprintBase;
+        private final Boolean autostart;
+
+        public Body(Effector<?> eff, ConfigBag params) {
+            this.effector = eff;
+            String newBlueprint = null;
+            Object yaml = params.get(BLUEPRINT_YAML);
+            if (yaml instanceof Map) {
+                newBlueprint = new Gson().toJson(yaml);
+            } else if (yaml instanceof String) {
+                newBlueprint = (String) yaml;
+            } else if (yaml!=null) {
+                throw new IllegalArgumentException(this+" requires map or string in "+BLUEPRINT_YAML+"; not "+yaml.getClass()+" ("+yaml+")");
+            }
+            String blueprintType = params.get(BLUEPRINT_TYPE);
+            if (blueprintType!=null) {
+                if (newBlueprint!=null) {
+                    throw new IllegalArgumentException(this+" cannot take both "+BLUEPRINT_TYPE+" and "+BLUEPRINT_YAML);
+                }
+                newBlueprint = "services: [ { type: "+blueprintType+" } ]";
+            }
+            if (newBlueprint==null) {
+                throw new IllegalArgumentException(this+" requires either "+BLUEPRINT_TYPE+" or "+BLUEPRINT_YAML);
+            }
+            blueprintBase = newBlueprint;
+            autostart = params.get(AUTO_START);
+        }
+
+        @Override
+        public List<String> call(ConfigBag params) {
+            params = getMergedParams(effector, params);
+            
+            String blueprint = blueprintBase;
+            if (!params.isEmpty()) { 
+                blueprint = blueprint+"\n"+"brooklyn.config: "+
+                    new Gson().toJson(params.getAllConfig());
+            }
+
+            log.debug(this+" adding children to "+entity()+":\n"+blueprint);
+            AddChildrenResult result = addChildren(entity(), blueprint, autostart, Duration.ZERO);
+            log.debug(this+" added children to "+entity()+": "+result.getChildren());
+            return result.getTask().getUnchecked();
+        }
+    }
+
+    public static class AddChildrenResult {
+        public final List<Entity> children;
+        public final Task<List<String>> task;
+        public AddChildrenResult(List<Entity> children, Task<List<String>> task) {
+            super();
+            this.children = children;
+            this.task = task;
+        }
+        public List<Entity> getChildren() {
+          return children;
+      }
+        public Task<List<String>> getTask() {
+          return task;
+      }
+    }
+    
+    public static AddChildrenResult addChildren(final EntityLocal parent, String yaml, Boolean start, Duration timeout) {
+        log.debug("Creating child of "+parent+" from yaml:\n{}", yaml);
+        
+        ManagementContext mgmt = parent.getApplication().getManagementContext();
+        CampPlatform camp = BrooklynServerConfig.getCampPlatform(mgmt).get();
+        
+        Reader input = new StringReader(yaml);
+        AssemblyTemplate at = camp.pdp().registerDeploymentPlan(input);
+
+        AssemblyTemplateInstantiator instantiator;
+        try {
+            instantiator = at.getInstantiator().newInstance();
+        } catch (Exception e) {
+            throw Exceptions.propagate(e);
+        }
+        if (instantiator instanceof AssemblyTemplateSpecInstantiator) {
+            BrooklynClassLoadingContext loader = JavaBrooklynClassLoadingContext.newDefault(mgmt);
+            EntitySpec<?> specA = ((AssemblyTemplateSpecInstantiator) instantiator).createSpec(at, camp, loader, false);
+
+            boolean promoted;
+
+            // see whether we can promote children
+            List<EntitySpec<?>> specs = MutableList.of();
+            if (hasNoNameOrCustomKeysOrRoot(at, specA)) {
+                // we can promote
+                promoted = true;
+                for (EntitySpec<?> specC: specA.getChildren()) {
+                    collapseSpec(specA, specC);
+                    specs.add(specC);
+                }
+            } else {
+                // 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);
+                }
+                promoted = false;
+                specs.add(specA);
+            }
+
+            final List<Entity> children = MutableList.of();
+            for (EntitySpec<?> spec: specs) {
+                Entity child = (Entity)parent.addChild(spec);
+                Entities.manage(child);
+                children.add(child);
+            }
+
+            String childrenCountString;
+            if (promoted) {
+                int size = children.size();
+                childrenCountString = size+" "+(size!=1 ? "children" : "child"); 
+            } else {
+                int size = specA.getChildren().size();
+                childrenCountString = "entity with "+size+" "+(size!=1 ? "children" : "child");
+            }
+
+            TaskBuilder<List<String>> taskM = Tasks.<List<String>>builder().name("add children")
+                .dynamic(true)
+                .tag(BrooklynTaskTags.NON_TRANSIENT_TASK_TAG)
+                .body(new Callable<List<String>>() {
+                    @Override public List<String> call() throws Exception {
+                        return ImmutableList.copyOf(Iterables.transform(children, EntityFunctions.id()));
+                    }})
+                    .description("Add" + (start==null ? " and potentially start" : start ? " and start" : "") + " "+childrenCountString);
+            TaskBuilder<?> taskS = Tasks.builder().parallel(true).name("add (parallel)")
+                .description(
+                    (start==null ? "Add or start" : start ? "Start" : "Add")+" each new entity");
+
+            // should we autostart?
+            for (Entity child: children) {
+                if (Boolean.TRUE.equals(start) || (start==null && child instanceof Startable)) {
+                    taskS.add(Effectors.invocation(child, Startable.START, ImmutableMap.of("locations", ImmutableList.of())));
+                } else {
+                    taskS.add(Tasks.builder().name("create").description("Created and added as child of "+parent)
+                        .body(new Runnable() { public void run() {} })
+                        .tag(BrooklynTaskTags.tagForTargetEntity(child))
+                        .build());
+                }
+            }
+            taskM.add(taskS.build());
+            Task<List<String>> task = Entities.submit(parent, taskM.build());
+
+            // wait a few ms in case start is trivially simple, save the client a call to get the task result
+            task.blockUntilEnded(timeout);
+
+            return new AddChildrenResult(children, task);
+        } else {
+            throw new IllegalStateException("Spec could not be parsed to supply a compatible instantiator");
+        }
+    }
+
+    /** worker method to combine specs */
+    @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());
+
+        // 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) 
+        targetToBeExpanded.configure(sourceToBeCollapsed.getConfig());
+        targetToBeExpanded.configure(sourceToBeCollapsed.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());
+    }
+
+    /** worker method to help determine whether child/children can be promoted */
+    @Beta //where should this live long-term?
+    public static boolean hasNoNameOrCustomKeysOrRoot(AssemblyTemplate template, EntitySpec<?> spec) {
+        if (!Strings.isEmpty(template.getName())) {
+            if (spec.getChildren().size()==1) {
+                String childName = Iterables.getOnlyElement(spec.getChildren()).getDisplayName();
+                if (Strings.isEmpty(childName) || childName.equals(template.getName())) {
+                    // if child has no name, or it's the same, could still promote
+                } else {
+                    return false;
+                }
+            } else {
+                // if name set at root and promoting children would be ambiguous, do not promote 
+                return false;
+            }
+        } else if (spec.getChildren().size()>1) {
+            // don't allow multiple children if a name is specified as a root
+            return false;
+        }
+        
+        Set<String> rootAttrs = template.getCustomAttributes().keySet();
+        for (String rootAttr: rootAttrs) {
+            if (rootAttr.equals("brooklyn.catalog") || rootAttr.equals("brooklyn.config")) {
+                // these do not block promotion
+                continue;
+            }
+            if (rootAttr.startsWith("brooklyn.")) {
+                // any others in 'brooklyn' namespace will block promotion
+                return false;
+            }
+            // location is allowed in both, and is copied on promotion
+            // (name also copied)
+            // others are root currently are ignored on promotion; they are usually metadata
+            // TODO might be nice to know what we are excluding
+        }
+        
+        return true;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f7ce1a25/core/src/main/java/brooklyn/entity/effector/AddEffector.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/effector/AddEffector.java b/core/src/main/java/brooklyn/entity/effector/AddEffector.java
index 7bea8f8..12ec10c 100644
--- a/core/src/main/java/brooklyn/entity/effector/AddEffector.java
+++ b/core/src/main/java/brooklyn/entity/effector/AddEffector.java
@@ -23,6 +23,7 @@ import java.util.Map;
 
 import brooklyn.config.ConfigKey;
 import brooklyn.entity.Effector;
+import brooklyn.entity.ParameterType;
 import brooklyn.entity.basic.ConfigKeys;
 import brooklyn.entity.basic.EntityInternal;
 import brooklyn.entity.basic.EntityLocal;
@@ -35,7 +36,24 @@ import brooklyn.util.text.Strings;
 import com.google.common.annotations.Beta;
 import com.google.common.base.Preconditions;
 
-/** Entity initializer which adds an effector to an entity 
+/** 
+ * Entity initializer which adds an effector to an entity.
+ * <p>
+ * This instance provides a {@link #newEffectorBuilder(Class, ConfigBag)} 
+ * which returns an abstract (body-less) effector defining:
+ * <li> the name from {@link #EFFECTOR_NAME};
+ * <li> the description from {@link #EFFECTOR_DESCRIPTION}
+ * <li> the parameters from {@link #EFFECTOR_PARAMETER_DEFS}
+ * <p>
+ * Callers should pass the effector to instantiate into the constructor.
+ * Often subclasses will supply a constructor which takes a ConfigBag of parameters,
+ * and a custom {@link #newEffectorBuilder(Class, ConfigBag)} which adds the body
+ * before passing to this class.
+ * <p>
+ * Note that the parameters passed to the call method in the body of the effector implementation
+ * are only those supplied by a user at runtime; in order to merge with default
+ * values, use {@link #getMergedParams(Effector, ConfigBag)}.
+ *  
  * @since 0.7.0 */
 @Beta
 public class AddEffector implements EntityInitializer {
@@ -83,4 +101,16 @@ public class AddEffector implements EntityInitializer {
         return eff;
     }
 
+    /** returns a ConfigBag containing the merger of the supplied parameters with default values on the effector-defined parameters */
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    public static ConfigBag getMergedParams(Effector<?> eff, ConfigBag params) {
+        ConfigBag result = ConfigBag.newInstanceCopying(params);
+        for (ParameterType<?> param: eff.getParameters()) {
+            ConfigKey key = Effectors.asConfigKey(param);
+            if (!result.containsKey(key))
+                result.configure(key, params.get(key));
+        }
+        return result;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f7ce1a25/core/src/main/java/brooklyn/entity/trait/StartableMethods.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/trait/StartableMethods.java b/core/src/main/java/brooklyn/entity/trait/StartableMethods.java
index e8f1039..b5be1ae 100644
--- a/core/src/main/java/brooklyn/entity/trait/StartableMethods.java
+++ b/core/src/main/java/brooklyn/entity/trait/StartableMethods.java
@@ -60,7 +60,7 @@ public class StartableMethods {
         if (log.isDebugEnabled()) log.debug("Stopped entity "+e);
 	}
 
-    /** Common implementation for restart in parent nodes; just invokes stop on all children of the entity */
+    /** Common implementation for restart in parent nodes; just invokes restart on all children of the entity */
     public static void restart(EntityLocal e) {
         log.debug("Restarting entity "+e);
         DynamicTasks.queueIfPossible(restartingChildren(e)).orSubmitAsync(e).getTask().getUnchecked();

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f7ce1a25/core/src/main/java/brooklyn/util/config/ConfigBag.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/util/config/ConfigBag.java b/core/src/main/java/brooklyn/util/config/ConfigBag.java
index d35fb78..72bd28c 100644
--- a/core/src/main/java/brooklyn/util/config/ConfigBag.java
+++ b/core/src/main/java/brooklyn/util/config/ConfigBag.java
@@ -396,11 +396,11 @@ public class ConfigBag {
         }
     }
 
-    protected <T> T get(ConfigKey<T> key, boolean remove) {
+    protected <T> T get(ConfigKey<T> key, boolean markUsed) {
         // TODO for now, no evaluation -- closure content / smart (self-extracting) keys are NOT supported
         // (need a clean way to inject that behaviour, as well as desired TypeCoercions)
         if (config.containsKey(key.getName()))
-            return coerceFirstNonNullKeyValue(key, getStringKey(key.getName(), remove));
+            return coerceFirstNonNullKeyValue(key, getStringKey(key.getName(), markUsed));
         
         return coerceFirstNonNullKeyValue(key);
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f7ce1a25/core/src/test/java/brooklyn/camp/lite/CampYamlLiteTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/camp/lite/CampYamlLiteTest.java b/core/src/test/java/brooklyn/camp/lite/CampYamlLiteTest.java
index 8eedabf..d8b2698 100644
--- a/core/src/test/java/brooklyn/camp/lite/CampYamlLiteTest.java
+++ b/core/src/test/java/brooklyn/camp/lite/CampYamlLiteTest.java
@@ -43,7 +43,13 @@ import brooklyn.catalog.CatalogPredicates;
 import brooklyn.catalog.internal.BasicBrooklynCatalog;
 import brooklyn.catalog.internal.CatalogDto;
 import brooklyn.entity.Entity;
+import brooklyn.entity.basic.ApplicationBuilder;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.effector.AddChildrenEffector;
+import brooklyn.entity.effector.Effectors;
 import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.management.Task;
 import brooklyn.management.internal.LocalManagementContext;
 import brooklyn.management.osgi.OsgiStandaloneTest;
 import brooklyn.test.entity.LocalManagementContextForTests;
@@ -51,6 +57,8 @@ import brooklyn.test.entity.TestApplication;
 import brooklyn.test.entity.TestEntity;
 import brooklyn.util.ResourceUtils;
 import brooklyn.util.collections.MutableList;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.config.ConfigBag;
 import brooklyn.util.stream.Streams;
 
 import com.google.common.base.Predicates;
@@ -89,7 +97,7 @@ public class CampYamlLiteTest {
         Assert.assertEquals(at.getName(), "sample");
         Assert.assertEquals(at.getPlatformComponentTemplates().links().size(), 1);
         
-        // now use brooklyn to instantiate
+        // now use brooklyn to instantiate - note it won't be faithful, but it will set some config keys
         Assembly assembly = at.getInstantiator().newInstance().instantiate(at, platform);
         
         TestApplication app = ((TestAppAssembly)assembly).getBrooklynApp();
@@ -106,6 +114,43 @@ public class CampYamlLiteTest {
         Assert.assertEquals( map.get("desc"), MockWebPlatform.APPSERVER.getDescription() );
     }
 
+    /** based on {@link PdpYamlTest} for parsing,
+     * then creating a {@link TestAppAssembly} */
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Test
+    public void testAddChildrenEffector() throws Exception {
+        // NB: as above, with this mock instantiator, not everything will be set nicely
+        MockWebPlatform.populate(platform, TestAppAssemblyInstantiator.class);
+        
+        String childYaml = Streams.readFullyString(getClass().getResourceAsStream("test-app-service-blueprint.yaml"));
+        AddChildrenEffector newEff = new AddChildrenEffector(ConfigBag.newInstance()
+            .configure(AddChildrenEffector.EFFECTOR_NAME, "add_tomcat")
+            .configure(AddChildrenEffector.BLUEPRINT_YAML, childYaml)
+            .configure(AddChildrenEffector.EFFECTOR_PARAMETER_DEFS, MutableMap.of("war", (Object)MutableMap.of(
+                "defaultValue", "foo.war"))) ) ;
+        TestApplication app = ApplicationBuilder.newManagedApp(EntitySpec.create(TestApplication.class).addInitializer(newEff), mgmt);
+
+        // test adding, with a parameter
+        Task<List> task = app.invoke(Effectors.effector(List.class, "add_tomcat").buildAbstract(), MutableMap.of("war", "foo.bar"));
+        List result = task.get();
+        
+        Entity newChild = Iterables.getOnlyElement(app.getChildren());
+        Assert.assertEquals(newChild.getConfig(ConfigKeys.newStringConfigKey("war")), "foo.bar");
+        
+        Assert.assertEquals(Iterables.getOnlyElement(result), newChild.getId());
+        Entities.unmanage(newChild);
+        
+        // and test default value
+        task = app.invoke(Effectors.effector(List.class, "add_tomcat").buildAbstract(), MutableMap.<String,Object>of());
+        result = task.get();
+        
+        newChild = Iterables.getOnlyElement(app.getChildren());
+        Assert.assertEquals(newChild.getConfig(ConfigKeys.newStringConfigKey("war")), "foo.war");
+        
+        Assert.assertEquals(Iterables.getOnlyElement(result), newChild.getId());
+        Entities.unmanage(newChild);
+    }
+
     @Test
     public void testYamlServiceForCatalog() {
         MockWebPlatform.populate(platform, TestAppAssemblyInstantiator.class);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f7ce1a25/core/src/test/java/brooklyn/camp/lite/TestAppAssemblyInstantiator.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/camp/lite/TestAppAssemblyInstantiator.java b/core/src/test/java/brooklyn/camp/lite/TestAppAssemblyInstantiator.java
index 6e242c8..e63bd1f 100644
--- a/core/src/test/java/brooklyn/camp/lite/TestAppAssemblyInstantiator.java
+++ b/core/src/test/java/brooklyn/camp/lite/TestAppAssemblyInstantiator.java
@@ -19,20 +19,24 @@
 package brooklyn.camp.lite;
 
 import io.brooklyn.camp.CampPlatform;
+import io.brooklyn.camp.spi.AbstractResource;
 import io.brooklyn.camp.spi.Assembly;
 import io.brooklyn.camp.spi.AssemblyTemplate;
 import io.brooklyn.camp.spi.PlatformComponentTemplate;
 import io.brooklyn.camp.spi.collection.ResolvableLink;
 import io.brooklyn.camp.spi.instantiate.BasicAssemblyTemplateInstantiator;
+
+import java.util.Map;
+
 import brooklyn.camp.brooklyn.api.AssemblyTemplateSpecInstantiator;
 import brooklyn.camp.brooklyn.api.HasBrooklynManagementContext;
-import brooklyn.entity.basic.ApplicationBuilder;
 import brooklyn.entity.proxying.EntitySpec;
 import brooklyn.management.ManagementContext;
 import brooklyn.management.classloading.BrooklynClassLoadingContext;
 import brooklyn.test.entity.TestApplication;
 import brooklyn.test.entity.TestEntity;
 import brooklyn.util.collections.MutableMap;
+import brooklyn.util.config.ConfigBag;
 
 /** simple illustrative instantiator which always makes a {@link TestApplication}, populated with {@link TestEntity} children,
  * all setting {@link TestEntity#CONF_NAME} for the name in the plan and in the service specs
@@ -47,21 +51,8 @@ public class TestAppAssemblyInstantiator extends BasicAssemblyTemplateInstantiat
         }
         ManagementContext mgmt = ((HasBrooklynManagementContext)platform).getBrooklynManagementContext();
         
-        // TODO when createSpec is working:
-//        TestApplication app = (TestApplication) mgmt.getEntityManager().createEntity( createSpec(template, platform) );
-//        mgmt.getEntityManager().manage(app);
-        
-        // workaround until above is reacy
-        TestApplication app = ApplicationBuilder.newManagedApp(EntitySpec.create(TestApplication.class)
-            .configure(TestEntity.CONF_NAME, template.getName())
-            .configure(TestEntity.CONF_MAP_THING, MutableMap.of("type", template.getType(), "desc", template.getDescription()))
-            , mgmt);
-        for (ResolvableLink<PlatformComponentTemplate> t: template.getPlatformComponentTemplates().links()) {
-            app.createAndManageChild(EntitySpec.create(TestEntity.class)
-                .configure(TestEntity.CONF_NAME, t.getName())
-                .configure(TestEntity.CONF_MAP_THING, MutableMap.of("type", t.resolve().getType(), "desc", t.resolve().getDescription()))
-                );
-        }
+        TestApplication app = (TestApplication) mgmt.getEntityManager().createEntity( createSpec(template, platform, null, false) );
+        mgmt.getEntityManager().manage(app);
 
         return new TestAppAssembly(app);
     }
@@ -71,16 +62,24 @@ public class TestAppAssemblyInstantiator extends BasicAssemblyTemplateInstantiat
         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()));
+        applyBrooklynConfig(template, app);
         
         for (ResolvableLink<PlatformComponentTemplate> t: template.getPlatformComponentTemplates().links()) {
-            // TODO use EntitySpec.child(...)
-//            app.child(EntitySpec.create(TestEntity.class)
-//                .configure(TestEntity.CONF_NAME, t.getName())
-//                .configure(TestEntity.CONF_MAP_THING, MutableMap.of("type", t.resolve().getType(), "desc", t.resolve().getDescription()))
-//                );
+            EntitySpec<TestEntity> spec = EntitySpec.create(TestEntity.class)
+                .configure(TestEntity.CONF_NAME, t.getName())
+                .configure(TestEntity.CONF_MAP_THING, MutableMap.of("type", t.resolve().getType(), "desc", t.resolve().getDescription()));
+            applyBrooklynConfig(t.resolve(), app);
+            app.child(spec);
         }
         
         return app;
     }
 
+    @SuppressWarnings("rawtypes")
+    private void applyBrooklynConfig(AbstractResource template, EntitySpec<TestApplication> app) {
+        Object bc = template.getCustomAttributes().get("brooklyn.config");
+        if (bc instanceof Map)
+            app.configure(ConfigBag.newInstance().putAll((Map)bc).getAllConfigAsConfigKeyMap());
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f7ce1a25/software/base/src/main/java/brooklyn/entity/software/ssh/SshCommandEffector.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/software/ssh/SshCommandEffector.java b/software/base/src/main/java/brooklyn/entity/software/ssh/SshCommandEffector.java
index 3cd537b..4cc1b5c 100644
--- a/software/base/src/main/java/brooklyn/entity/software/ssh/SshCommandEffector.java
+++ b/software/base/src/main/java/brooklyn/entity/software/ssh/SshCommandEffector.java
@@ -57,12 +57,12 @@ public final class SshCommandEffector extends AddEffector {
 
 
     protected static class Body extends EffectorBody<String> {
-        private Effector<?> effector;
-        private String command;
+        private final Effector<?> effector;
+        private final String command;
 
         public Body(Effector<?> eff, ConfigBag params) {
             this.effector = eff;
-            this.command = Preconditions.checkNotNull(params.get(EFFECTOR_COMMAND), "command must be supplied when defining an effector");
+            this.command = Preconditions.checkNotNull(params.get(EFFECTOR_COMMAND), "command must be supplied when defining this effector");
             // TODO could take a custom "env" aka effectorShellEnv
         }
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f7ce1a25/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/creation/BrooklynAssemblyTemplateInstantiator.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/creation/BrooklynAssemblyTemplateInstantiator.java b/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/creation/BrooklynAssemblyTemplateInstantiator.java
index 1d86581..d21259d 100644
--- a/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/creation/BrooklynAssemblyTemplateInstantiator.java
+++ b/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/creation/BrooklynAssemblyTemplateInstantiator.java
@@ -49,6 +49,7 @@ import brooklyn.entity.Entity;
 import brooklyn.entity.basic.BasicApplicationImpl;
 import brooklyn.entity.basic.Entities;
 import brooklyn.entity.basic.EntityLocal;
+import brooklyn.entity.effector.AddChildrenEffector;
 import brooklyn.entity.proxying.EntitySpec;
 import brooklyn.entity.trait.Startable;
 import brooklyn.management.ManagementContext;
@@ -61,9 +62,7 @@ import brooklyn.util.collections.MutableMap;
 import brooklyn.util.exceptions.Exceptions;
 import brooklyn.util.flags.TypeCoercions;
 import brooklyn.util.net.Urls;
-import brooklyn.util.text.Strings;
 
-import com.google.common.annotations.Beta;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
@@ -127,11 +126,10 @@ 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
-            // (normally this will be done by the resolveSpec call above)
-            if (app.getDisplayName()==null) app.displayName(oldApp.getDisplayName());
-            app.tags(oldApp.getTags());
-            app.locations(oldApp.getLocations());
+            // (transformations will be done by the resolveSpec call above, but we are collapsing oldApp so transfer to app=newApp)
+            AddChildrenEffector.collapseSpec(oldApp, app);
         }
         
         return app;
@@ -165,46 +163,7 @@ public class BrooklynAssemblyTemplateInstantiator implements AssemblyTemplateSpe
         if (childSpec.getType()==null || !Application.class.isAssignableFrom(childSpec.getType()))
             return false;
 
-        return hasNoNameOrCustomKeysOrRoot(template, app);
-    }
-
-    /** worker method to help determine whether child/children can be promoted */
-    @Beta
-    public static boolean hasNoNameOrCustomKeysOrRoot(AssemblyTemplate template, EntitySpec<?> spec) {
-        if (!Strings.isEmpty(template.getName())) {
-            if (spec.getChildren().size()==1) {
-                String childName = Iterables.getOnlyElement(spec.getChildren()).getDisplayName();
-                if (Strings.isEmpty(childName) || childName.equals(template.getName())) {
-                    // if child has no name, or it's the same, could still promote
-                } else {
-                    return false;
-                }
-            } else {
-                // if name set at root and promoting children would be ambiguous, do not promote 
-                return false;
-            }
-        } else if (spec.getChildren().size()>1) {
-            // don't allow multiple children if a name is specified as a root
-            return false;
-        }
-        
-        Set<String> rootAttrs = template.getCustomAttributes().keySet();
-        for (String rootAttr: rootAttrs) {
-            if (rootAttr.equals("brooklyn.catalog")) {
-                // this attr does not block promotion
-                continue;
-            }
-            if (rootAttr.startsWith("brooklyn.")) {
-                // any others in 'brooklyn' namespace will block promotion
-                return false;
-            }
-            // location is allowed in both, and is copied on promotion
-            // (name also copied)
-            // others are root currently are ignored on promotion; they are usually metadata
-            // TODO might be nice to know what we are excluding
-        }
-        
-        return true;
+        return AddChildrenEffector.hasNoNameOrCustomKeysOrRoot(template, app);
     }
 
     private List<EntitySpec<?>> buildTemplateServicesAsSpecs(BrooklynClassLoadingContext loader, AssemblyTemplate template, CampPlatform platform) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f7ce1a25/usage/rest-server/src/main/java/brooklyn/rest/resources/EntityResource.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/brooklyn/rest/resources/EntityResource.java b/usage/rest-server/src/main/java/brooklyn/rest/resources/EntityResource.java
index d9cd740..9792c28 100644
--- a/usage/rest-server/src/main/java/brooklyn/rest/resources/EntityResource.java
+++ b/usage/rest-server/src/main/java/brooklyn/rest/resources/EntityResource.java
@@ -21,43 +21,31 @@ package brooklyn.rest.resources;
 import static javax.ws.rs.core.Response.created;
 import static javax.ws.rs.core.Response.status;
 import static javax.ws.rs.core.Response.Status.ACCEPTED;
-import io.brooklyn.camp.brooklyn.spi.creation.BrooklynAssemblyTemplateInstantiator;
-import io.brooklyn.camp.spi.AssemblyTemplate;
-import io.brooklyn.camp.spi.instantiate.AssemblyTemplateInstantiator;
 
-import java.io.Reader;
-import java.io.StringReader;
 import java.net.URI;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.Callable;
 
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.ResponseBuilder;
 import javax.ws.rs.core.Response.Status;
 import javax.ws.rs.core.UriInfo;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import brooklyn.camp.brooklyn.api.AssemblyTemplateSpecInstantiator;
 import brooklyn.entity.Entity;
 import brooklyn.entity.basic.BrooklynTags;
 import brooklyn.entity.basic.BrooklynTags.NamedStringTag;
 import brooklyn.entity.basic.BrooklynTaskTags;
-import brooklyn.entity.basic.Entities;
-import brooklyn.entity.basic.EntityFunctions;
 import brooklyn.entity.basic.EntityLocal;
-import brooklyn.entity.effector.Effectors;
-import brooklyn.entity.proxying.EntitySpec;
-import brooklyn.entity.trait.Startable;
+import brooklyn.entity.effector.AddChildrenEffector;
 import brooklyn.location.Location;
 import brooklyn.management.Task;
-import brooklyn.management.classloading.BrooklynClassLoadingContext;
-import brooklyn.management.classloading.JavaBrooklynClassLoadingContext;
 import brooklyn.management.entitlement.EntitlementPredicates;
 import brooklyn.management.entitlement.Entitlements;
 import brooklyn.rest.api.EntityApi;
@@ -71,16 +59,10 @@ import brooklyn.rest.transform.TaskTransformer;
 import brooklyn.rest.util.WebResourceUtils;
 import brooklyn.util.ResourceUtils;
 import brooklyn.util.collections.MutableList;
-import brooklyn.util.exceptions.Exceptions;
-import brooklyn.util.task.TaskBuilder;
-import brooklyn.util.task.Tasks;
-import brooklyn.util.text.Strings;
 import brooklyn.util.time.Duration;
 
 import com.google.common.collect.Collections2;
 import com.google.common.collect.FluentIterable;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.io.Files;
@@ -126,119 +108,28 @@ public class EntityResource extends AbstractBrooklynRestResource implements Enti
         return getChildren(application, entity);
   }
   
-  @Override
-  public Response addChildren(String applicationToken, String entityToken, Boolean start, String timeoutS, String yaml) {
+    @Override
+    public Response addChildren(String applicationToken, String entityToken, Boolean start, String timeoutS, String yaml) {
         final EntityLocal parent = brooklyn().getEntity(applicationToken, entityToken);
         if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_ENTITY, parent)) {
             throw WebResourceUtils.unauthorized("User '%s' is not authorized to modify entity '%s'",
                 Entitlements.getEntitlementContext().user(), entityToken);
         }
-        Duration timeout = timeoutS==null ? Duration.millis(20) : Duration.of(timeoutS);
+        AddChildrenEffector.AddChildrenResult added = AddChildrenEffector.addChildren(parent, yaml, start, timeoutS==null ? Duration.millis(20) : Duration.of(timeoutS));
+        ResponseBuilder response;
         
-        log.debug("Creating child of "+parent+" from yaml:\n{}", yaml);
-        Reader input = new StringReader(yaml);
-        AssemblyTemplate at = camp().pdp().registerDeploymentPlan(input);
-
-        AssemblyTemplateInstantiator instantiator;
-        try {
-            instantiator = at.getInstantiator().newInstance();
-        } catch (Exception e) {
-            throw Exceptions.propagate(e);
-        }
-        if (instantiator instanceof AssemblyTemplateSpecInstantiator) {
-            BrooklynClassLoadingContext loader = JavaBrooklynClassLoadingContext.newDefault(mgmt());
-            EntitySpec<?> specA = ((AssemblyTemplateSpecInstantiator) instantiator).createSpec(at, camp(), loader, false);
-            
-            boolean promoted;
-            
-            // see whether we can promote children
-            List<EntitySpec<?>> specs = MutableList.of();
-            if (BrooklynAssemblyTemplateInstantiator.hasNoNameOrCustomKeysOrRoot(at, specA)) {
-                // we can promote
-                promoted = true;
-                for (EntitySpec<?> specC: specA.getChildren()) {
-                    if (!specA.getLocations().isEmpty()) {
-                        specC.locations(specA.getLocations());
-                        
-                        // TODO copying tags to all entities is not ideal;
-                        // in particular the BrooklynTags.YAML_SPEC tag will show all entities
-                        // even if just one was created
-                        specC.tags(specA.getTags());
-                        if (Strings.isEmpty(specC.getDisplayName()))
-                            specC.displayName(specA.getDisplayName());
-                    }
-                specs.add(specC);
-                }
-            } else {
-                // 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);
-                }
-                promoted = false;
-                specs.add(specA);
-            }
-            
-            final List<Entity> children = MutableList.of();
-            for (EntitySpec<?> spec: specs) {
-                Entity child = (Entity)parent.addChild(spec);
-                Entities.manage(child);
-                children.add(child);
-            }
-
-            String childrenCountString;
-            if (promoted) {
-                int size = children.size();
-                childrenCountString = size+" "+(size!=1 ? "children" : "child"); 
-            } else {
-                int size = specA.getChildren().size();
-                childrenCountString = "entity with "+size+" "+(size!=1 ? "children" : "child");
-            }
-
-            TaskBuilder<List<String>> taskM = Tasks.<List<String>>builder().name("add children")
-                .dynamic(true)
-                .tag(BrooklynTaskTags.NON_TRANSIENT_TASK_TAG)
-                .body(new Callable<List<String>>() {
-                    @Override public List<String> call() throws Exception {
-                        return ImmutableList.copyOf(Iterables.transform(children, EntityFunctions.id()));
-                    }})
-                .description("Add" + (start==null ? " and potentially start" : start ? " and start" : "") + " "+childrenCountString);
-            TaskBuilder<?> taskS = Tasks.builder().parallel(true).name("add (parallel)")
-                .description(
-                    (start==null ? "Add or start" : start ? "Start" : "Add")+" each new entity");
-
-            // should we autostart?
-            for (Entity child: children) {
-                if (Boolean.TRUE.equals(start) || (start==null && child instanceof Startable)) {
-                    taskS.add(Effectors.invocation(child, Startable.START, ImmutableMap.of("locations", ImmutableList.of())));
-                } else {
-                    taskS.add(Tasks.builder().name("create").description("Created and added as child of "+parent)
-                        .body(new Runnable() { public void run() {} })
-                        .tag(BrooklynTaskTags.tagForTargetEntity(child))
-                        .build());
-                }
-            }
-            taskM.add(taskS.build());
-            Task<List<String>> task = Entities.submit(parent, taskM.build());
-            
-            // wait a few ms in case start is trivially simple, save the client a call to get the task result
-            task.blockUntilEnded(timeout);
-
-            if (children.size()==1) {
-                Entity child = Iterables.getOnlyElement(children);
-                URI ref = uriInfo.getBaseUriBuilder()
-                    .path(EntityApi.class)
-                    .path(EntityApi.class, "get")
-                    .build(child.getApplicationId(), child.getId());
-                return created(ref).entity(TaskTransformer.taskSummary(task)).build();
-            } else {
-                return Response.status(Status.CREATED).entity(TaskTransformer.taskSummary(task)).build();
-            }
+        if (added.getChildren().size()==1) {
+            Entity child = Iterables.getOnlyElement(added.getChildren());
+            URI ref = uriInfo.getBaseUriBuilder()
+                .path(EntityApi.class)
+                .path(EntityApi.class, "get")
+                .build(child.getApplicationId(), child.getId());
+            response = created(ref);
         } else {
-            throw new IllegalStateException("Spec could not be parsed to supply a compatible instantiator");
+            response = Response.status(Status.CREATED);
         }
-  }
+        return response.entity(TaskTransformer.taskSummary(added.getTask())).build();
+    }
 
   @Override
   public List<TaskSummary> listTasks(String applicationId, String entityId) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f7ce1a25/utils/common/src/main/java/brooklyn/util/javalang/Reflections.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/javalang/Reflections.java b/utils/common/src/main/java/brooklyn/util/javalang/Reflections.java
index e5b78c6..3aaeca6 100644
--- a/utils/common/src/main/java/brooklyn/util/javalang/Reflections.java
+++ b/utils/common/src/main/java/brooklyn/util/javalang/Reflections.java
@@ -47,6 +47,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import brooklyn.util.collections.MutableList;
+import brooklyn.util.exceptions.Exceptions;
 
 import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
@@ -95,7 +96,7 @@ public class Reflections {
             v = invokeConstructorWithArgs(clazz, argValues);
             if (v.isPresent()) return v.get();
         } catch (Exception e) {
-            throw new IllegalStateException("Error invoking constructor for "+clazz+Arrays.toString(argValues)+": "+e);
+            throw new IllegalStateException("Error invoking constructor for "+clazz+Arrays.toString(argValues) + ": " + Exceptions.collapseText(e));
         }
         throw new IllegalStateException("No suitable constructor for "+clazz+Arrays.toString(argValues));
 	}
@@ -110,9 +111,9 @@ public class Reflections {
 		try {
 			return clazz.newInstance();
 		} catch (InstantiationException e) {
-			throw new ReflectionAccessException("Failed to create instance of class '" + classname + "' using class loader " + classLoader, e);
+			throw new ReflectionAccessException("Failed to create instance of class '" + classname + "' using class loader " + classLoader + ": " + Exceptions.collapseText(e), e);
 		} catch (IllegalAccessException e) {
-			throw new ReflectionAccessException("Failed to create instance of class '" + classname + "' using class loader " + classLoader, e);
+			throw new ReflectionAccessException("Failed to create instance of class '" + classname + "' using class loader " + classLoader + ": " + Exceptions.collapseText(e), e);
 		}
 	}
 
@@ -121,11 +122,11 @@ public class Reflections {
 		try {
 			return classLoader.loadClass(classname);
 		} catch (ClassNotFoundException e) {
-			throw new ReflectionNotFoundException("Failed to load class '" + classname + "' using class loader " + classLoader, e);
+			throw new ReflectionNotFoundException("Failed to load class '" + classname + "' using class loader " + classLoader + ": " + Exceptions.collapseText(e), e);
 		} catch (NoClassDefFoundError e) {
-			throw new ReflectionNotFoundException("Failed to load class '" + classname + "' using class loader " + classLoader, e);
+			throw new ReflectionNotFoundException("Failed to load class '" + classname + "' using class loader " + classLoader + ": " + Exceptions.collapseText(e), e);
 		} catch (UnsupportedClassVersionError e) {
-			throw new ReflectionNotFoundException("Failed to load class '" + classname + "' using class loader " + classLoader, e);
+			throw new ReflectionNotFoundException("Failed to load class '" + classname + "' using class loader " + classLoader + ": " + Exceptions.collapseText(e), e);
 		}
 	}
 
@@ -200,9 +201,9 @@ public class Reflections {
 		try {
 			return clazz.getConstructor(argTypes);
 		} catch (SecurityException e) {
-			throw new ReflectionAccessException("Failed to load constructor of class '" + clazz + " with argument types " + Arrays.asList(argTypes), e);
+			throw new ReflectionAccessException("Failed to load constructor of class '" + clazz + " with argument types " + Arrays.asList(argTypes) + ": " + Exceptions.collapseText(e), e);
 		} catch (NoSuchMethodException e) {
-			throw new ReflectionAccessException("Failed to load constructor of class '" + clazz + " with argument types " + Arrays.asList(argTypes), e);
+			throw new ReflectionAccessException("Failed to load constructor of class '" + clazz + " with argument types " + Arrays.asList(argTypes) + ": " + Exceptions.collapseText(e), e);
 		}
 	}
 
@@ -293,13 +294,13 @@ public class Reflections {
 				}
 			}
 		} catch (IllegalArgumentException e) {
-			throw new IllegalArgumentException(getIllegalArgumentsErrorMessage(constructor, argValues), e);
+			throw new IllegalArgumentException(getIllegalArgumentsErrorMessage(constructor, argValues)+": " + Exceptions.collapseText(e), e);
 		} catch (InstantiationException e) {
-			throw new ReflectionAccessException("Failed to create instance of '" + constructor.getDeclaringClass(), e);
+			throw new ReflectionAccessException("Failed to create instance of " + constructor.getDeclaringClass() + ": " + Exceptions.collapseText(e), e);
 		} catch (IllegalAccessException e) {
-			throw new ReflectionAccessException("Failed to create instance of '" + constructor.getDeclaringClass(), e);
+		    throw new ReflectionAccessException("Failed to create instance of " + constructor.getDeclaringClass() + ": " + Exceptions.collapseText(e), e);
 		} catch (InvocationTargetException e) {
-			throw new ReflectionAccessException("Failed to create instance of '" + constructor.getDeclaringClass(), e);
+		    throw new ReflectionAccessException("Failed to create instance of " + constructor.getDeclaringClass() + ": " + Exceptions.collapseText(e), e);
 		}
 	}
 
@@ -307,11 +308,11 @@ public class Reflections {
 		try {
 			return clazz.getMethod(methodName, argTypes);
 		} catch (NoClassDefFoundError e) {
-			throw new ReflectionNotFoundException("Failed to invoke method '" + methodName + " on class " + clazz + " with argument types " + Arrays.asList(argTypes) + ", using class loader " + clazz.getClassLoader(), e);
+			throw new ReflectionNotFoundException("Failed to invoke method " + methodName + " on class " + clazz + " with argument types " + Arrays.asList(argTypes) + ", using class loader " + clazz.getClassLoader() + ": " + Exceptions.collapseText(e), e);
 		} catch (NoSuchMethodException e) {
-			throw new ReflectionNotFoundException("Failed to invoke method '" + methodName + " on class " + clazz + " with argument types " + Arrays.asList(argTypes), e);
+			throw new ReflectionNotFoundException("Failed to invoke method " + methodName + " on class " + clazz + " with argument types " + Arrays.asList(argTypes) + ": " + Exceptions.collapseText(e), e);
 		} catch (SecurityException e) {
-			throw new ReflectionAccessException("Failed to invoke method '" + methodName + " on class " + clazz + " with argument types " + Arrays.asList(argTypes), e);
+			throw new ReflectionAccessException("Failed to invoke method " + methodName + " on class " + clazz + " with argument types " + Arrays.asList(argTypes) + ": " + Exceptions.collapseText(e), e);
 		}
 	}
 
@@ -327,7 +328,7 @@ public class Reflections {
 			throw new ReflectionNotFoundException("Cannot find method " + methodName + " on class " + clazz);
 
 		} catch (SecurityException e) {
-			throw new ReflectionAccessException("Failed to invoke method '" + methodName + " on class " + clazz, e);
+			throw new ReflectionAccessException("Failed to invoke method '" + methodName + " on class " + clazz + ": " + Exceptions.collapseText(e), e);
 		}
 	}
 
@@ -342,9 +343,9 @@ public class Reflections {
 		} catch (IllegalArgumentException e) {
 			throw new IllegalArgumentException(getIllegalArgumentsErrorMessage(method, argValues), e);
 		} catch (IllegalAccessException e) {
-			throw new ReflectionAccessException("Failed to invoke method '" + method.toGenericString() + " on class " + method.getDeclaringClass() + " with argument values " + Arrays.asList(argValues), e);
+			throw new ReflectionAccessException("Failed to invoke method '" + method.toGenericString() + " on class " + method.getDeclaringClass() + " with argument values " + Arrays.asList(argValues) + ": " + Exceptions.collapseText(e), e);
 		} catch (InvocationTargetException e) {
-			throw new ReflectionAccessException("Failed to invoke method '" + method.toGenericString() + " on class " + method.getDeclaringClass() + " with argument values " + Arrays.asList(argValues), e);
+			throw new ReflectionAccessException("Failed to invoke method '" + method.toGenericString() + " on class " + method.getDeclaringClass() + " with argument values " + Arrays.asList(argValues) + ": " + Exceptions.collapseText(e), e);
 		}
 	}
 
@@ -354,9 +355,9 @@ public class Reflections {
 		} catch (IllegalArgumentException e) {
 			throw new IllegalArgumentException(getIllegalArgumentsErrorMessage(method, argValues), e);
 		} catch (IllegalAccessException e) {
-			throw new ReflectionAccessException("Failed to invoke method '" + method.toGenericString() + " on class " + method.getDeclaringClass() + " with argument values " + Arrays.asList(argValues), e);
+			throw new ReflectionAccessException("Failed to invoke method '" + method.toGenericString() + " on class " + method.getDeclaringClass() + " with argument values " + Arrays.asList(argValues) + ": " + Exceptions.collapseText(e), e);
 		} catch (InvocationTargetException e) {
-			throw new ReflectionAccessException("Failed to invoke method '" + method.toGenericString() + " on class " + method.getDeclaringClass() + " with argument values " + Arrays.asList(argValues), e);
+			throw new ReflectionAccessException("Failed to invoke method '" + method.toGenericString() + " on class " + method.getDeclaringClass() + " with argument values " + Arrays.asList(argValues) + ": " + Exceptions.collapseText(e), e);
 		}
 	}
 
@@ -382,9 +383,9 @@ public class Reflections {
 				try {
 					result[index] = field.get(null);
 				} catch (IllegalArgumentException e) {
-					throw new ReflectionAccessException("Failed to load field '" + field.getName() + " from class " + clazz, e);
+					throw new ReflectionAccessException("Failed to load field '" + field.getName() + " from class " + clazz + ": " + Exceptions.collapseText(e), e);
 				} catch (IllegalAccessException e) {
-					throw new ReflectionAccessException("Failed to load field '" + field.getName() + " from class " + clazz, e);
+					throw new ReflectionAccessException("Failed to load field '" + field.getName() + " from class " + clazz + ": " + Exceptions.collapseText(e), e);
 				}
 			}
 		}