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/07/18 18:34:13 UTC

[04/13] git commit: promote snake-yaml dependency and Yamls utility to utils package, and use yaml parsing for map coercion to be much more flexible in terms of string-to-map coercion

promote snake-yaml dependency and Yamls utility to utils package, and use yaml parsing for map coercion to be much more flexible in terms of string-to-map coercion


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

Branch: refs/heads/master
Commit: a4f7a4c0e3db5220e936bcffb5d52c3731b22175
Parents: f94c2ae
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Thu Jul 17 23:29:12 2014 -0400
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Fri Jul 18 09:53:52 2014 -0400

----------------------------------------------------------------------
 camp/camp-base/pom.xml                          |   5 -
 .../java/io/brooklyn/camp/spi/pdp/Artifact.java |   3 +-
 .../camp/spi/pdp/ArtifactRequirement.java       |   3 +-
 .../java/io/brooklyn/camp/spi/pdp/Service.java  |   3 +-
 .../camp/spi/pdp/ServiceCharacteristic.java     |   3 +-
 .../brooklyn/camp/spi/resolve/PdpProcessor.java |   2 +-
 .../main/java/io/brooklyn/util/yaml/Yamls.java  |  89 +-------------
 .../pdp/DeploymentPlanToyInterpreterTest.java   |   2 +-
 .../java/brooklyn/util/flags/TypeCoercions.java |  85 +++++++++-----
 .../util/internal/TypeCoercionsTest.java        |  72 ++++++++++--
 usage/launcher/pom.xml                          |   7 ++
 utils/common/pom.xml                            |   5 +
 .../src/main/java/brooklyn/util/yaml/Yamls.java | 115 +++++++++++++++++++
 13 files changed, 253 insertions(+), 141 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a4f7a4c0/camp/camp-base/pom.xml
----------------------------------------------------------------------
diff --git a/camp/camp-base/pom.xml b/camp/camp-base/pom.xml
index b80ef0c..e8638b1 100644
--- a/camp/camp-base/pom.xml
+++ b/camp/camp-base/pom.xml
@@ -57,11 +57,6 @@
             <artifactId>commons-compress</artifactId>
             <version>${commons-compress.version}</version>
         </dependency>
-        <dependency>
-            <groupId>org.yaml</groupId>
-            <artifactId>snakeyaml</artifactId>
-            <version>${snakeyaml.version}</version>
-        </dependency>
         
             <!-- just for logging, not exported -->
             <!-- 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a4f7a4c0/camp/camp-base/src/main/java/io/brooklyn/camp/spi/pdp/Artifact.java
----------------------------------------------------------------------
diff --git a/camp/camp-base/src/main/java/io/brooklyn/camp/spi/pdp/Artifact.java b/camp/camp-base/src/main/java/io/brooklyn/camp/spi/pdp/Artifact.java
index 72f356e..697a3ba 100644
--- a/camp/camp-base/src/main/java/io/brooklyn/camp/spi/pdp/Artifact.java
+++ b/camp/camp-base/src/main/java/io/brooklyn/camp/spi/pdp/Artifact.java
@@ -18,8 +18,6 @@
  */
 package io.brooklyn.camp.spi.pdp;
 
-import io.brooklyn.util.yaml.Yamls;
-
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
@@ -27,6 +25,7 @@ import java.util.Map;
 import org.apache.commons.lang3.builder.ToStringBuilder;
 
 import brooklyn.util.collections.MutableMap;
+import brooklyn.util.yaml.Yamls;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a4f7a4c0/camp/camp-base/src/main/java/io/brooklyn/camp/spi/pdp/ArtifactRequirement.java
----------------------------------------------------------------------
diff --git a/camp/camp-base/src/main/java/io/brooklyn/camp/spi/pdp/ArtifactRequirement.java b/camp/camp-base/src/main/java/io/brooklyn/camp/spi/pdp/ArtifactRequirement.java
index 50f5335..da9936a 100644
--- a/camp/camp-base/src/main/java/io/brooklyn/camp/spi/pdp/ArtifactRequirement.java
+++ b/camp/camp-base/src/main/java/io/brooklyn/camp/spi/pdp/ArtifactRequirement.java
@@ -18,13 +18,12 @@
  */
 package io.brooklyn.camp.spi.pdp;
 
-import io.brooklyn.util.yaml.Yamls;
-
 import java.util.Map;
 
 import org.apache.commons.lang3.builder.ToStringBuilder;
 
 import brooklyn.util.collections.MutableMap;
+import brooklyn.util.yaml.Yamls;
 
 import com.google.common.collect.ImmutableMap;
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a4f7a4c0/camp/camp-base/src/main/java/io/brooklyn/camp/spi/pdp/Service.java
----------------------------------------------------------------------
diff --git a/camp/camp-base/src/main/java/io/brooklyn/camp/spi/pdp/Service.java b/camp/camp-base/src/main/java/io/brooklyn/camp/spi/pdp/Service.java
index 241b80c..5921176 100644
--- a/camp/camp-base/src/main/java/io/brooklyn/camp/spi/pdp/Service.java
+++ b/camp/camp-base/src/main/java/io/brooklyn/camp/spi/pdp/Service.java
@@ -18,8 +18,6 @@
  */
 package io.brooklyn.camp.spi.pdp;
 
-import io.brooklyn.util.yaml.Yamls;
-
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
@@ -27,6 +25,7 @@ import java.util.Map;
 import org.apache.commons.lang3.builder.ToStringBuilder;
 
 import brooklyn.util.collections.MutableMap;
+import brooklyn.util.yaml.Yamls;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a4f7a4c0/camp/camp-base/src/main/java/io/brooklyn/camp/spi/pdp/ServiceCharacteristic.java
----------------------------------------------------------------------
diff --git a/camp/camp-base/src/main/java/io/brooklyn/camp/spi/pdp/ServiceCharacteristic.java b/camp/camp-base/src/main/java/io/brooklyn/camp/spi/pdp/ServiceCharacteristic.java
index cc5227d..8b27e2a 100644
--- a/camp/camp-base/src/main/java/io/brooklyn/camp/spi/pdp/ServiceCharacteristic.java
+++ b/camp/camp-base/src/main/java/io/brooklyn/camp/spi/pdp/ServiceCharacteristic.java
@@ -18,13 +18,12 @@
  */
 package io.brooklyn.camp.spi.pdp;
 
-import io.brooklyn.util.yaml.Yamls;
-
 import java.util.Map;
 
 import org.apache.commons.lang3.builder.ToStringBuilder;
 
 import brooklyn.util.collections.MutableMap;
+import brooklyn.util.yaml.Yamls;
 
 import com.google.common.collect.ImmutableMap;
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a4f7a4c0/camp/camp-base/src/main/java/io/brooklyn/camp/spi/resolve/PdpProcessor.java
----------------------------------------------------------------------
diff --git a/camp/camp-base/src/main/java/io/brooklyn/camp/spi/resolve/PdpProcessor.java b/camp/camp-base/src/main/java/io/brooklyn/camp/spi/resolve/PdpProcessor.java
index 897ff23..3252aaf 100644
--- a/camp/camp-base/src/main/java/io/brooklyn/camp/spi/resolve/PdpProcessor.java
+++ b/camp/camp-base/src/main/java/io/brooklyn/camp/spi/resolve/PdpProcessor.java
@@ -27,7 +27,6 @@ import io.brooklyn.camp.spi.pdp.DeploymentPlan;
 import io.brooklyn.camp.spi.pdp.Service;
 import io.brooklyn.camp.spi.resolve.interpret.PlanInterpretationContext;
 import io.brooklyn.camp.spi.resolve.interpret.PlanInterpretationNode;
-import io.brooklyn.util.yaml.Yamls;
 
 import java.io.InputStream;
 import java.io.Reader;
@@ -41,6 +40,7 @@ import org.apache.commons.compress.archivers.ArchiveStreamFactory;
 import org.yaml.snakeyaml.error.YAMLException;
 
 import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.yaml.Yamls;
 
 import com.google.common.annotations.VisibleForTesting;
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a4f7a4c0/camp/camp-base/src/main/java/io/brooklyn/util/yaml/Yamls.java
----------------------------------------------------------------------
diff --git a/camp/camp-base/src/main/java/io/brooklyn/util/yaml/Yamls.java b/camp/camp-base/src/main/java/io/brooklyn/util/yaml/Yamls.java
index d275ce8..44974f0 100644
--- a/camp/camp-base/src/main/java/io/brooklyn/util/yaml/Yamls.java
+++ b/camp/camp-base/src/main/java/io/brooklyn/util/yaml/Yamls.java
@@ -18,90 +18,7 @@
  */
 package io.brooklyn.util.yaml;
 
-import java.io.Reader;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.common.collect.Iterables;
-
-public class Yamls {
-
-    private static final Logger log = LoggerFactory.getLogger(Yamls.class);
-    
-    @SuppressWarnings({ "unchecked", "rawtypes" })
-    public static <T> T getAs(Object x, Class<T> type) {
-        if (x==null) return null;
-        if (x instanceof Iterable || x instanceof Iterator) {
-            List result = new ArrayList();
-            Iterator xi;
-            if (Iterator.class.isAssignableFrom(x.getClass())) {
-                xi = (Iterator)x;
-            } else {
-                xi = ((Iterable)x).iterator();
-            }
-            while (xi.hasNext()) {
-                    result.add( xi.next() );
-            }
-            if (type.isAssignableFrom(Iterable.class)) return (T)result;
-            if (type.isAssignableFrom(Iterator.class)) return (T)result.iterator();
-            if (type.isAssignableFrom(List.class)) return (T)result;
-            x = Iterables.getOnlyElement(result);
-        }
-        // TODO more coercion?
-        return (T)x;
-    }
-
-    @SuppressWarnings("rawtypes")
-    public static void dump(int depth, Object r) {
-        if (r instanceof Iterable) {
-            for (Object ri : ((Iterable)r))
-                dump(depth+1, ri);
-        } else if (r instanceof Map) {
-            for (Object re: ((Map)r).entrySet()) {
-                for (int i=0; i<depth; i++) System.out.print(" ");
-                System.out.println(((Entry)re).getKey()+":");
-                dump(depth+1, ((Entry)re).getValue());
-            }
-        } else {
-            for (int i=0; i<depth; i++) System.out.print(" ");
-            if (r==null) System.out.println("<null>");
-            else System.out.println("<"+r.getClass().getSimpleName()+">"+" "+r);
-        }
-    }
-
-    /** simplifies new Yaml().loadAll, and converts to list to prevent single-use iterable bug in yaml */
-    @SuppressWarnings("unchecked")
-    public static Iterable<Object> parseAll(String yaml) {
-        Iterable<Object> result = new org.yaml.snakeyaml.Yaml().loadAll(yaml);
-        return (List<Object>) getAs(result, List.class);
-    }
-
-    /** as {@link #parseAll(String)} */
-    @SuppressWarnings("unchecked")
-    public static Iterable<Object> parseAll(Reader yaml) {
-        Iterable<Object> result = new org.yaml.snakeyaml.Yaml().loadAll(yaml);
-        return (List<Object>) getAs(result, List.class);
-    }
-
-    public static Object removeMultinameAttribute(Map<String,Object> obj, String ...equivalentNames) {
-        Object result = null;
-        for (String name: equivalentNames) {
-            Object candidate = obj.remove(name);
-            if (candidate!=null) {
-                if (result==null) result = candidate;
-                else if (!result.equals(candidate)) {
-                    log.warn("Different values for attributes "+Arrays.toString(equivalentNames)+"; " +
-                    		"preferring '"+result+"' to '"+candidate+"'");
-                }
-            }
-        }
-        return result;
-    }
+/** @deprecated since 0.7.0 use {@link brooklyn.util.yaml.Yamls} */
+@Deprecated
+public class Yamls extends brooklyn.util.yaml.Yamls {
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a4f7a4c0/camp/camp-base/src/test/java/io/brooklyn/camp/spi/pdp/DeploymentPlanToyInterpreterTest.java
----------------------------------------------------------------------
diff --git a/camp/camp-base/src/test/java/io/brooklyn/camp/spi/pdp/DeploymentPlanToyInterpreterTest.java b/camp/camp-base/src/test/java/io/brooklyn/camp/spi/pdp/DeploymentPlanToyInterpreterTest.java
index de922a1..29f8abf 100644
--- a/camp/camp-base/src/test/java/io/brooklyn/camp/spi/pdp/DeploymentPlanToyInterpreterTest.java
+++ b/camp/camp-base/src/test/java/io/brooklyn/camp/spi/pdp/DeploymentPlanToyInterpreterTest.java
@@ -21,7 +21,6 @@ package io.brooklyn.camp.spi.pdp;
 import io.brooklyn.camp.BasicCampPlatform;
 import io.brooklyn.camp.spi.resolve.PlanInterpreter.PlanInterpreterAdapter;
 import io.brooklyn.camp.spi.resolve.interpret.PlanInterpretationNode;
-import io.brooklyn.util.yaml.Yamls;
 
 import java.util.Map;
 
@@ -32,6 +31,7 @@ import org.testng.annotations.Test;
 
 import brooklyn.util.stream.Streams;
 import brooklyn.util.text.Strings;
+import brooklyn.util.yaml.Yamls;
 
 @Test
 public class DeploymentPlanToyInterpreterTest {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a4f7a4c0/core/src/main/java/brooklyn/util/flags/TypeCoercions.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/util/flags/TypeCoercions.java b/core/src/main/java/brooklyn/util/flags/TypeCoercions.java
index 1ee45ae..7e92ed5 100644
--- a/core/src/main/java/brooklyn/util/flags/TypeCoercions.java
+++ b/core/src/main/java/brooklyn/util/flags/TypeCoercions.java
@@ -18,7 +18,9 @@
  */
 package brooklyn.util.flags;
 
-import java.io.IOException;
+import groovy.lang.Closure;
+import groovy.time.TimeDuration;
+
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
@@ -39,23 +41,9 @@ import java.util.concurrent.atomic.AtomicLong;
 import javax.annotation.Nullable;
 import javax.annotation.concurrent.GuardedBy;
 
-import org.codehaus.jackson.map.ObjectMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.base.CaseFormat;
-import com.google.common.base.Function;
-import com.google.common.base.Preconditions;
-import com.google.common.base.Predicate;
-import com.google.common.base.Splitter;
-import com.google.common.collect.HashBasedTable;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Table;
-import com.google.common.net.HostAndPort;
-import com.google.common.primitives.Primitives;
-import com.google.common.reflect.TypeToken;
-
 import brooklyn.entity.basic.ClosureEntityFactory;
 import brooklyn.entity.basic.ConfigurableEntityFactory;
 import brooklyn.entity.basic.ConfigurableEntityFactoryFromEntityFactory;
@@ -70,10 +58,23 @@ import brooklyn.util.net.Cidr;
 import brooklyn.util.net.Networking;
 import brooklyn.util.net.UserAndHostAndPort;
 import brooklyn.util.text.StringEscapes.JavaStringEscapes;
+import brooklyn.util.text.Strings;
 import brooklyn.util.time.Duration;
-import groovy.lang.Closure;
-import groovy.time.TimeDuration;
+import brooklyn.util.yaml.Yamls;
 
+import com.google.common.base.CaseFormat;
+import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Table;
+import com.google.common.net.HostAndPort;
+import com.google.common.primitives.Primitives;
+import com.google.common.reflect.TypeToken;
+
+@SuppressWarnings("rawtypes")
 public class TypeCoercions {
 
     private static final Logger log = LoggerFactory.getLogger(TypeCoercions.class);
@@ -103,7 +104,7 @@ public class TypeCoercions {
     }
 
     /** @see #coerce(Object, Class) */
-    @SuppressWarnings({ "unchecked", "rawtypes" })
+    @SuppressWarnings({ "unchecked" })
     public static <T> T coerce(Object value, TypeToken<T> targetTypeToken) {
         if (value==null) return null;
         // does not actually cast generified contents; that is left to the caller
@@ -433,12 +434,14 @@ public class TypeCoercions {
             }
         });
         registerAdapter(Collection.class, Set.class, new Function<Collection,Set>() {
+            @SuppressWarnings("unchecked")
             @Override
             public Set apply(Collection input) {
                 return new LinkedHashSet(input);
             }
         });
         registerAdapter(Collection.class, List.class, new Function<Collection,List>() {
+            @SuppressWarnings("unchecked")
             @Override
             public List apply(Collection input) {
                 return new ArrayList(input);
@@ -485,12 +488,14 @@ public class TypeCoercions {
             }
         });
         registerAdapter(Closure.class, ConfigurableEntityFactory.class, new Function<Closure,ConfigurableEntityFactory>() {
+            @SuppressWarnings("unchecked")
             @Override
             public ConfigurableEntityFactory apply(Closure input) {
                 return new ClosureEntityFactory(input);
             }
         });
         registerAdapter(EntityFactory.class, ConfigurableEntityFactory.class, new Function<EntityFactory,ConfigurableEntityFactory>() {
+            @SuppressWarnings("unchecked")
             @Override
             public ConfigurableEntityFactory apply(EntityFactory input) {
                 if (input instanceof ConfigurableEntityFactory) return (ConfigurableEntityFactory)input;
@@ -498,6 +503,7 @@ public class TypeCoercions {
             }
         });
         registerAdapter(Closure.class, EntityFactory.class, new Function<Closure,EntityFactory>() {
+            @SuppressWarnings("unchecked")
             @Override
             public EntityFactory apply(Closure input) {
                 return new ClosureEntityFactory(input);
@@ -530,6 +536,7 @@ public class TypeCoercions {
             }
         });
         registerAdapter(Object.class, TimeDuration.class, new Function<Object,TimeDuration>() {
+            @SuppressWarnings("deprecation")
             @Override
             public TimeDuration apply(final Object input) {
                 log.warn("deprecated automatic coercion of Object to TimeDuration (set breakpoint in TypeCoercions to inspect, convert to Duration)");
@@ -627,20 +634,40 @@ public class TypeCoercions {
         registerAdapter(String.class, Map.class, new Function<String,Map>() {
             @Override
             public Map apply(final String input) {
-                // Auto-detect JSON. This allows complex data structures to be received over the REST API.
+                Exception error = null;
+                
+                // first try wrapping in braces if needed
+                if (!input.trim().startsWith("{")) {
+                    try {
+                        return apply("{ "+input+" }");
+                    } catch (Exception e) {
+                        Exceptions.propagateIfFatal(e);
+                        // prefer this error
+                        error = e;
+                        // fall back to parsing without braces, e.g. if it's multiline
+                    }
+                }
+
                 try {
-                    if (!input.isEmpty() && input.charAt(0) == '{') {
-                        return new ObjectMapper().readValue(input, Map.class);
+                    return Yamls.getAs( Yamls.parseAll(input), Map.class );
+                } catch (Exception e) {
+                    Exceptions.propagateIfFatal(e);
+                    if (error!=null && input.indexOf('\n')==-1) {
+                        // prefer the original error if it wasn't braced and wasn't multiline
+                        e = error;
                     }
-                } catch (IOException e) {
-                    // just fall through to the map parsing
+                    throw new IllegalArgumentException("Cannot parse string as map with flexible YAML parsing; "+
+                        (e instanceof ClassCastException ? "yaml treats it as a string" : 
+                        (e instanceof IllegalArgumentException && Strings.isNonEmpty(e.getMessage())) ? e.getMessage() :
+                        ""+e) );
                 }
-                
-                // TODO would be nice to accept YAML for complex data structures too, but it's not as simple as JSON to auto-detect.
-                
-                // Simple map parsing - supports "key1=value1,key2=value2" style input
-                // TODO we should respect quoted strings etc
-                return ImmutableMap.copyOf(Splitter.on(",").trimResults().omitEmptyStrings().withKeyValueSeparator("=").split(input));
+
+                // NB: previously we supported this also, when we did json above;
+                // yaml support is better as it supports quotes (and better than json because it allows dropping quotes)
+                // snake-yaml, our parser, also accepts key=value -- although i'm not sure this is strictly yaml compliant;
+                // our tests will catch it if snake behaviour changes, and we can reinstate this
+                // (but note it doesn't do quotes; see http://code.google.com/p/guava-libraries/issues/detail?id=412 for that):
+//                return ImmutableMap.copyOf(Splitter.on(",").trimResults().omitEmptyStrings().withKeyValueSeparator("=").split(input));
             }
         });
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a4f7a4c0/core/src/test/java/brooklyn/util/internal/TypeCoercionsTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/util/internal/TypeCoercionsTest.java b/core/src/test/java/brooklyn/util/internal/TypeCoercionsTest.java
index 05b4c72..6efffa2 100644
--- a/core/src/test/java/brooklyn/util/internal/TypeCoercionsTest.java
+++ b/core/src/test/java/brooklyn/util/internal/TypeCoercionsTest.java
@@ -32,17 +32,17 @@ import org.slf4j.LoggerFactory;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
+import brooklyn.entity.basic.Lifecycle;
+import brooklyn.util.flags.ClassCoercionException;
+import brooklyn.util.flags.TypeCoercions;
+import brooklyn.util.text.StringPredicates;
+
 import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.reflect.TypeToken;
 
-import brooklyn.entity.basic.Lifecycle;
-import brooklyn.util.flags.ClassCoercionException;
-import brooklyn.util.flags.TypeCoercions;
-import brooklyn.util.text.StringPredicates;
-
 public class TypeCoercionsTest {
 
     private static final Logger log = LoggerFactory.getLogger(TypeCoercionsTest.class);
@@ -191,15 +191,65 @@ public class TypeCoercionsTest {
     }
 
     @Test
-    public void testStringToMapCoercion() {
-        Map<?,?> s = TypeCoercions.coerce("a=1,b=2,c=3", Map.class);
-        Assert.assertEquals(s, ImmutableMap.of("a", "1", "b", "2", "c", "3"));
+    public void testJsonStringToMapCoercion() {
+        Map<?,?> s = TypeCoercions.coerce("{ \"a\" : \"1\", b : 2 }", Map.class);
+        Assert.assertEquals(s, ImmutableMap.of("a", "1", "b", 2));
     }
 
     @Test
-    public void testJsonStringToMapCoercion() {
-        Map<?,?> s = TypeCoercions.coerce("{ \"a\" : \"1\", \"b\" : \"2\", \"c\" : \"3\" }", Map.class);
-        Assert.assertEquals(s, ImmutableMap.of("a", "1", "b", "2", "c", "3"));
+    public void testJsonStringWithoutQuotesToMapCoercion() {
+        Map<?,?> s = TypeCoercions.coerce("{ a : 1 }", Map.class);
+        Assert.assertEquals(s, ImmutableMap.of("a", 1));
+    }
+
+    @Test
+    public void testJsonComplexTypesToMapCoercion() {
+        Map<?,?> s = TypeCoercions.coerce("{ a : [1, \"2\", '\"3\"'], b: { c: d, 'e': \"f\" } }", Map.class);
+        Assert.assertEquals(s, ImmutableMap.of("a", ImmutableList.<Object>of(1, "2", "\"3\""), 
+            "b", ImmutableMap.of("c", "d", "e", "f")));
+    }
+
+    @Test
+    public void testJsonStringWithoutBracesToMapCoercion() {
+        Map<?,?> s = TypeCoercions.coerce("a : 1", Map.class);
+        Assert.assertEquals(s, ImmutableMap.of("a", 1));
+    }
+
+    @Test
+    public void testJsonStringWithoutBracesWithMultipleToMapCoercion() {
+        Map<?,?> s = TypeCoercions.coerce("a : 1, b : 2", Map.class);
+        Assert.assertEquals(s, ImmutableMap.of("a", 1, "b", 2));
+    }
+
+    @Test
+    public void testKeyEqualsValueStringToMapCoercion() {
+        Map<?,?> s = TypeCoercions.coerce("a=1,b=2", Map.class);
+        Assert.assertEquals(s, ImmutableMap.of("a", "1", "b", "2"));
+    }
+
+    @Test(expectedExceptions=IllegalArgumentException.class)
+    public void testJsonStringWithoutBracesOrSpaceDisallowedAsMapCoercion() {
+        // yaml requires spaces after the colon
+        Map<?,?> s = TypeCoercions.coerce("a:1,b:2", Map.class);
+        Assert.assertEquals(s, ImmutableMap.of("a", 1, "b", 2));
+    }
+    
+    @Test
+    public void testEqualsInBracesMapCoercion() {
+        Map<?,?> s = TypeCoercions.coerce("{ a = 1, b = '2' }", Map.class);
+        Assert.assertEquals(s, ImmutableMap.of("a", 1, "b", "2"));
+    }
+
+    @Test
+    public void testKeyEqualsOrColonValueWithBracesStringToMapCoercion() {
+        Map<?,?> s = TypeCoercions.coerce("{ a=1, b: 2 }", Map.class);
+        Assert.assertEquals(s, ImmutableMap.of("a", "1", "b", 2));
+    }
+
+    @Test
+    public void testKeyEqualsOrColonValueWithoutBracesStringToMapCoercion() {
+        Map<?,?> s = TypeCoercions.coerce("a=1, b: 2", Map.class);
+        Assert.assertEquals(s, ImmutableMap.of("a", "1", "b", 2));
     }
 
     @Test

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a4f7a4c0/usage/launcher/pom.xml
----------------------------------------------------------------------
diff --git a/usage/launcher/pom.xml b/usage/launcher/pom.xml
index 57d3e24..1fb4fcd 100644
--- a/usage/launcher/pom.xml
+++ b/usage/launcher/pom.xml
@@ -86,6 +86,13 @@
         </dependency>
         <dependency>
             <groupId>io.brooklyn</groupId>
+            <artifactId>brooklyn-software-base</artifactId>
+            <version>${project.version}</version>
+            <classifier>tests</classifier>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>io.brooklyn</groupId>
             <artifactId>brooklyn-software-webapp</artifactId>
             <version>${project.version}</version>
             <scope>test</scope>

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a4f7a4c0/utils/common/pom.xml
----------------------------------------------------------------------
diff --git a/utils/common/pom.xml b/utils/common/pom.xml
index 24dec9f..54de72a 100644
--- a/utils/common/pom.xml
+++ b/utils/common/pom.xml
@@ -56,6 +56,11 @@
             <artifactId>commons-io</artifactId>
             <version>${commons-io.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.yaml</groupId>
+            <artifactId>snakeyaml</artifactId>
+            <version>${snakeyaml.version}</version>
+        </dependency>
         
         <dependency>
             <groupId>org.testng</groupId>

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a4f7a4c0/utils/common/src/main/java/brooklyn/util/yaml/Yamls.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/yaml/Yamls.java b/utils/common/src/main/java/brooklyn/util/yaml/Yamls.java
new file mode 100644
index 0000000..a22432f
--- /dev/null
+++ b/utils/common/src/main/java/brooklyn/util/yaml/Yamls.java
@@ -0,0 +1,115 @@
+/*
+ * 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.util.yaml;
+
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.Iterables;
+
+public class Yamls {
+
+    private static final Logger log = LoggerFactory.getLogger(Yamls.class);
+
+    /** returns the given yaml object (map or list or primitive) as the given yaml-supperted type 
+     * (map or list or primitive e.g. string, number, boolean).
+     * <p>
+     * if the object is an iterable containing a single element, and the type is not an iterable,
+     * this will attempt to unwrap it.
+     * 
+     * @throws IllegalArgumentException if the input is an iterable not containing a single element,
+     *   and the cast is requested to a non-iterable type 
+     * @throws ClassCastException if cannot be casted */
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    public static <T> T getAs(Object x, Class<T> type) {
+        if (x==null) return null;
+        if (x instanceof Iterable || x instanceof Iterator) {
+            List result = new ArrayList();
+            Iterator xi;
+            if (Iterator.class.isAssignableFrom(x.getClass())) {
+                xi = (Iterator)x;
+            } else {
+                xi = ((Iterable)x).iterator();
+            }
+            while (xi.hasNext()) {
+                result.add( xi.next() );
+            }
+            if (type.isAssignableFrom(Iterable.class)) return (T)result;
+            if (type.isAssignableFrom(Iterator.class)) return (T)result.iterator();
+            if (type.isAssignableFrom(List.class)) return (T)result;
+            x = Iterables.getOnlyElement(result);
+        }
+        return (T)x;
+    }
+
+    @SuppressWarnings("rawtypes")
+    public static void dump(int depth, Object r) {
+        if (r instanceof Iterable) {
+            for (Object ri : ((Iterable)r))
+                dump(depth+1, ri);
+        } else if (r instanceof Map) {
+            for (Object re: ((Map)r).entrySet()) {
+                for (int i=0; i<depth; i++) System.out.print(" ");
+                System.out.println(((Entry)re).getKey()+":");
+                dump(depth+1, ((Entry)re).getValue());
+            }
+        } else {
+            for (int i=0; i<depth; i++) System.out.print(" ");
+            if (r==null) System.out.println("<null>");
+            else System.out.println("<"+r.getClass().getSimpleName()+">"+" "+r);
+        }
+    }
+
+    /** simplifies new Yaml().loadAll, and converts to list to prevent single-use iterable bug in yaml */
+    @SuppressWarnings("unchecked")
+    public static Iterable<Object> parseAll(String yaml) {
+        Iterable<Object> result = new org.yaml.snakeyaml.Yaml().loadAll(yaml);
+        return (List<Object>) getAs(result, List.class);
+    }
+
+    /** as {@link #parseAll(String)} */
+    @SuppressWarnings("unchecked")
+    public static Iterable<Object> parseAll(Reader yaml) {
+        Iterable<Object> result = new org.yaml.snakeyaml.Yaml().loadAll(yaml);
+        return (List<Object>) getAs(result, List.class);
+    }
+
+    public static Object removeMultinameAttribute(Map<String,Object> obj, String ...equivalentNames) {
+        Object result = null;
+        for (String name: equivalentNames) {
+            Object candidate = obj.remove(name);
+            if (candidate!=null) {
+                if (result==null) result = candidate;
+                else if (!result.equals(candidate)) {
+                    log.warn("Different values for attributes "+Arrays.toString(equivalentNames)+"; " +
+                    		"preferring '"+result+"' to '"+candidate+"'");
+                }
+            }
+        }
+        return result;
+    }
+}