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 2021/05/28 15:15:18 UTC

[brooklyn-server] 06/07: use single-key map style for spec_hierarchy in live specs and entities, as well as catalog

This is an automated email from the ASF dual-hosted git repository.

heneveld pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/brooklyn-server.git

commit 8abcb0cf21da297f059c2b22441a2ace707e0bc1
Author: Alex Heneveld <al...@cloudsoftcorp.com>
AuthorDate: Fri May 28 15:15:41 2021 +0100

    use single-key map style for spec_hierarchy in live specs and entities, as well as catalog
---
 .../spi/creation/CampTypePlanTransformer.java      |   4 +-
 .../catalog/CatalogOsgiYamlTemplateTest.java       |  21 +--
 .../CatalogYamlEntityOsgiTypeRegistryTest.java     |  14 +-
 .../brooklyn/catalog/CatalogYamlEntityTest.java    |   5 +-
 .../apache/brooklyn/core/mgmt/BrooklynTags.java    | 197 +++++++++------------
 .../core/typereg/AbstractTypePlanTransformer.java  |  36 ++--
 .../typereg/JavaClassNameTypePlanTransformer.java  |   3 +-
 .../brooklyn/core/typereg/RegisteredTypes.java     |   2 -
 .../internal/StaticTypePlanTransformer.java        |   5 +-
 .../typereg/ExampleXmlTypePlanTransformer.java     |   3 +-
 .../brooklyn/rest/resources/EntityResource.java    |   5 +-
 .../brooklyn/rest/transform/TypeTransformer.java   |  17 +-
 12 files changed, 142 insertions(+), 170 deletions(-)

diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampTypePlanTransformer.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampTypePlanTransformer.java
index 5558429..c6d0cb3 100644
--- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampTypePlanTransformer.java
+++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampTypePlanTransformer.java
@@ -20,7 +20,6 @@ package org.apache.brooklyn.camp.brooklyn.spi.creation;
 
 import java.util.List;
 import java.util.Map;
-import java.util.Optional;
 import java.util.function.BiFunction;
 
 import org.apache.brooklyn.api.internal.AbstractBrooklynObjectSpec;
@@ -28,7 +27,6 @@ import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry.RegisteredTypeKind;
 import org.apache.brooklyn.api.typereg.RegisteredType;
 import org.apache.brooklyn.api.typereg.RegisteredType.TypeImplementationPlan;
 import org.apache.brooklyn.api.typereg.RegisteredTypeLoadingContext;
-import org.apache.brooklyn.core.mgmt.BrooklynTags;
 import org.apache.brooklyn.core.typereg.*;
 import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.guava.Maybe;
@@ -106,7 +104,7 @@ public class CampTypePlanTransformer extends AbstractTypePlanTransformer {
     @Override
     protected AbstractBrooklynObjectSpec<?, ?> createSpec(RegisteredType type, RegisteredTypeLoadingContext context) throws Exception {
         try {
-            return decorateWithHierarchySpecTag(new CampResolver(mgmt, type, context).createSpec(), type, "Brooklyn CAMP", null,
+            return decorateWithCommonTags(new CampResolver(mgmt, type, context).createSpec(), type, null, null,
                     prevHeadSpecSummary -> "Based on "+prevHeadSpecSummary);
 
         } catch (Exception e) {
diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlTemplateTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlTemplateTest.java
index e3b05ac..7436870 100644
--- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlTemplateTest.java
+++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogOsgiYamlTemplateTest.java
@@ -18,11 +18,8 @@
  */
 package org.apache.brooklyn.camp.brooklyn.catalog;
 
-import org.apache.brooklyn.core.mgmt.BrooklynTags.SpecHierarchyTag;
-import static org.testng.Assert.assertEquals;
-
+import com.google.common.collect.Iterables;
 import java.util.List;
-
 import org.apache.brooklyn.api.entity.Application;
 import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.entity.EntitySpec;
@@ -30,6 +27,7 @@ import org.apache.brooklyn.api.typereg.RegisteredType;
 import org.apache.brooklyn.camp.brooklyn.AbstractYamlTest;
 import org.apache.brooklyn.core.mgmt.BrooklynTags;
 import org.apache.brooklyn.core.mgmt.BrooklynTags.NamedStringTag;
+import org.apache.brooklyn.core.mgmt.BrooklynTags.SpecSummary;
 import org.apache.brooklyn.core.mgmt.EntityManagementUtils;
 import org.apache.brooklyn.core.mgmt.osgi.OsgiStandaloneTest;
 import org.apache.brooklyn.core.typereg.RegisteredTypePredicates;
@@ -38,10 +36,9 @@ import org.apache.brooklyn.test.Asserts;
 import org.apache.brooklyn.test.support.TestResourceUnavailableException;
 import org.apache.brooklyn.util.osgi.OsgiTestResources;
 import org.testng.Assert;
+import static org.testng.Assert.assertEquals;
 import org.testng.annotations.Test;
 
-import com.google.common.collect.Iterables;
-
 
 public class CatalogOsgiYamlTemplateTest extends AbstractYamlTest {
     
@@ -88,9 +85,9 @@ public class CatalogOsgiYamlTemplateTest extends AbstractYamlTest {
         String yaml = Iterables.getOnlyElement(yamls).getContents();
         Asserts.assertStringContains(yaml, "services:", "t1", "localhost");
 
-        SpecHierarchyTag yamlsH = BrooklynTags.findSpecHierarchyTag(spec.getTags());
+        List<SpecSummary> yamlsH = BrooklynTags.findSpecHierarchyTag(spec.getTags());
         Assert.assertNotNull(yamlsH);
-        Assert.assertEquals(yamlsH.getSpecList().size(), 1, "Expected 1 yaml tag in hierarchy; instead had: "+yamlsH);
+        Assert.assertEquals(yamlsH.size(), 1, "Expected 1 yaml tag in hierarchy; instead had: "+yamlsH);
 
         Assert.assertNull(BrooklynTags.getDepthInAncestorTag(spec.getTags()));
 
@@ -103,14 +100,14 @@ public class CatalogOsgiYamlTemplateTest extends AbstractYamlTest {
         List<NamedStringTag> yamls2 = BrooklynTags.findAllNamedStringTags(BrooklynTags.YAML_SPEC_KIND, child.getTags());
         Assert.assertEquals(yamls2.size(), 1, "Expected 1 yaml tag; instead had: "+yamls);
 
-        SpecHierarchyTag yamlsH2 = BrooklynTags.findSpecHierarchyTag( child.getTags() );
+        List<SpecSummary> yamlsH2 = BrooklynTags.findSpecHierarchyTag(child.getTags());
         Assert.assertNotNull(yamlsH2);
-        Assert.assertEquals(yamlsH2.getSpecList().size(), 1, "Expected 1 yaml tag in hierarchy; instead had: "+yamlsH2);
-        Asserts.assertStringContainsIgnoreCase(yamlsH2.getSpecList().iterator().next().contents.toString(),
+        Assert.assertEquals(yamlsH2.size(), 1, "Expected 1 yaml tag in hierarchy; instead had: "+yamlsH2);
+        Asserts.assertStringContainsIgnoreCase(yamlsH2.iterator().next().contents.toString(),
                 "# this sample comment should be included",
                 "SimpleEntity");
 
-        Assert.assertEquals(BrooklynTags.getDepthInAncestorTag(spec.getTags()), (Integer) 1);
+        Assert.assertEquals(BrooklynTags.getDepthInAncestorTag(child.getTags()), (Integer) 1);
     }
     
     private RegisteredType makeItem(String symbolicName, String templateType) {
diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityOsgiTypeRegistryTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityOsgiTypeRegistryTest.java
index 42677df..87b3b78 100644
--- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityOsgiTypeRegistryTest.java
+++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityOsgiTypeRegistryTest.java
@@ -18,11 +18,13 @@
  */
 package org.apache.brooklyn.camp.brooklyn.catalog;
 
+import java.util.List;
 import java.util.Map;
 import org.apache.brooklyn.api.typereg.RegisteredType;
 import org.apache.brooklyn.camp.brooklyn.spi.creation.CampTypePlanTransformer;
 import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog;
 import org.apache.brooklyn.core.mgmt.BrooklynTags;
+import org.apache.brooklyn.core.mgmt.BrooklynTags.SpecSummary;
 import org.apache.brooklyn.core.test.entity.TestEntity;
 import org.apache.brooklyn.core.typereg.RegisteredTypePredicates;
 import org.apache.brooklyn.entity.stock.BasicEntity;
@@ -227,19 +229,19 @@ public class CatalogYamlEntityOsgiTypeRegistryTest extends CatalogYamlEntityTest
                 "         - format: " + CampTypePlanTransformer.FORMAT,
                 "           summary:  Plan for " + symbolicName,
                 "           contents:  | " ,
-                "               line 1" ,
-                "               line 2" ,
+                "               line 1",
+                "               line 2",
                 "  itemType: entity",
                 "  item: " + BasicEntity.class.getName());
 
         RegisteredType item = mgmt().getTypeRegistry().get(symbolicName, TEST_VERSION);
 
-        BrooklynTags.SpecHierarchyTag specTag = BrooklynTags.findSpecHierarchyTag(item.getTags());
+        List<SpecSummary> specTag = BrooklynTags.findSpecHierarchyTag(item.getTags());
         Assert.assertNotNull(specTag);
-        assertEquals(specTag.getSpecList().size(), 1);
+        assertEquals(specTag.size(), 1);
 
-        Asserts.assertEquals(specTag.getSpecList().get(0).format, CampTypePlanTransformer.FORMAT);
-        Asserts.assertEquals(specTag.getSpecList().get(0).summary, "Plan for " + symbolicName);
+        Asserts.assertEquals(specTag.get(0).format, CampTypePlanTransformer.FORMAT);
+        Asserts.assertEquals(specTag.get(0).summary, "Plan for " + symbolicName);
         deleteCatalogRegisteredType(symbolicName);
     }
 
diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java
index e499639..a33329d 100644
--- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java
+++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java
@@ -26,6 +26,7 @@ import java.util.Set;
 import org.apache.brooklyn.api.objs.BrooklynObject;
 import org.apache.brooklyn.core.entity.Dumper;
 import org.apache.brooklyn.core.mgmt.BrooklynTags;
+import org.apache.brooklyn.core.mgmt.BrooklynTags.SpecSummary;
 import org.apache.brooklyn.util.collections.MutableSet;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNotNull;
@@ -271,9 +272,9 @@ public class CatalogYamlEntityTest extends AbstractYamlTest {
         assertEquals(entity.getEntityType().getName(), TestEntity.class.getName());
 
         // tests that the plan tag was set
-        BrooklynTags.SpecHierarchyTag specTag = BrooklynTags.findSpecHierarchyTag(entity.tags().getTags());
+        List<SpecSummary> specTag = BrooklynTags.findSpecHierarchyTag(entity.tags().getTags());
         Assert.assertNotNull(specTag);
-        assertEquals(specTag.getSpecList().size(), 3);
+        assertEquals(specTag.size(), 3);
 
         deleteCatalogRegisteredType(referencedSymbolicName);
         deleteCatalogRegisteredType(referrerSymbolicName);
diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/BrooklynTags.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/BrooklynTags.java
index 37f87e4..1b43c0a 100644
--- a/core/src/main/java/org/apache/brooklyn/core/mgmt/BrooklynTags.java
+++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/BrooklynTags.java
@@ -22,7 +22,9 @@ import com.google.common.reflect.TypeToken;
 import java.io.Serializable;
 import java.util.*;
 
-import com.google.common.base.MoreObjects;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+import org.apache.brooklyn.api.internal.AbstractBrooklynObjectSpec;
 import org.apache.brooklyn.core.resolve.jackson.BeanWithTypeUtils;
 import org.apache.brooklyn.util.collections.MutableList;
 
@@ -32,6 +34,7 @@ import com.google.common.annotations.Beta;
 import com.google.common.base.Function;
 import com.google.common.base.Objects;
 import com.google.common.collect.Lists;
+import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.core.flags.TypeCoercions;
 import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.slf4j.Logger;
@@ -46,8 +49,11 @@ public class BrooklynTags {
     // could deprecate this in favour of spec_hierarchy
     public static final String YAML_SPEC_KIND = "yaml_spec";
 
+    /** used as a single-key map key whose value is a list of {@link SpecSummary} maps, first one being the original source, then going in to conversions/definitions */
     public static final String SPEC_HIERARCHY = "spec_hierarchy";
 
+    /** used as a single-key map key whose value is a number 1 or more, indicating how many ancestors up need to be traversed
+     * to find where a particular entity was defined */
     public static final String DEPTH_IN_ANCESTOR = "depth_in_ancestor";
 
     public static final String NOTES_KIND = "notes";
@@ -60,21 +66,44 @@ public class BrooklynTags {
 
     /** find a tag which is a map of size one whose single key matches the key here, and if found return the value
      * coerced to the indicated type */
-    public static <T> T findSingleKeyMapValue(String key, TypeToken<T> type, Set<Object> tags) {
+    public static <T> T findSingleKeyMapValue(String key, TypeToken<T> type, Iterable<Object> tags) {
         if (tags==null) return null;
         for (Object tag: tags) {
-            if (tag instanceof Map && ((Map)tag).size()==1 && Objects.equal(key, ((Map)tag).keySet().iterator().next())) {
-                Object value = ((Map)tag).get(key);
+            if (isTagSingleKeyMap(tag, key)) {
+                java.lang.Object value = ((Map)tag).get(key);
                 return TypeCoercions.coerce(value, type);
             }
         }
         return null;
     }
-    /** convenience for {@link #findSingleKeyMapValue(String, TypeToken, Set)} */
-    public static <T> T findSingleKeyMapValue(String key, Class<T> type, Set<Object> tags) {
+
+    private static <Object> boolean isTagSingleKeyMap(Object tag, String key) {
+        return tag instanceof Map && ((Map) tag).size() == 1 && Objects.equal(key, ((Map) tag).keySet().iterator().next());
+    }
+
+    /** convenience for {@link #findSingleKeyMapValue(String, TypeToken, Iterable)} */
+    public static <T> T findSingleKeyMapValue(String key, Class<T> type, Iterable<Object> tags) {
         return findSingleKeyMapValue(key, TypeToken.of(type), tags);
     }
 
+    public static <T> void upsertSingleKeyMapValueTag(AbstractBrooklynObjectSpec<?, ?> spec, String key, T value) {
+        MutableList<Object> tags = MutableList.copyOf(spec.getTags());
+        AtomicInteger count = new AtomicInteger();
+        List<Object> newTags = tags.stream().map(t -> {
+            if (isTagSingleKeyMap(t, key)) {
+                count.incrementAndGet();
+                return MutableMap.of(key, value);
+            } else {
+                return t;
+            }
+        }).collect(Collectors.toList());
+        if (count.get()>0) {
+            spec.tagsReplace(newTags);
+        } else {
+            spec.tag(MutableMap.of(key, value));
+        }
+    }
+
     public static NamedStringTag findFirstNamedStringTag(String kind, Iterable<Object> tags) {
         return findFirstOfKind(kind, NamedStringTag.class, tags);
     }
@@ -182,47 +211,41 @@ public class BrooklynTags {
         }
     }
 
-    public static class SpecHierarchyTag implements Serializable, HasKind {
-        private static final long serialVersionUID = 3805124696862755492L;
-
-        public static final String KIND = SPEC_HIERARCHY;
-
-        public static class SpecSummary implements Serializable {
-            @JsonProperty
-            public final String summary;
-            @JsonProperty
-            public final String format;
-            @JsonProperty
-            public final Object contents;
+    public static class SpecSummary implements Serializable {
+        @JsonProperty
+        public final String summary;
+        @JsonProperty
+        public final String format;
+        @JsonProperty
+        public final Object contents;
 
-            private SpecSummary() { this(null, null, null); }; //for JSON
-            public SpecSummary(String summary, String format, Object contents) {
-                this.summary = summary;
-                this.format = format;
-                this.contents = contents;
-            }
+        private SpecSummary() { this(null, null, null); }; //for JSON
+        public SpecSummary(String summary, String format, Object contents) {
+            this.summary = summary;
+            this.format = format;
+            this.contents = contents;
+        }
 
-            @Override
-            public boolean equals(Object o) {
-                if (this == o) return true;
-                if (o == null || getClass() != o.getClass()) return false;
-                SpecSummary that = (SpecSummary) o;
-                return java.util.Objects.equals(summary, that.summary) && java.util.Objects.equals(format, that.format) && java.util.Objects.equals(contents, that.contents);
-            }
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            SpecSummary that = (SpecSummary) o;
+            return java.util.Objects.equals(summary, that.summary) && java.util.Objects.equals(format, that.format) && java.util.Objects.equals(contents, that.contents);
+        }
 
-            @Override
-            public int hashCode() {
-                return java.util.Objects.hash(summary, format, contents);
-            }
+        @Override
+        public int hashCode() {
+            return java.util.Objects.hash(summary, format, contents);
+        }
 
-            @Override
-            public String toString() {
-                return "SpecSummary{" +
-                        "summary='" + summary + '\'' +
-                        ", format='" + format + '\'' +
-                        ", contents=" + contents +
-                        '}';
-            }
+        @Override
+        public String toString() {
+            return "SpecSummary{" +
+                    "summary='" + summary + '\'' +
+                    ", format='" + format + '\'' +
+                    ", contents=" + contents +
+                    '}';
         }
 
         public static Builder builder() { return new Builder(); }
@@ -233,7 +256,9 @@ public class BrooklynTags {
             private String format;
             private Object contents;
 
-            private Builder() {}
+            private Builder() {
+            }
+
             private Builder(SpecSummary base) {
                 summary = base.summary;
                 format = base.format;
@@ -255,88 +280,36 @@ public class BrooklynTags {
                 return this;
             }
 
-
-            public SpecSummary buildSpecSummary() {
+            public SpecSummary build() {
                 return new SpecSummary(summary, format, contents);
             }
-
-            public SpecHierarchyTag buildSpecHierarchyTag() {
-                return new SpecHierarchyTag(SPEC_HIERARCHY, MutableList.of(buildSpecSummary()));
-            }
         }
 
-        @JsonProperty
-        String kind;
-
-        @JsonProperty
-        List<SpecSummary> specList;
-
-        // for JSON
-        private SpecHierarchyTag() {}
-
-        private SpecHierarchyTag(String kind, List<SpecSummary> specList) {
-            this.kind = kind;
-            this.specList = specList;
-        }
-
-        @Override
-        public String toString() {
-            return MoreObjects.toStringHelper(this)
-                    .omitNullValues()
-                    .add("kind", kind)
-                    .add("specList", specList)
-                    .toString();
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-            SpecHierarchyTag specTag = (SpecHierarchyTag) o;
-            return Objects.equal(kind, specTag.kind) && Objects.equal(specList, specTag.specList) ;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hashCode(kind, specList);
-        }
-
-        public String getKind() {
-            return kind;
-        }
-
-        public List<SpecSummary> getSpecList() {
-            return specList;
-        }
-
-        public void push(SpecSummary newFirstSpecTag) {
+        public static void pushToList(List<SpecSummary> specList, SpecSummary newFirstSpecTag) {
             specList.add(0, newFirstSpecTag);
         }
-        public void push(List<SpecSummary> newFirstSpecs) {
+        public static void pushToList(List<SpecSummary> specList, List<SpecSummary> newFirstSpecs) {
             if (newFirstSpecs==null || newFirstSpecs.isEmpty()) return;
             if (newFirstSpecs.size()==1) {
-                push(newFirstSpecs.iterator().next());
+                pushToList(specList, newFirstSpecs.iterator().next());
             } else {
                 List<SpecSummary> l = MutableList.copyOf(newFirstSpecs);
                 Collections.reverse(l);
-                l.forEach(this::push);
+                l.forEach(li -> pushToList(specList, li));
             }
         }
-        public void push(SpecHierarchyTag newFirstSpecs) {
-            push(newFirstSpecs.getSpecList());
-        }
 
-        public SpecSummary pop() {
-            if (getSpecList().isEmpty()) return null;
-            return getSpecList().remove(0);
+        public static SpecSummary popFromList(List<SpecSummary> specList) {
+            if (specList.isEmpty()) return null;
+            return specList.remove(0);
         }
 
-        public boolean modifyHeadSummary(java.util.function.Function<String, String> previousSummaryModification) {
-            if (!getSpecList().isEmpty() && previousSummaryModification!=null) {
-                SpecSummary oldHead = pop();
-                SpecSummary newPrevHead = SpecHierarchyTag.builder(oldHead).summary(
-                        previousSummaryModification.apply(oldHead.summary)).buildSpecSummary();
-                push(newPrevHead);
+        public static boolean modifyHeadSummary(List<SpecSummary> specList, java.util.function.Function<String, String> previousSummaryModification) {
+            if (!specList.isEmpty() && previousSummaryModification!=null) {
+                SpecSummary oldHead = popFromList(specList);
+                SpecSummary newPrevHead = SpecSummary.builder(oldHead).summary(
+                        previousSummaryModification.apply(oldHead.summary)).build();
+                pushToList(specList, newPrevHead);
                 return true;
             }
             return false;
@@ -416,8 +389,8 @@ public class BrooklynTags {
     }
 
 
-    public static SpecHierarchyTag findSpecHierarchyTag(Iterable<Object> tags) {
-        return findFirstOfKind(SpecHierarchyTag.KIND, SpecHierarchyTag.class, tags);
+    public static List<SpecSummary> findSpecHierarchyTag(Iterable<Object> tags) {
+        return findSingleKeyMapValue(SPEC_HIERARCHY, new TypeToken<List<SpecSummary>>() {}, tags);
     }
 
     public static Integer getDepthInAncestorTag(Set<Object> tags) {
diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/AbstractTypePlanTransformer.java b/core/src/main/java/org/apache/brooklyn/core/typereg/AbstractTypePlanTransformer.java
index 081957d..97832b7 100644
--- a/core/src/main/java/org/apache/brooklyn/core/typereg/AbstractTypePlanTransformer.java
+++ b/core/src/main/java/org/apache/brooklyn/core/typereg/AbstractTypePlanTransformer.java
@@ -30,8 +30,8 @@ import org.apache.brooklyn.api.typereg.RegisteredType;
 import org.apache.brooklyn.api.typereg.RegisteredTypeLoadingContext;
 import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog;
 import org.apache.brooklyn.core.mgmt.BrooklynTags;
-import org.apache.brooklyn.core.mgmt.BrooklynTags.SpecHierarchyTag;
-import org.apache.brooklyn.core.mgmt.BrooklynTags.SpecHierarchyTag.SpecSummary;
+import org.apache.brooklyn.core.mgmt.BrooklynTags.SpecSummary;
+import org.apache.brooklyn.util.collections.MutableList;
 import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.guava.Maybe;
@@ -168,10 +168,11 @@ public abstract class AbstractTypePlanTransformer implements BrooklynTypePlanTra
 
     protected abstract Object createBean(RegisteredType type, RegisteredTypeLoadingContext context) throws Exception;
 
-    protected AbstractBrooklynObjectSpec<?,?> decorateWithHierarchySpecTag(AbstractBrooklynObjectSpec<?, ?> spec, RegisteredType type,
-                                                                           final String format, @Nullable final String summary,
-                                                                           @Nullable Function<String,String> previousSummaryModification) {
-        final String specSummary = Strings.isNonBlank(summary)
+    protected AbstractBrooklynObjectSpec<?,?> decorateWithCommonTags(AbstractBrooklynObjectSpec<?, ?> spec, RegisteredType type,
+                                                                     @Nullable String format, @Nullable String summary,
+                                                                     @Nullable Function<String,String> previousSummaryModification) {
+        if (Strings.isBlank(format)) format = getFormatCode();
+        final String specSummaryText = Strings.isNonBlank(summary)
                 ? summary
                 : format + " plan" +
                     (Strings.isNonBlank(type.getSymbolicName())
@@ -180,25 +181,26 @@ public abstract class AbstractTypePlanTransformer implements BrooklynTypePlanTra
                                 ? " for "+type.getDisplayName()
                                 : "");
 
-        BrooklynTags.SpecHierarchyTag.Builder currentSpecTagBuilder = BrooklynTags.SpecHierarchyTag.builder()
+        SpecSummary specSummary = SpecSummary.builder()
                 .format(format)
-                .summary(specSummary)
-                .contents(type.getPlan().getPlanData());
+                .summary(specSummaryText)
+                .contents(type.getPlan().getPlanData())
+                .build();
 
-        SpecHierarchyTag specTag = BrooklynTags.findSpecHierarchyTag(spec.getTags());
+        List<SpecSummary> specTag = BrooklynTags.findSpecHierarchyTag(spec.getTags());
         if (specTag != null) {
-            specTag.modifyHeadSummary(previousSummaryModification);
-            specTag.push(currentSpecTagBuilder.buildSpecSummary());
+            SpecSummary.modifyHeadSummary(specTag, previousSummaryModification);
+            SpecSummary.pushToList(specTag, specSummary);
         } else {
-            specTag = currentSpecTagBuilder.buildSpecHierarchyTag();
-            spec.tag(specTag);
+            specTag = MutableList.of(specSummary);
         }
 
-        List<SpecSummary> sources = BrooklynTags.findSingleKeyMapValue(BrooklynTags.SPEC_HIERARCHY, new TypeToken<List<SpecSummary>>() {}, type.getTags());
+        List<SpecSummary> sources = BrooklynTags.findSpecHierarchyTag(type.getTags());
         if (sources != null) {
-            specTag.modifyHeadSummary(s -> "Converted for catalog to "+s);
-            specTag.push(sources);
+            SpecSummary.modifyHeadSummary(specTag, s -> "Converted for catalog to "+s);
+            SpecSummary.pushToList(specTag, sources);
         }
+        BrooklynTags.upsertSingleKeyMapValueTag(spec, BrooklynTags.SPEC_HIERARCHY, specTag);
 
         if (spec instanceof EntitySpec) {
             addDepthTagsWhereMissing( ((EntitySpec<?>)spec).getChildren(), 1 );
diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/JavaClassNameTypePlanTransformer.java b/core/src/main/java/org/apache/brooklyn/core/typereg/JavaClassNameTypePlanTransformer.java
index b5e2394..1e9fdcc 100644
--- a/core/src/main/java/org/apache/brooklyn/core/typereg/JavaClassNameTypePlanTransformer.java
+++ b/core/src/main/java/org/apache/brooklyn/core/typereg/JavaClassNameTypePlanTransformer.java
@@ -19,7 +19,6 @@
 package org.apache.brooklyn.core.typereg;
 
 import java.lang.reflect.Constructor;
-import java.util.List;
 
 import org.apache.brooklyn.api.internal.AbstractBrooklynObjectSpec;
 import org.apache.brooklyn.api.objs.BrooklynObject;
@@ -65,7 +64,7 @@ public class JavaClassNameTypePlanTransformer extends AbstractTypePlanTransforme
     @SuppressWarnings({ "unchecked" })
     @Override
     protected AbstractBrooklynObjectSpec<?,?> createSpec(RegisteredType type, RegisteredTypeLoadingContext context) throws Exception {
-        return decorateWithHierarchySpecTag(RegisteredTypes.newSpecInstance(mgmt, (Class<? extends BrooklynObject>) getType(type, context)), type, FORMAT, null, null);
+        return decorateWithCommonTags(RegisteredTypes.newSpecInstance(mgmt, (Class<? extends BrooklynObject>) getType(type, context)), type, null, null, null);
     }
 
     @Override
diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypes.java b/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypes.java
index 80d36ac..bd496fd 100644
--- a/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypes.java
+++ b/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypes.java
@@ -48,7 +48,6 @@ import org.apache.brooklyn.core.catalog.internal.CatalogUtils;
 import org.apache.brooklyn.core.config.ConfigKeys;
 import org.apache.brooklyn.core.mgmt.BrooklynTags;
 import org.apache.brooklyn.core.mgmt.BrooklynTags.NamedStringTag;
-import org.apache.brooklyn.core.mgmt.BrooklynTags.SpecHierarchyTag;
 import org.apache.brooklyn.core.objs.BrooklynObjectInternal;
 import org.apache.brooklyn.core.typereg.JavaClassNameTypePlanTransformer.JavaClassNameTypeImplementationPlan;
 import org.apache.brooklyn.util.collections.Jsonya;
@@ -60,7 +59,6 @@ import org.apache.brooklyn.util.text.NaturalOrderComparator;
 import org.apache.brooklyn.util.text.Strings;
 import org.apache.brooklyn.util.text.VersionComparator;
 import org.apache.brooklyn.util.yaml.Yamls;
-import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
diff --git a/core/src/test/java/org/apache/brooklyn/core/catalog/internal/StaticTypePlanTransformer.java b/core/src/test/java/org/apache/brooklyn/core/catalog/internal/StaticTypePlanTransformer.java
index 413bacd..a0e8388 100644
--- a/core/src/test/java/org/apache/brooklyn/core/catalog/internal/StaticTypePlanTransformer.java
+++ b/core/src/test/java/org/apache/brooklyn/core/catalog/internal/StaticTypePlanTransformer.java
@@ -18,7 +18,6 @@
  */
 package org.apache.brooklyn.core.catalog.internal;
 
-import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
@@ -93,9 +92,9 @@ public class StaticTypePlanTransformer extends AbstractTypePlanTransformer {
     @Override
     protected AbstractBrooklynObjectSpec<?, ?> createSpec(RegisteredType type, RegisteredTypeLoadingContext context) throws Exception {
         if (REGISTERED_SPECS.containsKey(type.getSymbolicName()))
-            return decorateWithHierarchySpecTag(get(type.getSymbolicName()), type, FORMAT, null, null);
+            return decorateWithCommonTags(get(type.getSymbolicName()), type, null, null, null);
         if (type.getPlan().getPlanData()!=null && REGISTERED_SPECS.containsKey(type.getPlan().getPlanData()))
-            return decorateWithHierarchySpecTag(get((String)type.getPlan().getPlanData()), type, FORMAT, null, null);
+            return decorateWithCommonTags(get((String)type.getPlan().getPlanData()), type, null, null, null);
         return null;
     }
 
diff --git a/core/src/test/java/org/apache/brooklyn/core/typereg/ExampleXmlTypePlanTransformer.java b/core/src/test/java/org/apache/brooklyn/core/typereg/ExampleXmlTypePlanTransformer.java
index ab8a6cd..320ac00 100644
--- a/core/src/test/java/org/apache/brooklyn/core/typereg/ExampleXmlTypePlanTransformer.java
+++ b/core/src/test/java/org/apache/brooklyn/core/typereg/ExampleXmlTypePlanTransformer.java
@@ -19,7 +19,6 @@
 package org.apache.brooklyn.core.typereg;
 
 import java.io.StringReader;
-import java.util.List;
 
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
@@ -68,7 +67,7 @@ public class ExampleXmlTypePlanTransformer extends AbstractTypePlanTransformer {
 
     @Override
     protected AbstractBrooklynObjectSpec<?, ?> createSpec(RegisteredType type, RegisteredTypeLoadingContext context) throws Exception {
-        return decorateWithHierarchySpecTag(toEntitySpec(parseXml((String)type.getPlan().getPlanData()),
+        return decorateWithCommonTags(toEntitySpec(parseXml((String)type.getPlan().getPlanData()),
             isApplicationExpected(type, context) ? 0 : 1), type, "example-xml", null, null);
     }
 
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/EntityResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/EntityResource.java
index 934e578..f21d6e9 100644
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/EntityResource.java
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/EntityResource.java
@@ -21,6 +21,7 @@ package org.apache.brooklyn.rest.resources;
 import static javax.ws.rs.core.Response.created;
 import static javax.ws.rs.core.Response.status;
 import static javax.ws.rs.core.Response.Status.ACCEPTED;
+import org.apache.brooklyn.core.mgmt.BrooklynTags.SpecSummary;
 import static org.apache.brooklyn.rest.util.WebResourceUtils.serviceAbsoluteUriBuilder;
 
 import java.net.URI;
@@ -315,7 +316,7 @@ public class EntityResource extends AbstractBrooklynRestResource implements Enti
     @Override
     public List<Object>  getSpecList(String applicationId, String entityId) {
         Entity entity = brooklyn().getEntity(applicationId, entityId);
-        BrooklynTags.SpecHierarchyTag specTag =  BrooklynTags.findSpecHierarchyTag(entity.tags().getTags());
-        return (List<Object>) resolving(specTag.getSpecList()).preferJson(true).resolve();
+        List<SpecSummary> specTag = BrooklynTags.findSpecHierarchyTag(entity.tags().getTags());
+        return (List<Object>) resolving(specTag).preferJson(true).resolve();
     }
 }
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/TypeTransformer.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/TypeTransformer.java
index 84f38b5..683d9d2 100644
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/TypeTransformer.java
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/TypeTransformer.java
@@ -18,7 +18,6 @@
  */
 package org.apache.brooklyn.rest.transform;
 
-import org.apache.brooklyn.core.mgmt.BrooklynTags.SpecHierarchyTag;
 import static org.apache.brooklyn.rest.util.WebResourceUtils.serviceUriBuilder;
 
 import java.net.URI;
@@ -46,6 +45,7 @@ import org.apache.brooklyn.api.typereg.RegisteredType;
 import org.apache.brooklyn.camp.brooklyn.spi.creation.CampTypePlanTransformer;
 import org.apache.brooklyn.core.entity.EntityDynamicType;
 import org.apache.brooklyn.core.mgmt.BrooklynTags;
+import org.apache.brooklyn.core.mgmt.BrooklynTags.SpecSummary;
 import org.apache.brooklyn.core.mgmt.ha.OsgiBundleInstallationResult;
 import org.apache.brooklyn.core.objs.BrooklynTypes;
 import org.apache.brooklyn.core.typereg.RegisteredTypePredicates;
@@ -61,6 +61,7 @@ import org.apache.brooklyn.rest.domain.SummaryComparators;
 import org.apache.brooklyn.rest.domain.TypeDetail;
 import org.apache.brooklyn.rest.domain.TypeSummary;
 import org.apache.brooklyn.rest.util.BrooklynRestResourceUtils;
+import org.apache.brooklyn.util.collections.MutableList;
 import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.guava.Maybe;
@@ -101,20 +102,22 @@ public class TypeTransformer {
         }
 
         // adding tag type spec hierarchy using hierarchy tag
-        SpecHierarchyTag currentSpecTag = SpecHierarchyTag.builder()
+        SpecSummary currentSpec = SpecSummary.builder()
                 .format(StringUtils.isBlank(item.getPlan().getPlanFormat()) ? CampTypePlanTransformer.FORMAT : item.getPlan().getPlanFormat())
                 // the default type implementation is camp in this location, but hierarchy tag provides the original implementation, so it takes precedence.
                 .summary((StringUtils.isBlank(item.getPlan().getPlanFormat()) ? CampTypePlanTransformer.FORMAT : item.getPlan().getPlanFormat()) + " implementation")
                 .contents(item.getPlan().getPlanData())
-                .buildSpecHierarchyTag();
+                .build();
 
-        SpecHierarchyTag specTag = BrooklynTags.findSpecHierarchyTag(item.getTags());
+        List<SpecSummary> specTag = BrooklynTags.findSpecHierarchyTag(item.getTags());
         if(specTag!= null){
-            currentSpecTag.modifyHeadSummary(s -> "Converted to "+s);
-            currentSpecTag.push(specTag);
+            SpecSummary.modifyHeadSummary(specTag, s -> "Converted to "+s);
+            SpecSummary.pushToList(specTag, currentSpec);
+        } else {
+            specTag = MutableList.of(currentSpec);
         }
 
-        result.setExtraField("specList", currentSpecTag.getSpecList());
+        result.setExtraField("specList", specTag);
         
         if (detail) {
             if (RegisteredTypes.isSubtypeOf(item, Entity.class)) {