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 2015/04/21 22:41:20 UTC

[01/16] incubator-brooklyn git commit: implement catalog multi-item in same file support

Repository: incubator-brooklyn
Updated Branches:
  refs/heads/master ff31a41d4 -> c4ca5b683


implement catalog multi-item in same file support

needs tests at this point, but seems to be working, and backwards compatible


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

Branch: refs/heads/master
Commit: 08cb3e75f7d9f6ccd87bffbf34ab149e56b7411e
Parents: 99fa0b1
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Mon Mar 30 17:21:34 2015 -0500
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Wed Apr 15 20:05:19 2015 -0500

----------------------------------------------------------------------
 .../java/brooklyn/catalog/BrooklynCatalog.java  |  18 ++
 .../catalog/internal/BasicBrooklynCatalog.java  | 168 ++++++++++++++-----
 .../catalog/internal/CatalogItemBuilder.java    |   5 +
 .../brooklyn/catalog/internal/CatalogUtils.java |   1 +
 .../catalog/internal/CatalogLoadTest.java       |   4 +-
 .../brooklyn/catalog/CatalogYamlEntityTest.java |   5 +-
 6 files changed, 156 insertions(+), 45 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/08cb3e75/api/src/main/java/brooklyn/catalog/BrooklynCatalog.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/catalog/BrooklynCatalog.java b/api/src/main/java/brooklyn/catalog/BrooklynCatalog.java
index e2b39b0..ce4386f 100644
--- a/api/src/main/java/brooklyn/catalog/BrooklynCatalog.java
+++ b/api/src/main/java/brooklyn/catalog/BrooklynCatalog.java
@@ -104,6 +104,24 @@ public interface BrooklynCatalog {
     CatalogItem<?,?> addItem(String yaml, boolean forceUpdate);
     
     /**
+     * Adds items (represented in yaml) to the catalog.
+     * Fails if the same version exists in catalog.
+     *
+     * @throws IllegalArgumentException if the yaml was invalid
+     */
+    Iterable<? extends CatalogItem<?,?>> addItems(String yaml);
+    
+    /**
+     * Adds items (represented in yaml) to the catalog.
+     * 
+     * @param forceUpdate If true allows catalog update even when an
+     * item exists with the same symbolicName and version
+     *
+     * @throws IllegalArgumentException if the yaml was invalid
+     */
+    Iterable<? extends CatalogItem<?,?>> addItems(String yaml, boolean forceUpdate);
+    
+    /**
      * adds an item to the 'manual' catalog;
      * this does not update the classpath or have a record to the java Class
      *

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/08cb3e75/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java b/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java
index 96760aa..8a562d0 100644
--- a/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java
+++ b/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java
@@ -28,7 +28,7 @@ import io.brooklyn.camp.spi.pdp.Service;
 
 import java.io.FileNotFoundException;
 import java.util.Collection;
-import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.Set;
@@ -37,6 +37,7 @@ import javax.annotation.Nullable;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.yaml.snakeyaml.Yaml;
 
 import brooklyn.basic.AbstractBrooklynObjectSpec;
 import brooklyn.basic.BrooklynObjectInternal.ConfigurationSupportInternal;
@@ -57,9 +58,11 @@ import brooklyn.management.internal.EntityManagementUtils;
 import brooklyn.management.internal.ManagementContextInternal;
 import brooklyn.policy.Policy;
 import brooklyn.policy.PolicySpec;
+import brooklyn.util.collections.MutableList;
 import brooklyn.util.collections.MutableMap;
 import brooklyn.util.collections.MutableSet;
 import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.flags.TypeCoercions;
 import brooklyn.util.guava.Maybe;
 import brooklyn.util.javalang.AggregateClassLoader;
 import brooklyn.util.javalang.LoadedClassLoader;
@@ -491,44 +494,112 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
         if (item instanceof CatalogItemDtoAbstract) return (CatalogItemDtoAbstract<T,SpecT>) item;
         throw new IllegalStateException("Cannot unwrap catalog item '"+item+"' (type "+item.getClass()+") to restore DTO");
     }
+    
+    @SuppressWarnings("unchecked")
+    private <T> Maybe<T> getFirstAs(Map<?,?> map, Class<T> type, String firstKey, String ...otherKeys) {
+        if (map==null) return Maybe.absent("No map available");
+        String foundKey = null;
+        Object value = null;
+        if (map.containsKey(firstKey)) foundKey = firstKey;
+        else for (String key: otherKeys) {
+            if (map.containsKey(key)) {
+                foundKey = key;
+                break;
+            }
+        }
+        if (foundKey==null) return Maybe.absent("Missing entry '"+firstKey+"'");
+        value = map.get(foundKey);
+        if (!type.isInstance(value)) return Maybe.absent("Entry for '"+firstKey+"' should be of type "+type+", not "+value.getClass());
+        return Maybe.of((T)value);
+    }
+    
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    private Maybe<Map<?,?>> getFirstAsMap(Map<?,?> map, String firstKey, String ...otherKeys) {
+        return (Maybe<Map<?,?>>)(Maybe) getFirstAs(map, Map.class, firstKey, otherKeys);
+    }
 
-    private CatalogItemDtoAbstract<?,?> getAbstractCatalogItem(String yaml) {
-        DeploymentPlan plan = makePlanFromYaml(yaml);
+    private List<CatalogItemDtoAbstract<?,?>> getAbstractCatalogItems(String yaml) {
+        Map<?,?> itemDef = Yamls.getAs(Yamls.parseAll(yaml), Map.class);
+        Map<?,?> catalogMetadata = getFirstAsMap(itemDef, "brooklyn.catalog", "catalog").orNull();
+        if (catalogMetadata==null)
+            log.warn("No `brooklyn.catalog` supplied in catalog request; using legacy mode for "+itemDef);
+        catalogMetadata = MutableMap.copyOf(catalogMetadata);
 
-        @SuppressWarnings("rawtypes")
-        Maybe<Map> possibleCatalog = plan.getCustomAttribute("brooklyn.catalog", Map.class, true);
-        MutableMap<String, Object> catalog = MutableMap.of();
-        if (possibleCatalog.isPresent()) {
-            @SuppressWarnings("unchecked")
-            Map<String, Object> catalog2 = (Map<String, Object>) possibleCatalog.get();
-            catalog.putAll(catalog2);
+        List<CatalogItemDtoAbstract<?, ?>> result = MutableList.of();
+        
+        addAbstractCatalogItems(catalogMetadata, result, null);
+        
+        itemDef.remove("brooklyn.catalog");
+        catalogMetadata.remove("item");
+        catalogMetadata.remove("items");
+        if (!itemDef.isEmpty()) {
+            log.debug("Reading brooklyn.catalog peer keys as item");
+            Map<String,?> rootItem = MutableMap.of("item", itemDef);
+            addAbstractCatalogItems(rootItem, result, catalogMetadata);
         }
+        
+        return result;
+    }
 
-        Collection<CatalogBundle> libraries = Collections.emptyList();
-        Maybe<Object> possibleLibraries = catalog.getMaybe("libraries");
-        if (possibleLibraries.isAbsent()) possibleLibraries = catalog.getMaybe("brooklyn.libraries");
-        if (possibleLibraries.isPresentAndNonNull()) {
-            if (!(possibleLibraries.get() instanceof Collection))
-                throw new IllegalArgumentException("Libraries should be a list, not "+possibleLibraries.get());
-            libraries = CatalogItemDtoAbstract.parseLibraries((Collection<?>) possibleLibraries.get());
-        }
+    enum CatalogItemTypes { ENTITY, TEMPLATE, POLICY, LOCATION }
+    
+    @SuppressWarnings("unchecked")
+    private void addAbstractCatalogItems(Map<?,?> itemMetadata, List<CatalogItemDtoAbstract<?, ?>> result, Map<?,?> parentMetadata) {
 
-        final String id = (String) catalog.getMaybe("id").orNull();
-        final String version = Strings.toString(catalog.getMaybe("version").orNull());
-        final String symbolicName = (String) catalog.getMaybe("symbolicName").orNull();
-        final String name = (String) catalog.getMaybe("name").orNull();
-        final String displayName = (String) catalog.getMaybe("displayName").orNull();
-        final String description = (String) catalog.getMaybe("description").orNull();
-        final String iconUrl = (String) catalog.getMaybe("iconUrl").orNull();
-        final String iconUrlUnderscore = (String) catalog.getMaybe("icon_url").orNull();
-        final String deprecated = (String) catalog.getMaybe("deprecated").orNull();
+        // TODO:
+//        get yaml
+//        (tests)
+//        docs used as test casts -- (doc assertions that these match -- or import -- known test casesyamls)
+//        multiple versions in catalog page
+
+        Map<Object,Object> catalogMetadata = MutableMap.builder().putAll(parentMetadata).putAll(itemMetadata).build();
+        
+        // libraries we treat specially, to append the list, with the child's list preferred in classloading order
+        List<?> librariesL = MutableList.copyOf(getFirstAs(itemMetadata, List.class, "brooklyn.libraries", "libraries").orNull())
+            .appendAll(getFirstAs(parentMetadata, List.class, "brooklyn.libraries", "libraries").orNull());
+        if (!librariesL.isEmpty())
+            catalogMetadata.put("brooklyn.libraries", librariesL);
+        Collection<CatalogBundle> libraries = CatalogItemDtoAbstract.parseLibraries(librariesL);
+
+        Object items = catalogMetadata.remove("items");
+        Object item = catalogMetadata.remove("item");
+
+        if (items!=null) {
+            for (Map<?,?> i: ((List<Map<?,?>>)items)) {
+                addAbstractCatalogItems(i, result, catalogMetadata);
+            }
+        }
+        
+        if (item==null) return;
+        
+        //now parse the metadata and apply to item
+        
+        CatalogItemTypes itemType = TypeCoercions.coerce(getFirstAs(catalogMetadata, String.class, "item.type", "itemType", "item_type").or(CatalogItemTypes.ENTITY.toString()), CatalogItemTypes.class);
+        
+        String id = getFirstAs(catalogMetadata, String.class, "id").orNull();
+        String version = getFirstAs(catalogMetadata, String.class, "version").orNull();
+        String symbolicName = getFirstAs(catalogMetadata, String.class, "symbolicName").orNull();
+        String displayName = getFirstAs(catalogMetadata, String.class, "displayName").orNull();
+        String name = getFirstAs(catalogMetadata, String.class, "name").orNull();
 
         if ((Strings.isNonBlank(id) || Strings.isNonBlank(symbolicName)) && 
                 Strings.isNonBlank(displayName) &&
                 Strings.isNonBlank(name) && !name.equals(displayName)) {
             log.warn("Name property will be ignored due to the existence of displayName and at least one of id, symbolicName");
         }
-
+        
+        // TODO use src yaml if avail
+        String yaml = new Yaml().dump(item);
+        
+        DeploymentPlan plan = null;
+        try {
+            plan = makePlanFromYaml(yaml);
+        } catch (Exception e) {
+            Exceptions.propagateIfFatal(e);
+            if (itemType==CatalogItemTypes.ENTITY || itemType==CatalogItemTypes.TEMPLATE)
+                log.warn("Could not parse item YAML for "+itemType+" (registering anyway): "+e+"\n"+yaml);
+        }
+        
         final String catalogSymbolicName;
         if (Strings.isNonBlank(symbolicName)) {
             catalogSymbolicName = symbolicName;
@@ -540,9 +611,9 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
             }
         } else if (Strings.isNonBlank(name)) {
             catalogSymbolicName = name;
-        } else if (Strings.isNonBlank(plan.getName())) {
+        } else if (plan!=null && Strings.isNonBlank(plan.getName())) {
             catalogSymbolicName = plan.getName();
-        } else if (plan.getServices().size()==1) {
+        } else if (plan!=null && plan.getServices().size()==1) {
             Service svc = Iterables.getOnlyElement(plan.getServices());
             if (Strings.isBlank(svc.getServiceType())) {
                 throw new IllegalStateException("CAMP service type not expected to be missing for " + svc);
@@ -577,6 +648,7 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
             catalogDisplayName = null;
         }
 
+        final String description = getFirstAs(catalogMetadata, String.class, "description").orNull();
         final String catalogDescription;
         if (Strings.isNonBlank(description)) {
             catalogDescription = description;
@@ -586,33 +658,30 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
             catalogDescription = null;
         }
 
-        final String catalogIconUrl;
-        if (Strings.isNonBlank(iconUrl)) {
-            catalogIconUrl = iconUrl;
-        } else if (Strings.isNonBlank(iconUrlUnderscore)) {
-            catalogIconUrl = iconUrlUnderscore;
-        } else {
-            catalogIconUrl = null;
-        }
+        final String catalogIconUrl = getFirstAs(catalogMetadata, String.class, "icon.url", "iconUrl", "icon_url").orNull();
 
+        final String deprecated = getFirstAs(catalogMetadata, String.class, "deprecated").orNull();
         final Boolean catalogDeprecated = Boolean.valueOf(deprecated);
 
         CatalogUtils.installLibraries(mgmt, libraries);
 
-        String versionedId = CatalogUtils.getVersionedId(catalogSymbolicName, catalogVersion);
+        String versionedId = CatalogUtils.getVersionedId(catalogSymbolicName, catalogVersion!=null ? catalogVersion : NO_VERSION);
         BrooklynClassLoadingContext loader = CatalogUtils.newClassLoadingContext(mgmt, versionedId, libraries);
+        
+        // TODO for entities, we parse. for templates we don't. (?)
         AbstractBrooklynObjectSpec<?, ?> spec = createSpec(catalogSymbolicName, plan, loader);
 
         CatalogItemDtoAbstract<?, ?> dto = createItemBuilder(spec, catalogSymbolicName, catalogVersion)
             .libraries(libraries)
             .displayName(catalogDisplayName)
             .description(catalogDescription)
+            .deprecated(catalogDeprecated)
             .iconUrl(catalogIconUrl)
             .plan(yaml)
             .build();
 
         dto.setManagementContext((ManagementContextInternal) mgmt);
-        return dto;
+        result.add(dto);
     }
 
     private AbstractBrooklynObjectSpec<?,?> createSpec(String symbolicName, DeploymentPlan plan, BrooklynClassLoadingContext loader) {
@@ -664,10 +733,27 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
     }
 
     @Override
+    public List<? extends CatalogItem<?,?>> addItems(String yaml) {
+        return addItems(yaml, false);
+    }
+
+    @Override
     public CatalogItem<?,?> addItem(String yaml, boolean forceUpdate) {
+        return Iterables.getOnlyElement(addItems(yaml, forceUpdate));
+    }
+    
+    @Override
+    public List<? extends CatalogItem<?,?>> addItems(String yaml, boolean forceUpdate) {
         log.debug("Adding manual catalog item to "+mgmt+": "+yaml);
         checkNotNull(yaml, "yaml");
-        CatalogItemDtoAbstract<?,?> itemDto = getAbstractCatalogItem(yaml);
+        List<CatalogItemDtoAbstract<?, ?>> result = getAbstractCatalogItems(yaml);
+        for (CatalogItemDtoAbstract<?, ?> item: result) {
+            addItemDto(item, forceUpdate);
+        }
+        return result;
+    }
+    
+    private CatalogItem<?,?> addItemDto(CatalogItemDtoAbstract<?, ?> itemDto, boolean forceUpdate) {
         checkItemNotExists(itemDto, forceUpdate);
 
         if (manualAdditionsCatalog==null) loadManualAdditionsCatalog();

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/08cb3e75/core/src/main/java/brooklyn/catalog/internal/CatalogItemBuilder.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/catalog/internal/CatalogItemBuilder.java b/core/src/main/java/brooklyn/catalog/internal/CatalogItemBuilder.java
index 2b16cc6..fb7a735 100644
--- a/core/src/main/java/brooklyn/catalog/internal/CatalogItemBuilder.java
+++ b/core/src/main/java/brooklyn/catalog/internal/CatalogItemBuilder.java
@@ -94,6 +94,11 @@ public class CatalogItemBuilder<CatalogItemType extends CatalogItemDtoAbstract<?
         return this;
     }
 
+    public CatalogItemBuilder<CatalogItemType> deprecated(boolean deprecated) {
+        dto.setDeprecated(deprecated);
+        return this;
+    }
+
     public CatalogItemBuilder<CatalogItemType> libraries(Collection<CatalogBundle> libraries) {
         dto.setLibraries(libraries);
         return this;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/08cb3e75/core/src/main/java/brooklyn/catalog/internal/CatalogUtils.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/catalog/internal/CatalogUtils.java b/core/src/main/java/brooklyn/catalog/internal/CatalogUtils.java
index ed4d06d..50ef436 100644
--- a/core/src/main/java/brooklyn/catalog/internal/CatalogUtils.java
+++ b/core/src/main/java/brooklyn/catalog/internal/CatalogUtils.java
@@ -200,6 +200,7 @@ public class CatalogUtils {
     }
 
     public static String getVersionedId(String id, String version) {
+        // TODO null checks
         return id + VERSION_DELIMITER + version;
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/08cb3e75/core/src/test/java/brooklyn/catalog/internal/CatalogLoadTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/catalog/internal/CatalogLoadTest.java b/core/src/test/java/brooklyn/catalog/internal/CatalogLoadTest.java
index f36d4c9..3ff89f4 100644
--- a/core/src/test/java/brooklyn/catalog/internal/CatalogLoadTest.java
+++ b/core/src/test/java/brooklyn/catalog/internal/CatalogLoadTest.java
@@ -44,8 +44,10 @@ public class CatalogLoadTest {
         return ResourceUtils.create(this).getResourceAsString(file);
     }
 
+    // CAMP YAML parsing not available in core, so YAML catalog tests are in camp, e.g. CatalogYamlEntitiesTest 
+    
     @Test
-    public void testLoadCatalog() {
+    public void testLoadXmlCatalog() {
         CatalogDto catalog = (CatalogDto) serializer.fromString(
                 loadFile("classpath://brooklyn/catalog/internal/osgi-catalog.xml"));
         assertNotNull(catalog);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/08cb3e75/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java
index 048a75f..5b79982 100644
--- a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java
+++ b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java
@@ -21,12 +21,9 @@ package io.brooklyn.camp.brooklyn.catalog;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertTrue;
 import static org.testng.Assert.fail;
-import brooklyn.test.TestResourceUnavailableException;
-import brooklyn.util.ResourceUtils;
 import io.brooklyn.camp.brooklyn.AbstractYamlTest;
 
 import java.io.InputStream;
-import java.net.URL;
 import java.util.Collection;
 
 import org.testng.Assert;
@@ -39,6 +36,8 @@ import brooklyn.entity.Entity;
 import brooklyn.entity.basic.BasicEntity;
 import brooklyn.management.osgi.OsgiStandaloneTest;
 import brooklyn.management.osgi.OsgiTestResources;
+import brooklyn.test.TestResourceUnavailableException;
+import brooklyn.util.ResourceUtils;
 
 import com.google.common.collect.Iterables;
 


[15/16] incubator-brooklyn git commit: This closes #605

Posted by he...@apache.org.
This closes #605


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

Branch: refs/heads/master
Commit: 2cfd875387489d81a50533d2d5853c18500025e7
Parents: ff31a41 1354d03
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Tue Apr 21 21:39:52 2015 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Tue Apr 21 21:39:52 2015 +0100

----------------------------------------------------------------------
 .../main/java/brooklyn/enricher/Enrichers.java  | 89 ++++++++++++++++++--
 .../brooklyn/enricher/basic/Aggregator.java     | 34 ++++++++
 .../java/brooklyn/entity/basic/ConfigKeys.java  |  5 +-
 .../brooklyn/entity/effector/AddSensor.java     |  5 ++
 .../EnrichersSlightlySimplerYamlTest.java       | 38 +++++++++
 .../test-webapp-with-averaging-enricher.yaml    | 47 +++++++++++
 .../java/brooklyn/util/javalang/Boxing.java     | 15 ++++
 .../java/brooklyn/util/math/MathPredicates.java | 32 +++++++
 .../java/brooklyn/util/javalang/BoxingTest.java | 37 ++++++++
 9 files changed, 291 insertions(+), 11 deletions(-)
----------------------------------------------------------------------



[03/16] incubator-brooklyn git commit: switch to redcarpet so we can view the md on github better

Posted by he...@apache.org.
switch to redcarpet so we can view the md on github better

(github only supports triple-backtick syntax)


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

Branch: refs/heads/master
Commit: 253197f75fa75bfadfc050a1e4ce96bdb394ed06
Parents: 456049b
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Sun Mar 29 21:10:39 2015 -0500
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Wed Apr 15 20:05:19 2015 -0500

----------------------------------------------------------------------
 docs/_config.yml | 1 +
 1 file changed, 1 insertion(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/253197f7/docs/_config.yml
----------------------------------------------------------------------
diff --git a/docs/_config.yml b/docs/_config.yml
index 10a5d5a..a24029d 100644
--- a/docs/_config.yml
+++ b/docs/_config.yml
@@ -18,6 +18,7 @@
 #
 
 encoding: utf-8
+markdown: redcarpet
 
 # where this will publish
 url_root: http://0.0.0.0:4000


[14/16] incubator-brooklyn git commit: entity spec looked up from catalog

Posted by he...@apache.org.
entity spec looked up from catalog

refactors Brooklyn{AssemblyTemplateInstantiator,ComponentTemplateResolver} so that resolution is shared between the two.  this could be cleaned up further.
also applies some better debug/logging and html/underscore-template escaping.


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

Branch: refs/heads/master
Commit: f6c5619d5bd3c7e92734fa08c16303da77453ddd
Parents: fb23d41
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Tue Apr 21 09:56:40 2015 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Tue Apr 21 21:11:34 2015 +0100

----------------------------------------------------------------------
 .../catalog/internal/BasicBrooklynCatalog.java  | 22 +++----
 .../internal/EntityManagementUtils.java         |  5 ++
 .../policy/basic/AbstractEntityAdjunct.java     |  5 +-
 .../java/brooklyn/util/task/ValueResolver.java  |  7 ++-
 .../BrooklynAssemblyTemplateInstantiator.java   | 63 +++++++-------------
 .../BrooklynComponentTemplateResolver.java      | 58 ++++++++++++++----
 .../brooklyn/catalog/CatalogYamlEntityTest.java |  6 +-
 .../create-step-template-entry.html             |  8 +--
 8 files changed, 104 insertions(+), 70 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f6c5619d/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java b/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java
index cd1f8c2..b49a240 100644
--- a/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java
+++ b/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java
@@ -621,16 +621,6 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
         CatalogItemType itemType = TypeCoercions.coerce(getFirstAs(catalogMetadata, Object.class, "itemType", "item_type").orNull(), CatalogItemType.class);
         BrooklynClassLoadingContext loader = CatalogUtils.newClassLoadingContext(mgmt, "<load>:0", libraryBundles);
 
-        PlanInterpreterGuessingType planInterpreter = new PlanInterpreterGuessingType(null, item, sourceYaml, itemType, loader, result).reconstruct();
-        if (!planInterpreter.isResolved()) {
-            throw Exceptions.create("Could not resolve item:\n"+sourceYaml, planInterpreter.getErrors());
-        }
-        itemType = planInterpreter.getCatalogItemType();
-        Map<?, ?> itemAsMap = planInterpreter.getItem();
-        // the "plan yaml" includes the services: ... or brooklyn.policies: ... outer key,
-        // as opposed to the rawer { type: xxx } map without that outer key which is valid as item input
-        // TODO this plan yaml is needed for subsequent reconstruction; would be nicer if it weren't! 
-        
         String id = getFirstAs(catalogMetadata, String.class, "id").orNull();
         String version = getFirstAs(catalogMetadata, String.class, "version").orNull();
         String symbolicName = getFirstAs(catalogMetadata, String.class, "symbolicName").orNull();
@@ -643,6 +633,18 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
             log.warn("Name property will be ignored due to the existence of displayName and at least one of id, symbolicName");
         }
 
+        PlanInterpreterGuessingType planInterpreter = new PlanInterpreterGuessingType(null, item, sourceYaml, itemType, loader, result).reconstruct();
+        if (!planInterpreter.isResolved()) {
+            throw Exceptions.create("Could not resolve item "
+                + (Strings.isNonBlank(id) ? id : Strings.isNonBlank(symbolicName) ? symbolicName : Strings.isNonBlank(name) ? name : "<no-name>")
+                + ":\n"+sourceYaml, planInterpreter.getErrors());
+        }
+        itemType = planInterpreter.getCatalogItemType();
+        Map<?, ?> itemAsMap = planInterpreter.getItem();
+        // the "plan yaml" includes the services: ... or brooklyn.policies: ... outer key,
+        // as opposed to the rawer { type: xxx } map without that outer key which is valid as item input
+        // TODO this plan yaml is needed for subsequent reconstruction; would be nicer if it weren't! 
+
         // if symname not set, infer from: id, then name, then item id, then item name
         if (Strings.isBlank(symbolicName)) {
             if (Strings.isNonBlank(id)) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f6c5619d/core/src/main/java/brooklyn/management/internal/EntityManagementUtils.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/EntityManagementUtils.java b/core/src/main/java/brooklyn/management/internal/EntityManagementUtils.java
index e6dc12a..046d56c 100644
--- a/core/src/main/java/brooklyn/management/internal/EntityManagementUtils.java
+++ b/core/src/main/java/brooklyn/management/internal/EntityManagementUtils.java
@@ -117,6 +117,11 @@ public class EntityManagementUtils {
                 assembly = instantiator.instantiate(at, camp);
                 return (T) mgmt.getEntityManager().getEntity(assembly.getId());
             } catch (UnsupportedOperationException e) {
+                if (at.getPlatformComponentTemplates()==null || at.getPlatformComponentTemplates().isEmpty()) {
+                    if (at.getCustomAttributes().containsKey("brooklyn.catalog"))
+                        throw new IllegalArgumentException("Unrecognized application blueprint format: expected an application, not a brooklyn.catalog");
+                    throw new IllegalArgumentException("Unrecognized application blueprint format: no services defined");
+                }
                 // map this (expected) error to a nicer message
                 throw new IllegalArgumentException("Unrecognized application blueprint format");
             } catch (Exception e) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f6c5619d/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java b/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java
index 394ad03..638818e 100644
--- a/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java
+++ b/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java
@@ -306,7 +306,10 @@ public abstract class AbstractEntityAdjunct extends AbstractBrooklynObject imple
     }
     
     protected <K> K getRequiredConfig(ConfigKey<K> key) {
-        return checkNotNull(config().get(key), key.getName());
+        K result = config().get(key);
+        if (result==null) 
+            throw new NullPointerException("Value required for '"+key.getName()+"' in "+this);
+        return result;
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f6c5619d/core/src/main/java/brooklyn/util/task/ValueResolver.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/util/task/ValueResolver.java b/core/src/main/java/brooklyn/util/task/ValueResolver.java
index 4bb557e..cf887a7 100644
--- a/core/src/main/java/brooklyn/util/task/ValueResolver.java
+++ b/core/src/main/java/brooklyn/util/task/ValueResolver.java
@@ -265,7 +265,12 @@ public class ValueResolver<T> implements DeferredSupplier<T> {
                 final Object vf = v;
                 Callable<Object> callable = new Callable<Object>() {
                     public Object call() throws Exception {
-                        return ((DeferredSupplier<?>) vf).get();
+                        try {
+                            Tasks.setBlockingDetails("Retrieving "+vf);
+                            return ((DeferredSupplier<?>) vf).get();
+                        } finally {
+                            Tasks.resetBlockingDetails();
+                        }
                     } };
                     
                 if (Boolean.TRUE.equals(embedResolutionInTask) || timeout!=null) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f6c5619d/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 af1cbb0..1519326 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
@@ -93,7 +93,7 @@ public class BrooklynAssemblyTemplateInstantiator implements AssemblyTemplateSpe
 
         BrooklynComponentTemplateResolver resolver = BrooklynComponentTemplateResolver.Factory.newInstance(
             loader, buildWrapperAppTemplate(template));
-        EntitySpec<? extends Application> app = resolver.resolveSpec();
+        EntitySpec<? extends Application> app = resolver.resolveSpec(null);
 
         // first build the children into an empty shell app
         List<EntitySpec<?>> childSpecs = buildTemplateServicesAsSpecs(loader, template, platform);
@@ -156,38 +156,33 @@ public class BrooklynAssemblyTemplateInstantiator implements AssemblyTemplateSpe
         for (ResolvableLink<PlatformComponentTemplate> ctl: template.getPlatformComponentTemplates().links()) {
             PlatformComponentTemplate appChildComponentTemplate = ctl.resolve();
             BrooklynComponentTemplateResolver entityResolver = BrooklynComponentTemplateResolver.Factory.newInstance(loader, appChildComponentTemplate);
-            EntitySpec<?> spec = resolveSpec(entityResolver, encounteredCatalogTypes);
+            EntitySpec<?> spec = resolveSpec(ResourceUtils.create(this), entityResolver, encounteredCatalogTypes);
 
             result.add(spec);
         }
         return result;
     }
 
-    protected EntitySpec<?> resolveSpec(
+    static EntitySpec<?> resolveSpec(ResourceUtils ru,
             BrooklynComponentTemplateResolver entityResolver,
             Set<String> encounteredCatalogTypes) {
-        ManagementContext mgmt = entityResolver.getLoader().getManagementContext();
-
         String brooklynType = entityResolver.getServiceTypeResolver().getBrooklynType(entityResolver.getDeclaredType());
         CatalogItem<Entity, EntitySpec<?>> item = entityResolver.getServiceTypeResolver().getCatalogItem(entityResolver, entityResolver.getDeclaredType());
 
-        //Take the symoblicName part of the catalog item only for recursion detection to prevent
-        //cross referencing of different versions. Not interested in non-catalog item types.
-        //Prevent catalog items self-referencing even if explicitly different version.
-        boolean firstOccurrence = (item == null || encounteredCatalogTypes.add(item.getSymbolicName()));
-        boolean recursiveButTryJava = !firstOccurrence;
-
         if (log.isTraceEnabled()) log.trace("Building CAMP template services: type="+brooklynType+"; item="+item+"; loader="+entityResolver.getLoader()+"; encounteredCatalogTypes="+encounteredCatalogTypes);
 
         EntitySpec<?> spec = null;
         String protocol = Urls.getProtocol(brooklynType);
         if (protocol != null) {
             if (BrooklynCampConstants.YAML_URL_PROTOCOL_WHITELIST.contains(protocol)) {
-                spec = tryResolveYamlURLReferenceSpec(brooklynType, entityResolver.getLoader(), encounteredCatalogTypes);
+                spec = tryResolveYamlUrlReferenceSpec(ru, brooklynType, entityResolver.getLoader(), encounteredCatalogTypes);
                 if (spec != null) {
                     entityResolver.populateSpec(spec);
                 }
             } else {
+                // TODO support https above
+                // TODO this will probably be logged if we refer to  chef:cookbook  or other service types which BCTR accepts;
+                // better would be to have BCTR supporting the calls above
                 log.warn("The reference " + brooklynType + " looks like an URL but the protocol " +
                         protocol + " isn't white listed (" + BrooklynCampConstants.YAML_URL_PROTOCOL_WHITELIST + "). " +
                         "Will try to load it as catalog item or java type.");
@@ -195,42 +190,20 @@ public class BrooklynAssemblyTemplateInstantiator implements AssemblyTemplateSpe
         }
 
         if (spec == null) {
-            // - Load a java class from current loader (item == null || entityResolver.isJavaTypePrefix())
-            // - Load a java class specified in an old-style catalog item (item != null && item.getJavaType() != null)
-            //   Old-style catalog items (can be defined in catalog.xml only) don't have structure, only a single type, so
-            //   they are loaded as a simple java type, only taking the class name from the catalog item instead of the
-            //   type value in the YAML. Classpath entries in the item are also used (through the catalog root classloader).
-            if (item == null || item.getJavaType() != null || entityResolver.isJavaTypePrefix()) {
-                spec = entityResolver.resolveSpec();
-
-            // Same as above case, but this time force java type loading (either as plain class or through an old-style
-            // catalog item, since we have already loaded a class item with the same name as the type value.
-            } else if (recursiveButTryJava) {
-                if (entityResolver.tryLoadEntityClass().isAbsent()) {
-                    throw new IllegalStateException("Recursive reference to " + brooklynType + " (and cannot be resolved as a Java type)");
-                }
-                spec = entityResolver.resolveSpec();
-
-            // Only case that's left is a catalog item with YAML content - try to parse it recursively
-            // including it's OSGi bundles in the loader classpath.
-            } else {
-                //TODO migrate to catalog.createSpec
-                spec = resolveCatalogYamlReferenceSpec(mgmt, item, encounteredCatalogTypes);
-                spec.catalogItemId(item.getId());
-                entityResolver.populateSpec(spec);
-            }
+            // load from java or yaml
+            spec = entityResolver.resolveSpec(encounteredCatalogTypes);
         }
 
         return spec;
     }
 
-    private EntitySpec<?> tryResolveYamlURLReferenceSpec(
+    private static EntitySpec<?> tryResolveYamlUrlReferenceSpec(
+            ResourceUtils ru,
             String brooklynType, BrooklynClassLoadingContext itemLoader,
             Set<String> encounteredCatalogTypes) {
         ManagementContext mgmt = itemLoader.getManagementContext();
         Reader yaml;
         try {
-            ResourceUtils ru = ResourceUtils.create(this);
             yaml = new InputStreamReader(ru.getResourceFromUrl(brooklynType), "UTF-8");
         } catch (Exception e) {
             log.warn("AssemblyTemplate type " + brooklynType + " which looks like a URL can't be fetched.", e);
@@ -247,7 +220,7 @@ public class BrooklynAssemblyTemplateInstantiator implements AssemblyTemplateSpe
         }
     }
 
-    private EntitySpec<?> resolveCatalogYamlReferenceSpec(
+    static EntitySpec<?> resolveCatalogYamlReferenceSpec(
             ManagementContext mgmt,
             CatalogItem<Entity, EntitySpec<?>> item,
             Set<String> encounteredCatalogTypes) {
@@ -259,7 +232,7 @@ public class BrooklynAssemblyTemplateInstantiator implements AssemblyTemplateSpe
         return createNestedSpec(mgmt, encounteredCatalogTypes, input, itemLoader);
     }
 
-    private EntitySpec<?> createNestedSpec(ManagementContext mgmt,
+    private static EntitySpec<?> createNestedSpec(ManagementContext mgmt,
             Set<String> encounteredCatalogTypes, Reader input,
             BrooklynClassLoadingContext itemLoader) {
         CampPlatform platform = BrooklynServerConfig.getCampPlatform(mgmt).get();
@@ -271,7 +244,7 @@ public class BrooklynAssemblyTemplateInstantiator implements AssemblyTemplateSpe
         } finally {
             BrooklynLoaderTracker.unsetLoader(itemLoader);
         }
-        return createNestedSpec(at, platform, itemLoader, encounteredCatalogTypes);
+        return createNestedSpecStatic(at, platform, itemLoader, encounteredCatalogTypes);
     }
 
     @Override
@@ -280,6 +253,14 @@ public class BrooklynAssemblyTemplateInstantiator implements AssemblyTemplateSpe
             CampPlatform platform,
             BrooklynClassLoadingContext itemLoader,
             Set<String> encounteredCatalogTypes) {
+        return createNestedSpecStatic(template, platform, itemLoader, encounteredCatalogTypes);
+    }
+    
+    private static EntitySpec<?> createNestedSpecStatic(
+        AssemblyTemplate template,
+        CampPlatform platform,
+        BrooklynClassLoadingContext itemLoader,
+        Set<String> encounteredCatalogTypes) {
         // In case we want to allow multiple top-level entities in a catalog we need to think
         // about what it would mean to subsequently call buildChildrenEntitySpecs on the list of top-level entities!
         try {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f6c5619d/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/creation/BrooklynComponentTemplateResolver.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/creation/BrooklynComponentTemplateResolver.java b/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/creation/BrooklynComponentTemplateResolver.java
index 67e5af1..618d4a3 100644
--- a/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/creation/BrooklynComponentTemplateResolver.java
+++ b/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/creation/BrooklynComponentTemplateResolver.java
@@ -56,6 +56,7 @@ import brooklyn.management.ManagementContextInjectable;
 import brooklyn.management.classloading.BrooklynClassLoadingContext;
 import brooklyn.management.classloading.JavaBrooklynClassLoadingContext;
 import brooklyn.management.internal.ManagementContextInternal;
+import brooklyn.util.ResourceUtils;
 import brooklyn.util.collections.MutableList;
 import brooklyn.util.collections.MutableSet;
 import brooklyn.util.config.ConfigBag;
@@ -209,20 +210,57 @@ public class BrooklynComponentTemplateResolver {
     }
 
     /** resolves the spec, updating the loader if a catalog item is loaded */
-    protected <T extends Entity> EntitySpec<T> resolveSpec() {
+    protected <T extends Entity> EntitySpec<T> resolveSpec(Set<String> encounteredCatalogTypes) {
         if (alreadyBuilt.getAndSet(true))
             throw new IllegalStateException("Spec can only be used once: "+this);
 
-        EntitySpec<T> spec = createSpec();
+        EntitySpec<T> spec = createSpec(encounteredCatalogTypes);
         populateSpec(spec);
 
         return spec;
     }
 
     @SuppressWarnings({ "unchecked", "rawtypes" })
-    protected <T extends Entity> EntitySpec<T> createSpec() {
-        Class<T> type = (Class<T>) loadEntityClass();
+    protected <T extends Entity> EntitySpec<T> createSpec(Set<String> encounteredCatalogTypes) {
+        CatalogItem<Entity, EntitySpec<?>> item = getServiceTypeResolver().getCatalogItem(this, getDeclaredType());
+        if (encounteredCatalogTypes==null) encounteredCatalogTypes = MutableSet.of();
+        
+        //Take the symoblicName part of the catalog item only for recursion detection to prevent
+        //cross referencing of different versions. Not interested in non-catalog item types.
+        //Prevent catalog items self-referencing even if explicitly different version.
+        boolean firstOccurrence = (item == null || encounteredCatalogTypes.add(item.getSymbolicName()));
+        boolean recursiveButTryJava = !firstOccurrence;
+
+        // - Load a java class from current loader (item == null || entityResolver.isJavaTypePrefix())
+        // - Load a java class specified in an old-style catalog item (item != null && item.getJavaType() != null)
+        //   Old-style catalog items (can be defined in catalog.xml only) don't have structure, only a single type, so
+        //   they are loaded as a simple java type, only taking the class name from the catalog item instead of the
+        //   type value in the YAML. Classpath entries in the item are also used (through the catalog root classloader).
+        if (item == null || item.getJavaType() != null || isJavaTypePrefix()) {
+            return createSpecFromJavaType();
+
+        // Same as above case, but this time force java type loading (either as plain class or through an old-style
+        // catalog item, since we have already loaded a class item with the same name as the type value.
+        } else if (recursiveButTryJava) {
+            if (tryLoadEntityClass().isAbsent()) {
+                throw new IllegalStateException("Recursive reference to " + item + " (and cannot be resolved as a Java type)");
+            }
+            return createSpecFromJavaType();
 
+        // Only case that's left is a catalog item with YAML content - try to parse it recursively
+        // including it's OSGi bundles in the loader classpath.
+        } else {
+            // TODO perhaps migrate to catalog.createSpec ?
+            EntitySpec<?> spec = BrooklynAssemblyTemplateInstantiator.resolveCatalogYamlReferenceSpec(mgmt, item, encounteredCatalogTypes);
+            spec.catalogItemId(item.getId());
+            
+            return (EntitySpec<T>)spec;
+        }
+    }
+    
+    protected <T extends Entity> EntitySpec<T> createSpecFromJavaType() {
+        Class<T> type = (Class<T>) loadEntityClass();
+        
         EntitySpec<T> spec;
         if (type.isInterface()) {
             spec = EntitySpec.create(type);
@@ -256,17 +294,15 @@ public class BrooklynComponentTemplateResolver {
 
         Object childrenObj = attrs.getStringKey("brooklyn.children");
         if (childrenObj != null) {
+            // Creating a new set of encounteredCatalogTypes means that this won't check things recursively;
+            // but we are looking at children so we probably *should* be resetting the recursive list we've looked at;
+            // (but see also, a previous comment here which suggested otherwise? - Apr 2015)
             Set<String> encounteredCatalogTypes = MutableSet.of();
 
             Iterable<Map<String,?>> children = (Iterable<Map<String,?>>)childrenObj;
             for (Map<String,?> childAttrs : children) {
                 BrooklynComponentTemplateResolver entityResolver = BrooklynComponentTemplateResolver.Factory.newInstance(loader, childAttrs);
-                BrooklynAssemblyTemplateInstantiator instantiator = new BrooklynAssemblyTemplateInstantiator();
-                // TODO: Creating a new set of encounteredCatalogTypes prevents the recursive definition check in
-                // BrooklynAssemblyTemplateInstantiator.resolveSpec from correctly determining if a YAML entity is
-                // defined recursively. However, the number of overrides of newInstance, and the number of places
-                // calling populateSpec make it difficult to pass encounteredCatalogTypes in as a parameter
-                EntitySpec<? extends Entity> childSpec = instantiator.resolveSpec(entityResolver, encounteredCatalogTypes);
+                EntitySpec<? extends Entity> childSpec = BrooklynAssemblyTemplateInstantiator.resolveSpec(ResourceUtils.create(this), entityResolver, encounteredCatalogTypes);
                 spec.child(childSpec);
             }
         }
@@ -411,7 +447,7 @@ public class BrooklynComponentTemplateResolver {
                 @SuppressWarnings("unchecked")
                 Map<String, Object> resolvedConfig = (Map<String, Object>)transformSpecialFlags(specConfig.getSpecConfiguration());
                 specConfig.setSpecConfiguration(resolvedConfig);
-                return Factory.newInstance(getLoader(), specConfig.getSpecConfiguration()).resolveSpec();
+                return Factory.newInstance(getLoader(), specConfig.getSpecConfiguration()).resolveSpec(null);
             }
             if (flag instanceof ManagementContextInjectable) {
                 log.debug("Injecting Brooklyn management context info object: {}", flag);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f6c5619d/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java
index e5eae35..1e321dc 100644
--- a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java
+++ b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java
@@ -40,6 +40,7 @@ import brooklyn.management.osgi.OsgiTestResources;
 import brooklyn.test.TestResourceUnavailableException;
 import brooklyn.util.ResourceUtils;
 import brooklyn.util.collections.MutableList;
+import brooklyn.util.exceptions.Exceptions;
 
 import com.google.common.collect.Iterables;
 
@@ -217,7 +218,7 @@ public class CatalogYamlEntityTest extends AbstractYamlTest {
         String yaml = "name: simple-app-yaml\n" +
                       "location: localhost\n" +
                       "services: \n" +
-                      "  - serviceType: " + ver(referrerSymbolicName);
+                      "  - type: " + ver(referrerSymbolicName);
         Entity app = createAndStartApplication(yaml);
 
         Entity simpleEntity = Iterables.getOnlyElement(app.getChildren());
@@ -344,7 +345,8 @@ public class CatalogYamlEntityTest extends AbstractYamlTest {
             // TODO only fails if using 'services', because that forces plan parsing; should fail in all cases
             addCatalogChildOSGiEntityWithServicesBlock(referrerSymbolicName, ver(referrerSymbolicName));
             fail("Expected to throw");
-        } catch (IllegalStateException e) {
+        } catch (Exception e) {
+            Exceptions.propagateIfFatal(e);
             assertTrue(e.getMessage().contains(referrerSymbolicName), "message was: "+e);
         }
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f6c5619d/usage/jsgui/src/main/webapp/assets/tpl/app-add-wizard/create-step-template-entry.html
----------------------------------------------------------------------
diff --git a/usage/jsgui/src/main/webapp/assets/tpl/app-add-wizard/create-step-template-entry.html b/usage/jsgui/src/main/webapp/assets/tpl/app-add-wizard/create-step-template-entry.html
index d70fd9b..b72bb10 100644
--- a/usage/jsgui/src/main/webapp/assets/tpl/app-add-wizard/create-step-template-entry.html
+++ b/usage/jsgui/src/main/webapp/assets/tpl/app-add-wizard/create-step-template-entry.html
@@ -18,16 +18,16 @@ specific language governing permissions and limitations
 under the License.
 -->
 
-<div class="template-lozenge frame" id="<%= id %>" data-name="<%= name %>" data-yaml="<%= planYaml %>">
+<div class="template-lozenge frame" id="<%- id %>" data-name="<%- name %>" data-yaml="<%- planYaml %>">
     <% if (iconUrl) { %>
     <div class="icon">
-        <img src="<%= iconUrl %>" alt="(icon)" />
+        <img src="<%- iconUrl %>" alt="(icon)" />
     </div>
     <% } %>
     <div class="blurb">
-        <div class="title"><%= name %></div>
+        <div class="title"><%- name %></div>
         <div class="description">
-            <%= description ? description : "" %>
+            <%- description ? description : "" %>
         </div>
     </div>
 </div>


[07/16] incubator-brooklyn git commit: support for multi-item catalog yaml

Posted by he...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/eaaca787/usage/rest-server/src/main/java/brooklyn/rest/resources/CatalogResource.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/brooklyn/rest/resources/CatalogResource.java b/usage/rest-server/src/main/java/brooklyn/rest/resources/CatalogResource.java
index 98e5a6b..a3eb161 100644
--- a/usage/rest-server/src/main/java/brooklyn/rest/resources/CatalogResource.java
+++ b/usage/rest-server/src/main/java/brooklyn/rest/resources/CatalogResource.java
@@ -22,6 +22,7 @@ import java.io.InputStream;
 import java.net.URI;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.Set;
 
@@ -60,6 +61,7 @@ import brooklyn.rest.filter.HaHotStateRequired;
 import brooklyn.rest.transform.CatalogTransformer;
 import brooklyn.rest.util.WebResourceUtils;
 import brooklyn.util.ResourceUtils;
+import brooklyn.util.collections.MutableMap;
 import brooklyn.util.collections.MutableSet;
 import brooklyn.util.exceptions.Exceptions;
 import brooklyn.util.stream.Streams;
@@ -96,7 +98,6 @@ public class CatalogResource extends AbstractBrooklynRestResource implements Cat
 
     static Set<String> missingIcons = MutableSet.of();
     
-    @SuppressWarnings("unchecked")
     @Override
     public Response create(String yaml) {
         if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.ADD_CATALOG_ITEM, yaml)) {
@@ -104,39 +105,24 @@ public class CatalogResource extends AbstractBrooklynRestResource implements Cat
                 Entitlements.getEntitlementContext().user());
         }
         
-        CatalogItem<?,?> item;
+        Iterable<? extends CatalogItem<?, ?>> items; 
         try {
-            item = brooklyn().getCatalog().addItem(yaml);
+            items = brooklyn().getCatalog().addItems(yaml);
         } catch (IllegalArgumentException e) {
             return Response.status(Status.BAD_REQUEST)
                     .type(MediaType.APPLICATION_JSON)
                     .entity(ApiError.of(e))
                     .build();
         }
-        String itemId = item.getId();
-        log.info("REST created catalog item: "+item);
-
-        // FIXME configurations/ not supported
-        switch (item.getCatalogItemType()) {
-        case TEMPLATE:
-            return Response.created(URI.create("applications/" + itemId + "/" + item.getVersion()))
-                    .entity(CatalogTransformer.catalogEntitySummary(brooklyn(), (CatalogItem<? extends Entity, EntitySpec<?>>) item))
-                    .build();
-        case ENTITY:
-            return Response.created(URI.create("entities/" + itemId + "/" + item.getVersion()))
-                    .entity(CatalogTransformer.catalogEntitySummary(brooklyn(), (CatalogItem<? extends Entity, EntitySpec<?>>) item))
-                    .build();
-        case POLICY:
-            return Response.created(URI.create("policies/" + itemId))
-                    .entity(CatalogTransformer.catalogPolicySummary(brooklyn(), (CatalogItem<? extends Policy, PolicySpec<?>>) item))
-                    .build();
-        case LOCATION:
-            return Response.created(URI.create("locations/" + itemId + "/" + item.getVersion()))
-                    .entity(CatalogTransformer.catalogLocationSummary(brooklyn(), (CatalogItem<? extends Location, LocationSpec<?>>) item))
-                    .build();
-        default:
-            throw new IllegalStateException("Unsupported catalog item type "+item.getCatalogItemType()+": "+item);
+
+        log.info("REST created catalog items: "+items);
+
+        Map<String,Object> result = MutableMap.of();
+        
+        for (CatalogItem<?,?> item: items) {
+            result.put(item.getId(), CatalogTransformer.catalogItemSummary(brooklyn(), item));
         }
+        return Response.status(Status.CREATED).entity(result).build();
     }
 
     @Override
@@ -223,17 +209,17 @@ public class CatalogResource extends AbstractBrooklynRestResource implements Cat
     }
 
     @Override
-    public List<CatalogEntitySummary> listEntities(String regex, String fragment) {
-        List<CatalogItemSummary> result = getCatalogItemSummariesMatchingRegexFragment(CatalogPredicates.IS_ENTITY, regex, fragment);
-        return cast(result, CatalogEntitySummary.class);
+    public List<CatalogEntitySummary> listEntities(String regex, String fragment, boolean allVersions) {
+        List<CatalogItemSummary> result = getCatalogItemSummariesMatchingRegexFragment(CatalogPredicates.IS_ENTITY, regex, fragment, allVersions);
+        return castList(result, CatalogEntitySummary.class);
     }
 
     @Override
-    public List<CatalogItemSummary> listApplications(String regex, String fragment) {
+    public List<CatalogItemSummary> listApplications(String regex, String fragment, boolean allVersions) {
         Predicate<CatalogItem<Application, EntitySpec<? extends Application>>> filter =
                 Predicates.and(CatalogPredicates.<Application,EntitySpec<? extends Application>>deprecated(false),
                         CatalogPredicates.IS_TEMPLATE);
-        return getCatalogItemSummariesMatchingRegexFragment(filter, regex, fragment);
+        return getCatalogItemSummariesMatchingRegexFragment(filter, regex, fragment, allVersions);
     }
 
     @Override
@@ -286,9 +272,9 @@ public class CatalogResource extends AbstractBrooklynRestResource implements Cat
     }
 
     @Override
-    public List<CatalogPolicySummary> listPolicies(String regex, String fragment) {
-        List<CatalogItemSummary> result = getCatalogItemSummariesMatchingRegexFragment(CatalogPredicates.IS_POLICY, regex, fragment);
-        return cast(result, CatalogPolicySummary.class);
+    public List<CatalogPolicySummary> listPolicies(String regex, String fragment, boolean allVersions) {
+        List<CatalogItemSummary> result = getCatalogItemSummariesMatchingRegexFragment(CatalogPredicates.IS_POLICY, regex, fragment, allVersions);
+        return castList(result, CatalogPolicySummary.class);
     }
 
     @Override
@@ -328,9 +314,9 @@ public class CatalogResource extends AbstractBrooklynRestResource implements Cat
     }
 
     @Override
-    public List<CatalogLocationSummary> listLocations(String regex, String fragment) {
-        List<CatalogItemSummary> result = getCatalogItemSummariesMatchingRegexFragment(CatalogPredicates.IS_LOCATION, regex, fragment);
-        return cast(result, CatalogLocationSummary.class);
+    public List<CatalogLocationSummary> listLocations(String regex, String fragment, boolean allVersions) {
+        List<CatalogItemSummary> result = getCatalogItemSummariesMatchingRegexFragment(CatalogPredicates.IS_LOCATION, regex, fragment, allVersions);
+        return castList(result, CatalogLocationSummary.class);
     }
 
     @Override
@@ -370,13 +356,15 @@ public class CatalogResource extends AbstractBrooklynRestResource implements Cat
     }
 
     @SuppressWarnings({ "unchecked", "rawtypes" })
-    private <T,SpecT> List<CatalogItemSummary> getCatalogItemSummariesMatchingRegexFragment(Predicate<CatalogItem<T,SpecT>> type, String regex, String fragment) {
+    private <T,SpecT> List<CatalogItemSummary> getCatalogItemSummariesMatchingRegexFragment(Predicate<CatalogItem<T,SpecT>> type, String regex, String fragment, boolean allVersions) {
         List filters = new ArrayList();
         filters.add(type);
         if (Strings.isNonEmpty(regex))
             filters.add(CatalogPredicates.xml(StringPredicates.containsRegex(regex)));
         if (Strings.isNonEmpty(fragment))
             filters.add(CatalogPredicates.xml(StringPredicates.containsLiteralIgnoreCase(fragment)));
+        if (!allVersions)
+            filters.add(CatalogPredicates.isBestVersion(mgmt()));
         
         filters.add(CatalogPredicates.entitledToSee(mgmt()));
 
@@ -464,7 +452,7 @@ public class CatalogResource extends AbstractBrooklynRestResource implements Cat
 
     // TODO Move to an appropriate utility class?
     @SuppressWarnings("unchecked")
-    private static <T> List<T> cast(List<? super T> list, Class<T> elementType) {
+    private static <T> List<T> castList(List<? super T> list, Class<T> elementType) {
         List<T> result = Lists.newArrayList();
         for (Object element : list) {
             result.add((T) element);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/eaaca787/usage/rest-server/src/main/java/brooklyn/rest/resources/LocationResource.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/brooklyn/rest/resources/LocationResource.java b/usage/rest-server/src/main/java/brooklyn/rest/resources/LocationResource.java
index 362d152..74185d7 100644
--- a/usage/rest-server/src/main/java/brooklyn/rest/resources/LocationResource.java
+++ b/usage/rest-server/src/main/java/brooklyn/rest/resources/LocationResource.java
@@ -29,7 +29,6 @@ import javax.ws.rs.core.Response;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import brooklyn.catalog.CatalogItem;
 import brooklyn.location.Location;
 import brooklyn.location.LocationDefinition;
 import brooklyn.location.basic.LocationConfigKeys;
@@ -51,6 +50,7 @@ import com.google.common.collect.FluentIterable;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
 
+@SuppressWarnings("deprecation")
 @HaHotStateRequired
 public class LocationResource extends AbstractBrooklynRestResource implements LocationApi {
 
@@ -142,7 +142,7 @@ public class LocationResource extends AbstractBrooklynRestResource implements Lo
               }
           }
 
-          CatalogItem<?, ?> item = brooklyn().getCatalog().addItem(Joiner.on("\n").join(yaml.build()));
+          brooklyn().getCatalog().addItems(Joiner.on("\n").join(yaml.build()));
           LocationDefinition l = brooklyn().getLocationRegistry().getDefinedLocationByName(name);
           return Response.created(URI.create(name))
                   .entity(LocationTransformer.newInstance(mgmt(), l, LocationDetailLevel.LOCAL_EXCLUDING_SECRET))

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/eaaca787/usage/rest-server/src/main/java/brooklyn/rest/transform/CatalogTransformer.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/brooklyn/rest/transform/CatalogTransformer.java b/usage/rest-server/src/main/java/brooklyn/rest/transform/CatalogTransformer.java
index adee3e2..053a9c3 100644
--- a/usage/rest-server/src/main/java/brooklyn/rest/transform/CatalogTransformer.java
+++ b/usage/rest-server/src/main/java/brooklyn/rest/transform/CatalogTransformer.java
@@ -49,13 +49,13 @@ import brooklyn.rest.domain.SensorSummary;
 import brooklyn.rest.domain.SummaryComparators;
 import brooklyn.rest.util.BrooklynRestResourceUtils;
 import brooklyn.util.collections.MutableMap;
+import brooklyn.util.exceptions.Exceptions;
 
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 
 public class CatalogTransformer {
 
-    @SuppressWarnings("unused")
     private static final org.slf4j.Logger log = LoggerFactory.getLogger(CatalogTransformer.class);
     
     public static CatalogEntitySummary catalogEntitySummary(BrooklynRestResourceUtils b, CatalogItem<? extends Entity,EntitySpec<?>> item) {
@@ -81,10 +81,27 @@ public class CatalogTransformer {
             item.isDeprecated(), makeLinks(item));
     }
 
+    @SuppressWarnings("unchecked")
     public static CatalogItemSummary catalogItemSummary(BrooklynRestResourceUtils b, CatalogItem<?,?> item) {
+        try {
+            switch (item.getCatalogItemType()) {
+            case TEMPLATE:
+            case ENTITY:
+                return catalogEntitySummary(b, (CatalogItem<? extends Entity, EntitySpec<?>>) item);
+            case POLICY:
+                return catalogPolicySummary(b, (CatalogItem<? extends Policy, PolicySpec<?>>) item);
+            case LOCATION:
+                return catalogLocationSummary(b, (CatalogItem<? extends Location, LocationSpec<?>>) item);
+            default:
+                log.warn("Unexpected catalog item type when getting self link (supplying generic item): "+item.getCatalogItemType()+" "+item);
+            }
+        } catch (Exception e) {
+            Exceptions.propagateIfFatal(e);
+            log.warn("Invalid item in catalog when converting REST summaries (supplying generic item), at "+item+": "+e, e);
+        }
         return new CatalogItemSummary(item.getSymbolicName(), item.getVersion(), item.getDisplayName(),
-                item.getJavaType(), item.getPlanYaml(),
-                item.getDescription(), tidyIconLink(b, item, item.getIconUrl()), item.isDeprecated(), makeLinks(item));
+            item.getJavaType(), item.getPlanYaml(),
+            item.getDescription(), tidyIconLink(b, item, item.getIconUrl()), item.isDeprecated(), makeLinks(item));
     }
 
     public static CatalogPolicySummary catalogPolicySummary(BrooklynRestResourceUtils b, CatalogItem<? extends Policy,PolicySpec<?>> item) {
@@ -104,12 +121,29 @@ public class CatalogTransformer {
     }
 
     protected static Map<String, URI> makeLinks(CatalogItem<?,?> item) {
-        return MutableMap.<String, URI>of();
+        return MutableMap.<String, URI>of().addIfNotNull("self", URI.create(getSelfLink(item)));
+    }
+
+    protected static String getSelfLink(CatalogItem<?,?> item) {
+        String itemId = item.getId();
+        switch (item.getCatalogItemType()) {
+        case TEMPLATE:
+            return "/v1/applications/" + itemId + "/" + item.getVersion();
+        case ENTITY:
+            return "/v1/entities/" + itemId + "/" + item.getVersion();
+        case POLICY:
+            return "/v1/policies/" + itemId + "/" + item.getVersion();
+        case LOCATION:
+            return "/v1/locations/" + itemId + "/" + item.getVersion();
+        default:
+            log.warn("Unexpected catalog item type when getting self link (not supplying self link): "+item.getCatalogItemType()+" "+item);
+            return null;
+        }
     }
-    
     private static String tidyIconLink(BrooklynRestResourceUtils b, CatalogItem<?,?> item, String iconUrl) {
         if (b.isUrlServerSideAndSafe(iconUrl))
             return "/v1/catalog/icon/"+item.getSymbolicName() + "/" + item.getVersion();
         return iconUrl;
     }
+    
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/eaaca787/usage/rest-server/src/test/java/brooklyn/rest/resources/CatalogResourceTest.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/test/java/brooklyn/rest/resources/CatalogResourceTest.java b/usage/rest-server/src/test/java/brooklyn/rest/resources/CatalogResourceTest.java
index eb8b848..69dc58d 100644
--- a/usage/rest-server/src/test/java/brooklyn/rest/resources/CatalogResourceTest.java
+++ b/usage/rest-server/src/test/java/brooklyn/rest/resources/CatalogResourceTest.java
@@ -27,6 +27,7 @@ import java.io.IOException;
 import java.net.URI;
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import javax.ws.rs.core.MediaType;
@@ -76,7 +77,7 @@ public class CatalogResourceTest extends BrooklynRestResourceTest {
 
     @Test
     /** based on CampYamlLiteTest */
-    public void testRegisterCustomEntityWithBundleWhereEntityIsFromCoreAndIconFromBundle() {
+    public void testRegisterCustomEntityTopLevelSyntaxWithBundleWhereEntityIsFromCoreAndIconFromBundle() {
         TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH);
 
         String symbolicName = "my.catalog.entity.id";
@@ -127,7 +128,7 @@ public class CatalogResourceTest extends BrooklynRestResourceTest {
     }
 
     @Test
-    public void testRegisterOSGiPolicy() {
+    public void testRegisterOsgiPolicyTopLevelSyntax() {
         TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH);
 
         String symbolicName = "my.catalog.policy.id";
@@ -146,8 +147,8 @@ public class CatalogResourceTest extends BrooklynRestResourceTest {
                 "brooklyn.policies:\n"+
                 "- type: " + policyType;
 
-        CatalogPolicySummary entityItem = client().resource("/v1/catalog")
-                .post(CatalogPolicySummary.class, yaml);
+        CatalogPolicySummary entityItem = Iterables.getOnlyElement( client().resource("/v1/catalog")
+                .post(new GenericType<Map<String,CatalogPolicySummary>>() {}, yaml).values() );
 
         Assert.assertNotNull(entityItem.getPlanYaml());
         Assert.assertTrue(entityItem.getPlanYaml().contains(policyType));
@@ -158,6 +159,14 @@ public class CatalogResourceTest extends BrooklynRestResourceTest {
 
     @Test
     public void testListAllEntities() {
+        List<CatalogEntitySummary> entities = client().resource("/v1/catalog/entities")
+                .get(new GenericType<List<CatalogEntitySummary>>() {});
+        assertTrue(entities.size() > 0);
+    }
+
+    @Test
+    public void testListAllEntitiesAsItem() {
+        // ensure things are happily downcasted and unknown properties ignored (e.g. sensors, effectors)
         List<CatalogItemSummary> entities = client().resource("/v1/catalog/entities")
                 .get(new GenericType<List<CatalogItemSummary>>() {});
         assertTrue(entities.size() > 0);
@@ -165,24 +174,24 @@ public class CatalogResourceTest extends BrooklynRestResourceTest {
 
     @Test
     public void testFilterListOfEntitiesByName() {
-        List<CatalogItemSummary> entities = client().resource("/v1/catalog/entities")
-                .queryParam("fragment", "reDISclusTER").get(new GenericType<List<CatalogItemSummary>>() {});
+        List<CatalogEntitySummary> entities = client().resource("/v1/catalog/entities")
+                .queryParam("fragment", "reDISclusTER").get(new GenericType<List<CatalogEntitySummary>>() {});
         assertEquals(entities.size(), 1);
 
         log.info("RedisCluster-like entities are: " + entities);
 
-        List<CatalogItemSummary> entities2 = client().resource("/v1/catalog/entities")
-                .queryParam("regex", "[Rr]ed.[sulC]+ter").get(new GenericType<List<CatalogItemSummary>>() {});
+        List<CatalogEntitySummary> entities2 = client().resource("/v1/catalog/entities")
+                .queryParam("regex", "[Rr]ed.[sulC]+ter").get(new GenericType<List<CatalogEntitySummary>>() {});
         assertEquals(entities2.size(), 1);
 
         assertEquals(entities, entities2);
     
-        List<CatalogItemSummary> entities3 = client().resource("/v1/catalog/entities")
-                .queryParam("fragment", "bweqQzZ").get(new GenericType<List<CatalogItemSummary>>() {});
+        List<CatalogEntitySummary> entities3 = client().resource("/v1/catalog/entities")
+                .queryParam("fragment", "bweqQzZ").get(new GenericType<List<CatalogEntitySummary>>() {});
         assertEquals(entities3.size(), 0);
 
-        List<CatalogItemSummary> entities4 = client().resource("/v1/catalog/entities")
-                .queryParam("regex", "bweq+z+").get(new GenericType<List<CatalogItemSummary>>() {});
+        List<CatalogEntitySummary> entities4 = client().resource("/v1/catalog/entities")
+                .queryParam("regex", "bweq+z+").get(new GenericType<List<CatalogEntitySummary>>() {});
         assertEquals(entities4.size(), 0);
     }
 
@@ -215,7 +224,7 @@ public class CatalogResourceTest extends BrooklynRestResourceTest {
     @Test
     public void testGetCatalogEntityIconDetails() throws IOException {
         String catalogItemId = "testGetCatalogEntityIconDetails";
-        addTestCatalogItem(catalogItemId);
+        addTestCatalogItemRedisAsEntity(catalogItemId);
         ClientResponse response = client().resource(URI.create("/v1/catalog/icon/" + catalogItemId + "/" + TEST_VERSION))
                 .get(ClientResponse.class);
         response.bufferEntity();
@@ -225,15 +234,16 @@ public class CatalogResourceTest extends BrooklynRestResourceTest {
         Assert.assertNotNull(image);
     }
 
-    private void addTestCatalogItem(String catalogItemId) {
-        addTestCatalogItem(catalogItemId, TEST_VERSION, "brooklyn.entity.nosql.redis.RedisStore");
+    private void addTestCatalogItemRedisAsEntity(String catalogItemId) {
+        addTestCatalogItem(catalogItemId, null, TEST_VERSION, "brooklyn.entity.nosql.redis.RedisStore");
     }
 
-    private void addTestCatalogItem(String catalogItemId, String version, String service) {
+    private void addTestCatalogItem(String catalogItemId, String itemType, String version, String service) {
         String yaml =
                 "brooklyn.catalog:\n"+
                 "  id: " + catalogItemId + "\n"+
                 "  name: My Catalog App\n"+
+                (itemType!=null ? "  item_type: "+itemType+"\n" : "")+
                 "  description: My description\n"+
                 "  icon_url: classpath:///redis-logo.png\n"+
                 "  version: " + version + "\n"+
@@ -248,8 +258,8 @@ public class CatalogResourceTest extends BrooklynRestResourceTest {
 
     @Test
     public void testListPolicies() {
-        Set<CatalogItemSummary> policies = client().resource("/v1/catalog/policies")
-                .get(new GenericType<Set<CatalogItemSummary>>() {});
+        Set<CatalogPolicySummary> policies = client().resource("/v1/catalog/policies")
+                .get(new GenericType<Set<CatalogPolicySummary>>() {});
 
         assertTrue(policies.size() > 0);
         CatalogItemSummary asp = null;
@@ -275,8 +285,9 @@ public class CatalogResourceTest extends BrooklynRestResourceTest {
                 "- type: " + locationType);
 
         // Create location item
-        CatalogLocationSummary locationItem = client().resource("/v1/catalog")
-                .post(CatalogLocationSummary.class, yaml);
+        Map<String, CatalogLocationSummary> items = client().resource("/v1/catalog")
+                .post(new GenericType<Map<String,CatalogLocationSummary>>() {}, yaml);
+        CatalogLocationSummary locationItem = Iterables.getOnlyElement(items.values());
 
         Assert.assertNotNull(locationItem.getPlanYaml());
         Assert.assertTrue(locationItem.getPlanYaml().contains(locationType));
@@ -343,10 +354,10 @@ public class CatalogResourceTest extends BrooklynRestResourceTest {
     public void testSetDeprecated() {
         String itemId = "my.catalog.item.id.for.deprecation";
         String serviceType = "brooklyn.entity.basic.BasicApplication";
-        addTestCatalogItem(itemId, TEST_VERSION, serviceType);
-        addTestCatalogItem(itemId, "2.0", serviceType);
-        List<CatalogItemSummary> applications = client().resource("/v1/catalog/applications")
-                .queryParam("fragment", itemId).get(new GenericType<List<CatalogItemSummary>>() {});
+        addTestCatalogItem(itemId, "template", TEST_VERSION, serviceType);
+        addTestCatalogItem(itemId, "template", "2.0", serviceType);
+        List<CatalogEntitySummary> applications = client().resource("/v1/catalog/applications")
+                .queryParam("fragment", itemId).queryParam("allVersions", "true").get(new GenericType<List<CatalogEntitySummary>>() {});
         assertEquals(applications.size(), 2);
         CatalogItemSummary summary0 = applications.get(0);
         CatalogItemSummary summary1 = applications.get(1);
@@ -359,8 +370,8 @@ public class CatalogResourceTest extends BrooklynRestResourceTest {
 
         assertEquals(getDeprecationResponse.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
 
-        List<CatalogItemSummary> applicationsAfterDeprecation = client().resource("/v1/catalog/applications")
-                .queryParam("fragment", "basicapp").get(new GenericType<List<CatalogItemSummary>>() {});
+        List<CatalogEntitySummary> applicationsAfterDeprecation = client().resource("/v1/catalog/applications")
+                .queryParam("fragment", "basicapp").queryParam("allVersions", "true").get(new GenericType<List<CatalogEntitySummary>>() {});
 
         assertEquals(applicationsAfterDeprecation.size(), 1);
         assertTrue(applicationsAfterDeprecation.contains(summary1));
@@ -370,8 +381,8 @@ public class CatalogResourceTest extends BrooklynRestResourceTest {
 
         assertEquals(getUnDeprecationResponse.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
 
-        List<CatalogItemSummary> applicationsAfterUnDeprecation = client().resource("/v1/catalog/applications")
-                .queryParam("fragment", "basicapp").get(new GenericType<List<CatalogItemSummary>>() {});
+        List<CatalogEntitySummary> applicationsAfterUnDeprecation = client().resource("/v1/catalog/applications")
+                .queryParam("fragment", "basicapp").queryParam("allVersions", "true").get(new GenericType<List<CatalogEntitySummary>>() {});
 
         assertEquals(applications, applicationsAfterUnDeprecation);
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/eaaca787/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
index 2bcbe87..d13edcb 100644
--- a/utils/common/src/main/java/brooklyn/util/yaml/Yamls.java
+++ b/utils/common/src/main/java/brooklyn/util/yaml/Yamls.java
@@ -317,6 +317,17 @@ public class Yamls {
         }
 
         @Beta
+        public String getMatchedYamlTextOrWarn() {
+            try {
+                return getMatchedYamlText();
+            } catch (Exception e) {
+                Exceptions.propagateIfFatal(e);
+                log.warn("Unable to match yaml text in "+this+": "+e, e);
+                return null;
+            }
+        }
+        
+        @Beta
         public String getMatchedYamlText() {
             if (!found()) return null;
             
@@ -484,6 +495,7 @@ b: 1
      * where {@link YamlExtract#isMatch()} is false and {@link YamlExtract#getError()} is set. */
     public static YamlExtract getTextOfYamlAtPath(String yaml, Object ...path) {
         YamlExtract result = new YamlExtract();
+        if (yaml==null) return result;
         try {
             int pathIndex = 0;
             result.yaml = yaml;


[11/16] incubator-brooklyn git commit: simplify yaml for averaging + summing enricher, plus test

Posted by he...@apache.org.
simplify yaml for averaging + summing enricher, plus test


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

Branch: refs/heads/master
Commit: 5af2925722042561838e12b0d5902d2852eef431
Parents: ff31a41
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Sat Apr 18 16:45:22 2015 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Sat Apr 18 17:21:23 2015 +0100

----------------------------------------------------------------------
 .../main/java/brooklyn/enricher/Enrichers.java  | 89 ++++++++++++++++++--
 .../brooklyn/enricher/basic/Aggregator.java     | 34 ++++++++
 .../EnrichersSlightlySimplerYamlTest.java       | 38 +++++++++
 .../test-webapp-with-averaging-enricher.yaml    | 47 +++++++++++
 .../java/brooklyn/util/math/MathPredicates.java | 32 +++++++
 5 files changed, 231 insertions(+), 9 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5af29257/core/src/main/java/brooklyn/enricher/Enrichers.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/enricher/Enrichers.java b/core/src/main/java/brooklyn/enricher/Enrichers.java
index 9b34b13..c8ee899 100644
--- a/core/src/main/java/brooklyn/enricher/Enrichers.java
+++ b/core/src/main/java/brooklyn/enricher/Enrichers.java
@@ -46,11 +46,13 @@ import brooklyn.util.flags.TypeCoercions;
 import brooklyn.util.text.StringPredicates;
 import brooklyn.util.text.Strings;
 
+import com.google.common.annotations.Beta;
 import com.google.common.base.Function;
 import com.google.common.base.Objects;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Predicate;
-import com.google.common.base.Predicates;
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
@@ -181,7 +183,8 @@ public class Enrichers {
         protected final AttributeSensor<S> aggregating;
         protected AttributeSensor<T> publishing;
         protected Entity fromEntity;
-        protected Function<? super Collection<S>, ? extends T> computing;
+        // use supplier so latest values of other fields can be used
+        protected Supplier<Function<? super Collection<S>, ? extends T>> computingSupplier;
         protected Boolean fromMembers;
         protected Boolean fromChildren;
         protected Boolean excludingBlank;
@@ -216,13 +219,25 @@ public class Enrichers {
             this.fromHardcodedProducers = ImmutableSet.copyOf(val);
             return self();
         }
+        @SuppressWarnings({ "unchecked", "rawtypes" })
         public B computing(Function<? super Collection<S>, ? extends T> val) {
-            this.computing = checkNotNull(val);
+            this.computingSupplier = (Supplier)Suppliers.ofInstance(checkNotNull(val));
             return self();
         }
-        @SuppressWarnings({ "unchecked", "rawtypes" })
         public B computingSum() {
-            // relies of TypeCoercion of result from Number to T, and type erasure for us to get away with it!
+            this.computingSupplier = new Supplier<Function<? super Collection<S>, ? extends T>>() {
+                @Override
+                @SuppressWarnings({ "unchecked", "rawtypes" })
+                public Function<? super Collection<S>, ? extends T> get() {
+                    // relies on TypeCoercion of result from Number to T, and type erasure for us to get away with it!
+                    return (Function)new ComputingSum((Number)defaultValueForUnreportedSensors, (Number)valueToReportIfNoSensors, publishing.getTypeToken());
+                }
+            };
+            return self();
+        }
+        @SuppressWarnings({ "unchecked", "rawtypes", "unused" })
+        private B computingSumLegacy() {
+            // since 0.7.0, kept in case we need to rebind to this
             Function<Collection<S>, Number> function = new Function<Collection<S>, Number>()  {
                 @Override public Number apply(Collection<S> input) {
                     return sum((Collection)input, (Number)defaultValueForUnreportedSensors, (Number)valueToReportIfNoSensors, (TypeToken) publishing.getTypeToken());
@@ -230,9 +245,21 @@ public class Enrichers {
             this.computing((Function)function);
             return self();
         }
-        @SuppressWarnings({ "unchecked", "rawtypes" })
+
         public B computingAverage() {
-            // relies of TypeCoercion of result from Number to T, and type erasure for us to get away with it!
+            this.computingSupplier = new Supplier<Function<? super Collection<S>, ? extends T>>() {
+                @Override
+                @SuppressWarnings({ "unchecked", "rawtypes" })
+                public Function<? super Collection<S>, ? extends T> get() {
+                    // relies on TypeCoercion of result from Number to T, and type erasure for us to get away with it!
+                    return (Function)new ComputingAverage((Number)defaultValueForUnreportedSensors, (Number)valueToReportIfNoSensors, publishing.getTypeToken());
+                }
+            };
+            return self();
+        }
+        @SuppressWarnings({ "unchecked", "rawtypes", "unused" })
+        private B computingAverageLegacy() {
+            // since 0.7.0, kept in case we need to rebind to this
             Function<Collection<S>, Number> function = new Function<Collection<S>, Number>() {
                 @Override public Number apply(Collection<S> input) {
                     return average((Collection)input, (Number)defaultValueForUnreportedSensors, (Number)valueToReportIfNoSensors, (TypeToken) publishing.getTypeToken());
@@ -240,6 +267,7 @@ public class Enrichers {
             this.computing((Function)function);
             return self();
         }
+        
         public B defaultValueForUnreportedSensors(S val) {
             this.defaultValueForUnreportedSensors = val;
             return self();
@@ -282,7 +310,7 @@ public class Enrichers {
                             .put(Aggregator.SOURCE_SENSOR, aggregating)
                             .putIfNotNull(Aggregator.FROM_CHILDREN, fromChildren)
                             .putIfNotNull(Aggregator.FROM_MEMBERS, fromMembers)
-                            .putIfNotNull(Aggregator.TRANSFORMATION, computing)
+                            .putIfNotNull(Aggregator.TRANSFORMATION, computingSupplier.get())
                             .putIfNotNull(Aggregator.FROM_HARDCODED_PRODUCERS, fromHardcodedProducers)
                             .putIfNotNull(Aggregator.ENTITY_FILTER, entityFilter)
                             .putIfNotNull(Aggregator.VALUE_FILTER, valueFilter)
@@ -297,7 +325,7 @@ public class Enrichers {
                     .add("aggregating", aggregating)
                     .add("publishing", publishing)
                     .add("fromEntity", fromEntity)
-                    .add("computing", computing)
+                    .add("computing", computingSupplier)
                     .add("fromMembers", fromMembers)
                     .add("fromChildren", fromChildren)
                     .add("excludingBlank", excludingBlank)
@@ -703,6 +731,49 @@ public class Enrichers {
         }
     }
 
+    @Beta
+    private abstract static class ComputingNumber<T extends Number> implements Function<Collection<T>, T> {
+        protected final Number defaultValueForUnreportedSensors;
+        protected final Number valueToReportIfNoSensors;
+        protected final TypeToken<T> typeToken;
+        @SuppressWarnings({ "rawtypes", "unchecked" })
+        public ComputingNumber(Number defaultValueForUnreportedSensors, Number valueToReportIfNoSensors, TypeToken<T> typeToken) {
+            this.defaultValueForUnreportedSensors = defaultValueForUnreportedSensors;
+            this.valueToReportIfNoSensors = valueToReportIfNoSensors;
+            if (typeToken!=null && TypeToken.of(Number.class).isAssignableFrom(typeToken.getType())) {
+                this.typeToken = typeToken;
+            } else if (typeToken==null || typeToken.isAssignableFrom(Number.class)) {
+                // use double if e.g. Object is supplied
+                this.typeToken = (TypeToken)TypeToken.of(Double.class);
+            } else {
+                throw new IllegalArgumentException("Type "+typeToken+" is not valid for "+this);
+            }
+        }
+        @Override public abstract T apply(Collection<T> input);
+    }
+
+    @Beta
+    public static class ComputingSum<T extends Number> extends ComputingNumber<T> {
+        public ComputingSum(Number defaultValueForUnreportedSensors, Number valueToReportIfNoSensors, TypeToken<T> typeToken) {
+            super(defaultValueForUnreportedSensors, valueToReportIfNoSensors, typeToken);
+        }
+        @SuppressWarnings({ "rawtypes", "unchecked" })
+        @Override public T apply(Collection<T> input) {
+            return (T) sum((Collection)input, (Number)defaultValueForUnreportedSensors, (Number)valueToReportIfNoSensors, typeToken);
+        }
+    }
+
+    @Beta
+    public static class ComputingAverage<T extends Number> extends ComputingNumber<T> {
+        public ComputingAverage(Number defaultValueForUnreportedSensors, Number valueToReportIfNoSensors, TypeToken<T> typeToken) {
+            super(defaultValueForUnreportedSensors, valueToReportIfNoSensors, typeToken);
+        }
+        @SuppressWarnings({ "rawtypes", "unchecked" })
+        @Override public T apply(Collection<T> input) {
+            return (T) average((Collection)input, (Number)defaultValueForUnreportedSensors, (Number)valueToReportIfNoSensors, typeToken);
+        }
+    }
+
     protected static <T extends Number> T average(Collection<T> vals, Number defaultValueForUnreportedSensors, Number valueToReportIfNoSensors, TypeToken<T> type) {
         Double doubleValueToReportIfNoSensors = (valueToReportIfNoSensors == null) ? null : valueToReportIfNoSensors.doubleValue();
         int count = count(vals, defaultValueForUnreportedSensors!=null);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5af29257/core/src/main/java/brooklyn/enricher/basic/Aggregator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/enricher/basic/Aggregator.java b/core/src/main/java/brooklyn/enricher/basic/Aggregator.java
index cb80431..d7f4fca 100644
--- a/core/src/main/java/brooklyn/enricher/basic/Aggregator.java
+++ b/core/src/main/java/brooklyn/enricher/basic/Aggregator.java
@@ -29,6 +29,7 @@ import org.slf4j.LoggerFactory;
 
 import brooklyn.config.BrooklynLogging;
 import brooklyn.config.ConfigKey;
+import brooklyn.enricher.Enrichers;
 import brooklyn.entity.Entity;
 import brooklyn.entity.basic.ConfigKeys;
 import brooklyn.event.AttributeSensor;
@@ -38,6 +39,7 @@ import brooklyn.event.SensorEventListener;
 import brooklyn.util.collections.MutableList;
 import brooklyn.util.collections.MutableMap;
 import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.flags.SetFromFlag;
 import brooklyn.util.text.StringPredicates;
 
 import com.google.common.base.Function;
@@ -54,7 +56,13 @@ public class Aggregator<T,U> extends AbstractAggregator<T,U> implements SensorEv
     private static final Logger LOG = LoggerFactory.getLogger(Aggregator.class);
 
     public static final ConfigKey<Sensor<?>> SOURCE_SENSOR = ConfigKeys.newConfigKey(new TypeToken<Sensor<?>>() {}, "enricher.sourceSensor");
+    
+    @SetFromFlag("transformation")
+    public static final ConfigKey<Object> TRANSFORMATION_UNTYPED = ConfigKeys.newConfigKey(Object.class, "enricher.transformation.untyped",
+        "Specifies a transformation, as a function from a collection to the value, or as a string matching a pre-defined named transformation, "
+        + "such as 'average' (for numbers), 'add' (for numbers), or 'list' (the default, putting any collection of items into a list)");
     public static final ConfigKey<Function<? super Collection<?>, ?>> TRANSFORMATION = ConfigKeys.newConfigKey(new TypeToken<Function<? super Collection<?>, ?>>() {}, "enricher.transformation");
+    
     public static final ConfigKey<Boolean> EXCLUDE_BLANK = ConfigKeys.newBooleanConfigKey("enricher.aggregator.excludeBlank", "Whether explicit nulls or blank strings should be excluded (default false); this only applies if no value filter set", false);
 
     protected Sensor<T> sourceSensor;
@@ -73,9 +81,35 @@ public class Aggregator<T,U> extends AbstractAggregator<T,U> implements SensorEv
     protected void setEntityLoadingConfig() {
         super.setEntityLoadingConfig();
         this.sourceSensor = (Sensor<T>) getRequiredConfig(SOURCE_SENSOR);
+        
+        Object t1 = config().get(TRANSFORMATION_UNTYPED);
+        if (t1 instanceof String) t1 = lookupTransformation((String)t1);
+        
         this.transformation = (Function<? super Collection<T>, ? extends U>) config().get(TRANSFORMATION);
+        if (this.transformation==null) {
+            this.transformation = (Function<? super Collection<T>, ? extends U>) t1;
+        } else if (t1!=null && !t1.equals(this.transformation)) {
+            throw new IllegalStateException("Cannot supply both "+TRANSFORMATION_UNTYPED+" and "+TRANSFORMATION+" unless they are equal.");
+        }
     }
         
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    protected Function<? super Collection<?>, ?> lookupTransformation(String t1) {
+        if ("average".equalsIgnoreCase(t1)) return new Enrichers.ComputingAverage(null, null, targetSensor.getTypeToken());
+        if ("sum".equalsIgnoreCase(t1)) return new Enrichers.ComputingAverage(null, null, targetSensor.getTypeToken());
+        if ("list".equalsIgnoreCase(t1)) return new ComputingList();
+        return null;
+    }
+
+    private class ComputingList<TT> implements Function<Collection<TT>, List<TT>> {
+        @Override
+        public List<TT> apply(Collection<TT> input) {
+            if (input==null) return null;
+            return MutableList.copyOf(input).asUnmodifiable();
+        }
+        
+    }
+    
     @Override
     protected void setEntityBeforeSubscribingProducerChildrenEvents() {
         BrooklynLogging.log(LOG, BrooklynLogging.levelDebugOrTraceIfReadOnly(producer),

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5af29257/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/EnrichersSlightlySimplerYamlTest.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/EnrichersSlightlySimplerYamlTest.java b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/EnrichersSlightlySimplerYamlTest.java
index 99165c5..9001824 100644
--- a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/EnrichersSlightlySimplerYamlTest.java
+++ b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/EnrichersSlightlySimplerYamlTest.java
@@ -21,6 +21,7 @@ package io.brooklyn.camp.brooklyn;
 import java.net.URI;
 import java.util.Collection;
 import java.util.Iterator;
+import java.util.List;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -32,9 +33,12 @@ import brooklyn.entity.basic.Attributes;
 import brooklyn.entity.basic.Entities;
 import brooklyn.entity.basic.EntityInternal;
 import brooklyn.entity.group.DynamicCluster;
+import brooklyn.entity.webapp.JavaWebAppSoftwareProcess;
 import brooklyn.event.basic.Sensors;
 import brooklyn.test.EntityTestUtils;
 import brooklyn.util.collections.CollectionFunctionals;
+import brooklyn.util.collections.MutableList;
+import brooklyn.util.math.MathPredicates;
 import brooklyn.util.text.StringPredicates;
 
 import com.google.common.base.Predicate;
@@ -88,6 +92,40 @@ public class EnrichersSlightlySimplerYamlTest extends AbstractYamlTest {
         // TODO would we want to allow "all-but-usual" as the default if nothing specified
     }
     
+    @Test(groups="Integration")
+    public void testWebappWithAveragingEnricher() throws Exception {
+        Entity app = createAndStartApplication(loadYaml("test-webapp-with-averaging-enricher.yaml"));
+        waitForApplicationTasks(app);
+        log.info("Started "+app+":");
+        Entities.dumpInfo(app);
+
+        List<JavaWebAppSoftwareProcess> appservers = MutableList.copyOf(Entities.descendants(app, JavaWebAppSoftwareProcess.class));
+        Assert.assertEquals(appservers.size(), 3);
+        
+        EntityInternal srv0 = (EntityInternal) appservers.get(0);
+        EntityInternal dwac = (EntityInternal) srv0.getParent();
+        EntityInternal cdwac = (EntityInternal) dwac.getParent();
+        
+        srv0.setAttribute(Sensors.newDoubleSensor("my.load"), 20.0);
+        
+        EntityTestUtils.assertAttributeEventually(dwac, Sensors.newSensor(Double.class, "my.load.averaged"),
+            MathPredicates.equalsApproximately(20));
+        EntityTestUtils.assertAttributeEventually(cdwac, Sensors.newSensor(Double.class, "my.load.averaged"),
+            MathPredicates.equalsApproximately(20));
+
+        srv0.setAttribute(Sensors.newDoubleSensor("my.load"), null);
+        EntityTestUtils.assertAttributeEventually(cdwac, Sensors.newSensor(Double.class, "my.load.averaged"),
+            Predicates.isNull());
+
+        ((EntityInternal) appservers.get(1)).setAttribute(Sensors.newDoubleSensor("my.load"), 10.0);
+        ((EntityInternal) appservers.get(2)).setAttribute(Sensors.newDoubleSensor("my.load"), 20.0);
+        EntityTestUtils.assertAttributeEventually(cdwac, Sensors.newSensor(Double.class, "my.load.averaged"),
+            MathPredicates.equalsApproximately(15));
+        srv0.setAttribute(Sensors.newDoubleSensor("my.load"), 0.0);
+        EntityTestUtils.assertAttributeEventually(cdwac, Sensors.newSensor(Double.class, "my.load.averaged"),
+            MathPredicates.equalsApproximately(10));
+    }
+    
     @Override
     protected Logger getLogger() {
         return log;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5af29257/usage/camp/src/test/resources/test-webapp-with-averaging-enricher.yaml
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/resources/test-webapp-with-averaging-enricher.yaml b/usage/camp/src/test/resources/test-webapp-with-averaging-enricher.yaml
new file mode 100644
index 0000000..d4fc6ee
--- /dev/null
+++ b/usage/camp/src/test/resources/test-webapp-with-averaging-enricher.yaml
@@ -0,0 +1,47 @@
+#
+# 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.
+#
+# example showing how enrichers can be set 
+#
+name: test-webapp-with-averaging-enricher
+description: Testing many enrichers
+services:
+- type: brooklyn.entity.webapp.ControlledDynamicWebAppCluster
+  initialSize: 3
+  location: localhost
+  
+  # define the web cluster, adding an averaging enricher to the cluster.
+  # this assumes the test fixture will set the "my.load" sensor on the member-specs in here. 
+  webClusterSpec:
+    $brooklyn:entitySpec:
+      type: brooklyn.entity.webapp.DynamicWebAppCluster
+      id: cluster
+      brooklyn.enrichers:
+      - type: brooklyn.enricher.basic.Aggregator
+        brooklyn.config:
+          enricher.sourceSensor: $brooklyn:sensor("my.load")
+          enricher.targetSensor: $brooklyn:sensor("my.load.averaged")
+          enricher.aggregating.fromMembers: true
+          transformation: average
+            
+  brooklyn.enrichers:
+  - type: brooklyn.enricher.basic.Propagator
+    brooklyn.config:
+      producer: $brooklyn:entity("cluster")
+      propagating:
+      - $brooklyn:sensor("my.load.averaged")

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5af29257/utils/common/src/main/java/brooklyn/util/math/MathPredicates.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/math/MathPredicates.java b/utils/common/src/main/java/brooklyn/util/math/MathPredicates.java
index 7a5b325..41f4024 100644
--- a/utils/common/src/main/java/brooklyn/util/math/MathPredicates.java
+++ b/utils/common/src/main/java/brooklyn/util/math/MathPredicates.java
@@ -20,6 +20,7 @@ package brooklyn.util.math;
 
 import javax.annotation.Nullable;
 
+import com.google.common.base.Preconditions;
 import com.google.common.base.Predicate;
 
 public class MathPredicates {
@@ -71,4 +72,35 @@ public class MathPredicates {
             }
         };
     }
+    
+    /**
+     * Creates a predicate comparing a given number with {@code val}. 
+     * A number of {@code null} passed to the predicate will always return false.
+     */
+    public static <T extends Number> Predicate<T> equalsApproximately(final Number val, final double delta) {
+        return new EqualsApproximately<T>(val, delta);
+    }
+    /** Convenience for {@link #equalsApproximately(double,double)} with a delta of 10^{-6}. */
+    public static <T extends Number> Predicate<T> equalsApproximately(final Number val) {
+        return equalsApproximately(val, 0.0000001);
+    }
+
+    private static final class EqualsApproximately<T extends Number> implements Predicate<T> {
+        private final double val;
+        private final double delta;
+        private EqualsApproximately(Number val, double delta) {
+            this.val = val.doubleValue();
+            Preconditions.checkArgument(delta>=0, "delta must be non-negative");
+            this.delta = delta;
+        }
+        public boolean apply(@Nullable T input) {
+            return (input == null) ? false : Math.abs(input.doubleValue() - val) <= delta;
+        }
+        @Override
+        public String toString() {
+            return "equals-approximately("+val+" +- "+delta+")";
+        }
+    }
+
+
 }


[08/16] incubator-brooklyn git commit: support for multi-item catalog yaml

Posted by he...@apache.org.
support for multi-item catalog yaml

adds many tests, and the rest of the features - template and policy and location; and source yaml.

also a few significant REST API changes:
* /v1/catalog/create API change returns a map (breaking)
* catalog items include more information for entity and policies


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

Branch: refs/heads/master
Commit: eaaca7874bc092acc5abac848eba30648836fd53
Parents: 08cb3e7
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Fri Apr 3 13:29:28 2015 -0400
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Thu Apr 16 01:25:39 2015 -0500

----------------------------------------------------------------------
 .../java/brooklyn/catalog/BrooklynCatalog.java  |   4 +
 .../java/brooklyn/location/LocationSpec.java    |   9 +-
 .../brooklyn/catalog/CatalogPredicates.java     |  12 +-
 .../catalog/internal/BasicBrooklynCatalog.java  | 214 +++++++++++++++----
 .../brooklyn/catalog/internal/CatalogUtils.java |   6 +
 .../location/basic/BasicLocationRegistry.java   |   2 +-
 .../location/basic/CatalogLocationResolver.java |   3 +-
 .../brooklyn/camp/lite/CampYamlLiteTest.java    |   6 +-
 .../entity/rebind/RebindCatalogItemTest.java    |  10 +-
 docs/guide/misc/release-notes.md                |   4 +
 .../camp/brooklyn/AbstractYamlTest.java         |   2 +-
 .../CatalogOsgiVersionMoreEntityTest.java       |  11 +-
 .../brooklyn/catalog/CatalogYamlEntityTest.java | 103 ++++++++-
 .../catalog/CatalogYamlLocationTest.java        | 124 +++++++++--
 .../brooklyn/catalog/CatalogYamlPolicyTest.java |  70 +++++-
 .../catalog/CatalogYamlTemplateTest.java        |  77 +++++++
 .../src/main/webapp/assets/js/view/catalog.js   |  57 +++--
 .../assets/tpl/catalog/add-catalog-entry.html   |   4 +-
 .../webapp/assets/tpl/catalog/add-entity.html   |  30 ---
 .../webapp/assets/tpl/catalog/add-yaml.html     |  30 +++
 .../main/java/brooklyn/rest/api/CatalogApi.java |  19 +-
 .../java/brooklyn/rest/api/LocationApi.java     |   5 +-
 .../rest/domain/CatalogEntitySummary.java       |   6 +
 .../rest/domain/CatalogItemSummary.java         |   3 +
 .../rest/domain/CatalogPolicySummary.java       |   3 +
 .../rest/resources/CatalogResource.java         |  66 +++---
 .../rest/resources/LocationResource.java        |   4 +-
 .../rest/transform/CatalogTransformer.java      |  44 +++-
 .../rest/resources/CatalogResourceTest.java     |  67 +++---
 .../src/main/java/brooklyn/util/yaml/Yamls.java |  12 ++
 30 files changed, 786 insertions(+), 221 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/eaaca787/api/src/main/java/brooklyn/catalog/BrooklynCatalog.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/catalog/BrooklynCatalog.java b/api/src/main/java/brooklyn/catalog/BrooklynCatalog.java
index ce4386f..5b11cc6 100644
--- a/api/src/main/java/brooklyn/catalog/BrooklynCatalog.java
+++ b/api/src/main/java/brooklyn/catalog/BrooklynCatalog.java
@@ -90,7 +90,9 @@ public interface BrooklynCatalog {
      * Fails if the same version exists in catalog.
      *
      * @throws IllegalArgumentException if the yaml was invalid
+     * @deprecated since 0.7.0 use {@link #addItems(String, boolean)}
      */
+    @Deprecated
     CatalogItem<?,?> addItem(String yaml);
     
     /**
@@ -100,7 +102,9 @@ public interface BrooklynCatalog {
      * item exists with the same symbolicName and version
      *
      * @throws IllegalArgumentException if the yaml was invalid
+     * @deprecated since 0.7.0 use {@link #addItems(String, boolean)}
      */
+    @Deprecated
     CatalogItem<?,?> addItem(String yaml, boolean forceUpdate);
     
     /**

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/eaaca787/api/src/main/java/brooklyn/location/LocationSpec.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/location/LocationSpec.java b/api/src/main/java/brooklyn/location/LocationSpec.java
index 7e3c4f9..10a6d7e 100644
--- a/api/src/main/java/brooklyn/location/LocationSpec.java
+++ b/api/src/main/java/brooklyn/location/LocationSpec.java
@@ -78,8 +78,11 @@ public class LocationSpec<T extends Location> extends AbstractBrooklynObjectSpec
      * original entity spec.
      */
     public static <T extends Location> LocationSpec<T> create(LocationSpec<T> spec) {
-        // FIXME Why can I not use LocationSpec<T>?
-        LocationSpec<?> result = create(spec.getType())
+        // need this to get LocationSpec<T> rather than LocationSpec<? extends T>
+        @SuppressWarnings("unchecked")
+        Class<T> exactType = (Class<T>)spec.getType();
+        
+        LocationSpec<T> result = create(exactType)
                 .displayName(spec.getDisplayName())
                 .tags(spec.getTags())
                 .configure(spec.getConfig())
@@ -176,7 +179,7 @@ public class LocationSpec<T extends Location> extends AbstractBrooklynObjectSpec
         return this;
     }
     
-    @SuppressWarnings("unchecked")
+    @SuppressWarnings({ "unchecked", "rawtypes" })
     public <E> LocationSpec<T> extensions(Map<Class<?>, ?> extensions) {
         for (Map.Entry<Class<?>, ?> entry : extensions.entrySet()) {
             extension((Class)entry.getKey(), entry.getValue());

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/eaaca787/core/src/main/java/brooklyn/catalog/CatalogPredicates.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/catalog/CatalogPredicates.java b/core/src/main/java/brooklyn/catalog/CatalogPredicates.java
index 4331a1a..2ffebdf 100644
--- a/core/src/main/java/brooklyn/catalog/CatalogPredicates.java
+++ b/core/src/main/java/brooklyn/catalog/CatalogPredicates.java
@@ -21,6 +21,7 @@ package brooklyn.catalog;
 import javax.annotation.Nullable;
 
 import brooklyn.catalog.CatalogItem.CatalogItemType;
+import brooklyn.catalog.internal.CatalogUtils;
 import brooklyn.entity.Application;
 import brooklyn.entity.Entity;
 import brooklyn.entity.proxying.EntitySpec;
@@ -126,5 +127,14 @@ public class CatalogPredicates {
             }
         };
     }
-    
+ 
+    public static <T,SpecT> Predicate<CatalogItem<T,SpecT>> isBestVersion(final ManagementContext mgmt) {
+        return new Predicate<CatalogItem<T,SpecT>>() {
+            @Override
+            public boolean apply(@Nullable CatalogItem<T,SpecT> item) {
+                return CatalogUtils.isBestVersion(mgmt, item);
+            }
+        };
+    }
+
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/eaaca787/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java b/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java
index 8a562d0..09ef02f 100644
--- a/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java
+++ b/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java
@@ -72,6 +72,7 @@ import brooklyn.util.text.Strings;
 import brooklyn.util.time.Duration;
 import brooklyn.util.time.Time;
 import brooklyn.util.yaml.Yamls;
+import brooklyn.util.yaml.Yamls.YamlExtract;
 
 import com.google.common.base.Function;
 import com.google.common.base.Predicate;
@@ -322,6 +323,11 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
         String yaml = loadedItem.getPlanYaml();
 
         if (yaml!=null) {
+            // preferred way is to parse the yaml, to resolve references late;
+            // the parsing on load is to populate some fields, but it is optional.
+            // TODO messy for location and policy that we need brooklyn.{locations,policies} root of the yaml, but it works;
+            // see related comment when the yaml is set, in addAbstractCatalogItems
+            // (not sure if anywhere else relies on that syntax; if not, it should be easy to fix!)
             DeploymentPlan plan = makePlanFromYaml(yaml);
             BrooklynClassLoadingContext loader = CatalogUtils.newClassLoadingContext(mgmt, item);
             SpecT spec;
@@ -384,7 +390,6 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
         }
     }
 
-    @SuppressWarnings("unchecked")
     private <T, SpecT> SpecT createPolicySpec(DeploymentPlan plan, BrooklynClassLoadingContext loader) {
         //Would ideally re-use io.brooklyn.camp.brooklyn.spi.creation.BrooklynEntityDecorationResolver.PolicySpecResolver
         //but it is CAMP specific and there is no easy way to get hold of it.
@@ -395,6 +400,11 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
 
         Object policy = Iterables.getOnlyElement((Iterable<?>)policies);
 
+        return createPolicySpec(loader, policy);
+    }
+
+    @SuppressWarnings("unchecked")
+    private <T, SpecT> SpecT createPolicySpec(BrooklynClassLoadingContext loader, Object policy) {
         Map<String, Object> config;
         if (policy instanceof String) {
             config = ImmutableMap.<String, Object>of("type", policy);
@@ -413,7 +423,6 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
         return (SpecT) spec;
     }
     
-    @SuppressWarnings("unchecked")
     private <T, SpecT> SpecT createLocationSpec(DeploymentPlan plan, BrooklynClassLoadingContext loader) {
         // See #createPolicySpec; this impl is modeled on that.
         // spec.catalogItemId is set by caller
@@ -424,6 +433,11 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
 
         Object location = Iterables.getOnlyElement((Iterable<?>)locations);
 
+        return createLocationSpec(loader, location); 
+    }
+
+    @SuppressWarnings("unchecked")
+    private <T, SpecT> SpecT createLocationSpec(BrooklynClassLoadingContext loader, Object location) {
         Map<String, Object> config;
         if (location instanceof String) {
             config = ImmutableMap.<String, Object>of("type", location);
@@ -457,7 +471,7 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
             } else {
                 throw new IllegalStateException("No class or resolver found for location type "+type);
             }
-        } 
+        }
     }
 
     @SuppressWarnings("unchecked")
@@ -509,7 +523,9 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
         }
         if (foundKey==null) return Maybe.absent("Missing entry '"+firstKey+"'");
         value = map.get(foundKey);
-        if (!type.isInstance(value)) return Maybe.absent("Entry for '"+firstKey+"' should be of type "+type+", not "+value.getClass());
+        if (type.equals(String.class) && Number.class.isInstance(value)) value = value.toString();
+        if (!type.isInstance(value)) 
+            throw new IllegalArgumentException("Entry for '"+firstKey+"' should be of type "+type+", not "+value.getClass());
         return Maybe.of((T)value);
     }
     
@@ -518,7 +534,7 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
         return (Maybe<Map<?,?>>)(Maybe) getFirstAs(map, Map.class, firstKey, otherKeys);
     }
 
-    private List<CatalogItemDtoAbstract<?,?>> getAbstractCatalogItems(String yaml) {
+    private List<CatalogItemDtoAbstract<?,?>> addAbstractCatalogItems(String yaml, Boolean whenAddingAsDtoShouldWeForce) {
         Map<?,?> itemDef = Yamls.getAs(Yamls.parseAll(yaml), Map.class);
         Map<?,?> catalogMetadata = getFirstAsMap(itemDef, "brooklyn.catalog", "catalog").orNull();
         if (catalogMetadata==null)
@@ -527,30 +543,36 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
 
         List<CatalogItemDtoAbstract<?, ?>> result = MutableList.of();
         
-        addAbstractCatalogItems(catalogMetadata, result, null);
+        addAbstractCatalogItems(Yamls.getTextOfYamlAtPath(yaml, "brooklyn.catalog").getMatchedYamlTextOrWarn(), 
+            catalogMetadata, result, null, whenAddingAsDtoShouldWeForce);
         
         itemDef.remove("brooklyn.catalog");
         catalogMetadata.remove("item");
         catalogMetadata.remove("items");
         if (!itemDef.isEmpty()) {
-            log.debug("Reading brooklyn.catalog peer keys as item");
+            log.debug("Reading brooklyn.catalog peer keys as item ('top-level syntax')");
             Map<String,?> rootItem = MutableMap.of("item", itemDef);
-            addAbstractCatalogItems(rootItem, result, catalogMetadata);
+            String rootItemYaml = yaml;
+            YamlExtract yamlExtract = Yamls.getTextOfYamlAtPath(rootItemYaml, "brooklyn.catalog");
+            String match = yamlExtract.withOriginalIndentation(true).withKeyIncluded(true).getMatchedYamlTextOrWarn();
+            if (match!=null) {
+                if (rootItemYaml.startsWith(match)) rootItemYaml = Strings.removeFromStart(rootItemYaml, match);
+                else rootItemYaml = Strings.replaceAllNonRegex(rootItemYaml, "\n"+match, "");
+            }
+            addAbstractCatalogItems("item:\n"+makeAsIndentedObject(rootItemYaml), rootItem, result, catalogMetadata, whenAddingAsDtoShouldWeForce);
         }
         
         return result;
     }
 
-    enum CatalogItemTypes { ENTITY, TEMPLATE, POLICY, LOCATION }
-    
     @SuppressWarnings("unchecked")
-    private void addAbstractCatalogItems(Map<?,?> itemMetadata, List<CatalogItemDtoAbstract<?, ?>> result, Map<?,?> parentMetadata) {
+    private void addAbstractCatalogItems(String sourceYaml, Map<?,?> itemMetadata, List<CatalogItemDtoAbstract<?, ?>> result, Map<?,?> parentMetadata, Boolean whenAddingAsDtoShouldWeForce) {
+
+        if (sourceYaml==null) sourceYaml = new Yaml().dump(itemMetadata);
 
         // TODO:
-//        get yaml
-//        (tests)
-//        docs used as test casts -- (doc assertions that these match -- or import -- known test casesyamls)
-//        multiple versions in catalog page
+//        docs used as test cases -- (doc assertions that these match -- or import -- known test cases yamls)
+//        multiple versions in web app root
 
         Map<Object,Object> catalogMetadata = MutableMap.builder().putAll(parentMetadata).putAll(itemMetadata).build();
         
@@ -565,16 +587,22 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
         Object item = catalogMetadata.remove("item");
 
         if (items!=null) {
+            int count = 0;
             for (Map<?,?> i: ((List<Map<?,?>>)items)) {
-                addAbstractCatalogItems(i, result, catalogMetadata);
+                addAbstractCatalogItems(Yamls.getTextOfYamlAtPath(sourceYaml, "items", count).getMatchedYamlTextOrWarn(), 
+                    i, result, catalogMetadata, whenAddingAsDtoShouldWeForce);
+                count++;
             }
         }
         
         if (item==null) return;
+
+        // now look at the actual item, first correcting the sourceYaml and interpreting the catalog metadata
+        String itemYaml = Yamls.getTextOfYamlAtPath(sourceYaml, "item").getMatchedYamlTextOrWarn();
+        if (itemYaml!=null) sourceYaml = itemYaml;
+        else sourceYaml = new Yaml().dump(item);
         
-        //now parse the metadata and apply to item
-        
-        CatalogItemTypes itemType = TypeCoercions.coerce(getFirstAs(catalogMetadata, String.class, "item.type", "itemType", "item_type").or(CatalogItemTypes.ENTITY.toString()), CatalogItemTypes.class);
+        CatalogItemType itemType = TypeCoercions.coerce(getFirstAs(catalogMetadata, Object.class, "item.type", "itemType", "item_type").orNull(), CatalogItemType.class);
         
         String id = getFirstAs(catalogMetadata, String.class, "id").orNull();
         String version = getFirstAs(catalogMetadata, String.class, "version").orNull();
@@ -587,19 +615,25 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
                 Strings.isNonBlank(name) && !name.equals(displayName)) {
             log.warn("Name property will be ignored due to the existence of displayName and at least one of id, symbolicName");
         }
-        
-        // TODO use src yaml if avail
-        String yaml = new Yaml().dump(item);
-        
+                
         DeploymentPlan plan = null;
         try {
-            plan = makePlanFromYaml(yaml);
+            plan = makePlanFromYaml(sourceYaml);
         } catch (Exception e) {
             Exceptions.propagateIfFatal(e);
-            if (itemType==CatalogItemTypes.ENTITY || itemType==CatalogItemTypes.TEMPLATE)
-                log.warn("Could not parse item YAML for "+itemType+" (registering anyway): "+e+"\n"+yaml);
+            if (itemType==CatalogItemType.ENTITY || itemType==CatalogItemType.TEMPLATE)
+                log.warn("Could not parse item YAML for "+itemType+" (registering anyway): "+e+"\n"+sourceYaml);
         }
         
+        if (Strings.isBlank(id)) {
+            // let ID be inferred, especially from name, to support style where only "name" is specified, with inline version
+            if (Strings.isNonBlank(symbolicName) && Strings.isNonBlank(version)) {
+                id = symbolicName + ":" + version;
+            } else if (Strings.isNonBlank(name)) {
+                id = name;
+            }
+        }
+
         final String catalogSymbolicName;
         if (Strings.isNonBlank(symbolicName)) {
             catalogSymbolicName = symbolicName;
@@ -609,8 +643,6 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
             } else {
                 catalogSymbolicName = id;
             }
-        } else if (Strings.isNonBlank(name)) {
-            catalogSymbolicName = name;
         } else if (plan!=null && Strings.isNonBlank(plan.getName())) {
             catalogSymbolicName = plan.getName();
         } else if (plan!=null && plan.getServices().size()==1) {
@@ -620,8 +652,8 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
             }
             catalogSymbolicName = svc.getServiceType();
         } else {
-            log.error("Can't infer catalog item symbolicName from the following plan:\n" + yaml);
-            throw new IllegalStateException("Can't infer catalog item symbolicName from catalog item description");
+            log.error("Can't infer catalog item symbolicName from the following plan:\n" + sourceYaml);
+            throw new IllegalStateException("Can't infer catalog item symbolicName from catalog item metadata");
         }
 
         final String catalogVersion;
@@ -668,23 +700,103 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
         String versionedId = CatalogUtils.getVersionedId(catalogSymbolicName, catalogVersion!=null ? catalogVersion : NO_VERSION);
         BrooklynClassLoadingContext loader = CatalogUtils.newClassLoadingContext(mgmt, versionedId, libraries);
         
-        // TODO for entities, we parse. for templates we don't. (?)
-        AbstractBrooklynObjectSpec<?, ?> spec = createSpec(catalogSymbolicName, plan, loader);
+        CatalogItemType inferredItemType = inferType(plan);
+        boolean usePlan;
+        if (inferredItemType!=null) {
+            if (itemType!=null) {
+                if (itemType==CatalogItemType.TEMPLATE && inferredItemType==CatalogItemType.ENTITY) {
+                    // template - we use the plan, but coupled with the type to make a catalog template item
+                    usePlan = true;
+                } else if (itemType==inferredItemType) {
+                    // if the plan showed the type, it is either a parsed entity or a legacy spec type
+                    usePlan = true;
+                    if (itemType==CatalogItemType.TEMPLATE || itemType==CatalogItemType.ENTITY) {
+                        // normal
+                    } else {
+                        log.warn("Legacy syntax used for "+itemType+" "+catalogSymbolicName+": should declare item.type and specify type");
+                    }
+                } else {
+                    throw new IllegalStateException("Explicit type "+itemType+" "+catalogSymbolicName+" does not match blueprint inferred type "+inferredItemType);
+                }
+            } else {
+                // no type was declared, use the inferred type from the plan
+                itemType = inferredItemType;
+                usePlan = true;
+            }
+        } else if (itemType!=null) {
+            if (itemType==CatalogItemType.TEMPLATE || itemType==CatalogItemType.ENTITY) {
+                log.warn("Incomplete plan detected for "+itemType+" "+catalogSymbolicName+"; will likely fail subsequently");
+                usePlan = true;
+            } else {
+                usePlan = false;
+            }
+        } else {
+            throw new IllegalStateException("Unable to detect type for "+catalogSymbolicName+"; error in catalog metadata or blueprint");
+        }
+        
+        AbstractBrooklynObjectSpec<?, ?> spec;
+        if (usePlan) {
+            spec = createSpecFromPlan(catalogSymbolicName, plan, loader);
+        } else {
+            String key;
+            switch (itemType) {
+            // we don't actually need the spec here, since the yaml is what is used at load time,
+            // but it isn't a bad idea to confirm that it resolves
+            case POLICY: 
+                spec = createPolicySpec(loader, item);
+                key = POLICIES_KEY;
+                break;
+            case LOCATION: 
+                spec = createLocationSpec(loader, item); 
+                key = LOCATIONS_KEY;
+                break;
+            default: throw new IllegalStateException("Cannot create "+itemType+" "+catalogSymbolicName+"; invalid metadata or blueprint");
+            }
+            // TODO currently we then convert yaml to legacy brooklyn.{location,policy} syntax for subsequent usage; 
+            // would be better to (in the other path) convert to {type: ..., brooklyn.config: ... } format and expect that elsewhere
+            sourceYaml = key + ":\n" + makeAsIndentedList(sourceYaml);
+        }
 
-        CatalogItemDtoAbstract<?, ?> dto = createItemBuilder(spec, catalogSymbolicName, catalogVersion)
+        CatalogItemDtoAbstract<?, ?> dto = createItemBuilder(itemType, spec, catalogSymbolicName, catalogVersion)
             .libraries(libraries)
             .displayName(catalogDisplayName)
             .description(catalogDescription)
             .deprecated(catalogDeprecated)
             .iconUrl(catalogIconUrl)
-            .plan(yaml)
+            .plan(sourceYaml)
             .build();
 
         dto.setManagementContext((ManagementContextInternal) mgmt);
+        if (whenAddingAsDtoShouldWeForce!=null) {
+            addItemDto(dto, whenAddingAsDtoShouldWeForce);
+        }
         result.add(dto);
     }
 
-    private AbstractBrooklynObjectSpec<?,?> createSpec(String symbolicName, DeploymentPlan plan, BrooklynClassLoadingContext loader) {
+    private String makeAsIndentedList(String yaml) {
+        String[] lines = yaml.split("\n");
+        lines[0] = "- "+lines[0];
+        for (int i=1; i<lines.length; i++)
+            lines[i] = "  " + lines[i];
+        return Strings.join(lines, "\n");
+    }
+
+    private String makeAsIndentedObject(String yaml) {
+        String[] lines = yaml.split("\n");
+        for (int i=0; i<lines.length; i++)
+            lines[i] = "  " + lines[i];
+        return Strings.join(lines, "\n");
+    }
+
+    private CatalogItemType inferType(DeploymentPlan plan) {
+        if (plan==null) return null;
+        if (isEntityPlan(plan)) return CatalogItemType.ENTITY; 
+        if (isPolicyPlan(plan)) return CatalogItemType.POLICY;
+        if (isLocationPlan(plan)) return CatalogItemType.LOCATION;
+        return null;
+    }
+
+    private AbstractBrooklynObjectSpec<?,?> createSpecFromPlan(String symbolicName, DeploymentPlan plan, BrooklynClassLoadingContext loader) {
         if (isPolicyPlan(plan)) {
             return createPolicySpec(plan, loader);
         } else if (isLocationPlan(plan)) {
@@ -694,7 +806,20 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
         }
     }
 
-    private CatalogItemBuilder<?> createItemBuilder(AbstractBrooklynObjectSpec<?, ?> spec, String itemId, String version) {
+    private CatalogItemBuilder<?> createItemBuilder(CatalogItemType itemType, AbstractBrooklynObjectSpec<?, ?> spec, String itemId, String version) {
+        if (itemType!=null) {
+            switch (itemType) {
+            case ENTITY: return CatalogItemBuilder.newEntity(itemId, version);
+            case TEMPLATE: return CatalogItemBuilder.newTemplate(itemId, version);
+            case POLICY: return CatalogItemBuilder.newPolicy(itemId, version);
+            case LOCATION: return CatalogItemBuilder.newLocation(itemId, version);
+            }
+            log.warn("Legacy syntax for "+itemId+"; unexpected item.type "+itemType);
+        } else {
+            log.warn("Legacy syntax for "+itemId+"; no item.type declared or inferred");
+        }
+        
+        // @deprecated - should not come here
         if (spec instanceof EntitySpec) {
             if (isApplicationSpec((EntitySpec<?>)spec)) {
                 return CatalogItemBuilder.newTemplate(itemId, version);
@@ -714,12 +839,16 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
         return !Boolean.TRUE.equals(spec.getConfig().get(EntityManagementUtils.WRAPPER_APP_MARKER));
     }
 
+    private boolean isEntityPlan(DeploymentPlan plan) {
+        return plan!=null && !plan.getServices().isEmpty() || !plan.getArtifacts().isEmpty();
+    }
+    
     private boolean isPolicyPlan(DeploymentPlan plan) {
-        return plan.getCustomAttributes().containsKey(POLICIES_KEY);
+        return !isEntityPlan(plan) && plan.getCustomAttributes().containsKey(POLICIES_KEY);
     }
 
     private boolean isLocationPlan(DeploymentPlan plan) {
-        return plan.getCustomAttributes().containsKey(LOCATIONS_KEY);
+        return !isEntityPlan(plan) && plan.getCustomAttributes().containsKey(LOCATIONS_KEY);
     }
 
     private DeploymentPlan makePlanFromYaml(String yaml) {
@@ -746,10 +875,11 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
     public List<? extends CatalogItem<?,?>> addItems(String yaml, boolean forceUpdate) {
         log.debug("Adding manual catalog item to "+mgmt+": "+yaml);
         checkNotNull(yaml, "yaml");
-        List<CatalogItemDtoAbstract<?, ?>> result = getAbstractCatalogItems(yaml);
-        for (CatalogItemDtoAbstract<?, ?> item: result) {
-            addItemDto(item, forceUpdate);
-        }
+        List<CatalogItemDtoAbstract<?, ?>> result = addAbstractCatalogItems(yaml, forceUpdate);
+        // previously we did this here, but now we do it on each item, in case #2 refers to #1
+//        for (CatalogItemDtoAbstract<?, ?> item: result) {
+//            addItemDto(item, forceUpdate);
+//        }
         return result;
     }
     

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/eaaca787/core/src/main/java/brooklyn/catalog/internal/CatalogUtils.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/catalog/internal/CatalogUtils.java b/core/src/main/java/brooklyn/catalog/internal/CatalogUtils.java
index 50ef436..c72fc9b 100644
--- a/core/src/main/java/brooklyn/catalog/internal/CatalogUtils.java
+++ b/core/src/main/java/brooklyn/catalog/internal/CatalogUtils.java
@@ -218,6 +218,12 @@ public class CatalogUtils {
         }
     }
 
+    public static boolean isBestVersion(ManagementContext mgmt, CatalogItem<?,?> item) {
+        CatalogItem<?, ?> bestVersion = getCatalogItemOptionalVersion(mgmt, item.getSymbolicName());
+        if (bestVersion==null) return false;
+        return (bestVersion.getVersion().equals(item.getVersion()));
+    }
+
     public static <T,SpecT> CatalogItem<T, SpecT> getCatalogItemOptionalVersion(ManagementContext mgmt, Class<T> type, String versionedId) {
         if (looksLikeVersionedId(versionedId)) {
             String id = getIdFromVersionedId(versionedId);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/eaaca787/core/src/main/java/brooklyn/location/basic/BasicLocationRegistry.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/location/basic/BasicLocationRegistry.java b/core/src/main/java/brooklyn/location/basic/BasicLocationRegistry.java
index f0b466e..c59298b 100644
--- a/core/src/main/java/brooklyn/location/basic/BasicLocationRegistry.java
+++ b/core/src/main/java/brooklyn/location/basic/BasicLocationRegistry.java
@@ -84,7 +84,7 @@ import com.google.common.collect.Sets;
  * it goes through the following steps:
  * 
  * <ol>
- *   <li>Call {@link BrooklynCatalog#addItem(String)}
+ *   <li>Call {@link BrooklynCatalog#addItems(String)}
  *     <ol>
  *       <li>This automatically calls {@link #updateDefinedLocation(CatalogItem)}
  *       <li>A LocationDefinition is creating, using as its id the {@link CatalogItem#getSymbolicName()}.

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/eaaca787/core/src/main/java/brooklyn/location/basic/CatalogLocationResolver.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/location/basic/CatalogLocationResolver.java b/core/src/main/java/brooklyn/location/basic/CatalogLocationResolver.java
index 4823b05..591873d 100644
--- a/core/src/main/java/brooklyn/location/basic/CatalogLocationResolver.java
+++ b/core/src/main/java/brooklyn/location/basic/CatalogLocationResolver.java
@@ -39,6 +39,7 @@ import brooklyn.management.ManagementContext;
  */
 public class CatalogLocationResolver implements LocationResolver {
 
+    @SuppressWarnings("unused")
     private static final Logger log = LoggerFactory.getLogger(CatalogLocationResolver.class);
 
     public static final String NAME = "brooklyn.catalog";
@@ -51,7 +52,7 @@ public class CatalogLocationResolver implements LocationResolver {
     }
     
     @Override
-    @SuppressWarnings({ "rawtypes" })
+    @SuppressWarnings({ "rawtypes", "unchecked" })
     public Location newLocationFromString(Map locationFlags, String spec, brooklyn.location.LocationRegistry registry) {
         String id = spec.substring(NAME.length()+1);
         CatalogItem<?, ?> item = CatalogUtils.getCatalogItemOptionalVersion(managementContext, id);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/eaaca787/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 b9b4bca..2739c79 100644
--- a/core/src/test/java/brooklyn/camp/lite/CampYamlLiteTest.java
+++ b/core/src/test/java/brooklyn/camp/lite/CampYamlLiteTest.java
@@ -156,7 +156,7 @@ public class CampYamlLiteTest {
     public void testYamlServiceForCatalog() {
         TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH);
 
-        CatalogItem<?, ?> realItem = mgmt.getCatalog().addItem(Streams.readFullyString(getClass().getResourceAsStream("test-app-service-blueprint.yaml")));
+        CatalogItem<?, ?> realItem = Iterables.getOnlyElement(mgmt.getCatalog().addItems(Streams.readFullyString(getClass().getResourceAsStream("test-app-service-blueprint.yaml"))));
         Iterable<CatalogItem<Object, Object>> retrievedItems = mgmt.getCatalog()
                 .getCatalogItems(CatalogPredicates.symbolicName(Predicates.equalTo("catalog-name")));
         
@@ -185,7 +185,7 @@ public class CampYamlLiteTest {
         String bundleUrl = OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL;
         String yaml = getSampleMyCatalogAppYaml(symbolicName, bundleUrl);
 
-        mgmt.getCatalog().addItem(yaml);
+        mgmt.getCatalog().addItems(yaml);
 
         assertMgmtHasSampleMyCatalogApp(symbolicName, bundleUrl);
     }
@@ -203,7 +203,7 @@ public class CampYamlLiteTest {
             CampPlatformWithJustBrooklynMgmt platform2 = new CampPlatformWithJustBrooklynMgmt(mgmt2);
             MockWebPlatform.populate(platform2, TestAppAssemblyInstantiator.class);
 
-            mgmt2.getCatalog().addItem(yaml);
+            mgmt2.getCatalog().addItems(yaml);
             String xml = ((BasicBrooklynCatalog) mgmt2.getCatalog()).toXmlString();
             ((BasicBrooklynCatalog) mgmt.getCatalog()).reset(CatalogDto.newDtoFromXmlContents(xml, "copy of temporary catalog"));
         } finally {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/eaaca787/core/src/test/java/brooklyn/entity/rebind/RebindCatalogItemTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/entity/rebind/RebindCatalogItemTest.java b/core/src/test/java/brooklyn/entity/rebind/RebindCatalogItemTest.java
index 4036dc5..e0260c3 100644
--- a/core/src/test/java/brooklyn/entity/rebind/RebindCatalogItemTest.java
+++ b/core/src/test/java/brooklyn/entity/rebind/RebindCatalogItemTest.java
@@ -212,8 +212,9 @@ public class RebindCatalogItemTest extends RebindTestFixtureWithApp {
                 "  version: " + TEST_VERSION + "\n" +
                 "services:\n" +
                 "- type: io.camp.mock:AppServer";
+        addItem(origManagementContext, yaml);
+        
         BasicBrooklynCatalog catalog = (BasicBrooklynCatalog) origManagementContext.getCatalog();
-        catalog.addItem(yaml);
         String catalogXml = catalog.toXmlString();
         catalog.reset(CatalogDto.newDtoFromXmlContents(catalogXml, "Test reset"));
         rebindAndAssertCatalogsAreEqual();
@@ -227,9 +228,10 @@ public class RebindCatalogItemTest extends RebindTestFixtureWithApp {
                 "  version: " + TEST_VERSION + "\n" +
                 "services:\n" +
                 "- type: io.camp.mock:AppServer";
-        BasicBrooklynCatalog catalog = (BasicBrooklynCatalog) origManagementContext.getCatalog();
-        CatalogItem<?, ?> catalogItem = catalog.addItem(yaml);
+        CatalogItem<?, ?> catalogItem = addItem(origManagementContext, yaml);
         assertNotNull(catalogItem, "catalogItem");
+        BasicBrooklynCatalog catalog = (BasicBrooklynCatalog) origManagementContext.getCatalog();
+        
         catalogItem.setDeprecated(true);
         catalog.persist(catalogItem);
         rebindAndAssertCatalogsAreEqual();
@@ -238,7 +240,7 @@ public class RebindCatalogItemTest extends RebindTestFixtureWithApp {
     }
 
     protected CatalogItem<?, ?> addItem(ManagementContext mgmt, String yaml) {
-        CatalogItem<?, ?> added = mgmt.getCatalog().addItem(yaml);
+        CatalogItem<?, ?> added = Iterables.getOnlyElement(mgmt.getCatalog().addItems(yaml));
         LOG.info("Added item to catalog: {}, id={}", added, added.getId());
         assertCatalogContains(mgmt.getCatalog(), added);
         return added;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/eaaca787/docs/guide/misc/release-notes.md
----------------------------------------------------------------------
diff --git a/docs/guide/misc/release-notes.md b/docs/guide/misc/release-notes.md
index 2b6dadf..1a324d6 100644
--- a/docs/guide/misc/release-notes.md
+++ b/docs/guide/misc/release-notes.md
@@ -47,6 +47,10 @@ For more information, please visit [brooklyn.io](http://brooklyn.io).
 * If `brooklyn.webconsole.security.https.required=true` is specified with no explicit port, 
   it now defaults to 8443; previously it would default to 8081 even in the case of `https`.
 
+* The /v1/catalog/create method now returns a map of ID to item map, instead of an item map, 
+  as the call supports multiple items defined in the YAML.
+  
+
 ### Community Activity
 
 Brooklyn has moved into the Apache Software Foundation.

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/eaaca787/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/AbstractYamlTest.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/AbstractYamlTest.java b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/AbstractYamlTest.java
index dd7ad58..b1e6817 100644
--- a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/AbstractYamlTest.java
+++ b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/AbstractYamlTest.java
@@ -153,7 +153,7 @@ public abstract class AbstractYamlTest {
     }
 
     protected void addCatalogItem(String catalogYaml) {
-        mgmt().getCatalog().addItem(catalogYaml, forceUpdate);
+        mgmt().getCatalog().addItems(catalogYaml, forceUpdate);
     }
 
     protected void deleteCatalogEntity(String catalogItem) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/eaaca787/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityTest.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityTest.java b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityTest.java
index 3527a5d..19c9ef8 100644
--- a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityTest.java
+++ b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityTest.java
@@ -18,16 +18,19 @@
  */
 package io.brooklyn.camp.brooklyn.catalog;
 
-import brooklyn.test.TestResourceUnavailableException;
 import io.brooklyn.camp.brooklyn.AbstractYamlTest;
 import io.brooklyn.camp.brooklyn.spi.creation.BrooklynEntityMatcher;
 
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
+import brooklyn.catalog.CatalogItem;
+import brooklyn.catalog.CatalogItem.CatalogItemType;
+import brooklyn.catalog.internal.CatalogUtils;
 import brooklyn.entity.Entity;
 import brooklyn.management.osgi.OsgiVersionMoreEntityTest;
 import brooklyn.policy.Policy;
+import brooklyn.test.TestResourceUnavailableException;
 import brooklyn.util.ResourceUtils;
 
 import com.google.common.collect.Iterables;
@@ -45,6 +48,12 @@ public class CatalogOsgiVersionMoreEntityTest extends AbstractYamlTest {
         TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), "/brooklyn/osgi/brooklyn-test-osgi-more-entities_0.1.0.jar");
 
         addCatalogItem(getLocalResource("more-entity-v1-osgi-catalog.yaml"));
+        CatalogItem<?, ?> item = CatalogUtils.getCatalogItemOptionalVersion(mgmt(), "more-entity");
+        Assert.assertNotNull(item);
+        Assert.assertEquals(item.getVersion(), "1.0");
+        Assert.assertEquals(item.getCatalogItemType(), CatalogItemType.ENTITY);
+        Assert.assertEquals(item.getLibraries().size(), 1);
+        
         Entity app = createAndStartApplication("services: [ { type: 'more-entity:1.0' } ]");
         Entity moreEntity = Iterables.getOnlyElement(app.getChildren());
         

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/eaaca787/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java
index 5b79982..1737718 100644
--- a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java
+++ b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java
@@ -25,6 +25,7 @@ import io.brooklyn.camp.brooklyn.AbstractYamlTest;
 
 import java.io.InputStream;
 import java.util.Collection;
+import java.util.List;
 
 import org.testng.Assert;
 import org.testng.annotations.Test;
@@ -38,6 +39,7 @@ import brooklyn.management.osgi.OsgiStandaloneTest;
 import brooklyn.management.osgi.OsgiTestResources;
 import brooklyn.test.TestResourceUnavailableException;
 import brooklyn.util.ResourceUtils;
+import brooklyn.util.collections.MutableList;
 
 import com.google.common.collect.Iterables;
 
@@ -59,6 +61,30 @@ public class CatalogYamlEntityTest extends AbstractYamlTest {
     }
 
     @Test
+    public void testAddCatalogItemAsSiblingOfCatalogMetadata() throws Exception {
+        TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH);
+
+        String symbolicName = "my.catalog.app.id.load";
+        addCatalogItem(
+            "brooklyn.catalog:",
+            "  id: " + symbolicName,
+            "  name: My Catalog App",
+            "  description: My description",
+            "  icon_url: classpath://path/to/myicon.jpg",
+            "  version: " + TEST_VERSION,
+            "  libraries:",
+            "  - url: " + OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL,
+            "",
+            "services:",
+            "- type: " + SIMPLE_ENTITY_TYPE);
+
+        CatalogItem<?, ?> item = mgmt().getCatalog().getCatalogItem(symbolicName, TEST_VERSION);
+        assertEquals(item.getSymbolicName(), symbolicName);
+
+        deleteCatalogEntity(symbolicName);
+    }
+
+    @Test
     public void testAddCatalogItemWithoutVersion() throws Exception {
         TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH);
 
@@ -76,6 +102,23 @@ public class CatalogYamlEntityTest extends AbstractYamlTest {
     }
 
     @Test
+    public void testAddCatalogItemWithInlinedVersion() throws Exception {
+        TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH);
+
+        String id = "inline_version.app";
+        addCatalogItem(
+            "brooklyn.catalog:",
+            "  name: " + id+":"+TEST_VERSION,
+            "  libraries:",
+            "  - " + OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL,
+            "services:",
+            "- type: " + SIMPLE_ENTITY_TYPE);
+        CatalogItem<?, ?> catalogItem = mgmt().getCatalog().getCatalogItem(id, TEST_VERSION);
+        assertEquals(catalogItem.getVersion(), TEST_VERSION);
+        mgmt().getCatalog().deleteCatalogItem(id, TEST_VERSION);
+    }
+
+    @Test
     public void testLaunchApplicationReferencingCatalog() throws Exception {
         TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH);
 
@@ -84,7 +127,7 @@ public class CatalogYamlEntityTest extends AbstractYamlTest {
     }
 
     @Test
-    public void testLaunchApplicationUnverionedCatalogReference() throws Exception {
+    public void testLaunchApplicationUnversionedCatalogReference() throws Exception {
         TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH);
 
         String symbolicName = "my.catalog.app.id.fail";
@@ -106,6 +149,27 @@ public class CatalogYamlEntityTest extends AbstractYamlTest {
 
         String referencedSymbolicName = "my.catalog.app.id.referenced";
         String referrerSymbolicName = "my.catalog.app.id.referring";
+        addCatalogOSGiEntities(referencedSymbolicName, SIMPLE_ENTITY_TYPE, referrerSymbolicName, ver(referencedSymbolicName));
+
+        String yaml = "name: simple-app-yaml\n" +
+                      "location: localhost\n" +
+                      "services: \n" +
+                      "  - serviceType: " + ver(referrerSymbolicName);
+        Entity app = createAndStartApplication(yaml);
+
+        Entity simpleEntity = Iterables.getOnlyElement(app.getChildren());
+        assertEquals(simpleEntity.getEntityType().getName(), SIMPLE_ENTITY_TYPE);
+
+        deleteCatalogEntity(referencedSymbolicName);
+        deleteCatalogEntity(referrerSymbolicName);
+    }
+
+    @Test
+    public void testLaunchApplicationWithCatalogReferencingOtherCatalogInTwoSteps() throws Exception {
+        TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH);
+
+        String referencedSymbolicName = "my.catalog.app.id.referenced";
+        String referrerSymbolicName = "my.catalog.app.id.referring";
         addCatalogOSGiEntity(referencedSymbolicName, SIMPLE_ENTITY_TYPE);
         addCatalogOSGiEntity(referrerSymbolicName, ver(referencedSymbolicName));
 
@@ -465,11 +529,32 @@ public class CatalogYamlEntityTest extends AbstractYamlTest {
             "  version: " + TEST_VERSION,
             "  libraries:",
             "  - url: " + OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL,
-            "",
-            "services:",
-            "- type: " + serviceType);
+            "  item:",
+            "    services:",
+            "    - type: " + serviceType);
     }
 
+    private void addCatalogOSGiEntities(String ...namesAndTypes) {
+        List<String> lines = MutableList.of(
+            "brooklyn.catalog:",
+            "  name: My Catalog App",
+            "  description: My description",
+            "  icon_url: classpath://path/to/myicon.jpg",
+            "  version: " + TEST_VERSION,
+            "  libraries:",
+            "  - url: " + OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL,
+            "  items:");
+        
+        for (int i=0; i<namesAndTypes.length; i+=2) {
+            lines.addAll(MutableList.of(
+            "  - id: " + namesAndTypes[i],
+            "    item:",
+            "      services:",
+            "      - type: " + namesAndTypes[i+1]));
+        }
+            
+        addCatalogItem(lines);
+    }
     private void addCatalogChildOSGiEntity(String symbolicName, String serviceType) {
         addCatalogItem(
             "brooklyn.catalog:",
@@ -480,11 +565,11 @@ public class CatalogYamlEntityTest extends AbstractYamlTest {
             "  version: " + TEST_VERSION,
             "  libraries:",
             "  - url: " + OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL,
-            "",
-            "services:",
-            "- type: " + BasicEntity.class.getName(),
-            "  brooklyn.children:",
-            "  - type: " + serviceType);
+            "  item:",
+            "    services:",
+            "    - type: " + BasicEntity.class.getName(),
+            "      brooklyn.children:",
+            "      - type: " + serviceType);
     }
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/eaaca787/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlLocationTest.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlLocationTest.java b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlLocationTest.java
index ef5aa0f..c0d41d2 100644
--- a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlLocationTest.java
+++ b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlLocationTest.java
@@ -22,16 +22,20 @@ import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNull;
 import io.brooklyn.camp.brooklyn.AbstractYamlTest;
 
+import java.util.Collection;
 import java.util.List;
 
+import org.testng.annotations.AfterMethod;
 import org.testng.annotations.Test;
 
 import brooklyn.catalog.CatalogItem;
+import brooklyn.catalog.CatalogItem.CatalogBundle;
 import brooklyn.catalog.CatalogPredicates;
 import brooklyn.entity.Entity;
 import brooklyn.event.basic.BasicConfigKey;
 import brooklyn.location.Location;
 import brooklyn.location.LocationDefinition;
+import brooklyn.location.LocationSpec;
 import brooklyn.location.basic.LocalhostMachineProvisioningLocation;
 import brooklyn.management.osgi.OsgiStandaloneTest;
 import brooklyn.test.TestResourceUnavailableException;
@@ -46,13 +50,63 @@ public class CatalogYamlLocationTest extends AbstractYamlTest {
     private static final String LOCALHOST_LOCATION_TYPE = LocalhostMachineProvisioningLocation.class.getName();
     private static final String SIMPLE_LOCATION_TYPE = "brooklyn.osgi.tests.SimpleLocation";
 
+    @AfterMethod
+    public void tearDown() {
+        for (CatalogItem<Location, LocationSpec<?>> ci : mgmt().getCatalog().getCatalogItems(CatalogPredicates.IS_LOCATION)) {
+            mgmt().getCatalog().deleteCatalogItem(ci.getSymbolicName(), ci.getVersion());
+        }
+    }
+    
     @Test
     public void testAddCatalogItem() throws Exception {
         assertEquals(countCatalogLocations(), 0);
 
         String symbolicName = "my.catalog.location.id.load";
-        addCatalogOSGiLocation(symbolicName, SIMPLE_LOCATION_TYPE);
+        addCatalogLocation(symbolicName, LOCALHOST_LOCATION_TYPE, null);
+        assertAdded(symbolicName, LOCALHOST_LOCATION_TYPE);
+        removeAndAssert(symbolicName);
+    }
+
+    @Test
+    public void testAddCatalogItemOsgi() throws Exception {
+        assertEquals(countCatalogLocations(), 0);
+
+        String symbolicName = "my.catalog.location.id.load";
+        addCatalogLocation(symbolicName, SIMPLE_LOCATION_TYPE, getOsgiLibraries());
+        assertAdded(symbolicName, SIMPLE_LOCATION_TYPE);
+        assertOsgi(symbolicName);
+        removeAndAssert(symbolicName);
+    }
+
+    @Test
+    public void testAddCatalogItemTopLevelItemSyntax() throws Exception {
+        assertEquals(countCatalogLocations(), 0);
+
+        String symbolicName = "my.catalog.location.id.load";
+        addCatalogLocationTopLevelItemSyntax(symbolicName, LOCALHOST_LOCATION_TYPE, null);
+        assertAdded(symbolicName, LOCALHOST_LOCATION_TYPE);
+        removeAndAssert(symbolicName);
+    }
+
+    @Test
+    public void testAddCatalogItemOsgiTopLevelItemSyntax() throws Exception {
+        assertEquals(countCatalogLocations(), 0);
 
+        String symbolicName = "my.catalog.location.id.load";
+        addCatalogLocationTopLevelItemSyntax(symbolicName, SIMPLE_LOCATION_TYPE, getOsgiLibraries());
+        assertAdded(symbolicName, SIMPLE_LOCATION_TYPE);
+        assertOsgi(symbolicName);
+        removeAndAssert(symbolicName);
+    }
+
+    private void assertOsgi(String symbolicName) {
+        CatalogItem<?, ?> item = mgmt().getCatalog().getCatalogItem(symbolicName, TEST_VERSION);
+        Collection<CatalogBundle> libs = item.getLibraries();
+        assertEquals(libs.size(), 1);
+        assertEquals(Iterables.getOnlyElement(libs).getUrl(), Iterables.getOnlyElement(getOsgiLibraries()));
+    }
+
+    private void assertAdded(String symbolicName, String expectedJavaType) {
         CatalogItem<?, ?> item = mgmt().getCatalog().getCatalogItem(symbolicName, TEST_VERSION);
         assertEquals(item.getSymbolicName(), symbolicName);
         assertEquals(countCatalogLocations(), 1);
@@ -61,7 +115,12 @@ public class CatalogYamlLocationTest extends AbstractYamlTest {
         LocationDefinition def = mgmt().getLocationRegistry().getDefinedLocationByName(symbolicName);
         assertEquals(def.getId(), symbolicName);
         assertEquals(def.getName(), symbolicName);
-
+        
+        LocationSpec<?> spec = (LocationSpec<?>)mgmt().getCatalog().createSpec(item);
+        assertEquals(spec.getType().getName(), expectedJavaType);
+    }
+    
+    private void removeAndAssert(String symbolicName) {
         // Deleting item: should be gone from catalog, and from location registry
         deleteCatalogEntity(symbolicName);
 
@@ -72,7 +131,7 @@ public class CatalogYamlLocationTest extends AbstractYamlTest {
     @Test
     public void testLaunchApplicationReferencingLocationClass() throws Exception {
         String symbolicName = "my.catalog.location.id.launch";
-        addCatalogLocation(symbolicName, LOCALHOST_LOCATION_TYPE);
+        addCatalogLocation(symbolicName, LOCALHOST_LOCATION_TYPE, null);
         runLaunchApplicationReferencingLocation(symbolicName, LOCALHOST_LOCATION_TYPE);
 
         deleteCatalogEntity(symbolicName);
@@ -81,7 +140,25 @@ public class CatalogYamlLocationTest extends AbstractYamlTest {
     @Test
     public void testLaunchApplicationReferencingLocationSpec() throws Exception {
         String symbolicName = "my.catalog.location.id.launch";
-        addCatalogLocation(symbolicName, LOCALHOST_LOCATION_SPEC);
+        addCatalogLocation(symbolicName, LOCALHOST_LOCATION_SPEC, null);
+        runLaunchApplicationReferencingLocation(symbolicName, LOCALHOST_LOCATION_TYPE);
+
+        deleteCatalogEntity(symbolicName);
+    }
+
+    @Test
+    public void testLaunchApplicationReferencingLocationClassTopLevelItemSyntax() throws Exception {
+        String symbolicName = "my.catalog.location.id.launch";
+        addCatalogLocationTopLevelItemSyntax(symbolicName, LOCALHOST_LOCATION_TYPE, null);
+        runLaunchApplicationReferencingLocation(symbolicName, LOCALHOST_LOCATION_TYPE);
+
+        deleteCatalogEntity(symbolicName);
+    }
+
+    @Test
+    public void testLaunchApplicationReferencingLocationSpecTopLevelSyntax() throws Exception {
+        String symbolicName = "my.catalog.location.id.launch";
+        addCatalogLocationTopLevelItemSyntax(symbolicName, LOCALHOST_LOCATION_SPEC, null);
         runLaunchApplicationReferencingLocation(symbolicName, LOCALHOST_LOCATION_TYPE);
 
         deleteCatalogEntity(symbolicName);
@@ -90,7 +167,7 @@ public class CatalogYamlLocationTest extends AbstractYamlTest {
     @Test
     public void testLaunchApplicationReferencingOsgiLocation() throws Exception {
         String symbolicName = "my.catalog.location.id.launch";
-        addCatalogOSGiLocation(symbolicName, SIMPLE_LOCATION_TYPE);
+        addCatalogLocation(symbolicName, SIMPLE_LOCATION_TYPE, getOsgiLibraries());
         runLaunchApplicationReferencingLocation(symbolicName, SIMPLE_LOCATION_TYPE);
         
         deleteCatalogEntity(symbolicName);
@@ -114,30 +191,49 @@ public class CatalogYamlLocationTest extends AbstractYamlTest {
         assertEquals(location.getConfig(new BasicConfigKey<String>(String.class, "config3")), "config3");
     }
 
-    private void addCatalogLocation(String symbolicName, String serviceType) {
-        addCatalogLocation(symbolicName, serviceType, ImmutableList.<String>of());
-    }
-
-    private void addCatalogOSGiLocation(String symbolicName, String serviceType) {
+    private List<String> getOsgiLibraries() {
         TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH);
-        addCatalogLocation(symbolicName, serviceType, ImmutableList.of(OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL));
+        return ImmutableList.of(OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL);
     }
     
-    private void addCatalogLocation(String symbolicName, String serviceType, List<String> libraries) {
+    private void addCatalogLocation(String symbolicName, String locationType, List<String> libraries) {
+        ImmutableList.Builder<String> yaml = ImmutableList.<String>builder().add(
+                "brooklyn.catalog:",
+                "  id: " + symbolicName,
+                "  name: My Catalog Location",
+                "  description: My description",
+                "  version: " + TEST_VERSION);
+        if (libraries!=null && libraries.size() > 0) {
+            yaml.add("  libraries:")
+                .addAll(Lists.transform(libraries, StringFunctions.prepend("  - url: ")));
+        }
+        yaml.add(
+                "  item.type: location",
+                "  item:",
+                "    type: " + locationType,
+                "    brooklyn.config:",
+                "      config1: config1",
+                "      config2: config2");
+        
+        
+        addCatalogItem(yaml.build());
+    }
+
+    private void addCatalogLocationTopLevelItemSyntax(String symbolicName, String locationType, List<String> libraries) {
         ImmutableList.Builder<String> yaml = ImmutableList.<String>builder().add(
                 "brooklyn.catalog:",
                 "  id: " + symbolicName,
                 "  name: My Catalog Location",
                 "  description: My description",
                 "  version: " + TEST_VERSION);
-        if (libraries.size() > 0) {
+        if (libraries!=null && libraries.size() > 0) {
             yaml.add("  libraries:")
                 .addAll(Lists.transform(libraries, StringFunctions.prepend("  - url: ")));
         }
         yaml.add(
                 "",
                 "brooklyn.locations:",
-                "- type: " + serviceType,
+                "- type: " + locationType,
                 "  brooklyn.config:",
                 "    config1: config1",
                 "    config2: config2");

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/eaaca787/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlPolicyTest.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlPolicyTest.java b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlPolicyTest.java
index d83c11a..b7370be 100644
--- a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlPolicyTest.java
+++ b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlPolicyTest.java
@@ -19,8 +19,6 @@
 package io.brooklyn.camp.brooklyn.catalog;
 
 import static org.testng.Assert.assertEquals;
-
-import brooklyn.test.TestResourceUnavailableException;
 import io.brooklyn.camp.brooklyn.AbstractYamlTest;
 
 import org.testng.annotations.Test;
@@ -31,6 +29,7 @@ import brooklyn.entity.Entity;
 import brooklyn.event.basic.BasicConfigKey;
 import brooklyn.management.osgi.OsgiStandaloneTest;
 import brooklyn.policy.Policy;
+import brooklyn.test.TestResourceUnavailableException;
 
 import com.google.common.collect.Iterables;
 
@@ -43,7 +42,21 @@ public class CatalogYamlPolicyTest extends AbstractYamlTest {
         assertEquals(countCatalogPolicies(), 0);
 
         String symbolicName = "my.catalog.policy.id.load";
-        addCatalogOSGiPolicy(symbolicName, SIMPLE_POLICY_TYPE);
+        addCatalogOsgiPolicy(symbolicName, SIMPLE_POLICY_TYPE);
+
+        CatalogItem<?, ?> item = mgmt().getCatalog().getCatalogItem(symbolicName, TEST_VERSION);
+        assertEquals(item.getSymbolicName(), symbolicName);
+        assertEquals(countCatalogPolicies(), 1);
+
+        deleteCatalogEntity(symbolicName);
+    }
+
+    @Test
+    public void testAddCatalogItemTopLevelSyntax() throws Exception {
+        assertEquals(countCatalogPolicies(), 0);
+
+        String symbolicName = "my.catalog.policy.id.load";
+        addCatalogOsgiPolicyTopLevelSyntax(symbolicName, SIMPLE_POLICY_TYPE);
 
         CatalogItem<?, ?> item = mgmt().getCatalog().getCatalogItem(symbolicName, TEST_VERSION);
         assertEquals(item.getSymbolicName(), symbolicName);
@@ -55,7 +68,7 @@ public class CatalogYamlPolicyTest extends AbstractYamlTest {
     @Test
     public void testLaunchApplicationReferencingPolicy() throws Exception {
         String symbolicName = "my.catalog.policy.id.launch";
-        addCatalogOSGiPolicy(symbolicName, SIMPLE_POLICY_TYPE);
+        addCatalogOsgiPolicy(symbolicName, SIMPLE_POLICY_TYPE);
         Entity app = createAndStartApplication(
             "name: simple-app-yaml",
             "location: localhost",
@@ -78,10 +91,35 @@ public class CatalogYamlPolicyTest extends AbstractYamlTest {
     }
 
     @Test
+    public void testLaunchApplicationReferencingPolicyTopLevelSyntax() throws Exception {
+        String symbolicName = "my.catalog.policy.id.launch";
+        addCatalogOsgiPolicyTopLevelSyntax(symbolicName, SIMPLE_POLICY_TYPE);
+        Entity app = createAndStartApplication(
+            "name: simple-app-yaml",
+            "location: localhost",
+            "services: ",
+            "  - type: brooklyn.entity.basic.BasicEntity\n" +
+            "    brooklyn.policies:\n" +
+            "    - type: " + ver(symbolicName),
+            "      brooklyn.config:",
+            "        config2: config2 override",
+            "        config3: config3");
+
+        Entity simpleEntity = Iterables.getOnlyElement(app.getChildren());
+        Policy policy = Iterables.getOnlyElement(simpleEntity.getPolicies());
+        assertEquals(policy.getPolicyType().getName(), SIMPLE_POLICY_TYPE);
+        assertEquals(policy.getConfig(new BasicConfigKey<String>(String.class, "config1")), "config1");
+        assertEquals(policy.getConfig(new BasicConfigKey<String>(String.class, "config2")), "config2 override");
+        assertEquals(policy.getConfig(new BasicConfigKey<String>(String.class, "config3")), "config3");
+
+        deleteCatalogEntity(symbolicName);
+    }
+    
+    @Test
     public void testLaunchApplicationWithCatalogReferencingOtherCatalog() throws Exception {
         String referencedSymbolicName = "my.catalog.policy.id.referenced";
         String referrerSymbolicName = "my.catalog.policy.id.referring";
-        addCatalogOSGiPolicy(referencedSymbolicName, SIMPLE_POLICY_TYPE);
+        addCatalogOsgiPolicy(referencedSymbolicName, SIMPLE_POLICY_TYPE);
 
         addCatalogItem(
             "brooklyn.catalog:",
@@ -112,7 +150,27 @@ public class CatalogYamlPolicyTest extends AbstractYamlTest {
         deleteCatalogEntity(referencedSymbolicName);
     }
 
-    private void addCatalogOSGiPolicy(String symbolicName, String serviceType) {
+    private void addCatalogOsgiPolicy(String symbolicName, String serviceType) {
+        TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH);
+
+        addCatalogItem(
+            "brooklyn.catalog:",
+            "  id: " + symbolicName,
+            "  name: My Catalog Policy",
+            "  description: My description",
+            "  icon_url: classpath://path/to/myicon.jpg",
+            "  version: " + TEST_VERSION,
+            "  libraries:",
+            "  - url: " + OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL,
+            "  item_type: policy",
+            "  item:",
+            "    type: " + serviceType,
+            "    brooklyn.config:",
+            "      config1: config1",
+            "      config2: config2");
+    }
+
+    private void addCatalogOsgiPolicyTopLevelSyntax(String symbolicName, String serviceType) {
         TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH);
 
         addCatalogItem(

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/eaaca787/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlTemplateTest.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlTemplateTest.java b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlTemplateTest.java
new file mode 100644
index 0000000..e473d2d
--- /dev/null
+++ b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlTemplateTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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 io.brooklyn.camp.brooklyn.catalog;
+
+import static org.testng.Assert.assertEquals;
+import io.brooklyn.camp.brooklyn.AbstractYamlTest;
+
+import org.testng.Assert;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+import brooklyn.catalog.CatalogItem;
+import brooklyn.catalog.CatalogItem.CatalogItemType;
+import brooklyn.management.osgi.OsgiStandaloneTest;
+import brooklyn.management.osgi.OsgiTestResources;
+import brooklyn.test.TestResourceUnavailableException;
+
+
+public class CatalogYamlTemplateTest extends AbstractYamlTest {
+    
+    private static final String SIMPLE_ENTITY_TYPE = OsgiTestResources.BROOKLYN_TEST_OSGI_ENTITIES_SIMPLE_ENTITY;
+
+    @Test
+    public void testAddCatalogItem() throws Exception {
+        TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH);
+        
+        addCatalogItem(
+            "brooklyn.catalog:",
+            "  id: t1",
+            "  item_type: template",
+            "  name: My Catalog App",
+            "  description: My description",
+            "  icon_url: classpath://path/to/myicon.jpg",
+            "  version: " + TEST_VERSION,
+            "  libraries:",
+            "  - url: " + OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL,
+            "  item:",
+            "    services:",
+            "    # this sample comment should be included",
+            "    - type: " + SIMPLE_ENTITY_TYPE);
+
+        CatalogItem<?, ?> item = mgmt().getCatalog().getCatalogItem("t1", TEST_VERSION);
+        assertEquals(item.getCatalogItemType(), CatalogItemType.TEMPLATE);
+        Assert.assertTrue(item.getPlanYaml().indexOf("sample comment")>=0,
+            "YAML did not include original comments; it was:\n"+item.getPlanYaml());
+        Assert.assertFalse(item.getPlanYaml().indexOf("description")>=0,
+            "YAML included metadata which should have been excluded; it was:\n"+item.getPlanYaml());
+
+        deleteCatalogEntity("t1");
+    }
+
+    // convenience for running in eclipse when the TestNG plugin drags in old version of snake yaml
+    public static void main(String[] args) {
+        TestListenerAdapter tla = new TestListenerAdapter();
+        TestNG testng = new TestNG();
+        testng.setTestClasses(new Class[] { CatalogYamlTemplateTest.class });
+        testng.addListener(tla);
+        testng.run();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/eaaca787/usage/jsgui/src/main/webapp/assets/js/view/catalog.js
----------------------------------------------------------------------
diff --git a/usage/jsgui/src/main/webapp/assets/js/view/catalog.js b/usage/jsgui/src/main/webapp/assets/js/view/catalog.js
index 6f75136..68b3c6f 100644
--- a/usage/jsgui/src/main/webapp/assets/js/view/catalog.js
+++ b/usage/jsgui/src/main/webapp/assets/js/view/catalog.js
@@ -24,7 +24,7 @@ define([
     "text!tpl/catalog/details-generic.html",
     "text!tpl/catalog/details-location.html",
     "text!tpl/catalog/add-catalog-entry.html",
-    "text!tpl/catalog/add-entity.html",
+    "text!tpl/catalog/add-yaml.html",
     "text!tpl/catalog/add-location.html",
     "text!tpl/catalog/nav-entry.html",
 
@@ -32,7 +32,7 @@ define([
 ], function(_, $, Backbone, Brooklyn,
         Location, Entity,
         CatalogPageHtml, DetailsEntityHtml, DetailsGenericHtml, LocationDetailsHtml,
-        AddCatalogEntryHtml, AddEntityHtml, AddLocationHtml, EntryHtml) {
+        AddCatalogEntryHtml, AddYamlHtml, AddLocationHtml, EntryHtml) {
 
     // Holds the currently active details type, e.g. applications, policies. Bit of a workaround
     // to share the active view with all instances of AccordionItemView, so clicking the 'reload
@@ -130,15 +130,21 @@ define([
         render: function (initialView) {
             this.$el.html(this.template());
             if (initialView) {
+                if (initialView == "entity") initialView = "yaml";
+                
                 this.$("[data-context='"+initialView+"']").addClass("active");
                 this.showFormForType(initialView)
             }
             return this;
         },
+        clearWithHtml: function(template) {
+            if (this.contextView) this.contextView.close();
+            this.context = undefined;
+            this.$(".btn").removeClass("active");
+            this.$("#catalog-add-form").html(template);
+        },
         beforeClose: function () {
-            if (this.contextView) {
-                this.contextView.close();
-            }
+            if (this.contextView) this.contextView.close();
         },
         showContext: function(event) {
             var $event = $(event.currentTarget);
@@ -152,13 +158,13 @@ define([
         },
         showFormForType: function (type) {
             this.context = type;
-            if (type == "entity") {
-                this.contextView = newEntityForm(this.options.parent);
+            if (type == "yaml" || type == "entity") {
+                this.contextView = newYamlForm(this, this.options.parent);
             } else if (type == "location") {
-                this.contextView = newLocationForm(this.options.parent);
+                this.contextView = newLocationForm(this, this.options.parent);
             } else if (type !== undefined) {
                 console.log("unknown catalog type " + type);
-                this.showFormForType("entity");
+                this.showFormForType("yaml");
                 return;
             }
             Backbone.history.navigate("/v1/catalog/new/" + type);
@@ -166,11 +172,10 @@ define([
         }
     });
 
-    function newEntityForm(parent) {
+    function newYamlForm(addView, addViewParent) {
         return new Brooklyn.view.Form({
-            template: _.template(AddEntityHtml),
+            template: _.template(AddYamlHtml),
             onSubmit: function (model) {
-                console.log("Submit entity", model.get("yaml"));
                 var submitButton = this.$(".catalog-submit-button");
                 // "loading" is an indicator to Bootstrap, not a string to display
                 submitButton.button("loading");
@@ -185,9 +190,13 @@ define([
                     .done(function (data, status, xhr) {
                         // Can extract location of new item with:
                         //model.url = Brooklyn.util.pathOf(xhr.getResponseHeader("Location"));
-                        self.close();  // one of the calls below should draw a different view
-                        parent.loadAccordionItem("entities", data.id);
-                        parent.loadAccordionItem("applications", data.id);
+                        if (_.size(data)==0) {
+                          addView.clearWithHtml( "No items supplied." );
+                        } else {
+                          addView.clearWithHtml( "Added: "+_.escape(_.keys(data).join(", ")) 
+                            + (_.size(data)==1 ? ". Loading..." : "") );
+                          addViewParent.loadAnyAccordionItem(_.size(data)==1 ? _.keys(data)[0] : undefined);
+                        }
                     })
                     .fail(function (xhr, status, error) {
                         submitButton.button("reset");
@@ -201,7 +210,7 @@ define([
     }
 
     // Could adapt to edit existing locations too.
-    function newLocationForm(parent) {
+    function newLocationForm(addView, addViewParent) {
         // Renders with config key list
         var body = new (Backbone.View.extend({
             beforeClose: function() {
@@ -235,10 +244,9 @@ define([
                 submitButton.button("loading");
                 location.set("config", configKeys);
                 location.save()
-                    .done(function (newModel) {
-                        newModel = new Location.Model(newModel);
-                        self.close();  // the call below should draw a different view
-                        parent.loadAccordionItem("locations", newModel.id);
+                    .done(function (data) {
+                        addView.clearWithHtml( "Added: "+data.id+". Loading..." ); 
+                        addViewParent.loadAccordionItem("locations", data.id);
                     })
                     .fail(function (response) {
                         submitButton.button("reset");
@@ -264,7 +272,7 @@ define([
             this.model = model;
         },
         url: function() {
-            return "/v1/catalog/" + this.name;
+            return "/v1/catalog/" + this.name+"?allVersions=true";
         }
     });
 
@@ -509,6 +517,13 @@ define([
             this.setDetailsView(newView);
         },
 
+        loadAnyAccordionItem: function (id) {
+            this.loadAccordionItem("entities", id);
+            this.loadAccordionItem("applications", id);
+            this.loadAccordionItem("policies", id);
+            this.loadAccordionItem("locations", id);
+        },
+
         loadAccordionItem: function (kind, id) {
             if (!this.accordion[kind]) {
                 console.error("No accordion for: " + kind);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/eaaca787/usage/jsgui/src/main/webapp/assets/tpl/catalog/add-catalog-entry.html
----------------------------------------------------------------------
diff --git a/usage/jsgui/src/main/webapp/assets/tpl/catalog/add-catalog-entry.html b/usage/jsgui/src/main/webapp/assets/tpl/catalog/add-catalog-entry.html
index 6bd8c00..238dd77 100644
--- a/usage/jsgui/src/main/webapp/assets/tpl/catalog/add-catalog-entry.html
+++ b/usage/jsgui/src/main/webapp/assets/tpl/catalog/add-catalog-entry.html
@@ -18,11 +18,11 @@ under the License.
 -->
 <div class="catalog-details">
 
-    <h2>Add a new...</h2>
+    <h2>Add to Catalog</h2>
     <br/>
 
     <div data-toggle="buttons-radio">
-        <button class="btn btn-large show-context" data-context="entity">Entity</button>
+        <button class="btn btn-large show-context" data-context="yaml">YAML</button>
         <button class="btn btn-large show-context" data-context="location">Location</button>
     </div>
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/eaaca787/usage/jsgui/src/main/webapp/assets/tpl/catalog/add-entity.html
----------------------------------------------------------------------
diff --git a/usage/jsgui/src/main/webapp/assets/tpl/catalog/add-entity.html b/usage/jsgui/src/main/webapp/assets/tpl/catalog/add-entity.html
deleted file mode 100644
index 9c64e90..0000000
--- a/usage/jsgui/src/main/webapp/assets/tpl/catalog/add-entity.html
+++ /dev/null
@@ -1,30 +0,0 @@
-<!--
-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.
--->
-<form>
-    <label for="new-blueprint">Enter blueprint:</label>
-    <textarea id='new-blueprint' name='yaml' rows='5' cols='15'></textarea>
-
-    <button class='catalog-submit-button btn' data-loading-text='Saving...'>Submit</button>
-    <p class="catalog-save-error hide">
-        <span class="alert-error">
-            <strong>Error:</strong><br/>
-            <span class="catalog-error-message"></span>
-        </span>
-    </p>
-</form>

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/eaaca787/usage/jsgui/src/main/webapp/assets/tpl/catalog/add-yaml.html
----------------------------------------------------------------------
diff --git a/usage/jsgui/src/main/webapp/assets/tpl/catalog/add-yaml.html b/usage/jsgui/src/main/webapp/assets/tpl/catalog/add-yaml.html
new file mode 100644
index 0000000..9c64e90
--- /dev/null
+++ b/usage/jsgui/src/main/webapp/assets/tpl/catalog/add-yaml.html
@@ -0,0 +1,30 @@
+<!--
+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.
+-->
+<form>
+    <label for="new-blueprint">Enter blueprint:</label>
+    <textarea id='new-blueprint' name='yaml' rows='5' cols='15'></textarea>
+
+    <button class='catalog-submit-button btn' data-loading-text='Saving...'>Submit</button>
+    <p class="catalog-save-error hide">
+        <span class="alert-error">
+            <strong>Error:</strong><br/>
+            <span class="catalog-error-message"></span>
+        </span>
+    </p>
+</form>

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/eaaca787/usage/rest-api/src/main/java/brooklyn/rest/api/CatalogApi.java
----------------------------------------------------------------------
diff --git a/usage/rest-api/src/main/java/brooklyn/rest/api/CatalogApi.java b/usage/rest-api/src/main/java/brooklyn/rest/api/CatalogApi.java
index bf097e2..49549fd 100644
--- a/usage/rest-api/src/main/java/brooklyn/rest/api/CatalogApi.java
+++ b/usage/rest-api/src/main/java/brooklyn/rest/api/CatalogApi.java
@@ -64,7 +64,8 @@ public interface CatalogApi {
 
     @Consumes
     @POST
-    @ApiOperation(value = "Add a catalog item (e.g. new type of entity, policy or location) by uploading YAML descriptor", responseClass = "String")
+    @ApiOperation(value = "Add a catalog item (e.g. new type of entity, policy or location) by uploading YAML descriptor "
+        + "Return value is map of ID to CatalogItemSummary, with code 201 CREATED.", responseClass = "Response")
     public Response create(
             @ApiParam(name = "yaml", value = "YAML descriptor of catalog item", required = true)
             @Valid String yaml);
@@ -135,7 +136,9 @@ public interface CatalogApi {
         @ApiParam(name = "regex", value = "Regular expression to search for")
         @QueryParam("regex") @DefaultValue("") String regex,
         @ApiParam(name = "fragment", value = "Substring case-insensitive to search for")
-        @QueryParam("fragment") @DefaultValue("") String fragment);
+        @QueryParam("fragment") @DefaultValue("") String fragment,
+        @ApiParam(name = "allVersions", value = "Include all versions (defaults false, only returning the best version)")
+        @QueryParam("allVersions") @DefaultValue("false") boolean includeAllVersions);
 
     @GET
     @Path("/applications")
@@ -144,7 +147,9 @@ public interface CatalogApi {
             @ApiParam(name = "regex", value = "Regular expression to search for")
             @QueryParam("regex") @DefaultValue("") String regex,
             @ApiParam(name = "fragment", value = "Substring case-insensitive to search for")
-            @QueryParam("fragment") @DefaultValue("") String fragment);
+            @QueryParam("fragment") @DefaultValue("") String fragment,
+            @ApiParam(name = "allVersions", value = "Include all versions (defaults false, only returning the best version)")
+            @QueryParam("allVersions") @DefaultValue("false") boolean includeAllVersions);
 
     /** @deprecated since 0.7.0 use {@link #getEntity(String, String)} */
     @Deprecated
@@ -203,7 +208,9 @@ public interface CatalogApi {
             @ApiParam(name = "regex", value = "Regular expression to search for")
             @QueryParam("regex") @DefaultValue("") String regex,
             @ApiParam(name = "fragment", value = "Substring case-insensitive to search for")
-            @QueryParam("fragment") @DefaultValue("") String fragment);
+            @QueryParam("fragment") @DefaultValue("") String fragment,
+            @ApiParam(name = "allVersions", value = "Include all versions (defaults false, only returning the best version)")
+            @QueryParam("allVersions") @DefaultValue("false") boolean includeAllVersions);
     
     /** @deprecated since 0.7.0 use {@link #getPolicy(String, String)} */
     @Deprecated
@@ -236,7 +243,9 @@ public interface CatalogApi {
             @ApiParam(name = "regex", value = "Regular expression to search for")
             @QueryParam("regex") @DefaultValue("") String regex,
             @ApiParam(name = "fragment", value = "Substring case-insensitive to search for")
-            @QueryParam("fragment") @DefaultValue("") String fragment);
+            @QueryParam("fragment") @DefaultValue("") String fragment,
+            @ApiParam(name = "allVersions", value = "Include all versions (defaults false, only returning the best version)")
+            @QueryParam("allVersions") @DefaultValue("false") boolean includeAllVersions);
     
     /** @deprecated since 0.7.0 use {@link #getLocation(String, String)} */
     @Deprecated

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/eaaca787/usage/rest-api/src/main/java/brooklyn/rest/api/LocationApi.java
----------------------------------------------------------------------
diff --git a/usage/rest-api/src/main/java/brooklyn/rest/api/LocationApi.java b/usage/rest-api/src/main/java/brooklyn/rest/api/LocationApi.java
index a5c5207..5035c19 100644
--- a/usage/rest-api/src/main/java/brooklyn/rest/api/LocationApi.java
+++ b/usage/rest-api/src/main/java/brooklyn/rest/api/LocationApi.java
@@ -41,6 +41,7 @@ import brooklyn.rest.domain.LocationSummary;
 import com.wordnik.swagger.core.ApiOperation;
 import com.wordnik.swagger.core.ApiParam;
 
+@SuppressWarnings("deprecation")
 @Path("/v1/locations")
 @Apidoc("Locations")
 @Produces(MediaType.APPLICATION_JSON)
@@ -79,9 +80,7 @@ public interface LocationApi {
             @DefaultValue("false")
             @QueryParam("full") String fullConfig);
 
-    /**
-     * @deprecated since 0.7.0; use {@link CatalogApi#create(String)}
-     */
+    /** @deprecated since 0.7.0 use {@link CatalogApi#create(String)} with a location definition */
     @POST
     @ApiOperation(value = "Create a new location definition", responseClass = "String")
     @Deprecated

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/eaaca787/usage/rest-api/src/main/java/brooklyn/rest/domain/CatalogEntitySummary.java
----------------------------------------------------------------------
diff --git a/usage/rest-api/src/main/java/brooklyn/rest/domain/CatalogEntitySummary.java b/usage/rest-api/src/main/java/brooklyn/rest/domain/CatalogEntitySummary.java
index c08e82a..268437a 100644
--- a/usage/rest-api/src/main/java/brooklyn/rest/domain/CatalogEntitySummary.java
+++ b/usage/rest-api/src/main/java/brooklyn/rest/domain/CatalogEntitySummary.java
@@ -23,13 +23,19 @@ import java.util.Map;
 import java.util.Set;
 
 import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+import org.codehaus.jackson.map.annotate.JsonSerialize.Inclusion;
 
 public class CatalogEntitySummary extends CatalogItemSummary {
 
     private static final long serialVersionUID = 1063908984191424539L;
     
+    @JsonSerialize(include=Inclusion.NON_EMPTY)
     private final Set<EntityConfigSummary> config;
+    
+    @JsonSerialize(include=Inclusion.NON_EMPTY)
     private final Set<SensorSummary> sensors;
+    @JsonSerialize(include=Inclusion.NON_EMPTY)
     private final Set<EffectorSummary> effectors;
 
     public CatalogEntitySummary(

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/eaaca787/usage/rest-api/src/main/java/brooklyn/rest/domain/CatalogItemSummary.java
----------------------------------------------------------------------
diff --git a/usage/rest-api/src/main/java/brooklyn/rest/domain/CatalogItemSummary.java b/usage/rest-api/src/main/java/brooklyn/rest/domain/CatalogItemSummary.java
index db979e2..79b1d27 100644
--- a/usage/rest-api/src/main/java/brooklyn/rest/domain/CatalogItemSummary.java
+++ b/usage/rest-api/src/main/java/brooklyn/rest/domain/CatalogItemSummary.java
@@ -23,6 +23,7 @@ import java.net.URI;
 import java.util.Map;
 
 import org.apache.commons.lang.builder.EqualsBuilder;
+import org.codehaus.jackson.annotate.JsonIgnoreProperties;
 import org.codehaus.jackson.annotate.JsonProperty;
 import org.codehaus.jackson.map.annotate.JsonSerialize;
 import org.codehaus.jackson.map.annotate.JsonSerialize.Inclusion;
@@ -32,6 +33,8 @@ import com.google.common.collect.ImmutableMap;
 
 /** variant of Catalog*ItemDto objects for JS/JSON serialization;
  * see also, subclasses */
+@JsonIgnoreProperties(ignoreUnknown = true)
+// ignore unknown, ie properties from subclasses (entity)
 public class CatalogItemSummary implements HasId, HasName, Serializable {
 
     private static final long serialVersionUID = -823483595879417681L;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/eaaca787/usage/rest-api/src/main/java/brooklyn/rest/domain/CatalogPolicySummary.java
----------------------------------------------------------------------
diff --git a/usage/rest-api/src/main/java/brooklyn/rest/domain/CatalogPolicySummary.java b/usage/rest-api/src/main/java/brooklyn/rest/domain/CatalogPolicySummary.java
index f30006a..75cc6ae 100644
--- a/usage/rest-api/src/main/java/brooklyn/rest/domain/CatalogPolicySummary.java
+++ b/usage/rest-api/src/main/java/brooklyn/rest/domain/CatalogPolicySummary.java
@@ -23,6 +23,8 @@ import java.util.Map;
 import java.util.Set;
 
 import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+import org.codehaus.jackson.map.annotate.JsonSerialize.Inclusion;
 
 import com.google.common.collect.ImmutableSet;
 
@@ -30,6 +32,7 @@ public class CatalogPolicySummary extends CatalogItemSummary {
 
     private static final long serialVersionUID = -588856488327394445L;
     
+    @JsonSerialize(include=Inclusion.NON_EMPTY)
     private final Set<PolicyConfigSummary> config;
 
     public CatalogPolicySummary(


[10/16] incubator-brooklyn git commit: explain the top-level-blueprint catalog syntax, and discourage it

Posted by he...@apache.org.
explain the top-level-blueprint catalog syntax, and discourage it

addresses @neykov 's code review comment


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

Branch: refs/heads/master
Commit: a0ddec104b5741b188916960b95333cf1eca3a97
Parents: 1f529fc
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Sun Apr 12 23:02:22 2015 -0500
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Thu Apr 16 01:25:40 2015 -0500

----------------------------------------------------------------------
 docs/guide/ops/catalog/index.md | 37 ++++++++++++++++++++++++------------
 1 file changed, 25 insertions(+), 12 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a0ddec10/docs/guide/ops/catalog/index.md
----------------------------------------------------------------------
diff --git a/docs/guide/ops/catalog/index.md b/docs/guide/ops/catalog/index.md
index 9eeca9c..401979c 100644
--- a/docs/guide/ops/catalog/index.md
+++ b/docs/guide/ops/catalog/index.md
@@ -49,17 +49,6 @@ brooklyn.catalog:
       <blueprint-or-resource-definition>
 ```
 
-In some cases it is desired to define a default blueprint in a catalog file,
-so that the catalog file can be used unchanged to launch an application.
-To support this use case, the following format is also supported:
-
-```yaml
-<blueprint-definition>
-brooklyn.catalog:
-  <catalog-metadata>
-```
-
-
 
 #### Catalog Metadata
 
@@ -93,7 +82,8 @@ In addition to the above fields, exactly **one** of the following is also requir
 - `items`: a list of catalog items, where each entry in the map follows the same schema as
   the `brooklyn.catalog` value, and the keys in these map override any metadata specified as
   a sibling of this `items` key (or, in the case of `libraries` they add to the list);
-  if there are references between items, then order is important, with forward references not supported.
+  if there are references between items, then order is important, 
+  `items` are processed in order, depth-first, and forward references are not supported.
 
 The following optional catalog metadata is supported:
   
@@ -204,6 +194,29 @@ The items this will install are:
   (This must be supplied after `riak-cluster`, because it refers to `riak-cluster`.)
 
 
+#### Legacy Syntax
+
+The following legacy and experimental syntax is also supported:
+
+```yaml
+<blueprint-definition>
+brooklyn.catalog:
+  <catalog-metadata>
+```
+
+In this format, the `brooklyn.catalog` block is optional;
+and an `id` in the `<blueprint-definition>` will be used to determine the catalog ID. 
+This is primarily supplied for OASIS CAMP 1.1 compatibility,
+where the same YAML blueprint can be POSTed to the catalog endpoint to add to a catalog
+or POSTed to the applications endpoint to deploy an instance.
+(This syntax is discouraged as the latter usage, 
+POSTing to the applications endpoint,
+will ignored the `brooklyn.catalog` information;
+this means references to any `item` blocks in the `<catalog-metadata>` will not be resolved,
+and any OSGi `libraries` defined there will not be loaded.)
+
+
+
 ### Templates and the Add-Application Wizard
 
 When a `template` is added to the catalog, the blueprint will appear in the 'Create Application' dialog


[09/16] incubator-brooklyn git commit: address code review for multi-item catalog

Posted by he...@apache.org.
address code review for multi-item catalog

* use `itemType` instead of `item.type`
* infer itemType from the class - prompted major refactor of how items are parsed, but cleaner now (at least code; logic has some TODO improvements listed)
* use service spec format for entity
* add atomically (and lookup types against just-added items)


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

Branch: refs/heads/master
Commit: 6f75f40bc3fa142d754afdffacc842b1f8da2654
Parents: a0ddec1
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Tue Apr 14 13:17:57 2015 -0500
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Thu Apr 16 01:25:40 2015 -0500

----------------------------------------------------------------------
 .../main/java/brooklyn/catalog/CatalogItem.java |   3 +-
 .../io/brooklyn/camp/BasicCampPlatform.java     |   7 +-
 .../catalog/internal/BasicBrooklynCatalog.java  | 512 +++++++++++--------
 .../location/geo/UtraceHostGeoLookup.java       |   4 +-
 .../policy/basic/AbstractEntityAdjunct.java     |  13 +
 docs/guide/ops/catalog/index.md                 |  38 +-
 .../creation/BrooklynYamlTypeInstantiator.java  |   3 +-
 .../camp/brooklyn/AbstractYamlTest.java         |  10 +-
 .../brooklyn/catalog/CatalogYamlCombiTest.java  | 145 ++++++
 .../brooklyn/catalog/CatalogYamlEntityTest.java | 141 ++++-
 .../brooklyn/catalog/CatalogYamlPolicyTest.java |  11 +-
 .../catalog/CatalogYamlTemplateTest.java        |  33 +-
 .../src/test/resources/couchbase-w-loadgen.yaml |   2 +-
 .../src/main/java/brooklyn/util/yaml/Yamls.java |  33 +-
 14 files changed, 672 insertions(+), 283 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f75f40b/api/src/main/java/brooklyn/catalog/CatalogItem.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/catalog/CatalogItem.java b/api/src/main/java/brooklyn/catalog/CatalogItem.java
index 8abaddc..a857a53 100644
--- a/api/src/main/java/brooklyn/catalog/CatalogItem.java
+++ b/api/src/main/java/brooklyn/catalog/CatalogItem.java
@@ -84,7 +84,8 @@ public interface CatalogItem<T,SpecT> extends BrooklynObject, Rebindable {
 
     public String toXmlString();
 
-    /** @return The underlying YAML for this item, if known */
+    /** @return The underlying YAML for this item, if known; 
+     * currently including `services:` or `brooklyn.policies:` prefix (but this will likely be removed) */
     @Nullable public String getPlanYaml();
 
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f75f40b/camp/camp-base/src/main/java/io/brooklyn/camp/BasicCampPlatform.java
----------------------------------------------------------------------
diff --git a/camp/camp-base/src/main/java/io/brooklyn/camp/BasicCampPlatform.java b/camp/camp-base/src/main/java/io/brooklyn/camp/BasicCampPlatform.java
index 7f6d4b0..16a3dee 100644
--- a/camp/camp-base/src/main/java/io/brooklyn/camp/BasicCampPlatform.java
+++ b/camp/camp-base/src/main/java/io/brooklyn/camp/BasicCampPlatform.java
@@ -131,8 +131,11 @@ public class BasicCampPlatform extends CampPlatform {
         
         @Override
         protected void finalize() throws Throwable {
-            if (!committed.get())
-                log.warn("transaction "+this+" was never applied");
+            if (!committed.get()) {
+                // normal, in the case of errors (which might occur when catalog tries to figure out the right plan format); shouldn't happen otherwise
+                // if we want log.warn visibility of these, then we will have to supply an abandon() method on this interface and ensure that is invoked on errors
+                log.debug("transaction "+this+" was never applied");
+            }
             super.finalize();
         }
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f75f40b/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java b/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java
index b773247..680eaf1 100644
--- a/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java
+++ b/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java
@@ -24,7 +24,6 @@ import io.brooklyn.camp.CampPlatform;
 import io.brooklyn.camp.spi.AssemblyTemplate;
 import io.brooklyn.camp.spi.instantiate.AssemblyTemplateInstantiator;
 import io.brooklyn.camp.spi.pdp.DeploymentPlan;
-import io.brooklyn.camp.spi.pdp.Service;
 
 import java.io.FileNotFoundException;
 import java.util.Collection;
@@ -48,13 +47,11 @@ import brooklyn.catalog.CatalogItem.CatalogBundle;
 import brooklyn.catalog.CatalogItem.CatalogItemType;
 import brooklyn.catalog.CatalogPredicates;
 import brooklyn.config.BrooklynServerConfig;
-import brooklyn.entity.proxying.EntitySpec;
 import brooklyn.location.Location;
 import brooklyn.location.LocationSpec;
 import brooklyn.location.basic.BasicLocationRegistry;
 import brooklyn.management.ManagementContext;
 import brooklyn.management.classloading.BrooklynClassLoadingContext;
-import brooklyn.management.internal.EntityManagementUtils;
 import brooklyn.management.internal.ManagementContextInternal;
 import brooklyn.policy.Policy;
 import brooklyn.policy.PolicySpec;
@@ -75,6 +72,7 @@ import brooklyn.util.yaml.Yamls;
 import brooklyn.util.yaml.Yamls.YamlExtract;
 
 import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
 import com.google.common.base.Predicate;
 import com.google.common.base.Predicates;
 import com.google.common.base.Throwables;
@@ -366,6 +364,18 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
         return spec;
     }
 
+    private <T, SpecT> SpecT createSpec(String optionalId, CatalogItemType ciType, DeploymentPlan plan, BrooklynClassLoadingContext loader) {
+        Preconditions.checkNotNull(ciType, "catalog item type for "+plan); 
+        switch (ciType) {
+        case TEMPLATE:
+        case ENTITY: 
+            return createEntitySpec(optionalId, plan, loader);
+        case LOCATION: return createLocationSpec(plan, loader);
+        case POLICY: return createPolicySpec(plan, loader);
+        }
+        throw new IllegalStateException("Unknown CI Type "+ciType+" for "+plan);
+    }
+    
     @SuppressWarnings("unchecked")
     private <T, SpecT> SpecT createEntitySpec(String symbolicName, DeploymentPlan plan, BrooklynClassLoadingContext loader) {
         CampPlatform camp = BrooklynServerConfig.getCampPlatform(mgmt).get();
@@ -382,7 +392,8 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
         try {
             AssemblyTemplateInstantiator instantiator = at.getInstantiator().newInstance();
             if (instantiator instanceof AssemblyTemplateSpecInstantiator) {
-                return (SpecT) ((AssemblyTemplateSpecInstantiator)instantiator).createNestedSpec(at, camp, loader, MutableSet.of(symbolicName));
+                return (SpecT) ((AssemblyTemplateSpecInstantiator)instantiator).createNestedSpec(at, camp, loader, 
+                    symbolicName==null ? MutableSet.<String>of() : MutableSet.of(symbolicName));
             }
             throw new IllegalStateException("Unable to instantiate YAML; incompatible instantiator "+instantiator+" for "+at);
         } catch (Exception e) {
@@ -405,17 +416,18 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
 
     @SuppressWarnings("unchecked")
     private <T, SpecT> SpecT createPolicySpec(BrooklynClassLoadingContext loader, Object policy) {
-        Map<String, Object> config;
+        // TODO this (and LocationSpec) lack the loop-prevention which createEntitySpec has (hence different signature)
+        Map<String, Object> itemMap;
         if (policy instanceof String) {
-            config = ImmutableMap.<String, Object>of("type", policy);
+            itemMap = ImmutableMap.<String, Object>of("type", policy);
         } else if (policy instanceof Map) {
-            config = (Map<String, Object>) policy;
+            itemMap = (Map<String, Object>) policy;
         } else {
             throw new IllegalStateException("Policy expected to be string or map. Unsupported object type " + policy.getClass().getName() + " (" + policy.toString() + ")");
         }
 
-        String type = (String) checkNotNull(Yamls.getMultinameAttribute(config, "policy_type", "policyType", "type"), "policy type");
-        Map<String, Object> brooklynConfig = (Map<String, Object>) config.get("brooklyn.config");
+        String type = (String) checkNotNull(Yamls.getMultinameAttribute(itemMap, "policy_type", "policyType", "type"), "policy type");
+        Map<String, Object> brooklynConfig = (Map<String, Object>) itemMap.get("brooklyn.config");
         PolicySpec<? extends Policy> spec = PolicySpec.create(loader.loadClass(type, Policy.class));
         if (brooklynConfig != null) {
             spec.configure(brooklynConfig);
@@ -438,17 +450,17 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
 
     @SuppressWarnings("unchecked")
     private <T, SpecT> SpecT createLocationSpec(BrooklynClassLoadingContext loader, Object location) {
-        Map<String, Object> config;
+        Map<String, Object> itemMap;
         if (location instanceof String) {
-            config = ImmutableMap.<String, Object>of("type", location);
+            itemMap = ImmutableMap.<String, Object>of("type", location);
         } else if (location instanceof Map) {
-            config = (Map<String, Object>) location;
+            itemMap = (Map<String, Object>) location;
         } else {
             throw new IllegalStateException("Location expected to be string or map. Unsupported object type " + location.getClass().getName() + " (" + location.toString() + ")");
         }
 
-        String type = (String) checkNotNull(Yamls.getMultinameAttribute(config, "location_type", "locationType", "type"), "location type");
-        Map<String, Object> brooklynConfig = (Map<String, Object>) config.get("brooklyn.config");
+        String type = (String) checkNotNull(Yamls.getMultinameAttribute(itemMap, "location_type", "locationType", "type"), "location type");
+        Map<String, Object> brooklynConfig = (Map<String, Object>) itemMap.get("brooklyn.config");
         Maybe<Class<? extends Location>> javaClass = loader.tryLoadClass(type, Location.class);
         if (javaClass.isPresent()) {
             LocationSpec<?> spec = LocationSpec.create(javaClass.get());
@@ -534,17 +546,17 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
         return (Maybe<Map<?,?>>)(Maybe) getFirstAs(map, Map.class, firstKey, otherKeys);
     }
 
-    private List<CatalogItemDtoAbstract<?,?>> addAbstractCatalogItems(String yaml, Boolean whenAddingAsDtoShouldWeForce) {
+    private List<CatalogItemDtoAbstract<?,?>> collectCatalogItems(String yaml) {
         Map<?,?> itemDef = Yamls.getAs(Yamls.parseAll(yaml), Map.class);
-        Map<?,?> catalogMetadata = getFirstAsMap(itemDef, "brooklyn.catalog", "catalog").orNull();
+        Map<?,?> catalogMetadata = getFirstAsMap(itemDef, "brooklyn.catalog").orNull();
         if (catalogMetadata==null)
             log.warn("No `brooklyn.catalog` supplied in catalog request; using legacy mode for "+itemDef);
         catalogMetadata = MutableMap.copyOf(catalogMetadata);
 
         List<CatalogItemDtoAbstract<?, ?>> result = MutableList.of();
         
-        addAbstractCatalogItems(Yamls.getTextOfYamlAtPath(yaml, "brooklyn.catalog").getMatchedYamlTextOrWarn(), 
-            catalogMetadata, result, null, whenAddingAsDtoShouldWeForce);
+        collectCatalogItems(Yamls.getTextOfYamlAtPath(yaml, "brooklyn.catalog").getMatchedYamlTextOrWarn(), 
+            catalogMetadata, result, null);
         
         itemDef.remove("brooklyn.catalog");
         catalogMetadata.remove("item");
@@ -559,34 +571,40 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
                 if (rootItemYaml.startsWith(match)) rootItemYaml = Strings.removeFromStart(rootItemYaml, match);
                 else rootItemYaml = Strings.replaceAllNonRegex(rootItemYaml, "\n"+match, "");
             }
-            addAbstractCatalogItems("item:\n"+makeAsIndentedObject(rootItemYaml), rootItem, result, catalogMetadata, whenAddingAsDtoShouldWeForce);
+            collectCatalogItems("item:\n"+makeAsIndentedObject(rootItemYaml), rootItem, result, catalogMetadata);
         }
         
         return result;
     }
 
     @SuppressWarnings("unchecked")
-    private void addAbstractCatalogItems(String sourceYaml, Map<?,?> itemMetadata, List<CatalogItemDtoAbstract<?, ?>> result, Map<?,?> parentMetadata, Boolean whenAddingAsDtoShouldWeForce) {
+    private void collectCatalogItems(String sourceYaml, Map<?,?> itemMetadata, List<CatalogItemDtoAbstract<?, ?>> result, Map<?,?> parentMetadata) {
 
         if (sourceYaml==null) sourceYaml = new Yaml().dump(itemMetadata);
 
         Map<Object,Object> catalogMetadata = MutableMap.builder().putAll(parentMetadata).putAll(itemMetadata).build();
         
         // libraries we treat specially, to append the list, with the child's list preferred in classloading order
-        List<?> librariesL = MutableList.copyOf(getFirstAs(itemMetadata, List.class, "brooklyn.libraries", "libraries").orNull())
+        List<?> librariesNew = MutableList.copyOf(getFirstAs(itemMetadata, List.class, "brooklyn.libraries", "libraries").orNull());
+        Collection<CatalogBundle> libraryBundlesNew = CatalogItemDtoAbstract.parseLibraries(librariesNew);
+        
+        List<?> librariesCombined = MutableList.copyOf(librariesNew)
             .appendAll(getFirstAs(parentMetadata, List.class, "brooklyn.libraries", "libraries").orNull());
-        if (!librariesL.isEmpty())
-            catalogMetadata.put("brooklyn.libraries", librariesL);
-        Collection<CatalogBundle> libraries = CatalogItemDtoAbstract.parseLibraries(librariesL);
+        if (!librariesCombined.isEmpty())
+            catalogMetadata.put("brooklyn.libraries", librariesCombined);
+        Collection<CatalogBundle> libraryBundles = CatalogItemDtoAbstract.parseLibraries(librariesCombined);
 
+        // TODO as this may take a while if downloading, the REST call should be async
+        CatalogUtils.installLibraries(mgmt, libraryBundlesNew);
+        
         Object items = catalogMetadata.remove("items");
         Object item = catalogMetadata.remove("item");
 
         if (items!=null) {
             int count = 0;
             for (Map<?,?> i: ((List<Map<?,?>>)items)) {
-                addAbstractCatalogItems(Yamls.getTextOfYamlAtPath(sourceYaml, "items", count).getMatchedYamlTextOrWarn(), 
-                    i, result, catalogMetadata, whenAddingAsDtoShouldWeForce);
+                collectCatalogItems(Yamls.getTextOfYamlAtPath(sourceYaml, "items", count).getMatchedYamlTextOrWarn(), 
+                    i, result, catalogMetadata);
                 count++;
             }
         }
@@ -598,7 +616,18 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
         if (itemYaml!=null) sourceYaml = itemYaml;
         else sourceYaml = new Yaml().dump(item);
         
-        CatalogItemType itemType = TypeCoercions.coerce(getFirstAs(catalogMetadata, Object.class, "item.type", "itemType", "item_type").orNull(), CatalogItemType.class);
+        CatalogItemType itemType = TypeCoercions.coerce(getFirstAs(catalogMetadata, Object.class, "itemType", "item_type").orNull(), CatalogItemType.class);
+        BrooklynClassLoadingContext loader = CatalogUtils.newClassLoadingContext(mgmt, "<load>:0", libraryBundles);
+
+        PlanInterpreterGuessingType planInterpreter = new PlanInterpreterGuessingType(null, item, sourceYaml, itemType, loader, result).reconstruct();
+        if (!planInterpreter.isResolved()) {
+            throw new IllegalStateException("Could not resolve plan: "+sourceYaml);
+        }
+        itemType = planInterpreter.getCatalogItemType();
+        Map<?, ?> itemAsMap = planInterpreter.getItem();
+        // the "plan yaml" includes the services: ... or brooklyn.policies: ... outer key,
+        // as opposed to the rawer { type: xxx } map without that outer key which is valid as item input
+        // TODO this plan yaml is needed for subsequent reconstruction; would be nicer if it weren't! 
         
         String id = getFirstAs(catalogMetadata, String.class, "id").orNull();
         String version = getFirstAs(catalogMetadata, String.class, "version").orNull();
@@ -611,164 +640,238 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
                 Strings.isNonBlank(name) && !name.equals(displayName)) {
             log.warn("Name property will be ignored due to the existence of displayName and at least one of id, symbolicName");
         }
-                
-        DeploymentPlan plan = null;
-        try {
-            plan = makePlanFromYaml(sourceYaml);
-        } catch (Exception e) {
-            Exceptions.propagateIfFatal(e);
-            if (itemType==CatalogItemType.ENTITY || itemType==CatalogItemType.TEMPLATE)
-                log.warn("Could not parse item YAML for "+itemType+" (registering anyway): "+e+"\n"+sourceYaml);
-        }
-        
-        if (Strings.isBlank(id)) {
-            // let ID be inferred, especially from name, to support style where only "name" is specified, with inline version
-            if (Strings.isNonBlank(symbolicName) && Strings.isNonBlank(version)) {
-                id = symbolicName + ":" + version;
+
+        // if symname not set, infer from: id, then name, then item id, then item name
+        if (Strings.isBlank(symbolicName)) {
+            if (Strings.isNonBlank(id)) {
+                if (CatalogUtils.looksLikeVersionedId(id)) {
+                    symbolicName = CatalogUtils.getIdFromVersionedId(id);
+                } else {
+                    symbolicName = id;
+                }
             } else if (Strings.isNonBlank(name)) {
-                id = name;
+                if (CatalogUtils.looksLikeVersionedId(name)) {
+                    symbolicName = CatalogUtils.getIdFromVersionedId(name);
+                } else {
+                    symbolicName = name;
+                }
+            } else {
+                symbolicName = setFromItemIfUnset(symbolicName, itemAsMap, "id");
+                symbolicName = setFromItemIfUnset(symbolicName, itemAsMap, "name");
+                if (Strings.isBlank(symbolicName)) {
+                    log.error("Can't infer catalog item symbolicName from the following plan:\n" + sourceYaml);
+                    throw new IllegalStateException("Can't infer catalog item symbolicName from catalog item metadata");
+                }
             }
         }
 
-        final String catalogSymbolicName;
-        if (Strings.isNonBlank(symbolicName)) {
-            catalogSymbolicName = symbolicName;
-        } else if (Strings.isNonBlank(id)) {
-            if (Strings.isNonBlank(id) && CatalogUtils.looksLikeVersionedId(id)) {
-                catalogSymbolicName = CatalogUtils.getIdFromVersionedId(id);
-            } else {
-                catalogSymbolicName = id;
+        // if version not set, infer from: id, then from name, then item version
+        if (CatalogUtils.looksLikeVersionedId(id)) {
+            String versionFromId = CatalogUtils.getVersionFromVersionedId(id);
+            if (versionFromId != null && Strings.isNonBlank(version) && !versionFromId.equals(version)) {
+                throw new IllegalArgumentException("Discrepency between version set in id " + versionFromId + " and version property " + version);
             }
-        } else if (plan!=null && Strings.isNonBlank(plan.getName())) {
-            catalogSymbolicName = plan.getName();
-        } else if (plan!=null && plan.getServices().size()==1) {
-            Service svc = Iterables.getOnlyElement(plan.getServices());
-            if (Strings.isBlank(svc.getServiceType())) {
-                throw new IllegalStateException("CAMP service type not expected to be missing for " + svc);
+            version = versionFromId;
+        }
+        if (Strings.isBlank(version)) {
+            if (CatalogUtils.looksLikeVersionedId(name)) {
+                version = CatalogUtils.getVersionFromVersionedId(name);
+            } else if (Strings.isBlank(version)) {
+                version = setFromItemIfUnset(version, itemAsMap, "version");
+                if (version==null) {
+                    log.warn("No version specified for catalog item " + symbolicName + ". Using default value.");
+                    version = null;
+                }
             }
-            catalogSymbolicName = svc.getServiceType();
-        } else {
-            log.error("Can't infer catalog item symbolicName from the following plan:\n" + sourceYaml);
-            throw new IllegalStateException("Can't infer catalog item symbolicName from catalog item metadata");
         }
-
-        final String catalogVersion;
-        if (CatalogUtils.looksLikeVersionedId(id)) {
-            catalogVersion = CatalogUtils.getVersionFromVersionedId(id);
-            if (version != null  && !catalogVersion.equals(version)) {
-                throw new IllegalArgumentException("Discrepency between version set in id " + catalogVersion + " and version property " + version);
+        
+        // if not set, ID can come from symname:version, failing that, from the plan.id, failing that from the sym name
+        if (Strings.isBlank(id)) {
+            // let ID be inferred, especially from name, to support style where only "name" is specified, with inline version
+            if (Strings.isNonBlank(symbolicName) && Strings.isNonBlank(version)) {
+                id = symbolicName + ":" + version;
+            }
+            id = setFromItemIfUnset(id, itemAsMap, "id");
+            if (Strings.isBlank(id)) {
+                if (Strings.isNonBlank(symbolicName)) {
+                    id = symbolicName;
+                } else {
+                    log.error("Can't infer catalog item id from the following plan:\n" + sourceYaml);
+                    throw new IllegalStateException("Can't infer catalog item id from catalog item metadata");
+                }
             }
-        } else if (Strings.isNonBlank(version)) {
-            catalogVersion = version;
-        } else {
-            log.warn("No version specified for catalog item " + catalogSymbolicName + ". Using default value.");
-            catalogVersion = null;
         }
 
-        final String catalogDisplayName;
-        if (Strings.isNonBlank(displayName)) {
-            catalogDisplayName = displayName;
-        } else if (Strings.isNonBlank(name)) {
-            catalogDisplayName = name;
-        } else if (Strings.isNonBlank(plan.getName())) {
-            catalogDisplayName = plan.getName();
-        } else {
-            catalogDisplayName = null;
+        if (Strings.isBlank(displayName)) {
+            if (Strings.isNonBlank(name)) displayName = name;
+            displayName = setFromItemIfUnset(displayName, itemAsMap, "name");
         }
 
-        final String description = getFirstAs(catalogMetadata, String.class, "description").orNull();
-        final String catalogDescription;
-        if (Strings.isNonBlank(description)) {
-            catalogDescription = description;
-        } else if (Strings.isNonBlank(plan.getDescription())) {
-            catalogDescription = plan.getDescription();
-        } else {
-            catalogDescription = null;
-        }
+        String description = getFirstAs(catalogMetadata, String.class, "description").orNull();
+        description = setFromItemIfUnset(description, itemAsMap, "description");
 
-        final String catalogIconUrl = getFirstAs(catalogMetadata, String.class, "icon.url", "iconUrl", "icon_url").orNull();
+        // icon.url is discouraged, but kept for legacy compatibility; should deprecate this
+        final String catalogIconUrl = getFirstAs(catalogMetadata, String.class, "iconUrl", "icon_url", "icon.url").orNull();
 
         final String deprecated = getFirstAs(catalogMetadata, String.class, "deprecated").orNull();
         final Boolean catalogDeprecated = Boolean.valueOf(deprecated);
 
-        CatalogUtils.installLibraries(mgmt, libraries);
+        // run again now that we know the ID
+        planInterpreter = new PlanInterpreterGuessingType(id, item, sourceYaml, itemType, loader, result).reconstruct();
+        if (!planInterpreter.isResolved()) {
+            throw new IllegalStateException("Could not resolve plan once id and itemType are known (recursive reference?): "+sourceYaml);
+        }
+        String sourcePlanYaml = planInterpreter.getPlanYaml();
+        
+        CatalogItemDtoAbstract<?, ?> dto = createItemBuilder(itemType, symbolicName, version)
+            .libraries(libraryBundles)
+            .displayName(displayName)
+            .description(description)
+            .deprecated(catalogDeprecated)
+            .iconUrl(catalogIconUrl)
+            .plan(sourcePlanYaml)
+            .build();
+
+        dto.setManagementContext((ManagementContextInternal) mgmt);
+        result.add(dto);
+    }
+
+    private String setFromItemIfUnset(String oldValue, Map<?,?> item, String fieldAttr) {
+        if (Strings.isNonBlank(oldValue)) return oldValue;
+        if (item!=null) {
+            Object newValue = item.get(fieldAttr);
+            if (newValue instanceof String && Strings.isNonBlank((String)newValue)) 
+                return (String)newValue;
+        }
+        return oldValue;
+    }
+
+    
+    private class PlanInterpreterGuessingType {
 
-        String versionedId = CatalogUtils.getVersionedId(catalogSymbolicName, catalogVersion!=null ? catalogVersion : NO_VERSION);
-        BrooklynClassLoadingContext loader = CatalogUtils.newClassLoadingContext(mgmt, versionedId, libraries);
+        final String id;
+        final Map<?,?> item;
+        final String itemYaml;
+        final BrooklynClassLoadingContext loader;
+        final List<CatalogItemDtoAbstract<?, ?>> itemsDefinedSoFar;
         
-        CatalogItemType inferredItemType = inferType(plan);
-        boolean usePlan;
-        if (inferredItemType!=null) {
-            if (itemType!=null) {
-                if (itemType==CatalogItemType.TEMPLATE && inferredItemType==CatalogItemType.ENTITY) {
-                    // template - we use the plan, but coupled with the type to make a catalog template item
-                    usePlan = true;
-                } else if (itemType==inferredItemType) {
-                    // if the plan showed the type, it is either a parsed entity or a legacy spec type
-                    usePlan = true;
-                    if (itemType==CatalogItemType.TEMPLATE || itemType==CatalogItemType.ENTITY) {
-                        // normal
-                    } else {
-                        log.warn("Legacy syntax used for "+itemType+" "+catalogSymbolicName+": should declare item.type and specify type");
-                    }
-                } else {
-                    throw new IllegalStateException("Explicit type "+itemType+" "+catalogSymbolicName+" does not match blueprint inferred type "+inferredItemType);
-                }
+        CatalogItemType catalogItemType;
+        String planYaml;
+        @SuppressWarnings("unused")
+        DeploymentPlan plan;
+        AbstractBrooklynObjectSpec<?,?> spec;
+        boolean resolved = false;
+        
+        public PlanInterpreterGuessingType(@Nullable String id, Object item, String itemYaml, @Nullable CatalogItemType optionalCiType, 
+                BrooklynClassLoadingContext loader, List<CatalogItemDtoAbstract<?,?>> itemsDefinedSoFar) {
+            // ID is useful to prevent recursive references (currently for entities only)
+            this.id = id;
+            
+            if (item instanceof String) {
+                // if just a string supplied, wrap as map
+                this.item = MutableMap.of("type", item);
+                this.itemYaml = "type:\n"+makeAsIndentedObject(itemYaml);                
             } else {
-                // no type was declared, use the inferred type from the plan
-                itemType = inferredItemType;
-                usePlan = true;
+                this.item = (Map<?,?>)item;
+                this.itemYaml = itemYaml;
             }
-        } else if (itemType!=null) {
-            if (itemType==CatalogItemType.TEMPLATE || itemType==CatalogItemType.ENTITY) {
-                log.warn("Incomplete plan detected for "+itemType+" "+catalogSymbolicName+"; will likely fail subsequently");
-                usePlan = true;
-            } else {
-                usePlan = false;
+            this.catalogItemType = optionalCiType;
+            this.loader = loader;
+            this.itemsDefinedSoFar = itemsDefinedSoFar;
+        }
+
+        public PlanInterpreterGuessingType reconstruct() {
+            attemptType(null, CatalogItemType.ENTITY);
+            attemptType(null, CatalogItemType.TEMPLATE);
+            
+            attemptType("services", CatalogItemType.ENTITY);
+            attemptType(POLICIES_KEY, CatalogItemType.POLICY);
+            attemptType(LOCATIONS_KEY, CatalogItemType.LOCATION);
+            
+            if (!resolved && catalogItemType==CatalogItemType.TEMPLATE) {
+                // anything goes, for an explicit template, because we can't easily recurse into the types
+                planYaml = itemYaml;
+                resolved = true;
             }
-        } else {
-            throw new IllegalStateException("Unable to detect type for "+catalogSymbolicName+"; error in catalog metadata or blueprint");
+            
+            return this;
         }
         
-        AbstractBrooklynObjectSpec<?, ?> spec;
-        if (usePlan) {
-            spec = createSpecFromPlan(catalogSymbolicName, plan, loader);
-        } else {
-            String key;
-            switch (itemType) {
-            // we don't actually need the spec here, since the yaml is what is used at load time,
-            // but it isn't a bad idea to confirm that it resolves
-            case POLICY: 
-                spec = createPolicySpec(loader, item);
-                key = POLICIES_KEY;
-                break;
-            case LOCATION: 
-                spec = createLocationSpec(loader, item); 
-                key = LOCATIONS_KEY;
-                break;
-            default: throw new IllegalStateException("Cannot create "+itemType+" "+catalogSymbolicName+"; invalid metadata or blueprint");
+        public boolean isResolved() { return resolved; }
+        
+        public CatalogItemType getCatalogItemType() {
+            return catalogItemType; 
+        }
+        
+        public String getPlanYaml() {
+            return planYaml;
+        }
+        
+        private boolean attemptType(String key, CatalogItemType candidateCiType) {
+            if (resolved) return false;
+            if (catalogItemType!=null && catalogItemType!=candidateCiType) return false;
+            
+            final String candidateYaml;
+            if (key==null) candidateYaml = itemYaml;
+            else {
+                if (item.containsKey(key))
+                    candidateYaml = itemYaml;
+                else
+                    candidateYaml = key + ":\n" + makeAsIndentedList(itemYaml);
+            }
+            // first look in collected items, if a key is given
+            String type = (String) item.get("type");
+            if (type!=null && key!=null) {
+                for (CatalogItemDtoAbstract<?,?> candidate: itemsDefinedSoFar) {
+                    if (type.equals(candidate.getSymbolicName()) || type.equals(candidate.getId())) {
+                        // matched - exit
+                        catalogItemType = candidateCiType;
+                        planYaml = candidateYaml;
+                        resolved = true;
+                        return true;
+                    }
+                }
+            }
+            
+            // then try parsing plan - this will use loader
+            try {
+                DeploymentPlan candidatePlan = makePlanFromYaml(candidateYaml);
+                spec = createSpec(id, candidateCiType, candidatePlan, loader);
+                if (spec!=null) {
+                    catalogItemType = candidateCiType;
+                    plan = candidatePlan;
+                    planYaml = candidateYaml;
+                    resolved = true;
+                }
+                return true;
+            } catch (Exception e) {
+                Exceptions.propagateIfFatal(e);
             }
-            // TODO currently we then convert yaml to legacy brooklyn.{location,policy} syntax for subsequent usage; 
-            // would be better to (in the other path) convert to {type: ..., brooklyn.config: ... } format and expect that elsewhere
-            sourceYaml = key + ":\n" + makeAsIndentedList(sourceYaml);
+            
+            // finally try parsing a cut-down plan, in case there is a nested reference to a newly defined catalog item
+            if (type!=null && key!=null) {
+                try {
+                    String cutDownYaml = key + ":\n" + makeAsIndentedList("type: "+type);
+                    DeploymentPlan candidatePlan = makePlanFromYaml(cutDownYaml);
+                    Object cutdownSpec = createSpec(id, candidateCiType, candidatePlan, loader);
+                    if (cutdownSpec!=null) {
+                        catalogItemType = candidateCiType;
+                        planYaml = candidateYaml;
+                        resolved = true;
+                    }
+                    return true;
+                } catch (Exception e) {
+                    Exceptions.propagateIfFatal(e);
+                }
+            }
+            
+            return false;
         }
-
-        CatalogItemDtoAbstract<?, ?> dto = createItemBuilder(itemType, spec, catalogSymbolicName, catalogVersion)
-            .libraries(libraries)
-            .displayName(catalogDisplayName)
-            .description(catalogDescription)
-            .deprecated(catalogDeprecated)
-            .iconUrl(catalogIconUrl)
-            .plan(sourceYaml)
-            .build();
-
-        dto.setManagementContext((ManagementContextInternal) mgmt);
-        if (whenAddingAsDtoShouldWeForce!=null) {
-            addItemDto(dto, whenAddingAsDtoShouldWeForce);
+        public Map<?,?> getItem() {
+            return item;
         }
-        result.add(dto);
     }
-
+    
     private String makeAsIndentedList(String yaml) {
         String[] lines = yaml.split("\n");
         lines[0] = "- "+lines[0];
@@ -784,74 +887,41 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
         return Strings.join(lines, "\n");
     }
 
-    private CatalogItemType inferType(DeploymentPlan plan) {
-        if (plan==null) return null;
-        if (isEntityPlan(plan)) return CatalogItemType.ENTITY; 
-        if (isPolicyPlan(plan)) return CatalogItemType.POLICY;
-        if (isLocationPlan(plan)) return CatalogItemType.LOCATION;
-        return null;
-    }
-
-    private AbstractBrooklynObjectSpec<?,?> createSpecFromPlan(String symbolicName, DeploymentPlan plan, BrooklynClassLoadingContext loader) {
-        if (isPolicyPlan(plan)) {
-            return createPolicySpec(plan, loader);
-        } else if (isLocationPlan(plan)) {
-            return createLocationSpec(plan, loader);
-        } else {
-            return createEntitySpec(symbolicName, plan, loader);
-        }
-    }
-
-    private CatalogItemBuilder<?> createItemBuilder(CatalogItemType itemType, AbstractBrooklynObjectSpec<?, ?> spec, String itemId, String version) {
-        if (itemType!=null) {
-            switch (itemType) {
-            case ENTITY: return CatalogItemBuilder.newEntity(itemId, version);
-            case TEMPLATE: return CatalogItemBuilder.newTemplate(itemId, version);
-            case POLICY: return CatalogItemBuilder.newPolicy(itemId, version);
-            case LOCATION: return CatalogItemBuilder.newLocation(itemId, version);
-            }
-            log.warn("Legacy syntax for "+itemId+"; unexpected item.type "+itemType);
-        } else {
-            log.warn("Legacy syntax for "+itemId+"; no item.type declared or inferred");
+    private CatalogItemBuilder<?> createItemBuilder(CatalogItemType itemType, String itemId, String version) {
+        Preconditions.checkNotNull(itemType, "itemType required");
+        switch (itemType) {
+        case ENTITY: return CatalogItemBuilder.newEntity(itemId, version);
+        case TEMPLATE: return CatalogItemBuilder.newTemplate(itemId, version);
+        case POLICY: return CatalogItemBuilder.newPolicy(itemId, version);
+        case LOCATION: return CatalogItemBuilder.newLocation(itemId, version);
         }
-        
-        // @deprecated - should not come here
-        if (spec instanceof EntitySpec) {
-            if (isApplicationSpec((EntitySpec<?>)spec)) {
-                return CatalogItemBuilder.newTemplate(itemId, version);
-            } else {
-                return CatalogItemBuilder.newEntity(itemId, version);
-            }
-        } else if (spec instanceof PolicySpec) {
-            return CatalogItemBuilder.newPolicy(itemId, version);
-        } else if (spec instanceof LocationSpec) {
-            return CatalogItemBuilder.newLocation(itemId, version);
-        } else {
-            throw new IllegalStateException("Unknown spec type " + spec.getClass().getName() + " (" + spec + ")");
-        }
-    }
-
-    private boolean isApplicationSpec(EntitySpec<?> spec) {
-        return !Boolean.TRUE.equals(spec.getConfig().get(EntityManagementUtils.WRAPPER_APP_MARKER));
+        throw new IllegalStateException("Unexpected itemType: "+itemType);
     }
 
-    private boolean isEntityPlan(DeploymentPlan plan) {
-        return plan!=null && !plan.getServices().isEmpty() || !plan.getArtifacts().isEmpty();
-    }
-    
-    private boolean isPolicyPlan(DeploymentPlan plan) {
-        return !isEntityPlan(plan) && plan.getCustomAttributes().containsKey(POLICIES_KEY);
-    }
-
-    private boolean isLocationPlan(DeploymentPlan plan) {
-        return !isEntityPlan(plan) && plan.getCustomAttributes().containsKey(LOCATIONS_KEY);
-    }
+    // these kept as their logic may prove useful; Apr 2015
+//    private boolean isApplicationSpec(EntitySpec<?> spec) {
+//        return !Boolean.TRUE.equals(spec.getConfig().get(EntityManagementUtils.WRAPPER_APP_MARKER));
+//    }
+//
+//    private boolean isEntityPlan(DeploymentPlan plan) {
+//        return plan!=null && !plan.getServices().isEmpty() || !plan.getArtifacts().isEmpty();
+//    }
+//    
+//    private boolean isPolicyPlan(DeploymentPlan plan) {
+//        return !isEntityPlan(plan) && plan.getCustomAttributes().containsKey(POLICIES_KEY);
+//    }
+//
+//    private boolean isLocationPlan(DeploymentPlan plan) {
+//        return !isEntityPlan(plan) && plan.getCustomAttributes().containsKey(LOCATIONS_KEY);
+//    }
 
     private DeploymentPlan makePlanFromYaml(String yaml) {
         CampPlatform camp = BrooklynServerConfig.getCampPlatform(mgmt).get();
         return camp.pdp().parseDeploymentPlan(Streams.newReaderWithContents(yaml));
     }
 
+    //------------------------
+    
     @Override
     public CatalogItem<?,?> addItem(String yaml) {
         return addItem(yaml, false);
@@ -871,11 +941,11 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
     public List<? extends CatalogItem<?,?>> addItems(String yaml, boolean forceUpdate) {
         log.debug("Adding manual catalog item to "+mgmt+": "+yaml);
         checkNotNull(yaml, "yaml");
-        List<CatalogItemDtoAbstract<?, ?>> result = addAbstractCatalogItems(yaml, forceUpdate);
-        // previously we did this here, but now we do it on each item, in case #2 refers to #1
-//        for (CatalogItemDtoAbstract<?, ?> item: result) {
-//            addItemDto(item, forceUpdate);
-//        }
+        List<CatalogItemDtoAbstract<?, ?>> result = collectCatalogItems(yaml);
+        // do this at the end for atomic updates; if there are intra-yaml references, we handle them specially
+        for (CatalogItemDtoAbstract<?, ?> item: result) {
+            addItemDto(item, forceUpdate);
+        }
         return result;
     }
     

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f75f40b/core/src/main/java/brooklyn/location/geo/UtraceHostGeoLookup.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/location/geo/UtraceHostGeoLookup.java b/core/src/main/java/brooklyn/location/geo/UtraceHostGeoLookup.java
index 37ed9f7..10b21df 100644
--- a/core/src/main/java/brooklyn/location/geo/UtraceHostGeoLookup.java
+++ b/core/src/main/java/brooklyn/location/geo/UtraceHostGeoLookup.java
@@ -34,6 +34,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.javalang.JavaClassNames;
 import brooklyn.util.net.Networking;
 import brooklyn.util.time.Duration;
 import brooklyn.util.time.Durations;
@@ -129,7 +130,8 @@ Beyond this you get blacklisted and requests may time out, or return none.
                 try {
                     result.set(retrieveHostGeoInfo(address));
                 } catch (Exception e) {
-                    throw Exceptions.propagate(e);
+                    log.warn("Error computing geo info for "+address+"; internet issues or too many requests to (free) servers for "+JavaClassNames.simpleClassName(UtraceHostGeoLookup.this)+": "+e);
+                    log.debug("Detail of host geo error: "+e, e);
                 }
             }
         };

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f75f40b/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java b/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java
index bbd589d..1748de8 100644
--- a/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java
+++ b/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java
@@ -40,6 +40,7 @@ import brooklyn.config.ConfigMap;
 import brooklyn.enricher.basic.AbstractEnricher;
 import brooklyn.entity.Entity;
 import brooklyn.entity.Group;
+import brooklyn.entity.basic.ConfigKeys;
 import brooklyn.entity.basic.Entities;
 import brooklyn.entity.basic.EntityInternal;
 import brooklyn.entity.basic.EntityLocal;
@@ -58,6 +59,7 @@ import brooklyn.util.flags.FlagUtils;
 import brooklyn.util.flags.SetFromFlag;
 import brooklyn.util.flags.TypeCoercions;
 import brooklyn.util.guava.Maybe;
+import brooklyn.util.text.Strings;
 
 import com.google.common.annotations.Beta;
 import com.google.common.base.Objects;
@@ -168,6 +170,17 @@ public abstract class AbstractEntityAdjunct extends AbstractBrooklynObject imple
             Preconditions.checkArgument(flags.get("displayName") instanceof CharSequence, "'displayName' property should be a string");
             setDisplayName(flags.remove("displayName").toString());
         }
+        
+        // set leftover flags should as config items; particularly useful when these have come from a brooklyn.config map 
+        for (Object flag: flags.keySet()) {
+            ConfigKey<Object> key = ConfigKeys.newConfigKey(Object.class, Strings.toString(flag));
+            if (config().getRaw(key).isPresent()) {
+                log.warn("Config '"+flag+"' on "+this+" conflicts with key already set; ignoring");
+            } else {
+                config().set(key, flags.get(flag));
+            }
+        }
+        
         return this;
     }
     

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f75f40b/docs/guide/ops/catalog/index.md
----------------------------------------------------------------------
diff --git a/docs/guide/ops/catalog/index.md b/docs/guide/ops/catalog/index.md
index 401979c..a2e4408 100644
--- a/docs/guide/ops/catalog/index.md
+++ b/docs/guide/ops/catalog/index.md
@@ -77,8 +77,9 @@ services:
 
 In addition to the above fields, exactly **one** of the following is also required:
 
-- `item`: the blueprint (in the usual YAML format) for an entity or application template,
-  or a map containing `type` and optional `brooklyn.config` for policies and locations; **or**
+- `item`: the YAML for a service or policy or location specification 
+  (a map containing `type` and optional `brooklyn.config`)
+  or a full application blueprint (in the usual YAML format) for a template; **or*
 - `items`: a list of catalog items, where each entry in the map follows the same schema as
   the `brooklyn.catalog` value, and the keys in these map override any metadata specified as
   a sibling of this `items` key (or, in the case of `libraries` they add to the list);
@@ -87,17 +88,17 @@ In addition to the above fields, exactly **one** of the following is also requir
 
 The following optional catalog metadata is supported:
   
-- `item.type`: the type of the item being defined.
-  If omitted, all `item` definitions are taken to be entities;
-  for any type other than an entity, this field must be supplied.
-  The supported types are:
+- `itemType`: the type of the item being defined.
+  When adding a template (see below) this must be set.
+  In most other cases this can be omitted and type type will be inferred.
+  The supported item types are:
   - `entity`
   - `template`
   - `policy`
   - `location`
 - `name`: a nicely formatted display name for the item, used when presenting it in a GUI
 - `description`: supplies an extended textual description for the item
-- `icon.url`: points to an icon for the item, used when presenting it in a GUI.
+- `iconUrl`: points to an icon for the item, used when presenting it in a GUI.
   The URL prefix `classpath` is supported but these URLs may *not* refer items in any OSGi bundle in the `libraries` section 
   (to prevent requiring all OSGi bundles to be loaded at launch).
   Icons are instead typically installed either at the server from which the OSGi bundles or catalog items are supplied 
@@ -131,14 +132,13 @@ and its implementation will be the Java class `brooklyn.entity.nosql.riak.RiakNo
 brooklyn.catalog:
   id: datastore
   version: 1.0
-  item.type: template
-  icon.url: classpath://brooklyn/entity/nosql/riak/riak.png
+  itemType: template
+  iconUrl: classpath://brooklyn/entity/nosql/riak/riak.png
   name: Datastore (Riak)
   description: Riak is an open-source NoSQL key-value data store.
   item:
-    services:
-    - type: brooklyn.entity.nosql.riak.RiakNode
-      name: Riak Node
+    type: brooklyn.entity.nosql.riak.RiakNode
+    name: Riak Node
 ```
 
 
@@ -149,22 +149,20 @@ This YAML will install three items:
 ```yaml
 brooklyn.catalog:
   version: 1.1
-  icon.url: classpath://brooklyn/entity/nosql/riak/riak.png
+  iconUrl: classpath://brooklyn/entity/nosql/riak/riak.png
   description: Riak is an open-source NoSQL key-value data store.
   items:
     - id: riak-node
       item:
-        services:
-        - type: brooklyn.entity.nosql.riak.RiakNode
-          name: Riak Node
+        type: brooklyn.entity.nosql.riak.RiakNode
+        name: Riak Node
     - id: riak-cluster
       item:
-        services:
-        - type: brooklyn.entity.nosql.riak.RiakCluster
-          name: Riak Cluster
+        type: brooklyn.entity.nosql.riak.RiakCluster
+        name: Riak Cluster
     - id: datastore
       name: Datastore (Riak Cluster)
-      item.type: template
+      itemType: template
       item:
         services:
         - type: riak-cluster

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f75f40b/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/creation/BrooklynYamlTypeInstantiator.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/creation/BrooklynYamlTypeInstantiator.java b/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/creation/BrooklynYamlTypeInstantiator.java
index 2cde40e..c5f3dcb 100644
--- a/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/creation/BrooklynYamlTypeInstantiator.java
+++ b/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/creation/BrooklynYamlTypeInstantiator.java
@@ -197,10 +197,9 @@ public abstract class BrooklynYamlTypeInstantiator {
     public <T> Class<? extends T> getType(@Nonnull Class<T> type) {
         try {
             return getClassLoadingContext().loadClass(getTypeName().get(), type);
-//            return loadClass(type, getTypeName().get(), factory.mgmt, factory.contextForLogging);
         } catch (Exception e) {
             Exceptions.propagateIfFatal(e);
-            log.warn("Unable to resolve " + type + " " + getTypeName().get() + " (rethrowing) in spec " + factory.contextForLogging);
+            log.debug("Unable to resolve " + type + " " + getTypeName().get() + " (rethrowing) in spec " + factory.contextForLogging);
             throw Exceptions.propagate(e);
         }
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f75f40b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/AbstractYamlTest.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/AbstractYamlTest.java b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/AbstractYamlTest.java
index b1e6817..41489ba 100644
--- a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/AbstractYamlTest.java
+++ b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/AbstractYamlTest.java
@@ -104,7 +104,7 @@ public abstract class AbstractYamlTest {
     }
     
     protected Entity createAndStartApplication(String... multiLineYaml) throws Exception {
-        return createAndStartApplication(join(multiLineYaml));
+        return createAndStartApplication(joinLines(multiLineYaml));
     }
     
     protected Entity createAndStartApplication(String input) throws Exception {
@@ -145,11 +145,11 @@ public abstract class AbstractYamlTest {
     }
 
     protected void addCatalogItem(Iterable<String> catalogYaml) {
-        addCatalogItem(join(catalogYaml));
+        addCatalogItem(joinLines(catalogYaml));
     }
 
     protected void addCatalogItem(String... catalogYaml) {
-        addCatalogItem(join(catalogYaml));
+        addCatalogItem(joinLines(catalogYaml));
     }
 
     protected void addCatalogItem(String catalogYaml) {
@@ -164,11 +164,11 @@ public abstract class AbstractYamlTest {
         return LOG;
     }
 
-    private String join(Iterable<String> catalogYaml) {
+    private String joinLines(Iterable<String> catalogYaml) {
         return Joiner.on("\n").join(catalogYaml);
     }
 
-    private String join(String[] catalogYaml) {
+    private String joinLines(String[] catalogYaml) {
         return Joiner.on("\n").join(catalogYaml);
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f75f40b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlCombiTest.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlCombiTest.java b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlCombiTest.java
new file mode 100644
index 0000000..cbb1d1e
--- /dev/null
+++ b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlCombiTest.java
@@ -0,0 +1,145 @@
+/*
+ * 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 io.brooklyn.camp.brooklyn.catalog;
+
+import io.brooklyn.camp.brooklyn.AbstractYamlTest;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import brooklyn.catalog.CatalogItem;
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.BasicEntity;
+import brooklyn.entity.basic.BasicStartable;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.basic.Entities;
+import brooklyn.policy.Policy;
+import brooklyn.policy.ha.ServiceRestarter;
+import brooklyn.util.exceptions.Exceptions;
+
+import com.google.common.collect.Iterables;
+
+
+public class CatalogYamlCombiTest extends AbstractYamlTest {
+
+    private static final Logger log = LoggerFactory.getLogger(CatalogYamlCombiTest.class);
+    
+    @Test
+    public void testBRefEntityA() throws Exception {
+        addCatalogItem(
+            "brooklyn.catalog:",
+            "  version: "+TEST_VERSION,
+            "  items:",
+            "  - item:",
+            "      id: A",
+            "      type: "+BasicEntity.class.getName(),
+            "      brooklyn.config: { a: 1, b: 0 }",
+            "  - item:",
+            "      id: B",
+            "      type: A",
+            "      brooklyn.config: { b: 1 }");
+
+        CatalogItem<?, ?> item = mgmt().getCatalog().getCatalogItem("B", TEST_VERSION);
+        Assert.assertNotNull(item);
+
+        Entity a = launchEntity("A");
+        Assert.assertTrue(BasicEntity.class.isInstance(a), "Wrong type: "+a);
+        Assert.assertEquals(a.config().get(ConfigKeys.newIntegerConfigKey("a")), (Integer)1);
+        Assert.assertEquals(a.config().get(ConfigKeys.newIntegerConfigKey("b")), (Integer)0);
+
+        Entity b = launchEntity("B");
+        Assert.assertTrue(BasicEntity.class.isInstance(b), "Wrong type: "+b);
+        Assert.assertEquals(b.config().get(ConfigKeys.newIntegerConfigKey("a")), (Integer)1);
+        Assert.assertEquals(b.config().get(ConfigKeys.newIntegerConfigKey("b")), (Integer)1);
+
+        deleteCatalogEntity("A");
+        
+        // now loading B makes an error
+        try {
+            launchEntity("B");
+            Assert.fail("B should not be launchable");
+        } catch (Exception e) {
+            Exceptions.propagateIfFatal(e);
+            log.info("Got expected error: "+e);
+        }
+        
+        deleteCatalogEntity("B");
+    }
+
+    @Test
+    public void testBRefPolicyALocationZ() throws Exception {
+        addCatalogItem(
+            "brooklyn.catalog:",
+            "  version: "+TEST_VERSION,
+            "  id: Z",
+            "  items:",
+            "  - item: ",
+            "      type: localhost",
+            "      brooklyn.config: { z: 9 }");
+        addCatalogItem(
+            "brooklyn.catalog:",
+            "  version: "+TEST_VERSION,
+            "  items:",
+            "  - item_type: policy", 
+            "    item:",
+            "      id: A",
+            "      type: "+ServiceRestarter.class.getName(),
+            "      brooklyn.config: { a: 99 }",
+            "  - item:",
+            "      id: B",
+            "      type: "+BasicStartable.class.getName(),
+            "      location: Z",
+            "      brooklyn.policies:",
+            "      - type: A");
+
+        CatalogItem<?, ?> item = mgmt().getCatalog().getCatalogItem("A", TEST_VERSION);
+        Assert.assertNotNull(item);
+
+        Entity b = launchEntity("B", false);
+        Assert.assertTrue(BasicStartable.class.isInstance(b), "Wrong type: "+b);
+        Entities.dumpInfo(b);
+        
+        Assert.assertEquals(Iterables.getOnlyElement(b.getLocations()).getConfig(ConfigKeys.newIntegerConfigKey("z")), (Integer)9);
+        
+        Policy p = Iterables.getOnlyElement(b.getPolicies());
+        Assert.assertTrue(ServiceRestarter.class.isInstance(p), "Wrong type: "+p);
+        Assert.assertEquals(p.getConfig(ConfigKeys.newIntegerConfigKey("a")), (Integer)99);
+        
+        deleteCatalogEntity("A");
+        deleteCatalogEntity("B");
+        deleteCatalogEntity("Z");
+    }
+
+    private Entity launchEntity(String symbolicName) throws Exception {
+        return launchEntity(symbolicName, true);
+    }
+    
+    private Entity launchEntity(String symbolicName, boolean includeLocation) throws Exception {
+        String yaml = "name: simple-app-yaml\n" +
+                      (includeLocation ? "location: localhost\n" : "") +
+                      "services: \n" +
+                      "  - type: "+ver(symbolicName);
+        Entity app = createAndStartApplication(yaml);
+        return Iterables.getOnlyElement(app.getChildren());
+    }
+
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f75f40b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java
index 1737718..e5eae35 100644
--- a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java
+++ b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java
@@ -49,6 +49,21 @@ public class CatalogYamlEntityTest extends AbstractYamlTest {
     private static final String SIMPLE_ENTITY_TYPE = OsgiTestResources.BROOKLYN_TEST_OSGI_ENTITIES_SIMPLE_ENTITY;
 
     @Test
+    public void testAddCatalogItemVerySimple() throws Exception {
+        String symbolicName = "my.catalog.app.id.load";
+        addCatalogItem(
+            "brooklyn.catalog:",
+            "  id: " + symbolicName,
+            "  version: " + TEST_VERSION,
+            "  item:",
+            "    type: "+ BasicEntity.class.getName());
+
+        CatalogItem<?, ?> item = mgmt().getCatalog().getCatalogItem(symbolicName, TEST_VERSION);
+        assertTrue(item.getPlanYaml().indexOf("services:")>=0, "expected 'services:' block: "+item+"\n"+item.getPlanYaml());
+
+        deleteCatalogEntity(symbolicName);
+    }
+    @Test
     public void testAddCatalogItem() throws Exception {
         TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH);
 
@@ -61,7 +76,52 @@ public class CatalogYamlEntityTest extends AbstractYamlTest {
     }
 
     @Test
-    public void testAddCatalogItemAsSiblingOfCatalogMetadata() throws Exception {
+    public void testAddCatalogItemTypeAsString() throws Exception {
+        TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH);
+
+        String symbolicName = "my.catalog.app.id.load";
+        addCatalogItem(
+            "brooklyn.catalog:",
+            "  id: " + symbolicName,
+            "  name: My Catalog App",
+            "  description: My description",
+            "  icon_url: classpath://path/to/myicon.jpg",
+            "  version: " + TEST_VERSION,
+            "  libraries:",
+            "  - url: " + OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL,
+            "  item: " + SIMPLE_ENTITY_TYPE);
+
+        CatalogItem<?, ?> item = mgmt().getCatalog().getCatalogItem(symbolicName, TEST_VERSION);
+        assertEquals(item.getSymbolicName(), symbolicName);
+
+        deleteCatalogEntity(symbolicName);
+    }
+
+    @Test
+    public void testAddCatalogItemTypeExplicitTypeAsString() throws Exception {
+        TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH);
+
+        String symbolicName = "my.catalog.app.id.load";
+        addCatalogItem(
+            "brooklyn.catalog:",
+            "  id: " + symbolicName,
+            "  name: My Catalog App",
+            "  description: My description",
+            "  icon_url: classpath://path/to/myicon.jpg",
+            "  version: " + TEST_VERSION,
+            "  item_type: entity",
+            "  libraries:",
+            "  - url: " + OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL,
+            "  item: " + SIMPLE_ENTITY_TYPE);
+
+        CatalogItem<?, ?> item = mgmt().getCatalog().getCatalogItem(symbolicName, TEST_VERSION);
+        assertEquals(item.getSymbolicName(), symbolicName);
+
+        deleteCatalogEntity(symbolicName);
+    }
+
+    @Test
+    public void testAddCatalogItemTopLevelSyntax() throws Exception {
         TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH);
 
         String symbolicName = "my.catalog.app.id.load";
@@ -94,8 +154,8 @@ public class CatalogYamlEntityTest extends AbstractYamlTest {
             "  name: " + id,
             "  libraries:",
             "  - " + OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL,
-            "services:",
-            "- type: " + SIMPLE_ENTITY_TYPE);
+            "  item:",
+            "    type: "+ SIMPLE_ENTITY_TYPE);
         CatalogItem<?, ?> catalogItem = mgmt().getCatalog().getCatalogItem(id, BrooklynCatalog.DEFAULT_VERSION);
         assertEquals(catalogItem.getVersion(), "0.0.0.SNAPSHOT");
         mgmt().getCatalog().deleteCatalogItem(id, "0.0.0.SNAPSHOT");
@@ -151,6 +211,9 @@ public class CatalogYamlEntityTest extends AbstractYamlTest {
         String referrerSymbolicName = "my.catalog.app.id.referring";
         addCatalogOSGiEntities(referencedSymbolicName, SIMPLE_ENTITY_TYPE, referrerSymbolicName, ver(referencedSymbolicName));
 
+        CatalogItem<?, ?> referrer = mgmt().getCatalog().getCatalogItem(referrerSymbolicName, TEST_VERSION);
+        Assert.assertTrue(referrer.getPlanYaml().indexOf("services")>=0, "expected services in: "+referrer.getPlanYaml());
+        
         String yaml = "name: simple-app-yaml\n" +
                       "location: localhost\n" +
                       "services: \n" +
@@ -199,7 +262,7 @@ public class CatalogYamlEntityTest extends AbstractYamlTest {
             "name: simple-app-yaml",
             "location: localhost",
             "services:",
-            "- serviceType: "+BasicEntity.class.getName(),
+            "- type: "+BasicEntity.class.getName(),
             "  brooklyn.children:",
             "  - type: " + ver(referrerSymbolicName));
 
@@ -221,6 +284,40 @@ public class CatalogYamlEntityTest extends AbstractYamlTest {
     }
 
     @Test
+    public void testLaunchApplicationChildWithCatalogReferencingOtherCatalogServicesBlock() throws Exception {
+        TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH);
+
+        String referencedSymbolicName = "my.catalog.app.id.child.referenced";
+        String referrerSymbolicName = "my.catalog.app.id.child.referring";
+        addCatalogOSGiEntity(referencedSymbolicName, SIMPLE_ENTITY_TYPE);
+        addCatalogChildOSGiEntityWithServicesBlock(referrerSymbolicName, ver(referencedSymbolicName));
+
+        Entity app = createAndStartApplication(
+            "name: simple-app-yaml",
+            "location: localhost",
+            "services:",
+            "- serviceType: "+BasicEntity.class.getName(),
+            "  brooklyn.children:",
+            "  - type: " + ver(referrerSymbolicName));
+
+        Collection<Entity> children = app.getChildren();
+        assertEquals(children.size(), 1);
+        Entity child = Iterables.getOnlyElement(children);
+        assertEquals(child.getEntityType().getName(), BasicEntity.class.getName());
+        Collection<Entity> grandChildren = child.getChildren();
+        assertEquals(grandChildren.size(), 1);
+        Entity grandChild = Iterables.getOnlyElement(grandChildren);
+        assertEquals(grandChild.getEntityType().getName(), BasicEntity.class.getName());
+        Collection<Entity> grandGrandChildren = grandChild.getChildren();
+        assertEquals(grandGrandChildren.size(), 1);
+        Entity grandGrandChild = Iterables.getOnlyElement(grandGrandChildren);
+        assertEquals(grandGrandChild.getEntityType().getName(), SIMPLE_ENTITY_TYPE);
+
+        deleteCatalogEntity(referencedSymbolicName);
+        deleteCatalogEntity(referrerSymbolicName);
+    }
+    
+    @Test
     public void testLaunchApplicationWithTypeUsingJavaColonPrefix() throws Exception {
         TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH);
 
@@ -244,10 +341,11 @@ public class CatalogYamlEntityTest extends AbstractYamlTest {
 
         String referrerSymbolicName = "my.catalog.app.id.child.referring";
         try {
-            addCatalogChildOSGiEntity(referrerSymbolicName, ver(referrerSymbolicName));
-            fail("Expected to throw IllegalStateException");
+            // TODO only fails if using 'services', because that forces plan parsing; should fail in all cases
+            addCatalogChildOSGiEntityWithServicesBlock(referrerSymbolicName, ver(referrerSymbolicName));
+            fail("Expected to throw");
         } catch (IllegalStateException e) {
-            assertTrue(e.getMessage().contains("Could not find "+referrerSymbolicName));
+            assertTrue(e.getMessage().contains(referrerSymbolicName), "message was: "+e);
         }
     }
 
@@ -454,7 +552,7 @@ public class CatalogYamlEntityTest extends AbstractYamlTest {
                     "- type: " + symbolicName);
             fail("Catalog addition expected to fail due to non-existent java type " + symbolicName);
         } catch (IllegalStateException e) {
-            assertEquals(e.getMessage(), "Recursive reference to " + symbolicName + " (and cannot be resolved as a Java type)");
+            assertTrue(e.toString().contains("recursive"), "Unexpected error message: "+e);
         }
     }
     
@@ -480,7 +578,7 @@ public class CatalogYamlEntityTest extends AbstractYamlTest {
                 "- type: " + versionedId);
             fail("Catalog addition expected to fail due to non-existent java type " + versionedId);
         } catch (IllegalStateException e) {
-            assertEquals(e.getMessage(), "Recursive reference to " + versionedId + " (and cannot be resolved as a Java type)");
+            assertTrue(e.toString().contains("recursive"), "Unexpected error message: "+e);
         }
     }
 
@@ -497,7 +595,7 @@ public class CatalogYamlEntityTest extends AbstractYamlTest {
                     "- type: " + SIMPLE_ENTITY_TYPE);
             fail("Catalog addition expected to fail due to non-existent java type " + SIMPLE_ENTITY_TYPE);
         } catch (IllegalStateException e) {
-            assertEquals(e.getMessage(), "Recursive reference to " + SIMPLE_ENTITY_TYPE + " (and cannot be resolved as a Java type)");
+            assertTrue(e.toString().contains("recursive"), "Unexpected error message: "+e);
         }
     }
     
@@ -530,8 +628,7 @@ public class CatalogYamlEntityTest extends AbstractYamlTest {
             "  libraries:",
             "  - url: " + OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL,
             "  item:",
-            "    services:",
-            "    - type: " + serviceType);
+            "    type: " + serviceType);
     }
 
     private void addCatalogOSGiEntities(String ...namesAndTypes) {
@@ -549,13 +646,12 @@ public class CatalogYamlEntityTest extends AbstractYamlTest {
             lines.addAll(MutableList.of(
             "  - id: " + namesAndTypes[i],
             "    item:",
-            "      services:",
-            "      - type: " + namesAndTypes[i+1]));
+            "      type: " + namesAndTypes[i+1]));
         }
             
         addCatalogItem(lines);
     }
-    private void addCatalogChildOSGiEntity(String symbolicName, String serviceType) {
+    private void addCatalogChildOSGiEntityWithServicesBlock(String symbolicName, String serviceType) {
         addCatalogItem(
             "brooklyn.catalog:",
             "  id: " + symbolicName,
@@ -571,5 +667,20 @@ public class CatalogYamlEntityTest extends AbstractYamlTest {
             "      brooklyn.children:",
             "      - type: " + serviceType);
     }
+    private void addCatalogChildOSGiEntity(String symbolicName, String serviceType) {
+        addCatalogItem(
+            "brooklyn.catalog:",
+            "  id: " + symbolicName,
+            "  name: My Catalog App",
+            "  description: My description",
+            "  icon_url: classpath://path/to/myicon.jpg",
+            "  version: " + TEST_VERSION,
+            "  libraries:",
+            "  - url: " + OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL,
+            "  item:",
+            "    type: " + BasicEntity.class.getName(),
+            "    brooklyn.children:",
+            "    - type: " + serviceType);
+    }
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f75f40b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlPolicyTest.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlPolicyTest.java b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlPolicyTest.java
index b7370be..d38fee0 100644
--- a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlPolicyTest.java
+++ b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlPolicyTest.java
@@ -139,7 +139,7 @@ public class CatalogYamlPolicyTest extends AbstractYamlTest {
         String yaml = "name: simple-app-yaml\n" +
                       "location: localhost\n" +
                       "services: \n" +
-                      "  - serviceType: "+ ver(referrerSymbolicName);
+                      "- type: "+ ver(referrerSymbolicName);
 
         Entity app = createAndStartApplication(yaml);
 
@@ -150,7 +150,7 @@ public class CatalogYamlPolicyTest extends AbstractYamlTest {
         deleteCatalogEntity(referencedSymbolicName);
     }
 
-    private void addCatalogOsgiPolicy(String symbolicName, String serviceType) {
+    private void addCatalogOsgiPolicy(String symbolicName, String policyType) {
         TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH);
 
         addCatalogItem(
@@ -162,15 +162,14 @@ public class CatalogYamlPolicyTest extends AbstractYamlTest {
             "  version: " + TEST_VERSION,
             "  libraries:",
             "  - url: " + OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL,
-            "  item_type: policy",
             "  item:",
-            "    type: " + serviceType,
+            "    type: " + policyType,
             "    brooklyn.config:",
             "      config1: config1",
             "      config2: config2");
     }
 
-    private void addCatalogOsgiPolicyTopLevelSyntax(String symbolicName, String serviceType) {
+    private void addCatalogOsgiPolicyTopLevelSyntax(String symbolicName, String policyType) {
         TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH);
 
         addCatalogItem(
@@ -184,7 +183,7 @@ public class CatalogYamlPolicyTest extends AbstractYamlTest {
             "  - url: " + OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL,
             "",
             "brooklyn.policies:",
-            "- type: " + serviceType,
+            "- type: " + policyType,
             "  brooklyn.config:",
             "    config1: config1",
             "    config2: config2");

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f75f40b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlTemplateTest.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlTemplateTest.java b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlTemplateTest.java
index e473d2d..8fbcf65 100644
--- a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlTemplateTest.java
+++ b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlTemplateTest.java
@@ -39,6 +39,31 @@ public class CatalogYamlTemplateTest extends AbstractYamlTest {
 
     @Test
     public void testAddCatalogItem() throws Exception {
+        CatalogItem<?, ?> item = makeItem();
+        assertEquals(item.getCatalogItemType(), CatalogItemType.TEMPLATE);
+        Assert.assertTrue(item.getPlanYaml().indexOf("sample comment")>=0,
+            "YAML did not include original comments; it was:\n"+item.getPlanYaml());
+        Assert.assertFalse(item.getPlanYaml().indexOf("description")>=0,
+            "YAML included metadata which should have been excluded; it was:\n"+item.getPlanYaml());
+
+        deleteCatalogEntity("t1");
+    }
+
+    @Test
+    public void testAddCatalogItemAndCheckSource() throws Exception {
+        // this will fail with the Eclipse TestNG plugin -- use the static main instead to run in eclipse!
+        // see Yamls.KnownClassVersionException for details
+        
+        CatalogItem<?, ?> item = makeItem();
+        Assert.assertTrue(item.getPlanYaml().indexOf("sample comment")>=0,
+            "YAML did not include original comments; it was:\n"+item.getPlanYaml());
+        Assert.assertFalse(item.getPlanYaml().indexOf("description")>=0,
+            "YAML included metadata which should have been excluded; it was:\n"+item.getPlanYaml());
+
+        deleteCatalogEntity("t1");
+    }
+
+    private CatalogItem<?, ?> makeItem() {
         TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH);
         
         addCatalogItem(
@@ -57,13 +82,7 @@ public class CatalogYamlTemplateTest extends AbstractYamlTest {
             "    - type: " + SIMPLE_ENTITY_TYPE);
 
         CatalogItem<?, ?> item = mgmt().getCatalog().getCatalogItem("t1", TEST_VERSION);
-        assertEquals(item.getCatalogItemType(), CatalogItemType.TEMPLATE);
-        Assert.assertTrue(item.getPlanYaml().indexOf("sample comment")>=0,
-            "YAML did not include original comments; it was:\n"+item.getPlanYaml());
-        Assert.assertFalse(item.getPlanYaml().indexOf("description")>=0,
-            "YAML included metadata which should have been excluded; it was:\n"+item.getPlanYaml());
-
-        deleteCatalogEntity("t1");
+        return item;
     }
 
     // convenience for running in eclipse when the TestNG plugin drags in old version of snake yaml

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f75f40b/usage/launcher/src/test/resources/couchbase-w-loadgen.yaml
----------------------------------------------------------------------
diff --git a/usage/launcher/src/test/resources/couchbase-w-loadgen.yaml b/usage/launcher/src/test/resources/couchbase-w-loadgen.yaml
index 70ed435..101511e 100644
--- a/usage/launcher/src/test/resources/couchbase-w-loadgen.yaml
+++ b/usage/launcher/src/test/resources/couchbase-w-loadgen.yaml
@@ -30,7 +30,7 @@ services:
   createBuckets: [ { bucket: default } ]
   brooklyn.config:
     provisioning.properties:
-      minRam: 16384
+      minRam: 16g
       minCores: 4
   brooklyn.policies:
   - type: brooklyn.policy.autoscaling.AutoScalerPolicy

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f75f40b/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
index b2b81a9..2ad9eee 100644
--- a/utils/common/src/main/java/brooklyn/util/yaml/Yamls.java
+++ b/utils/common/src/main/java/brooklyn/util/yaml/Yamls.java
@@ -26,6 +26,7 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 import javax.annotation.Nullable;
 
@@ -211,7 +212,31 @@ public class Yamls {
             try {
                 return mark.getIndex();
             } catch (NoSuchMethodError e) {
-                throw new IllegalStateException("Class version error. This can happen if using a TestNG plugin in your IDE "
+                try {
+                    getClass().getClassLoader().loadClass("org.testng.TestNG");
+                } catch (ClassNotFoundException e1) {
+                    // not using TestNG
+                    Exceptions.propagateIfFatal(e1);
+                    throw e;
+                }
+                if (!LOGGED_TESTNG_WARNING.getAndSet(true)) {
+                    log.warn("Detected TestNG/SnakeYAML version incompatibilities: "
+                        + "some YAML source reconstruction will be unavailable. "
+                        + "This can happen with TestNG plugins which force an older version of SnakeYAML "
+                        + "which does not support Mark.getIndex. "
+                        + "It should not occur from maven CLI runs. "
+                        + "(Subsequent occurrences will be silently dropped, and source code reconstructed from YAML.)");
+                }
+                // using TestNG
+                throw new KnownClassVersionException(e);
+            }
+        }
+        
+        static AtomicBoolean LOGGED_TESTNG_WARNING = new AtomicBoolean();
+        static class KnownClassVersionException extends IllegalStateException {
+            private static final long serialVersionUID = -1620847775786753301L;
+            public KnownClassVersionException(Throwable e) {
+                super("Class version error. This can happen if using a TestNG plugin in your IDE "
                     + "which is an older version, dragging in an older version of SnakeYAML which does not support Mark.getIndex.", e);
             }
         }
@@ -322,7 +347,11 @@ public class Yamls {
                 return getMatchedYamlText();
             } catch (Exception e) {
                 Exceptions.propagateIfFatal(e);
-                log.warn("Unable to match yaml text in "+this+": "+e, e);
+                if (e instanceof KnownClassVersionException) {
+                    log.debug("Known class version exception; no yaml text being matched for "+this+": "+e);
+                } else {
+                    log.warn("Unable to match yaml text in "+this+": "+e, e);
+                }
                 return null;
             }
         }


[16/16] incubator-brooklyn git commit: This closes #585

Posted by he...@apache.org.
This closes #585


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

Branch: refs/heads/master
Commit: c4ca5b6833530a9b0ed2e7bbc3cc18998f46dfeb
Parents: 2cfd875 f6c5619
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Tue Apr 21 21:40:01 2015 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Tue Apr 21 21:40:01 2015 +0100

----------------------------------------------------------------------
 .../java/brooklyn/catalog/BrooklynCatalog.java  |  22 +
 .../main/java/brooklyn/catalog/CatalogItem.java |   3 +-
 .../java/brooklyn/location/LocationSpec.java    |   9 +-
 .../io/brooklyn/camp/BasicCampPlatform.java     |   7 +-
 .../brooklyn/catalog/CatalogPredicates.java     |  12 +-
 .../catalog/internal/BasicBrooklynCatalog.java  | 586 ++++++++++++++-----
 .../catalog/internal/CatalogItemBuilder.java    |   5 +
 .../brooklyn/catalog/internal/CatalogUtils.java |  24 +-
 .../location/basic/BasicLocationRegistry.java   |   2 +-
 .../location/basic/CatalogLocationResolver.java |   3 +-
 .../location/geo/UtraceHostGeoLookup.java       |   4 +-
 .../internal/EntityManagementUtils.java         |  18 +-
 .../policy/basic/AbstractEntityAdjunct.java     |  22 +-
 .../java/brooklyn/util/task/ValueResolver.java  |   7 +-
 .../brooklyn/camp/lite/CampYamlLiteTest.java    |   6 +-
 .../catalog/internal/CatalogLoadTest.java       |   4 +-
 .../catalog/internal/CatalogVersioningTest.java |  23 +
 .../entity/rebind/RebindCatalogItemTest.java    |  10 +-
 docs/_config.yml                                |   1 +
 docs/guide/misc/release-notes.md                |   4 +
 docs/guide/ops/catalog/index.md                 | 347 +++++++----
 .../java/brooklyn/entity/nosql/riak/riak.png    | Bin 0 -> 110651 bytes
 .../BrooklynAssemblyTemplateInstantiator.java   |  63 +-
 .../BrooklynComponentTemplateResolver.java      |  58 +-
 .../creation/BrooklynYamlTypeInstantiator.java  |   3 +-
 .../camp/brooklyn/AbstractYamlTest.java         |  12 +-
 .../CatalogOsgiVersionMoreEntityTest.java       |  11 +-
 .../brooklyn/catalog/CatalogYamlCombiTest.java  | 145 +++++
 .../brooklyn/catalog/CatalogYamlEntityTest.java | 241 +++++++-
 .../catalog/CatalogYamlLocationTest.java        | 124 +++-
 .../brooklyn/catalog/CatalogYamlPolicyTest.java |  73 ++-
 .../catalog/CatalogYamlTemplateTest.java        |  96 +++
 .../catalog/CatalogYamlVersioningTest.java      |  56 +-
 .../src/main/webapp/assets/js/view/catalog.js   |  57 +-
 .../create-step-template-entry.html             |   8 +-
 .../assets/tpl/catalog/add-catalog-entry.html   |   4 +-
 .../webapp/assets/tpl/catalog/add-entity.html   |  30 -
 .../webapp/assets/tpl/catalog/add-yaml.html     |  30 +
 .../src/test/resources/couchbase-w-loadgen.yaml |   2 +-
 .../main/java/brooklyn/rest/api/CatalogApi.java |  19 +-
 .../java/brooklyn/rest/api/LocationApi.java     |   5 +-
 .../rest/domain/CatalogEntitySummary.java       |   6 +
 .../rest/domain/CatalogItemSummary.java         |   3 +
 .../rest/domain/CatalogPolicySummary.java       |   3 +
 .../rest/resources/CatalogResource.java         |  66 +--
 .../rest/resources/LocationResource.java        |   4 +-
 .../rest/transform/CatalogTransformer.java      |  44 +-
 .../rest/resources/CatalogResourceTest.java     |  67 ++-
 .../brooklyn/util/exceptions/Exceptions.java    |   2 +-
 .../src/main/java/brooklyn/util/yaml/Yamls.java | 383 +++++++++++-
 .../test/java/brooklyn/util/yaml/YamlsTest.java | 127 ++++
 51 files changed, 2326 insertions(+), 535 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c4ca5b68/core/src/main/java/brooklyn/util/task/ValueResolver.java
----------------------------------------------------------------------


[05/16] incubator-brooklyn git commit: code review - yamls better error msg

Posted by he...@apache.org.
code review - yamls better error msg


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

Branch: refs/heads/master
Commit: 1f529fc7a60cfa66c5f7d4627ea71c542feeb54d
Parents: 90be105
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Thu Apr 9 21:43:43 2015 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Thu Apr 16 01:25:39 2015 -0500

----------------------------------------------------------------------
 utils/common/src/main/java/brooklyn/util/yaml/Yamls.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/1f529fc7/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
index d13edcb..b2b81a9 100644
--- a/utils/common/src/main/java/brooklyn/util/yaml/Yamls.java
+++ b/utils/common/src/main/java/brooklyn/util/yaml/Yamls.java
@@ -81,7 +81,7 @@ public class Yamls {
             x = Iterables.getOnlyElement(result);
         }
         if (type.isInstance(x)) return (T)x;
-        throw new ClassCastException("Cannot convert "+x+" to "+type);
+        throw new ClassCastException("Cannot convert "+x+" ("+x.getClass()+") to "+type);
     }
 
     /**


[06/16] incubator-brooklyn git commit: a few more catalog multi-item yaml doc tweaks. examples confirmed working!

Posted by he...@apache.org.
a few more catalog multi-item yaml doc tweaks. examples confirmed working!


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

Branch: refs/heads/master
Commit: 90be105fe17abb6bdb87b029be0a030c7ee6802d
Parents: eaaca78
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Wed Apr 8 00:32:23 2015 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Thu Apr 16 01:25:39 2015 -0500

----------------------------------------------------------------------
 .../catalog/internal/BasicBrooklynCatalog.java  |  4 --
 docs/guide/ops/catalog/index.md                 | 75 ++++++++++----------
 2 files changed, 36 insertions(+), 43 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/90be105f/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java b/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java
index 09ef02f..b773247 100644
--- a/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java
+++ b/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java
@@ -570,10 +570,6 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
 
         if (sourceYaml==null) sourceYaml = new Yaml().dump(itemMetadata);
 
-        // TODO:
-//        docs used as test cases -- (doc assertions that these match -- or import -- known test cases yamls)
-//        multiple versions in web app root
-
         Map<Object,Object> catalogMetadata = MutableMap.builder().putAll(parentMetadata).putAll(itemMetadata).build();
         
         // libraries we treat specially, to append the list, with the child's list preferred in classloading order

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/90be105f/docs/guide/ops/catalog/index.md
----------------------------------------------------------------------
diff --git a/docs/guide/ops/catalog/index.md b/docs/guide/ops/catalog/index.md
index 8f627d4..9eeca9c 100644
--- a/docs/guide/ops/catalog/index.md
+++ b/docs/guide/ops/catalog/index.md
@@ -12,7 +12,7 @@ children:
 
 Brooklyn provides a **catalog**, which is a persisted collection of versioned blueprints and other resources. 
 Blueprints in the catalog can be deployed directly, via the Brooklyn REST API or the web console,
-or referenced in other blueprints.
+or referenced in other blueprints using their `id`.
 
  
 ### Catalog Items YAML Syntax
@@ -75,22 +75,25 @@ The following metadata is *required* for all items:
   this field disambiguates between blueprints of the same `id`.
   Note that this is typically *not* the version of the software being installed,
   but rather the version of the blueprint. For more information on versioning, see below.
+  (Also note YAML treats numbers differently to Strings. Explicit quotes may sometimes be required.)
 
 To reference a catalog item in another blueprint, simply reference its ID and optionally its version number.
-For example: 
+For instance, if we've added an item with metadata `{ id: datastore, version: "1.0" }` (such as the example below),
+we could refer to it in another blueprint with: 
 
 ```yaml
 services:
 - type: datastore:1.0
 ```
 
-In addition, exactly **one** of the following is also required:
+In addition to the above fields, exactly **one** of the following is also required:
 
 - `item`: the blueprint (in the usual YAML format) for an entity or application template,
   or a map containing `type` and optional `brooklyn.config` for policies and locations; **or**
 - `items`: a list of catalog items, where each entry in the map follows the same schema as
   the `brooklyn.catalog` value, and the keys in these map override any metadata specified as
-  a sibling of this `items` key (or, in the case of `libraries` they add to the list)
+  a sibling of this `items` key (or, in the case of `libraries` they add to the list);
+  if there are references between items, then order is important, with forward references not supported.
 
 The following optional catalog metadata is supported:
   
@@ -109,11 +112,16 @@ The following optional catalog metadata is supported:
   (to prevent requiring all OSGi bundles to be loaded at launch).
   Icons are instead typically installed either at the server from which the OSGi bundles or catalog items are supplied 
   or in the `conf` folder of the Brooklyn distro.
-- `libraries`: a list of pointers to OSGi bundles required for the catalog item.
-  This can be omitted if blueprints are pure YAML and everything required is included in the catalog file, at URLs, 
-  or in the Brooklyn classpath. If custom Java code or bundled resources is used, the OSGi JARs supply
+- `libraries`: a list of pointers to OSGi bundles required for the catalog item,.
+  This can be omitted if blueprints are pure YAML and everything required is included in the classpath and catalog.
+  Where custom Java code or bundled resources is needed, however, OSGi JARs supply
   a convenient packaging format and a very powerful versioning format.
-  Note that these URL's should point at immutable OSGi bundles;
+  Libraries should be supplied in the form 
+  `libraries: [ "http://...", "http://..." ]`, 
+  or as
+  `libraries: [ { name: symbolic-name, version: 1.0, url: http://... }, ... ]` if `symbolic-name:1.0` 
+  might already be installed from a different URL and you want to skip the download.
+  Note that these URLs should point at immutable OSGi bundles;
   if the contents at any of these URLs changes, the behaviour of the blueprint may change 
   whenever a bundle is reloaded in a Brooklyn server,
   and if entities have been deployed against that version, their behavior may change in subtle or potentially incompatible ways.
@@ -126,9 +134,8 @@ The following optional catalog metadata is supported:
 
 The following example installs the `RiakNode` entity, making it also available as an application template,
 with a nice display name, description, and icon.
-It can be referred in other blueprints to as `datastore:1.0`, 
-and its implementation will be the Java `brooklyn.entity.nosql.riak.RiakNode`
-loaded from a classpath including an OSGi bundle and Brooklyn.
+It can be referred in other blueprints to as `datastore:1.0`,
+and its implementation will be the Java class `brooklyn.entity.nosql.riak.RiakNode` included with Brooklyn.
 
 ```yaml
 brooklyn.catalog:
@@ -138,9 +145,8 @@ brooklyn.catalog:
   icon.url: classpath://brooklyn/entity/nosql/riak/riak.png
   name: Datastore (Riak)
   description: Riak is an open-source NoSQL key-value data store.
-  libraries:
-    - url: http://example.com/path/to/my-dependency-1.2.3.jar
   item:
+    services:
     - type: brooklyn.entity.nosql.riak.RiakNode
       name: Riak Node
 ```
@@ -155,13 +161,22 @@ brooklyn.catalog:
   version: 1.1
   icon.url: classpath://brooklyn/entity/nosql/riak/riak.png
   description: Riak is an open-source NoSQL key-value data store.
-  libraries:
-    - url: http://example.com/path/to/my-dependency-1.2.3.jar
   items:
+    - id: riak-node
+      item:
+        services:
+        - type: brooklyn.entity.nosql.riak.RiakNode
+          name: Riak Node
+    - id: riak-cluster
+      item:
+        services:
+        - type: brooklyn.entity.nosql.riak.RiakCluster
+          name: Riak Cluster
     - id: datastore
       name: Datastore (Riak Cluster)
       item.type: template
       item:
+        services:
         - type: riak-cluster
           location: 
             jclouds:softlayer:
@@ -175,27 +190,18 @@ brooklyn.catalog:
             provisioning.properties:
               # you can also define machine specs
               minRam: 8gb
-    - id: riak-cluster
-      item:
-        - type: brooklyn.entity.nosql.riak.RiakCluster
-          name: Riak Cluster
-    - id: riak-node
-      item:
-        - type: brooklyn.entity.nosql.riak.RiakNode
-          name: Riak Node
 ```
 
 The items this will install are:
 
-- `riak-cluster` is available as a convenience short name for the full class, as an entity,
-  with an additional OSGi bundle installed
-- `datastore` provides the `riak-cluster` blueprint, in SoftLayer and with the given size and machine spec, 
+- `riak-node`, as before, but with a different name
+- `riak-cluster` as a convenience short name for the `brooklyn.entity.nosql.riak.RiakCluster` class
+- `datastore`, now pointing at the `riak-cluster` blueprint, in SoftLayer and with the given size and machine spec, 
   as the default implementation for anyone
   requesting a `datastore` (and if installed atop the previous example, new references to `datastore` 
   will access this version because it is a higher number);
-  because it is a template, users will have the opportunity to edit the YAML (see below)
-- `riak-node` is also installed, as before (but with a different name)
-
+  because it is a template, users will have the opportunity to edit the YAML (see below).
+  (This must be supplied after `riak-cluster`, because it refers to `riak-cluster`.)
 
 
 ### Templates and the Add-Application Wizard
@@ -268,15 +274,6 @@ the latest non-snapshot version will be loaded when an entity is instantiated.
 
 
 
-
 <!--
-TODO: Should improve the 'Create Application' dialog, so that the two versions don't appear on the front page.
-Currently they are indistinguisable, if they have the same description/icon.
+TODO: make test cases from the code snippets here, and when building the docs assert that they match test cases
 -->
-
-
-<!--
-TODO: Add section that explains how to add plain entities to the catalog and use them either from the App Wizard,
-(and entity UI) or embed the catalog id + version in another YAML
--->
-


[02/16] incubator-brooklyn git commit: update catalog docs for the new planned multi-item syntax

Posted by he...@apache.org.
update catalog docs for the new planned multi-item syntax


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

Branch: refs/heads/master
Commit: 99fa0b1ab238656f5adccf7b39bc3f3cadf45bb5
Parents: 253197f
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Sun Mar 29 21:03:23 2015 -0500
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Wed Apr 15 20:05:19 2015 -0500

----------------------------------------------------------------------
 docs/guide/ops/catalog/index.md                 | 317 ++++++++++++-------
 .../java/brooklyn/entity/nosql/riak/riak.png    | Bin 0 -> 110651 bytes
 2 files changed, 207 insertions(+), 110 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/99fa0b1a/docs/guide/ops/catalog/index.md
----------------------------------------------------------------------
diff --git a/docs/guide/ops/catalog/index.md b/docs/guide/ops/catalog/index.md
index dd1b991..8f627d4 100644
--- a/docs/guide/ops/catalog/index.md
+++ b/docs/guide/ops/catalog/index.md
@@ -2,93 +2,237 @@
 title: Catalog
 layout: website-normal
 children:
-- { section: Catalog Items }
+- { section: General YAML Schema }
+- { section: Catalog Metadata }
+- { section: Catalog YAML Examples }
 - { section: Adding to the Catalog, title: Adding and Deleting } 
+- { section: Templates and the Add-Application Wizard, title: Templates }
 - { section: Versioning } 
-- { section: special-reqs, title: Wizard } 
 ---
 
-Brooklyn provides a **catalog**, which is a persisted collection of versioned blueprints. 
-These can be deployed directly or referenced by other blueprints. 
-Blueprints in the catalog can be deployed via the Brooklyn REST API, or from 
-the web-console's "Catalog" tab of the "Create Application" wizard dialog box.
+Brooklyn provides a **catalog**, which is a persisted collection of versioned blueprints and other resources. 
+Blueprints in the catalog can be deployed directly, via the Brooklyn REST API or the web console,
+or referenced in other blueprints.
 
+ 
+### Catalog Items YAML Syntax
 
-<!--
-TODO: Clean up confusion in terminology between Catalog item and Blueprint (and Java blueprint?)?
--->
+An item or items to be added to the catalog is defined by a YAML file,
+specifying the catalog metadata for the items and the actual blueprint or resource definition.
 
-### Catalog Items
 
-An item to be added to the catalog is defined in YAML. This follows the syntax of a 
-YAML blueprint with an addition `brooklyn.catalog` section giving 
-the metadata needed to register the blueprint in the catalog:
+#### General YAML Schema
+ 
+A single catalog item can be defined following this general structure:
 
-{% highlight yaml %}
+```yaml
 brooklyn.catalog:
-  id: my-MySQL
-  version: 1.0
-  iconUrl: classpath://mysql.png
-  description: MySql is an open source relational database management system (RDBMS)
-  libraries:
-    - url: http://example.com/path/to/my-dependency-1.2.3.jar
-    - url: http://example.com/path/to/my-other-dependency-4.5.6.jar
+  <catalog-metadata>
+  item:
+    <blueprint-or-resource-definition>
+```
 
-services:
-- type: brooklyn.entity.database.mysql.MySqlNode
-{% endhighlight %}
-
-To explain the `brooklyn.catalog` fields:
-
-- The `id: MySQL` line specifies a unique ID used by Brooklyn to identify the catalog item. 
-  Other blueprints can reference the catalog item using this id.
-- The `version: 1.0` line provides a unique version for the *blueprint*. 
-  Note that this is typically *not* the version of the software being installed (in this case MySQL).
-- The `iconUrl: classpath://...` is an optional line where an icon can be specified 
-  for use with the item (in the "Add Application" dialog and elsewhere).
-  Note that `classpath` URLs *cannot* refer to items in the OSGi bundle 
-  (to prevent requiring all OSGi bundles to be loaded at launch);
-  use the server supplying the OSGi bundles or the `conf` folder of the Brooklyn distro instead.
-- The `description: ...` line, also optional, allows supplying a free-format description of the blueprint.
-
-
-The `libraries` section references OSGi bundles required for the blueprint. It can be omitted if everything
-required by the blueprint is already on the Brooklyn classpath.
-These URL's should be to stable OSGi bundles;
-if the contents at any of these URLs changes, the behaviour of the blueprint may change 
-whenever a bundle is reloaded in a Brooklyn server,
-and if entities have been deployed against that version, their behavior may change in subtle or potentially incompatible ways.
-To avoid this situation, it is highly recommended to use OSGi version stamps as part of the URL.
+
+To define multiple catalog items in a single YAML,
+where they may share some metadata,
+use the following structure:
+
+```yaml
+brooklyn.catalog:
+  <catalog-metadata>
+  items:
+  - <additional-catalog-metadata>
+    item:
+      <blueprint-or-resource-definition>
+  - <additional-catalog-metadata>
+    item:
+      <blueprint-or-resource-definition>
+```
+
+In some cases it is desired to define a default blueprint in a catalog file,
+so that the catalog file can be used unchanged to launch an application.
+To support this use case, the following format is also supported:
+
+```yaml
+<blueprint-definition>
+brooklyn.catalog:
+  <catalog-metadata>
+```
+
+
+
+#### Catalog Metadata
+
+Catalog metadata fields supply the additional information required In order to register an item in the catalog. 
+These fields can be supplied as `key: value` entries 
+where either the `<catalog-metadata>` or `<additional-catalog-metadata>` placeholders are,
+with the latter overriding the former unless otherwise specfied below.
+
+The following metadata is *required* for all items:
+
+- `id`: a human-friendly unique identifier for how this catalog item will be referenced from blueprints
+- `version`: multiple versions of a blueprint can be installed and used simultaneously;
+  this field disambiguates between blueprints of the same `id`.
+  Note that this is typically *not* the version of the software being installed,
+  but rather the version of the blueprint. For more information on versioning, see below.
 
 To reference a catalog item in another blueprint, simply reference its ID and optionally its version number.
 For example: 
 
-{% highlight yaml %}
+```yaml
 services:
-- type: my-MySQL:1.0
-{% endhighlight %}
+- type: datastore:1.0
+```
+
+In addition, exactly **one** of the following is also required:
+
+- `item`: the blueprint (in the usual YAML format) for an entity or application template,
+  or a map containing `type` and optional `brooklyn.config` for policies and locations; **or**
+- `items`: a list of catalog items, where each entry in the map follows the same schema as
+  the `brooklyn.catalog` value, and the keys in these map override any metadata specified as
+  a sibling of this `items` key (or, in the case of `libraries` they add to the list)
+
+The following optional catalog metadata is supported:
+  
+- `item.type`: the type of the item being defined.
+  If omitted, all `item` definitions are taken to be entities;
+  for any type other than an entity, this field must be supplied.
+  The supported types are:
+  - `entity`
+  - `template`
+  - `policy`
+  - `location`
+- `name`: a nicely formatted display name for the item, used when presenting it in a GUI
+- `description`: supplies an extended textual description for the item
+- `icon.url`: points to an icon for the item, used when presenting it in a GUI.
+  The URL prefix `classpath` is supported but these URLs may *not* refer items in any OSGi bundle in the `libraries` section 
+  (to prevent requiring all OSGi bundles to be loaded at launch).
+  Icons are instead typically installed either at the server from which the OSGi bundles or catalog items are supplied 
+  or in the `conf` folder of the Brooklyn distro.
+- `libraries`: a list of pointers to OSGi bundles required for the catalog item.
+  This can be omitted if blueprints are pure YAML and everything required is included in the catalog file, at URLs, 
+  or in the Brooklyn classpath. If custom Java code or bundled resources is used, the OSGi JARs supply
+  a convenient packaging format and a very powerful versioning format.
+  Note that these URL's should point at immutable OSGi bundles;
+  if the contents at any of these URLs changes, the behaviour of the blueprint may change 
+  whenever a bundle is reloaded in a Brooklyn server,
+  and if entities have been deployed against that version, their behavior may change in subtle or potentially incompatible ways.
+  To avoid this situation, it is highly recommended to use OSGi version stamps as part of the URL.
+
+
+#### Catalog YAML Examples
+
+##### A Simple Example
+
+The following example installs the `RiakNode` entity, making it also available as an application template,
+with a nice display name, description, and icon.
+It can be referred in other blueprints to as `datastore:1.0`, 
+and its implementation will be the Java `brooklyn.entity.nosql.riak.RiakNode`
+loaded from a classpath including an OSGi bundle and Brooklyn.
+
+```yaml
+brooklyn.catalog:
+  id: datastore
+  version: 1.0
+  item.type: template
+  icon.url: classpath://brooklyn/entity/nosql/riak/riak.png
+  name: Datastore (Riak)
+  description: Riak is an open-source NoSQL key-value data store.
+  libraries:
+    - url: http://example.com/path/to/my-dependency-1.2.3.jar
+  item:
+    - type: brooklyn.entity.nosql.riak.RiakNode
+      name: Riak Node
+```
+
+
+##### Multiple Items
+
+This YAML will install three items:
+
+```yaml
+brooklyn.catalog:
+  version: 1.1
+  icon.url: classpath://brooklyn/entity/nosql/riak/riak.png
+  description: Riak is an open-source NoSQL key-value data store.
+  libraries:
+    - url: http://example.com/path/to/my-dependency-1.2.3.jar
+  items:
+    - id: datastore
+      name: Datastore (Riak Cluster)
+      item.type: template
+      item:
+        - type: riak-cluster
+          location: 
+            jclouds:softlayer:
+              region: sjc01
+              # identity and credential must be set unless they are specified in your brooklyn.properties
+              # identity: XXX
+              # credential: XXX
+          brooklyn.config:
+            # the default size is 3 but this can be changed to suit your requirements
+            initial.size: 3
+            provisioning.properties:
+              # you can also define machine specs
+              minRam: 8gb
+    - id: riak-cluster
+      item:
+        - type: brooklyn.entity.nosql.riak.RiakCluster
+          name: Riak Cluster
+    - id: riak-node
+      item:
+        - type: brooklyn.entity.nosql.riak.RiakNode
+          name: Riak Node
+```
+
+The items this will install are:
+
+- `riak-cluster` is available as a convenience short name for the full class, as an entity,
+  with an additional OSGi bundle installed
+- `datastore` provides the `riak-cluster` blueprint, in SoftLayer and with the given size and machine spec, 
+  as the default implementation for anyone
+  requesting a `datastore` (and if installed atop the previous example, new references to `datastore` 
+  will access this version because it is a higher number);
+  because it is a template, users will have the opportunity to edit the YAML (see below)
+- `riak-node` is also installed, as before (but with a different name)
+
+
+
+### Templates and the Add-Application Wizard
+
+When a `template` is added to the catalog, the blueprint will appear in the 'Create Application' dialog
+as shown here:
+
+[![MySQL in Brooklyn Catalog](mysql-in-catalog-w700.png "MySQL in Brooklyn Catalog")](mysql-in-catalog.png) 
+
+
+
+### Catalog Management
+
+The Catalog tab in the web console will show all versions of catalog items,
+and allow you to add new items.
 
 
-### Adding to the Catalog
+##### Adding to the Catalog
 
-To add a catalog item to the catalog, `POST` the YAML file to `/v1/catalog` endpoint in
-Brooklyn's REST API.
+In addition to the GUI, items can be added to the catalog via the REST API
+with a `POST` of the YAML file to `/v1/catalog` endpoint.
 To do this using `curl`:
 
-{% highlight bash %}
-curl http://127.0.0.1:8081/v1/catalog --data-binary @/path/to/mysql-catalog.yaml
-{% endhighlight %}
+```bash
+curl http://127.0.0.1:8081/v1/catalog --data-binary @/path/to/riak.catalog.bom
+```
 
 
 
-### Deleting from the Catalog
+##### Deleting from the Catalog
 
 You can delete a versioned item from the catalog using the same endpoint as the REST API. 
-For example, to delete the item with id `my-MySQL` and version `1.0` with `curl`:
+For example, to delete the item with id `datastore` and version `1.0` with `curl`:
 
-{% highlight bash %}
-curl -X DELETE http://127.0.0.1:8081/v1/catalog/entities/MySQL/1.0
-{% endhighlight %}
+```bash
+curl -X DELETE http://127.0.0.1:8081/v1/catalog/entities/datastore/1.0
+```
 
 **Note:** Catalog items should not be deleted if there are running apps which were created using the same item. 
 During rebinding the catalog item is used to reconstruct the entity.
@@ -101,9 +245,10 @@ in a future release.
 Deprecation applies to a specific version of a catalog item, so the full
 id including the version number is passed to the REST API as follows:
 
-{% highlight bash %}
+```bash
 curl -X POST http://127.0.0.1:8081/v1/catalog/entities/MySQL:1.0/deprecated/true
-{% endhighlight %}
+```
+
 
 ### Versioning
 
@@ -122,41 +267,7 @@ When referencing a blueprint, if a version number is not specified
 the latest non-snapshot version will be loaded when an entity is instantiated.
 
 
-<a id="special-reqs"/>
-
-### Special Requirements for the "Create Application" Wizard Dialog
 
-For a blueprint in the catalog to be accessible via the 'Create Application' dialog, it must be an Application 
-(i.e. the entity at the root of the blueprint must implement `brooklyn.entity.Application`).
-In contrast, if a YAML blueprint is deployed direct via the REST API, then this is not necessary.
-
-For example, the MySql catalog item defined previously could be re-written to use a
-`brooklyn.entity.basic.BasicApplication`, because no application-specific logic is 
-required other than to pass-through the start and stop commands.
-the `MySqlNode` is added as a child of the `BasicApplication`.
-
-{% highlight yaml %}
-brooklyn.catalog:
-  id: my-MySQL
-  version: 1.0
-  iconUrl: classpath://mysql.png
-  description: MySql is an open source relational database management system (RDBMS)
-
-name: MySQL Database
-services:
-- type: brooklyn.entity.basic.BasicApplication
-  brooklyn.children:
-  - type: brooklyn.entity.database.mysql.MySqlNode
-{% endhighlight %}
-
-When added to the catalog via the HTTP `POST` command, the blueprint will appear in the 'Create Application' dialog
-as shown here:
-
-[![MySQL in Brooklyn Catalog](mysql-in-catalog-w700.png "MySQL in Brooklyn Catalog")](mysql-in-catalog.png) 
-
-When deploying a new version of a blueprint, the catalog will show both the previous and the new versions 
-of the blueprint. You may wish to delete the older version, assuming no applications currently running
-are using that old version.
 
 <!--
 TODO: Should improve the 'Create Application' dialog, so that the two versions don't appear on the front page.
@@ -169,17 +280,3 @@ TODO: Add section that explains how to add plain entities to the catalog and use
 (and entity UI) or embed the catalog id + version in another YAML
 -->
 
-<!--
-TODO: Add documentation to explain that the brooklyn.catalog section can contain a libraries array, each item pointing to 
-an OSGi bundle where the code for the blueprint is hosted. Every type from the blueprint will be searched for in the 
-libraries first and then on the standard Brooklyn classpath.*
--->
-
-<!--
-TODO: Add documentation about adding policies to the catalog, and explaining how to add items to 
-the UI using the plus icon on the catalog tab*
-
-TODO: describe entity addition (this just covers app addition)
-
-TODO: describe how to use the web-console GUI
--->

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/99fa0b1a/software/nosql/src/main/java/brooklyn/entity/nosql/riak/riak.png
----------------------------------------------------------------------
diff --git a/software/nosql/src/main/java/brooklyn/entity/nosql/riak/riak.png b/software/nosql/src/main/java/brooklyn/entity/nosql/riak/riak.png
new file mode 100644
index 0000000..a230b04
Binary files /dev/null and b/software/nosql/src/main/java/brooklyn/entity/nosql/riak/riak.png differ


[12/16] incubator-brooklyn git commit: accept primitive names (e.g. `int`) when adding sensors

Posted by he...@apache.org.
accept primitive names (e.g. `int`) when adding sensors


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

Branch: refs/heads/master
Commit: 1354d0361d34447d0e439574bd008f42aa5dc105
Parents: 5af2925
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Mon Apr 20 13:31:47 2015 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Mon Apr 20 13:33:01 2015 +0100

----------------------------------------------------------------------
 .../java/brooklyn/entity/basic/ConfigKeys.java  |  5 +--
 .../brooklyn/entity/effector/AddSensor.java     |  5 +++
 .../java/brooklyn/util/javalang/Boxing.java     | 15 ++++++++
 .../java/brooklyn/util/javalang/BoxingTest.java | 37 ++++++++++++++++++++
 4 files changed, 60 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/1354d036/core/src/main/java/brooklyn/entity/basic/ConfigKeys.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/ConfigKeys.java b/core/src/main/java/brooklyn/entity/basic/ConfigKeys.java
index 85b83dc..2b22a90 100644
--- a/core/src/main/java/brooklyn/entity/basic/ConfigKeys.java
+++ b/core/src/main/java/brooklyn/entity/basic/ConfigKeys.java
@@ -235,10 +235,11 @@ public class ConfigKeys {
         public static final ConfigKey<Object> DEFAULT_VALUE = ConfigKeys.newConfigKey(Object.class, "defaultValue");
         
         public static ConfigKey<?> newInstance(ConfigBag keyDefs) {
-            // TODO dynamic typing - see TYPE key commented out above
             String typeName = Strings.toString(keyDefs.getStringKey("type"));
-            if (Strings.isNonBlank(typeName))
+            if (Strings.isNonBlank(typeName)) {
+                // TODO dynamic typing - see TYPE key commented out above; also see AddSensor.getType for type lookup
                 log.warn("Setting 'type' is not currently supported for dynamic config keys; ignoring in definition of "+keyDefs);
+            }
             
             Class<Object> type = Object.class;
             String name = keyDefs.get(NAME);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/1354d036/core/src/main/java/brooklyn/entity/effector/AddSensor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/effector/AddSensor.java b/core/src/main/java/brooklyn/entity/effector/AddSensor.java
index 9e80b45..318d78a 100644
--- a/core/src/main/java/brooklyn/entity/effector/AddSensor.java
+++ b/core/src/main/java/brooklyn/entity/effector/AddSensor.java
@@ -28,6 +28,8 @@ import brooklyn.entity.proxying.EntityInitializer;
 import brooklyn.event.AttributeSensor;
 import brooklyn.event.basic.Sensors;
 import brooklyn.util.config.ConfigBag;
+import brooklyn.util.guava.Maybe;
+import brooklyn.util.javalang.Boxing;
 import brooklyn.util.time.Duration;
 
 import com.google.common.annotations.Beta;
@@ -79,6 +81,9 @@ public class AddSensor<T> implements EntityInitializer {
     @SuppressWarnings("unchecked")
     protected Class<T> getType(String className) {
         try {
+            // TODO use OSGi loader (low priority however); also ensure that allows primitives
+            Maybe<Class<?>> primitive = Boxing.getPrimitiveType(className);
+            if (primitive.isPresent()) return (Class<T>) primitive.get();
             return (Class<T>) Class.forName(className);
         } catch (ClassNotFoundException e) {
             if (!className.contains(".")) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/1354d036/utils/common/src/main/java/brooklyn/util/javalang/Boxing.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/javalang/Boxing.java b/utils/common/src/main/java/brooklyn/util/javalang/Boxing.java
index 296291e..72f5138 100644
--- a/utils/common/src/main/java/brooklyn/util/javalang/Boxing.java
+++ b/utils/common/src/main/java/brooklyn/util/javalang/Boxing.java
@@ -20,6 +20,8 @@ package brooklyn.util.javalang;
 
 import java.lang.reflect.Array;
 
+import brooklyn.util.guava.Maybe;
+
 import com.google.common.collect.ImmutableBiMap;
 
 public class Boxing {
@@ -42,6 +44,19 @@ public class Boxing {
             .put(Void.TYPE, Void.class)
             .build();
     
+    /** Returns the unboxed type for the given primitive type name, if available;
+     * e.g. {@link Integer#TYPE} for <code>int</code> (distinct from <code>Integer.class</code>),
+     * or null if not a primitive.
+     * */
+    public static Maybe<Class<?>> getPrimitiveType(String typeName) {
+        if (typeName!=null) {
+            for (Class<?> t: PRIMITIVE_TO_BOXED.keySet()) {
+                if (typeName.equals(t.getName())) return Maybe.<Class<?>>of(t);
+            }
+        }
+        return Maybe.absent("Not a primitive: "+typeName);
+    }
+    
     public static Class<?> boxedType(Class<?> type) {
         if (PRIMITIVE_TO_BOXED.containsKey(type))
             return PRIMITIVE_TO_BOXED.get(type);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/1354d036/utils/common/src/test/java/brooklyn/util/javalang/BoxingTest.java
----------------------------------------------------------------------
diff --git a/utils/common/src/test/java/brooklyn/util/javalang/BoxingTest.java b/utils/common/src/test/java/brooklyn/util/javalang/BoxingTest.java
new file mode 100644
index 0000000..de1ffcb
--- /dev/null
+++ b/utils/common/src/test/java/brooklyn/util/javalang/BoxingTest.java
@@ -0,0 +1,37 @@
+/*
+ * 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.javalang;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class BoxingTest {
+
+    @Test
+    public static void testIntPrimitiveAndBoxed() {
+        Assert.assertEquals(Integer.TYPE.getName(), "int");
+        
+        Class<?> t = Boxing.getPrimitiveType("int").get();
+        Assert.assertEquals(t, Integer.TYPE);
+        
+        Class<?> bt = Boxing.boxedType(t);
+        Assert.assertEquals(bt, Integer.class);
+    }
+
+}


[04/16] incubator-brooklyn git commit: add yaml support for extracting yaml items with comments and replacing extracts

Posted by he...@apache.org.
add yaml support for extracting yaml items with comments and replacing extracts


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

Branch: refs/heads/master
Commit: 456049b32a83030f3a55eede27e18fa16bf7cb43
Parents: 4338a8f
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Sun Mar 29 17:57:40 2015 -0500
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Wed Apr 15 20:05:19 2015 -0500

----------------------------------------------------------------------
 .../src/main/java/brooklyn/util/yaml/Yamls.java | 342 ++++++++++++++++++-
 .../test/java/brooklyn/util/yaml/YamlsTest.java | 127 +++++++
 2 files changed, 461 insertions(+), 8 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/456049b3/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
index ad56bfe..2bcbe87 100644
--- a/utils/common/src/main/java/brooklyn/util/yaml/Yamls.java
+++ b/utils/common/src/main/java/brooklyn/util/yaml/Yamls.java
@@ -19,6 +19,7 @@
 package brooklyn.util.yaml;
 
 import java.io.Reader;
+import java.io.StringReader;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Iterator;
@@ -26,10 +27,23 @@ import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 
+import javax.annotation.Nullable;
+
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.yaml.snakeyaml.Yaml;
+import org.yaml.snakeyaml.error.Mark;
+import org.yaml.snakeyaml.nodes.MappingNode;
+import org.yaml.snakeyaml.nodes.Node;
+import org.yaml.snakeyaml.nodes.NodeId;
+import org.yaml.snakeyaml.nodes.NodeTuple;
+import org.yaml.snakeyaml.nodes.ScalarNode;
+import org.yaml.snakeyaml.nodes.SequenceNode;
 
 import brooklyn.util.collections.Jsonya;
+import brooklyn.util.collections.MutableList;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.text.Strings;
 
 import com.google.common.annotations.Beta;
 import com.google.common.collect.Iterables;
@@ -38,12 +52,13 @@ 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).
+    /** returns the given (yaml-parsed) object as the given yaml type.
+     * <p>
+     * if the object is an iterable or iterator this method will fully expand it as a list. 
+     * if the requested type is not an iterable or iterator, and the list contains a single item, this will take that single item.
+     * <p>
+     * in other cases this method simply does a type-check and cast (no other type coercion).
      * <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 */
@@ -61,12 +76,12 @@ public class Yamls {
             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;
+            if (type.isAssignableFrom(Iterator.class)) return (T)result.iterator();
             x = Iterables.getOnlyElement(result);
         }
-        return (T)x;
+        if (type.isInstance(x)) return (T)x;
+        throw new ClassCastException("Cannot convert "+x+" to "+type);
     }
 
     /**
@@ -175,4 +190,315 @@ public class Yamls {
         }
         return result;
     }
+    
+    @Beta
+    public static class YamlExtract {
+        String yaml;
+        NodeTuple focusTuple;
+        Node prev, key, focus, next;
+        Exception error;
+        boolean includeKey = false, includePrecedingComments = true, includeOriginalIndentation = false;
+        
+        private int indexStart(Node node, boolean defaultIsYamlEnd) {
+            if (node==null) return defaultIsYamlEnd ? yaml.length() : 0;
+            return index(node.getStartMark());
+        }
+        private int indexEnd(Node node, boolean defaultIsYamlEnd) {
+            if (!found() || node==null) return defaultIsYamlEnd ? yaml.length() : 0;
+            return index(node.getEndMark());
+        }
+        private int index(Mark mark) {
+            try {
+                return mark.getIndex();
+            } catch (NoSuchMethodError e) {
+                throw new IllegalStateException("Class version error. This can happen if using a TestNG plugin in your IDE "
+                    + "which is an older version, dragging in an older version of SnakeYAML which does not support Mark.getIndex.", e);
+            }
+        }
+
+        public int getEndOfPrevious() {
+            return indexEnd(prev, false);
+        }
+        @Nullable public Node getKey() {
+            return key;
+        }
+        public Node getResult() {
+            return focus;
+        }
+        public int getStartOfThis() {
+            if (includeKey && focusTuple!=null) return indexStart(focusTuple.getKeyNode(), false);
+            return indexStart(focus, false);
+        }
+        private int getStartColumnOfThis() {
+            if (includeKey && focusTuple!=null) return focusTuple.getKeyNode().getStartMark().getColumn();
+            return focus.getStartMark().getColumn();
+        }
+        public int getEndOfThis() {
+            return getEndOfThis(false);
+        }
+        private int getEndOfThis(boolean goToEndIfNoNext) {
+            if (next==null && goToEndIfNoNext) return yaml.length();
+            return indexEnd(focus, false);
+        }
+        public int getStartOfNext() {
+            return indexStart(next, true);
+        }
+
+        private static int initialWhitespaceLength(String x) {
+            int i=0;
+            while (i < x.length() && x.charAt(i)==' ') i++;
+            return i;
+        }
+        
+        public String getFullYamlTextOriginal() {
+            return yaml;
+        }
+
+        /** Returns the original YAML with the found item replaced by the given replacement YAML.
+         * @param replacement YAML to put in for the found item;
+         * this YAML typically should not have any special indentation -- if required when replacing it will be inserted.
+         * <p>
+         * if replacing an inline map entry, the supplied entry must follow the structure being replaced;
+         * for example, if replacing the value in <code>key: value</code> with a map,
+         * supplying a replacement <code>subkey: value</code> would result in invalid yaml;
+         * the replacement must be supplied with a newline, either before the subkey or after.
+         * (if unsure we believe it is always valid to include an initial newline or comment with newline.)
+         */
+        public String getFullYamlTextWithExtractReplaced(String replacement) {
+            if (!found()) throw new IllegalStateException("Cannot perform replacement when item was not matched.");
+            String result = yaml.substring(0, getStartOfThis());
+            
+            String[] newLines = replacement.split("\n");
+            for (int i=1; i<newLines.length; i++)
+                newLines[i] = Strings.makePaddedString("", getStartColumnOfThis(), "", " ") + newLines[i];
+            result += Strings.lines(newLines);
+            if (replacement.endsWith("\n")) result += "\n";
+            
+            int end = getEndOfThis();
+            result += yaml.substring(end);
+            
+            return result;
+        }
+
+        /** Specifies whether the key should be included in the found text, 
+         * when calling {@link #getMatchedYamlText()} or {@link #getFullYamlTextWithExtractReplaced(String)},
+         * if the found item is a map entry.
+         * Defaults to false.
+         * @return this object, for use in a fluent constructions
+         */
+        public YamlExtract withKeyIncluded(boolean includeKey) {
+            this.includeKey = includeKey;
+            return this;
+        }
+
+        /** Specifies whether comments preceding the found item should be included, 
+         * when calling {@link #getMatchedYamlText()} or {@link #getFullYamlTextWithExtractReplaced(String)}.
+         * This will not include comments which are indented further than the item,
+         * as those will typically be aligned with the previous item
+         * (whereas comments whose indentation is the same or less than the found item
+         * will typically be aligned with this item).
+         * Defaults to true.
+         * @return this object, for use in a fluent constructions
+         */
+        public YamlExtract withPrecedingCommentsIncluded(boolean includePrecedingComments) {
+            this.includePrecedingComments = includePrecedingComments;
+            return this;
+        }
+
+        /** Specifies whether the original indentation should be preserved
+         * (and in the case of the first line, whether whitespace should be inserted so its start column is preserved), 
+         * when calling {@link #getMatchedYamlText()}.
+         * Defaults to false, the returned text will be outdented as far as possible.
+         * @return this object, for use in a fluent constructions
+         */
+        public YamlExtract withOriginalIndentation(boolean includeOriginalIndentation) {
+            this.includeOriginalIndentation = includeOriginalIndentation;
+            return this;
+        }
+
+        @Beta
+        public String getMatchedYamlText() {
+            if (!found()) return null;
+            
+            String[] body = yaml.substring(getStartOfThis(), getEndOfThis(true)).split("\n", -1);
+            
+            int firstLineIndentationOfFirstThing;
+            if (focusTuple!=null) {
+                firstLineIndentationOfFirstThing = focusTuple.getKeyNode().getStartMark().getColumn();
+            } else {
+                firstLineIndentationOfFirstThing = focus.getStartMark().getColumn();
+            }
+            int firstLineIndentationToAdd;
+            if (focusTuple!=null && (includeKey || body.length==1)) {
+                firstLineIndentationToAdd = focusTuple.getKeyNode().getStartMark().getColumn();
+            } else {
+                firstLineIndentationToAdd = focus.getStartMark().getColumn();
+            }
+            
+            
+            String firstLineIndentationToAddS = Strings.makePaddedString("", firstLineIndentationToAdd, "", " ");
+            String subsequentLineIndentationToRemoveS = firstLineIndentationToAddS;
+
+/* complexities of indentation:
+
+x: a
+ bc
+ 
+should become
+
+a
+ bc
+
+whereas
+
+- a: 0
+  b: 1
+  
+selecting 0 should give
+
+a: 0
+b: 1
+
+ */
+            List<String> result = MutableList.of();
+            if (includePrecedingComments) {
+                String[] preceding = yaml.substring(getEndOfPrevious(), getStartOfThis()).split("\n");
+                // suppress comments which are on the same line as the previous item or indented more than firstLineIndentation,
+                // ensuring appropriate whitespace is added to preceding[0] if it starts mid-line
+                if (preceding.length>0 && prev!=null) {
+                    preceding[0] = Strings.makePaddedString("", prev.getEndMark().getColumn(), "", " ") + preceding[0];
+                }
+                for (String p: preceding) {
+                    int w = initialWhitespaceLength(p);
+                    p = p.substring(w);
+                    if (p.startsWith("#")) {
+                        // only add if the hash is not indented further than the first line
+                        if (w <= firstLineIndentationOfFirstThing) {
+                            if (includeOriginalIndentation) p = firstLineIndentationToAddS + p;
+                            result.add(p);
+                        }
+                    }
+                }
+            }
+            
+            boolean doneFirst = false;
+            for (String p: body) {
+                if (!doneFirst) {
+                    if (includeOriginalIndentation) {
+                        // have to insert the right amount of spacing
+                        p = firstLineIndentationToAddS + p;
+                    }
+                    result.add(p);
+                    doneFirst = true;
+                } else {
+                    if (includeOriginalIndentation) {
+                        result.add(p);
+                    } else {
+                        result.add(Strings.removeFromStart(p, subsequentLineIndentationToRemoveS));
+                    }
+                }
+            }
+            return Strings.join(result, "\n");
+        }
+        
+        boolean found() {
+            return focus != null;
+        }
+        
+        public Exception getError() {
+            return error;
+        }
+        
+        @Override
+        public String toString() {
+            return "Extract["+focus+";prev="+prev+";key="+key+";next="+next+"]";
+        }
+    }
+    
+    private static void findTextOfYamlAtPath(YamlExtract result, int pathIndex, Object ...path) {
+        if (pathIndex>=path.length) {
+            // we're done
+            return;
+        }
+        
+        Object pathItem = path[pathIndex];
+        Node node = result.focus;
+        
+        if (node.getNodeId()==NodeId.mapping && pathItem instanceof String) {
+            // find key
+            Iterator<NodeTuple> ti = ((MappingNode)node).getValue().iterator();
+            while (ti.hasNext()) {
+                NodeTuple t = ti.next();
+                Node key = t.getKeyNode();
+                if (key.getNodeId()==NodeId.scalar) {
+                    if (pathItem.equals( ((ScalarNode)key).getValue() )) {
+                        result.key = key;
+                        result.focus = t.getValueNode();
+                        if (pathIndex+1<path.length) {
+                            // there are more path items, so the key here is a previous node
+                            result.prev = key;
+                        } else {
+                            result.focusTuple = t;
+                        }
+                        findTextOfYamlAtPath(result, pathIndex+1, path);
+                        if (result.next==null) {
+                            if (ti.hasNext()) result.next = ti.next().getKeyNode();
+                        }
+                        return;
+                    } else {
+                        result.prev = t.getValueNode();
+                    }
+                } else {
+                    throw new IllegalStateException("Key "+key+" is not a scalar, searching for "+pathItem+" at depth "+pathIndex+" of "+Arrays.asList(path));
+                }
+            }
+            throw new IllegalStateException("Did not find "+pathItem+" in "+node+" at depth "+pathIndex+" of "+Arrays.asList(path));
+            
+        } else if (node.getNodeId()==NodeId.sequence && pathItem instanceof Number) {
+            // find list item
+            List<Node> nl = ((SequenceNode)node).getValue();
+            int i = ((Number)pathItem).intValue();
+            if (i>=nl.size()) 
+                throw new IllegalStateException("Index "+i+" is out of bounds in "+node+", searching for "+pathItem+" at depth "+pathIndex+" of "+Arrays.asList(path));
+            if (i>0) result.prev = nl.get(i-1);
+            result.key = null;
+            result.focus = nl.get(i);
+            findTextOfYamlAtPath(result, pathIndex+1, path);
+            if (result.next==null) {
+                if (nl.size()>i+1) result.next = nl.get(i+1);
+            }
+            return;
+            
+        } else {
+            throw new IllegalStateException("Node "+node+" does not match selector "+pathItem+" at depth "+pathIndex+" of "+Arrays.asList(path));
+        }
+        
+        // unreachable
+    }
+    
+    
+    /** Given a path, where each segment consists of a string (key) or number (element in list),
+     * this will find the YAML text for that element
+     * <p>
+     * If not found this will return a {@link YamlExtract} 
+     * where {@link YamlExtract#isMatch()} is false and {@link YamlExtract#getError()} is set. */
+    public static YamlExtract getTextOfYamlAtPath(String yaml, Object ...path) {
+        YamlExtract result = new YamlExtract();
+        try {
+            int pathIndex = 0;
+            result.yaml = yaml;
+            result.focus = new Yaml().compose(new StringReader(yaml));
+    
+            findTextOfYamlAtPath(result, pathIndex, path);
+            return result;
+        } catch (NoSuchMethodError e) {
+            throw new IllegalStateException("Class version error. This can happen if using a TestNG plugin in your IDE "
+                + "which is an older version, dragging in an older version of SnakeYAML which does not support Mark.getIndex.", e);
+        } catch (Exception e) {
+            Exceptions.propagateIfFatal(e);
+            log.debug("Unable to find element in yaml (setting in result): "+e);
+            result.error = e;
+            return result;
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/456049b3/utils/common/src/test/java/brooklyn/util/yaml/YamlsTest.java
----------------------------------------------------------------------
diff --git a/utils/common/src/test/java/brooklyn/util/yaml/YamlsTest.java b/utils/common/src/test/java/brooklyn/util/yaml/YamlsTest.java
index 27437d9..36c146b 100644
--- a/utils/common/src/test/java/brooklyn/util/yaml/YamlsTest.java
+++ b/utils/common/src/test/java/brooklyn/util/yaml/YamlsTest.java
@@ -20,14 +20,30 @@ package brooklyn.util.yaml;
 
 import static org.testng.Assert.assertEquals;
 
+import java.util.Iterator;
+import java.util.List;
+
+import org.testng.Assert;
+import org.testng.TestNG;
 import org.testng.annotations.Test;
 
+import brooklyn.util.collections.MutableList;
+
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 
 public class YamlsTest {
 
     @Test
+    public void testGetAs() throws Exception {
+        MutableList<String> list = MutableList.of("x");
+        assertEquals(Yamls.getAs(list.iterator(), List.class), list);
+        assertEquals(Yamls.getAs(list.iterator(), Iterable.class), list);
+        assertEquals(Yamls.getAs(list.iterator(), Iterator.class), list.iterator());
+        assertEquals(Yamls.getAs(list.iterator(), String.class), "x");
+    }
+        
+    @Test
     public void testGetAt() throws Exception {
         // leaf of map
         assertEquals(Yamls.getAt("k1: v", ImmutableList.of("k1")), "v");
@@ -44,4 +60,115 @@ public class YamlsTest {
         assertEquals(Yamls.getAt("k1: [v1, v2]", ImmutableList.<String>of("k1", "[0]")), "v1");
         assertEquals(Yamls.getAt("k1: [v1, v2]", ImmutableList.<String>of("k1", "[1]")), "v2");
     }
+    
+    
+    @Test
+    public void testExtractMap() {
+        String sample = "#before\nk1:\n- v1\nk2:\n  # comment\n  k21: v21\nk3: v3\n#after\n";
+        
+        Assert.assertEquals(Yamls.getTextOfYamlAtPath(sample, "k1").withKeyIncluded(true).getMatchedYamlText(),
+            sample.substring(0, sample.indexOf("k2")));
+        Assert.assertEquals(Yamls.getTextOfYamlAtPath(sample, "k3").withKeyIncluded(true).getMatchedYamlText(),
+            sample.substring(sample.indexOf("k3")));
+        
+        // comments and no key, outdented - the default
+        Assert.assertEquals(Yamls.getTextOfYamlAtPath(sample, "k2", "k21").getMatchedYamlText(),
+            "# comment\nv21");
+        Assert.assertEquals(Yamls.getTextOfYamlAtPath(sample, "k2", "k21").getMatchedYamlText(),
+            "# comment\nv21");
+        // comments and key
+        Assert.assertEquals(Yamls.getTextOfYamlAtPath(sample, "k2", "k21").withKeyIncluded(true).getMatchedYamlText(),
+            "# comment\nk21: v21");
+        // no comments
+        Assert.assertEquals(Yamls.getTextOfYamlAtPath(sample, "k2", "k21").withKeyIncluded(true).withPrecedingCommentsIncluded(false).getMatchedYamlText(),
+            "k21: v21");
+        // no comments and no key
+        Assert.assertEquals(Yamls.getTextOfYamlAtPath(sample, "k2", "k21").withPrecedingCommentsIncluded(false).getMatchedYamlText(),
+            "v21");
+
+        // comments and no key, not outdented
+        Assert.assertEquals(Yamls.getTextOfYamlAtPath(sample, "k2", "k21").withOriginalIndentation(true).getMatchedYamlText(),
+            "  # comment\n  v21");
+        // comments and key
+        Assert.assertEquals(Yamls.getTextOfYamlAtPath(sample, "k2", "k21").withKeyIncluded(true).withOriginalIndentation(true).getMatchedYamlText(),
+            "  # comment\n  k21: v21");
+        // no comments
+        Assert.assertEquals(Yamls.getTextOfYamlAtPath(sample, "k2", "k21").withKeyIncluded(true).withPrecedingCommentsIncluded(false).withOriginalIndentation(true).getMatchedYamlText(),
+            "  k21: v21");
+        // no comments and no key
+        Assert.assertEquals(Yamls.getTextOfYamlAtPath(sample, "k2", "k21").withPrecedingCommentsIncluded(false).withOriginalIndentation(true).getMatchedYamlText(),
+            "  v21");
+    }
+
+    @Test
+    public void testExtractInList() {
+        String sample = 
+            "- a\n" +
+            "- b: 2\n" +
+            "- # c\n" +
+            " c1:\n" +
+            "  1\n" +
+            " c2:\n" +
+            "  2\n" +
+            "-\n" +
+            " - a # for a\n" +
+            " # for b\n" +
+            " - b\n";
+        
+        Assert.assertEquals(Yamls.getTextOfYamlAtPath(sample, 0).getMatchedYamlText(), "a");
+        Assert.assertEquals(Yamls.getTextOfYamlAtPath(sample, 1, "b").getMatchedYamlText(), "2");
+        Assert.assertEquals(Yamls.getTextOfYamlAtPath(sample, 3, 0).getMatchedYamlText(), 
+            "a"
+            // TODO comments after on same line not yet included - would be nice to add
+//            "a # for a"
+            );
+        
+        // out-dent
+        Assert.assertEquals(Yamls.getTextOfYamlAtPath(sample, 2).getMatchedYamlText(), "c1:\n 1\nc2:\n 2\n");
+        // don't outdent
+        Assert.assertEquals(Yamls.getTextOfYamlAtPath(sample, 2).withOriginalIndentation(true).getMatchedYamlText(), " c1:\n  1\n c2:\n  2\n");
+        Assert.assertEquals(Yamls.getTextOfYamlAtPath(sample, 3, 0).withOriginalIndentation(true).getMatchedYamlText(), 
+            "   a"
+            // as above, comments after not included
+//            "   a # for a"
+            );
+
+        // with preceding comments
+        // TODO final item includes newline (and comments) after - this behaviour might change, it's inconsistent,
+        // but it means the final comments aren't lost
+        Assert.assertEquals(Yamls.getTextOfYamlAtPath(sample, 3, 1).getMatchedYamlText(), "# for b\nb\n");
+        
+        // exclude preceding comments
+        Assert.assertEquals(Yamls.getTextOfYamlAtPath(sample, 3, 1).withPrecedingCommentsIncluded(false).getMatchedYamlText(), "b\n");
+    }
+    
+    @Test
+    public void testExtractMapIgnoringPreviousComments() {
+        String sample = "a: 1 # one\n"
+            + "b: 2 # two";
+        Assert.assertEquals(Yamls.getTextOfYamlAtPath(sample, "b").getMatchedYamlText(),
+            "2 # two");
+    }
+    
+    @Test
+    public void testExtractMapWithOddWhitespace() {
+        Assert.assertEquals(Yamls.getTextOfYamlAtPath("x: a\n bc", "x").getMatchedYamlText(),
+            "a\n bc");
+    }
+
+    @Test
+    public void testReplace() {
+        Assert.assertEquals(Yamls.getTextOfYamlAtPath("x: a\n bc", "x").getFullYamlTextWithExtractReplaced("\nc: 1\nd: 2"),
+            "x: \n   c: 1\n   d: 2");
+    }
+
+    // convenience, since running with older TestNG IDE plugin will fail (older snakeyaml dependency);
+    // if you run as a java app it doesn't bring in the IDE TestNG jar version, and it works
+    public static void main(String[] args) {
+        TestNG testng = new TestNG();
+        testng.setTestClasses(new Class[] { YamlsTest.class });
+//        testng.setVerbose(9);
+        testng.run();
+    }
+    
 }


[13/16] incubator-brooklyn git commit: catalog multi-item code review issues, incl version and better error msgs

Posted by he...@apache.org.
catalog multi-item code review issues, incl version and better error msgs


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

Branch: refs/heads/master
Commit: fb23d41fc4501d2750d0180b878f6aaf3e39e542
Parents: 6f75f40
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Mon Apr 20 13:32:38 2015 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Mon Apr 20 15:02:35 2015 +0100

----------------------------------------------------------------------
 .../catalog/internal/BasicBrooklynCatalog.java  | 60 +++++++++++++++-----
 .../brooklyn/catalog/internal/CatalogUtils.java | 17 +++++-
 .../internal/EntityManagementUtils.java         | 13 ++++-
 .../policy/basic/AbstractEntityAdjunct.java     |  4 ++
 .../catalog/internal/CatalogVersioningTest.java | 23 ++++++++
 docs/guide/ops/catalog/index.md                 | 12 ++--
 .../catalog/CatalogYamlVersioningTest.java      | 56 +++++++++++++++++-
 .../brooklyn/util/exceptions/Exceptions.java    |  2 +-
 8 files changed, 161 insertions(+), 26 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/fb23d41f/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java b/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java
index 680eaf1..cd1f8c2 100644
--- a/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java
+++ b/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java
@@ -584,7 +584,9 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
 
         Map<Object,Object> catalogMetadata = MutableMap.builder().putAll(parentMetadata).putAll(itemMetadata).build();
         
-        // libraries we treat specially, to append the list, with the child's list preferred in classloading order
+        // brooklyn.libraries we treat specially, to append the list, with the child's list preferred in classloading order
+        // `libraries` is supported in some places as a legacy syntax; it should always be `brooklyn.libraries` for new apps
+        // TODO in 0.8.0 require brooklyn.libraries, don't allow "libraries" on its own
         List<?> librariesNew = MutableList.copyOf(getFirstAs(itemMetadata, List.class, "brooklyn.libraries", "libraries").orNull());
         Collection<CatalogBundle> libraryBundlesNew = CatalogItemDtoAbstract.parseLibraries(librariesNew);
         
@@ -621,7 +623,7 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
 
         PlanInterpreterGuessingType planInterpreter = new PlanInterpreterGuessingType(null, item, sourceYaml, itemType, loader, result).reconstruct();
         if (!planInterpreter.isResolved()) {
-            throw new IllegalStateException("Could not resolve plan: "+sourceYaml);
+            throw Exceptions.create("Could not resolve item:\n"+sourceYaml, planInterpreter.getErrors());
         }
         itemType = planInterpreter.getCatalogItemType();
         Map<?, ?> itemAsMap = planInterpreter.getItem();
@@ -761,6 +763,7 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
         DeploymentPlan plan;
         AbstractBrooklynObjectSpec<?,?> spec;
         boolean resolved = false;
+        List<Exception> errors = MutableList.of();
         
         public PlanInterpreterGuessingType(@Nullable String id, Object item, String itemYaml, @Nullable CatalogItemType optionalCiType, 
                 BrooklynClassLoadingContext loader, List<CatalogItemDtoAbstract<?,?>> itemsDefinedSoFar) {
@@ -781,12 +784,17 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
         }
 
         public PlanInterpreterGuessingType reconstruct() {
-            attemptType(null, CatalogItemType.ENTITY);
-            attemptType(null, CatalogItemType.TEMPLATE);
-            
-            attemptType("services", CatalogItemType.ENTITY);
-            attemptType(POLICIES_KEY, CatalogItemType.POLICY);
-            attemptType(LOCATIONS_KEY, CatalogItemType.LOCATION);
+            if (catalogItemType==CatalogItemType.TEMPLATE) {
+                // template *must* be explicitly defined, and if so, none of the other calls apply
+                attemptType(null, CatalogItemType.TEMPLATE);
+                
+            } else {
+                attemptType(null, CatalogItemType.ENTITY);
+
+                attemptType("services", CatalogItemType.ENTITY);
+                attemptType(POLICIES_KEY, CatalogItemType.POLICY);
+                attemptType(LOCATIONS_KEY, CatalogItemType.LOCATION);
+            }
             
             if (!resolved && catalogItemType==CatalogItemType.TEMPLATE) {
                 // anything goes, for an explicit template, because we can't easily recurse into the types
@@ -799,6 +807,12 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
         
         public boolean isResolved() { return resolved; }
         
+        /** Returns potentially useful errors encountered while guessing types. 
+         * May only be available where the type is known. */
+        public List<Exception> getErrors() {
+            return errors;
+        }
+        
         public CatalogItemType getCatalogItemType() {
             return catalogItemType; 
         }
@@ -821,14 +835,21 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
             }
             // first look in collected items, if a key is given
             String type = (String) item.get("type");
+            String version = null;
+            if (CatalogUtils.looksLikeVersionedId(type)) {
+                version = CatalogUtils.getVersionFromVersionedId(type);
+                type = CatalogUtils.getIdFromVersionedId(type);
+            }
             if (type!=null && key!=null) {
                 for (CatalogItemDtoAbstract<?,?> candidate: itemsDefinedSoFar) {
                     if (type.equals(candidate.getSymbolicName()) || type.equals(candidate.getId())) {
-                        // matched - exit
-                        catalogItemType = candidateCiType;
-                        planYaml = candidateYaml;
-                        resolved = true;
-                        return true;
+                        if (version==null || version.equals(candidate.getVersion())) {
+                            // matched - exit
+                            catalogItemType = candidateCiType;
+                            planYaml = candidateYaml;
+                            resolved = true;
+                            return true;
+                        }
                     }
                 }
             }
@@ -846,6 +867,19 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
                 return true;
             } catch (Exception e) {
                 Exceptions.propagateIfFatal(e);
+                // record the error if we have reason to expect this guess to succeed
+                if (item.containsKey("services") && (candidateCiType==CatalogItemType.ENTITY || candidateCiType==CatalogItemType.TEMPLATE)) {
+                    // explicit services supplied, so plan should have been parseable for an entity or a a service
+                    errors.add(e);
+                } else if (catalogItemType!=null && key!=null) {
+                    // explicit itemType supplied, so plan should be parseable in the cases where we're given a key
+                    // (when we're not given a key, the previous block should apply)
+                    errors.add(e);
+                } else {
+                    // all other cases, the error is probably due to us not getting the type right, ignore it
+                    if (log.isTraceEnabled())
+                        log.trace("Guessing type of plan, it looks like it isn't "+candidateCiType+"/"+key+": "+e);
+                }
             }
             
             // finally try parsing a cut-down plan, in case there is a nested reference to a newly defined catalog item

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/fb23d41f/core/src/main/java/brooklyn/catalog/internal/CatalogUtils.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/catalog/internal/CatalogUtils.java b/core/src/main/java/brooklyn/catalog/internal/CatalogUtils.java
index c72fc9b..7c9aa86 100644
--- a/core/src/main/java/brooklyn/catalog/internal/CatalogUtils.java
+++ b/core/src/main/java/brooklyn/catalog/internal/CatalogUtils.java
@@ -176,7 +176,22 @@ public class CatalogUtils {
     }
 
     public static boolean looksLikeVersionedId(String versionedId) {
-        return versionedId != null && versionedId.indexOf(VERSION_DELIMITER) != -1;
+        if (versionedId==null) return false;
+        int fi = versionedId.indexOf(VERSION_DELIMITER);
+        if (fi<0) return false;
+        int li = versionedId.lastIndexOf(VERSION_DELIMITER);
+        if (li!=fi) {
+            // if multiple colons, we say it isn't a versioned reference; the prefix in that case must understand any embedded versioning scheme
+            // this fixes the case of:  http://localhost:8080
+            return false;
+        }
+        String candidateVersion = versionedId.substring(li+1);
+        if (!candidateVersion.matches("[0-9]+(|(\\.|_).*)")) {
+            // version must start with a number, followed if by anything with full stop or underscore before any other characters
+            // e.g.  foo:1  or foo:1.1  or foo:1_SNAPSHOT all supported, but not e.g. foo:bar (or chef:cookbook or docker:my/image)
+            return false;
+        }
+        return true;
     }
 
     public static String getIdFromVersionedId(String versionedId) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/fb23d41f/core/src/main/java/brooklyn/management/internal/EntityManagementUtils.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/EntityManagementUtils.java b/core/src/main/java/brooklyn/management/internal/EntityManagementUtils.java
index 6734654..e6dc12a 100644
--- a/core/src/main/java/brooklyn/management/internal/EntityManagementUtils.java
+++ b/core/src/main/java/brooklyn/management/internal/EntityManagementUtils.java
@@ -112,8 +112,17 @@ public class EntityManagementUtils {
             Entities.startManagement((Application)app, mgmt);
             return (T) app;
         } else {
-            assembly = instantiator.instantiate(at, camp);
-            return (T) mgmt.getEntityManager().getEntity(assembly.getId());
+            // currently, all brooklyn plans should produce the above; currently this will always throw Unsupported  
+            try {
+                assembly = instantiator.instantiate(at, camp);
+                return (T) mgmt.getEntityManager().getEntity(assembly.getId());
+            } catch (UnsupportedOperationException e) {
+                // map this (expected) error to a nicer message
+                throw new IllegalArgumentException("Unrecognized application blueprint format");
+            } catch (Exception e) {
+                Exceptions.propagateIfFatal(e);
+                throw new IllegalArgumentException("Invalid plan: "+at, e);
+            }
         }
     }
     

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/fb23d41f/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java b/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java
index 1748de8..394ad03 100644
--- a/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java
+++ b/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java
@@ -76,6 +76,10 @@ public abstract class AbstractEntityAdjunct extends AbstractBrooklynObject imple
 
     private boolean _legacyNoConstructionInit;
 
+    /**
+     * @deprecated since 0.7.0; leftover properties are put into config, since when coming from yaml this is normal.
+     */
+    @Deprecated
     protected Map<String,Object> leftoverProperties = Maps.newLinkedHashMap();
 
     protected transient ExecutionContext execution;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/fb23d41f/core/src/test/java/brooklyn/catalog/internal/CatalogVersioningTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/catalog/internal/CatalogVersioningTest.java b/core/src/test/java/brooklyn/catalog/internal/CatalogVersioningTest.java
index 179ce8f..6c4dcc0 100644
--- a/core/src/test/java/brooklyn/catalog/internal/CatalogVersioningTest.java
+++ b/core/src/test/java/brooklyn/catalog/internal/CatalogVersioningTest.java
@@ -22,6 +22,7 @@ import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.assertTrue;
 
+import org.testng.Assert;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
@@ -52,6 +53,28 @@ public class CatalogVersioningTest {
     }
 
     @Test
+    public void testParsingVersion() {
+        assertVersionParsesAs("foo:1", "foo", "1");
+        assertVersionParsesAs("foo", null, null);
+        assertVersionParsesAs("foo:1.1", "foo", "1.1");
+        assertVersionParsesAs("foo:1_SNAPSHOT", "foo", "1_SNAPSHOT");
+        assertVersionParsesAs("foo:10.9.8_SNAPSHOT", "foo", "10.9.8_SNAPSHOT");
+        assertVersionParsesAs("foo:bar", null, null);
+        assertVersionParsesAs("chef:cookbook", null, null);
+        assertVersionParsesAs("http://foo:8080", null, null);
+    }
+
+    private static void assertVersionParsesAs(String versionedId, String id, String version) {
+        if (version==null) {
+            Assert.assertFalse(CatalogUtils.looksLikeVersionedId(versionedId));
+        } else {
+            Assert.assertTrue(CatalogUtils.looksLikeVersionedId(versionedId));
+            Assert.assertEquals(CatalogUtils.getIdFromVersionedId(versionedId), id);
+            Assert.assertEquals(CatalogUtils.getVersionFromVersionedId(versionedId), version);
+        }
+    }
+
+    @Test
     public void testAddVersioned() {
         String symbolicName = "sampleId";
         String version = "0.1.0";

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/fb23d41f/docs/guide/ops/catalog/index.md
----------------------------------------------------------------------
diff --git a/docs/guide/ops/catalog/index.md b/docs/guide/ops/catalog/index.md
index a2e4408..8477204 100644
--- a/docs/guide/ops/catalog/index.md
+++ b/docs/guide/ops/catalog/index.md
@@ -82,7 +82,7 @@ In addition to the above fields, exactly **one** of the following is also requir
   or a full application blueprint (in the usual YAML format) for a template; **or*
 - `items`: a list of catalog items, where each entry in the map follows the same schema as
   the `brooklyn.catalog` value, and the keys in these map override any metadata specified as
-  a sibling of this `items` key (or, in the case of `libraries` they add to the list);
+  a sibling of this `items` key (or, in the case of `brooklyn.libraries` they add to the list);
   if there are references between items, then order is important, 
   `items` are processed in order, depth-first, and forward references are not supported.
 
@@ -99,18 +99,18 @@ The following optional catalog metadata is supported:
 - `name`: a nicely formatted display name for the item, used when presenting it in a GUI
 - `description`: supplies an extended textual description for the item
 - `iconUrl`: points to an icon for the item, used when presenting it in a GUI.
-  The URL prefix `classpath` is supported but these URLs may *not* refer items in any OSGi bundle in the `libraries` section 
+  The URL prefix `classpath` is supported but these URLs may *not* refer items in any OSGi bundle in the `brooklyn.libraries` section 
   (to prevent requiring all OSGi bundles to be loaded at launch).
   Icons are instead typically installed either at the server from which the OSGi bundles or catalog items are supplied 
   or in the `conf` folder of the Brooklyn distro.
-- `libraries`: a list of pointers to OSGi bundles required for the catalog item,.
+- `brooklyn.libraries`: a list of pointers to OSGi bundles required for the catalog item.
   This can be omitted if blueprints are pure YAML and everything required is included in the classpath and catalog.
   Where custom Java code or bundled resources is needed, however, OSGi JARs supply
   a convenient packaging format and a very powerful versioning format.
   Libraries should be supplied in the form 
-  `libraries: [ "http://...", "http://..." ]`, 
+  `brooklyn.libraries: [ "http://...", "http://..." ]`, 
   or as
-  `libraries: [ { name: symbolic-name, version: 1.0, url: http://... }, ... ]` if `symbolic-name:1.0` 
+  `brooklyn.libraries: [ { name: symbolic-name, version: 1.0, url: http://... }, ... ]` if `symbolic-name:1.0` 
   might already be installed from a different URL and you want to skip the download.
   Note that these URLs should point at immutable OSGi bundles;
   if the contents at any of these URLs changes, the behaviour of the blueprint may change 
@@ -211,7 +211,7 @@ or POSTed to the applications endpoint to deploy an instance.
 POSTing to the applications endpoint,
 will ignored the `brooklyn.catalog` information;
 this means references to any `item` blocks in the `<catalog-metadata>` will not be resolved,
-and any OSGi `libraries` defined there will not be loaded.)
+and any OSGi `brooklyn.libraries` defined there will not be loaded.)
 
 
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/fb23d41f/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlVersioningTest.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlVersioningTest.java b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlVersioningTest.java
index 5b43c99..c1176ea 100644
--- a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlVersioningTest.java
+++ b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlVersioningTest.java
@@ -24,6 +24,7 @@ import static org.testng.Assert.assertTrue;
 import static org.testng.Assert.fail;
 import io.brooklyn.camp.brooklyn.AbstractYamlTest;
 
+import org.testng.Assert;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
@@ -32,6 +33,9 @@ import brooklyn.catalog.CatalogItem;
 import brooklyn.catalog.CatalogPredicates;
 import brooklyn.catalog.internal.BasicBrooklynCatalog;
 import brooklyn.entity.Entity;
+import brooklyn.entity.basic.BasicApplication;
+import brooklyn.entity.basic.BasicEntity;
+import brooklyn.entity.basic.ConfigKeys;
 
 import com.google.common.base.Predicates;
 import com.google.common.collect.Iterables;
@@ -144,7 +148,7 @@ public class CatalogYamlVersioningTest extends AbstractYamlTest {
         String parentName = "parentId";
         String v1 = "0.1.0";
         String v2 = "0.2.0";
-        String expectedType = "brooklyn.entity.basic.BasicApplication";
+        String expectedType = BasicApplication.class.getName();
 
         addCatalogEntity(symbolicName, v1, expectedType);
         addCatalogEntity(symbolicName, v2);
@@ -163,7 +167,7 @@ public class CatalogYamlVersioningTest extends AbstractYamlTest {
         String parentName = "parentId";
         String v1 = "0.1.0";
         String v2 = "0.2.0";
-        String expectedType = "brooklyn.entity.basic.BasicApplication";
+        String expectedType = BasicApplication.class.getName();
 
         addCatalogEntity(symbolicName, v1);
         addCatalogEntity(symbolicName, v2, expectedType);
@@ -176,6 +180,52 @@ public class CatalogYamlVersioningTest extends AbstractYamlTest {
         assertEquals(app.getEntityType().getName(), expectedType);
     }
 
+    private void doTestVersionedReferenceJustAdded(boolean isVersionImplicitSyntax) throws Exception {
+        addCatalogItem(            "brooklyn.catalog:",
+            "  version: 0.9",
+            "  items:",
+            "  - id: referrent",
+            "    item:",
+            "      type: "+BasicEntity.class.getName(),
+            "  - id: referrent",
+            "    version: 1.1",
+            "    item:",
+            "      type: "+BasicEntity.class.getName(),
+            "      brooklyn.config: { foo: bar }",
+            "  - id: referrer",
+            "    version: 1.0",
+            "    item:",
+            (isVersionImplicitSyntax ? 
+                "      type: referrent:1.1" :
+                "      type: referrent\n" +
+                "      version: 1.1"));
+        
+        Iterable<CatalogItem<Object, Object>> items = catalog.getCatalogItems(CatalogPredicates.symbolicName(Predicates.equalTo("referrer")));
+        Assert.assertEquals(Iterables.size(items), 1, "Wrong number of: "+items);
+        CatalogItem<Object, Object> item = Iterables.getOnlyElement(items);
+        Assert.assertEquals(item.getVersion(), "1.0");
+        
+        Entity app = createAndStartApplication(
+            "services:",
+            (isVersionImplicitSyntax ? 
+                "- type: referrer:1.0" :
+                "- type: referrer\n" +
+                "  version: 1.0") );
+        Entity child = Iterables.getOnlyElement(app.getChildren());
+        Assert.assertTrue(child instanceof BasicEntity, "Wrong child: "+child);
+        Assert.assertEquals(child.getConfig(ConfigKeys.newStringConfigKey("foo")), "bar");
+    }
+
+    @Test
+    public void testVersionedReferenceJustAddedExplicitVersion() throws Exception {
+        doTestVersionedReferenceJustAdded(false);
+    }
+    
+    @Test
+    public void testVersionedReferenceJustAddedImplicitVersionSyntax() throws Exception {
+        doTestVersionedReferenceJustAdded(true);
+    }
+    
     private void assertSingleCatalogItem(String symbolicName, String version) {
         Iterable<CatalogItem<Object, Object>> items = catalog.getCatalogItems(CatalogPredicates.symbolicName(Predicates.equalTo(symbolicName)));
         CatalogItem<Object, Object> item = Iterables.getOnlyElement(items);
@@ -184,7 +234,7 @@ public class CatalogYamlVersioningTest extends AbstractYamlTest {
     }
     
     private void addCatalogEntity(String symbolicName, String version) {
-        addCatalogEntity(symbolicName, version, "brooklyn.entity.basic.BasicEntity");
+        addCatalogEntity(symbolicName, version, BasicEntity.class.getName());
     }
 
     private void addCatalogEntity(String symbolicName, String version, String type) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/fb23d41f/utils/common/src/main/java/brooklyn/util/exceptions/Exceptions.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/exceptions/Exceptions.java b/utils/common/src/main/java/brooklyn/util/exceptions/Exceptions.java
index 066ecea..4303e4e 100644
--- a/utils/common/src/main/java/brooklyn/util/exceptions/Exceptions.java
+++ b/utils/common/src/main/java/brooklyn/util/exceptions/Exceptions.java
@@ -271,7 +271,7 @@ public class Exceptions {
         }
         if (exceptions.isEmpty()) {
             if (Strings.isBlank(prefix)) return new CompoundRuntimeException("(empty compound exception)", exceptions);
-            return new CompoundRuntimeException(prefix+": (empty compound exception)", exceptions);
+            return new CompoundRuntimeException(prefix, exceptions);
         }
         if (Strings.isBlank(prefix)) return new CompoundRuntimeException(exceptions.size()+" errors, including: " + Exceptions.collapseText(exceptions.iterator().next()), exceptions);
         return new CompoundRuntimeException(prefix+", "+exceptions.size()+" errors including: " + Exceptions.collapseText(exceptions.iterator().next()), exceptions);