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 2017/09/25 09:29:34 UTC

[03/11] brooklyn-server git commit: REST API for bundles, types, and subtypes

REST API for bundles, types, and subtypes

also tweaks to other classes to make them more usable, eg VersionedName comparables and bundle removal giving an OsgiBundleInstallationResult

test in BundleAndTypeAndSubtypeResourcesTest based on CatalogResourceTest, basically giving parity in terms of functionality and test coverage


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

Branch: refs/heads/master
Commit: 9b337035992f77c7e156bba4d3b3ce35438ca180
Parents: 36de666
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Thu Sep 7 16:43:45 2017 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Thu Sep 7 17:15:00 2017 +0100

----------------------------------------------------------------------
 .../brooklyn/api/catalog/BrooklynCatalog.java   |    1 -
 .../typereg/RegisteredTypeLoadingContext.java   |    2 +-
 .../catalog/internal/BasicBrooklynCatalog.java  |  106 +-
 .../core/mgmt/ha/OsgiArchiveInstaller.java      |   18 +-
 .../mgmt/ha/OsgiBundleInstallationResult.java   |   21 +-
 .../brooklyn/core/mgmt/ha/OsgiManager.java      |   89 +-
 .../core/typereg/RegisteredTypePredicates.java  |   54 +-
 .../brooklyn/core/typereg/RegisteredTypes.java  |   43 +-
 .../org/apache/brooklyn/rest/api/BundleApi.java |  158 +++
 .../apache/brooklyn/rest/api/CatalogApi.java    |    3 +
 .../apache/brooklyn/rest/api/SubtypeApi.java    |   96 ++
 .../org/apache/brooklyn/rest/api/TypeApi.java   |   94 ++
 .../rest/domain/AdjunctConfigSummary.java       |   85 ++
 .../domain/BundleInstallationRestResult.java    |   67 ++
 .../brooklyn/rest/domain/BundleSummary.java     |   99 ++
 .../brooklyn/rest/domain/ConfigSummary.java     |   17 +-
 .../rest/domain/EnricherConfigSummary.java      |   54 +-
 .../rest/domain/PolicyConfigSummary.java        |   53 +-
 .../brooklyn/rest/domain/TaskSummary.java       |    9 +-
 .../apache/brooklyn/rest/domain/TypeDetail.java |   86 ++
 .../brooklyn/rest/domain/TypeSummary.java       |  287 +++++
 .../apache/brooklyn/rest/BrooklynRestApi.java   |    8 +-
 .../brooklyn/rest/resources/BundleResource.java |  186 +++
 .../rest/resources/CatalogResource.java         |   40 +-
 .../rest/resources/SubtypeResource.java         |   82 ++
 .../brooklyn/rest/resources/TypeResource.java   |  186 +++
 .../rest/transform/EntityTransformer.java       |   25 +-
 .../rest/transform/TypeTransformer.java         |  194 ++++
 .../BundleAndTypeAndSubtypeResourcesTest.java   | 1093 ++++++++++++++++++
 .../brooklyn/util/osgi/VersionedName.java       |   24 +-
 30 files changed, 3062 insertions(+), 218 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/9b337035/api/src/main/java/org/apache/brooklyn/api/catalog/BrooklynCatalog.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/org/apache/brooklyn/api/catalog/BrooklynCatalog.java b/api/src/main/java/org/apache/brooklyn/api/catalog/BrooklynCatalog.java
index cbc5b2b..9e19a81 100644
--- a/api/src/main/java/org/apache/brooklyn/api/catalog/BrooklynCatalog.java
+++ b/api/src/main/java/org/apache/brooklyn/api/catalog/BrooklynCatalog.java
@@ -61,7 +61,6 @@ public interface BrooklynCatalog {
     <T,SpecT> Iterable<CatalogItem<T,SpecT>> getCatalogItems();
 
     /** convenience for filtering items in the catalog; see CatalogPredicates for useful filters */
-//    XXX
     <T,SpecT> Iterable<CatalogItem<T,SpecT>> getCatalogItems(Predicate<? super CatalogItem<T,SpecT>> filter);
 
     /** persists the catalog item to the object store, if persistence is enabled */

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/9b337035/api/src/main/java/org/apache/brooklyn/api/typereg/RegisteredTypeLoadingContext.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/org/apache/brooklyn/api/typereg/RegisteredTypeLoadingContext.java b/api/src/main/java/org/apache/brooklyn/api/typereg/RegisteredTypeLoadingContext.java
index d37666e..925b6b1 100644
--- a/api/src/main/java/org/apache/brooklyn/api/typereg/RegisteredTypeLoadingContext.java
+++ b/api/src/main/java/org/apache/brooklyn/api/typereg/RegisteredTypeLoadingContext.java
@@ -44,7 +44,7 @@ public interface RegisteredTypeLoadingContext {
      * the instantiator can avoid recursive cycles */
     @Nonnull public Set<String> getAlreadyEncounteredTypes();
     
-    /** A loader to use, supplying additional search paths */
+    /** A loader to use, supplying preferred or additional bundles and search paths */
     @Nullable public BrooklynClassLoadingContext getLoader();
     
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/9b337035/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java
index bfca5ca..0de825e 100644
--- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java
+++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java
@@ -1381,43 +1381,8 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
         Maybe<OsgiManager> osgiManager = ((ManagementContextInternal)mgmt).getOsgiManager();
         if (osgiManager.isPresent() && AUTO_WRAP_CATALOG_YAML_AS_BUNDLE) {
             // wrap in a bundle to be managed; need to get bundle and version from yaml
-            Map<?, ?> cm = BasicBrooklynCatalog.getCatalogMetadata(yaml);
-            VersionedName vn = BasicBrooklynCatalog.getVersionedName( cm, false );
-            if (vn==null) {
-                // for better legacy compatibiity, if id specified at root use that
-                String id = (String) cm.get("id");
-                if (Strings.isNonBlank(id)) {
-                    vn = VersionedName.fromString(id);
-                }
-                vn = new VersionedName(vn!=null && Strings.isNonBlank(vn.getSymbolicName()) ? vn.getSymbolicName() : "brooklyn-catalog-bom-"+Identifiers.makeRandomId(8), 
-                    vn!=null && vn.getVersionString()!=null ? vn.getVersionString() : getFirstAs(cm, String.class, "version").or(NO_VERSION));
-            }
-            log.debug("Wrapping supplied BOM as "+vn);
-            Manifest mf = new Manifest();
-            mf.getMainAttributes().putValue(Constants.BUNDLE_SYMBOLICNAME, vn.getSymbolicName());
-            mf.getMainAttributes().putValue(Constants.BUNDLE_VERSION, vn.getOsgiVersionString());
-            mf.getMainAttributes().putValue(Constants.BUNDLE_MANIFESTVERSION, "2");
-            mf.getMainAttributes().putValue(Attributes.Name.MANIFEST_VERSION.toString(), OSGI_MANIFEST_VERSION_VALUE);
-            mf.getMainAttributes().putValue(BROOKLYN_WRAPPED_BOM_BUNDLE, Boolean.TRUE.toString());
-            
-            BundleMaker bm = new BundleMaker(mgmt);
-            File bf = bm.createTempBundle(vn.getSymbolicName(), mf, MutableMap.of(
-                new ZipEntry(CATALOG_BOM), (InputStream) new ByteArrayInputStream(yaml.getBytes())) );
-
-            OsgiBundleInstallationResult result = null;
-            try {
-                result = osgiManager.get().install(null, new FileInputStream(bf), true, true, forceUpdate).get();
-            } catch (FileNotFoundException e) {
-                throw Exceptions.propagate(e);
-            } finally {
-                bf.delete();
-            }
-            if (result.getCode().isError()) {
-                // rollback done by install call above
-                throw new IllegalStateException(result.getMessage());
-            }
-            uninstallEmptyWrapperBundles();
-            return toLegacyCatalogItems(result.getCatalogItemsInstalled());
+            OsgiBundleInstallationResult result = addItemsOsgi(yaml, forceUpdate, osgiManager);
+            return toLegacyCatalogItems(result.getTypesInstalled());
 
             // if all items pertaining to an older anonymous catalog.bom bundle have been overridden
             // we delete those later; see list of wrapper bundles kept in OsgiManager
@@ -1426,10 +1391,73 @@ public class BasicBrooklynCatalog implements BrooklynCatalog {
         return addItems(yaml, null, forceUpdate);
     }
     
+    /** Like {@link #addItems(String, boolean)} but returning the {@link OsgiBundleInstallationResult} for use from new environments.
+     * If not using OSGi the bundle/code/etc fields are null but the types will always be set. */
+    @SuppressWarnings("deprecation")
+    public OsgiBundleInstallationResult addItemsBundleResult(String yaml, boolean forceUpdate) {
+        Maybe<OsgiManager> osgiManager = ((ManagementContextInternal)mgmt).getOsgiManager();
+        if (osgiManager.isPresent() && AUTO_WRAP_CATALOG_YAML_AS_BUNDLE) {
+            // wrap in a bundle to be managed; need to get bundle and version from yaml
+            return addItemsOsgi(yaml, forceUpdate, osgiManager);
+
+            // if all items pertaining to an older anonymous catalog.bom bundle have been overridden
+            // we delete those later; see list of wrapper bundles kept in OsgiManager
+        }
+        // fallback to non-OSGi for tests and other environments
+        List<? extends CatalogItem<?, ?>> items = addItems(yaml, null, forceUpdate);
+        OsgiBundleInstallationResult result = new OsgiBundleInstallationResult();
+        for (CatalogItem<?, ?> ci: items) {
+            RegisteredType rt = mgmt.getTypeRegistry().get(ci.getId());
+            result.getTypesInstalled().add(rt!=null ? rt : RegisteredTypes.of(ci));
+        }
+        return result;
+    }
+
+    protected OsgiBundleInstallationResult addItemsOsgi(String yaml, boolean forceUpdate, Maybe<OsgiManager> osgiManager) {
+        Map<?, ?> cm = BasicBrooklynCatalog.getCatalogMetadata(yaml);
+        VersionedName vn = BasicBrooklynCatalog.getVersionedName( cm, false );
+        if (vn==null) {
+            // for better legacy compatibiity, if id specified at root use that
+            String id = (String) cm.get("id");
+            if (Strings.isNonBlank(id)) {
+                vn = VersionedName.fromString(id);
+            }
+            vn = new VersionedName(vn!=null && Strings.isNonBlank(vn.getSymbolicName()) ? vn.getSymbolicName() : "brooklyn-catalog-bom-"+Identifiers.makeRandomId(8), 
+                vn!=null && vn.getVersionString()!=null ? vn.getVersionString() : getFirstAs(cm, String.class, "version").or(NO_VERSION));
+        }
+        log.debug("Wrapping supplied BOM as "+vn);
+        Manifest mf = new Manifest();
+        mf.getMainAttributes().putValue(Constants.BUNDLE_SYMBOLICNAME, vn.getSymbolicName());
+        mf.getMainAttributes().putValue(Constants.BUNDLE_VERSION, vn.getOsgiVersionString());
+        mf.getMainAttributes().putValue(Constants.BUNDLE_MANIFESTVERSION, "2");
+        mf.getMainAttributes().putValue(Attributes.Name.MANIFEST_VERSION.toString(), OSGI_MANIFEST_VERSION_VALUE);
+        mf.getMainAttributes().putValue(BROOKLYN_WRAPPED_BOM_BUNDLE, Boolean.TRUE.toString());
+        
+        BundleMaker bm = new BundleMaker(mgmt);
+        File bf = bm.createTempBundle(vn.getSymbolicName(), mf, MutableMap.of(
+            new ZipEntry(CATALOG_BOM), (InputStream) new ByteArrayInputStream(yaml.getBytes())) );
+
+        OsgiBundleInstallationResult result = null;
+        try {
+            result = osgiManager.get().install(null, new FileInputStream(bf), true, true, forceUpdate).get();
+        } catch (FileNotFoundException e) {
+            throw Exceptions.propagate(e);
+        } finally {
+            bf.delete();
+        }
+        if (result.getCode().isError()) {
+            // rollback done by install call above
+            throw new IllegalStateException(result.getMessage());
+        }
+        uninstallEmptyWrapperBundles();
+        return result;
+    }
+    
     @SuppressWarnings("deprecation")
-    private List<CatalogItem<?,?>> toLegacyCatalogItems(Iterable<String> itemIds) {
+    private List<CatalogItem<?,?>> toLegacyCatalogItems(Iterable<RegisteredType> list) {
         List<CatalogItem<?,?>> result = MutableList.of();
-        for (String id: itemIds) {
+        for (RegisteredType t: list) {
+            String id = t.getId();
             CatalogItem<?, ?> item = CatalogUtils.getCatalogItemOptionalVersion(mgmt, id);
             if (item==null) {
                 // using new Type Registry (OSGi addition); 

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/9b337035/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiArchiveInstaller.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiArchiveInstaller.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiArchiveInstaller.java
index 2b837ba..15430e5 100644
--- a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiArchiveInstaller.java
+++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiArchiveInstaller.java
@@ -60,6 +60,7 @@ import org.osgi.framework.Constants;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.base.Function;
 import com.google.common.base.Objects;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.Iterables;
@@ -446,7 +447,7 @@ class OsgiArchiveInstaller {
                             Iterable<RegisteredType> items = mgmt().getTypeRegistry().getMatching(RegisteredTypePredicates.containingBundle(result.getMetadata()));
                             log.debug("Adding items from bundle "+result.getVersionedName()+": "+items);
                             for (RegisteredType ci: items) {
-                                result.catalogItemsInstalled.add(ci.getId());
+                                result.addType(ci);
                             }
                         } catch (Exception e) {
                             // unable to install new items; rollback bundles
@@ -494,15 +495,20 @@ class OsgiArchiveInstaller {
                 log.debug(result.message+" (Brooklyn load deferred)");
             } else {
                 startRunnable.run();
-                if (!result.catalogItemsInstalled.isEmpty()) {
+                if (!result.typesInstalled.isEmpty()) {
                     // show fewer info messages, only for 'interesting' and non-deferred installations
                     // (rebind is deferred, as are tests, but REST is not)
                     final int MAX_TO_LIST_EXPLICITLY = 5;
-                    MutableList<String> firstN = MutableList.copyOf(Iterables.limit(result.catalogItemsInstalled, MAX_TO_LIST_EXPLICITLY));
+                    Iterable<String> firstN = Iterables.transform(MutableList.copyOf(Iterables.limit(result.typesInstalled, MAX_TO_LIST_EXPLICITLY)),
+                        new Function<RegisteredType,String>() {
+                            @Override public String apply(RegisteredType input) {
+                                return input.getVersionedName().toString();
+                            }
+                        });
                     log.info(result.message+", items: "+firstN+
-                        (result.catalogItemsInstalled.size() > MAX_TO_LIST_EXPLICITLY ? " (and others, "+result.catalogItemsInstalled.size()+" total)" : "") );
-                    if (log.isDebugEnabled() && result.catalogItemsInstalled.size()>MAX_TO_LIST_EXPLICITLY) {
-                        log.debug(result.message+", all items: "+result.catalogItemsInstalled);
+                        (result.typesInstalled.size() > MAX_TO_LIST_EXPLICITLY ? " (and others, "+result.typesInstalled.size()+" total)" : "") );
+                    if (log.isDebugEnabled() && result.typesInstalled.size()>MAX_TO_LIST_EXPLICITLY) {
+                        log.debug(result.message+", all items: "+result.typesInstalled);
                     }
                 } else {
                     log.debug(result.message+" (into Brooklyn), with no catalog items");

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/9b337035/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiBundleInstallationResult.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiBundleInstallationResult.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiBundleInstallationResult.java
index c611553..d65f105 100644
--- a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiBundleInstallationResult.java
+++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiBundleInstallationResult.java
@@ -21,6 +21,7 @@ package org.apache.brooklyn.core.mgmt.ha;
 import java.util.List;
 
 import org.apache.brooklyn.api.typereg.ManagedBundle;
+import org.apache.brooklyn.api.typereg.RegisteredType;
 import org.apache.brooklyn.util.collections.MutableList;
 import org.apache.brooklyn.util.osgi.VersionedName;
 import org.osgi.framework.Bundle;
@@ -48,14 +49,21 @@ public class OsgiBundleInstallationResult {
         /** bundle successfully installed to OSGi container but there was an error launching it, 
          * either the OSGi bundle start, catalog items load, or (most commonly) validating the catalog items;
          * bundle may be installed (currently it is in most/all places, but behaviour TBC) so caller may have to uninstall it */
-        ERROR_LAUNCHING_BUNDLE(true);
+        ERROR_LAUNCHING_BUNDLE(true),
+        // codes below used for deletion
+        BUNDLE_REMOVED(false),
+        ERROR_REMOVING_BUNDLE_IN_USE(true),
+        ERROR_REMOVING_BUNDLE_OTHER(true);
         
         final boolean isError;
         ResultCode(boolean isError) { this.isError = isError; }
         
         public boolean isError() { return isError; }
     }
-    final List<String> catalogItemsInstalled = MutableList.of();
+    final List<RegisteredType> typesInstalled = MutableList.of();
+    /** @deprecated since 0.12.0 use {@link #typesInstalled} */
+    @Deprecated
+    private final List<String> catalogItemsInstalled = MutableList.of();
     
     public String getMessage() {
         return message;
@@ -69,6 +77,11 @@ public class OsgiBundleInstallationResult {
     public ResultCode getCode() {
         return code;
     }
+    public List<RegisteredType> getTypesInstalled() {
+        return typesInstalled;
+    }
+    /** @deprecated since 0.12.0 use {@link #getTypesInstalled()} */
+    @Deprecated
     public List<String> getCatalogItemsInstalled() {
         return ImmutableList.copyOf(catalogItemsInstalled);
     }
@@ -89,4 +102,8 @@ public class OsgiBundleInstallationResult {
     public String toString() {
         return OsgiBundleInstallationResult.class.getSimpleName()+"["+code+", "+metadata+", "+message+"]";
     }
+    public void addType(RegisteredType ci) {
+        typesInstalled.add(ci);
+        catalogItemsInstalled.add(ci.getId());        
+    }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/9b337035/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiManager.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiManager.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiManager.java
index d0c7800..603927a 100644
--- a/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiManager.java
+++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/ha/OsgiManager.java
@@ -46,6 +46,7 @@ import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.core.BrooklynVersion;
 import org.apache.brooklyn.core.catalog.internal.CatalogBundleLoader;
 import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.mgmt.ha.OsgiBundleInstallationResult.ResultCode;
 import org.apache.brooklyn.core.server.BrooklynServerConfig;
 import org.apache.brooklyn.core.server.BrooklynServerPaths;
 import org.apache.brooklyn.core.typereg.RegisteredTypePredicates;
@@ -373,6 +374,11 @@ public class OsgiManager {
         return installer.install();
     }
     
+    /** Convenience for {@link #uninstallUploadedBundle(ManagedBundle, boolean)} without forcing, and throwing on error */
+    public OsgiBundleInstallationResult uninstallUploadedBundle(ManagedBundle bundleMetadata) {
+        return uninstallUploadedBundle(bundleMetadata, false).get();
+    }
+    
     /**
      * Removes this bundle from Brooklyn management, 
      * removes all catalog items it defined,
@@ -382,27 +388,78 @@ public class OsgiManager {
      * behaviour of such things is not guaranteed. They will work for many things
      * but attempts to load new classes may fail.
      * <p>
-     * Callers should typically fail if anything from this bundle is in use.
+     * Callers should typically fail prior to invoking if anything from this bundle is in use.
+     * <p>
+     * This does not throw but returns a reference containing errors and result for caller to inspect and handle. 
      */
-    public void uninstallUploadedBundle(ManagedBundle bundleMetadata) {
-        uninstallCatalogItemsFromBundle( bundleMetadata.getVersionedName() );
+    public ReferenceWithError<OsgiBundleInstallationResult> uninstallUploadedBundle(ManagedBundle bundleMetadata, boolean force) {
+        OsgiBundleInstallationResult result = new OsgiBundleInstallationResult();
+        result.metadata = bundleMetadata;
+        List<Throwable> errors = MutableList.of();
+        boolean uninstalledItems = false;
         
-        if (!managedBundlesRecord.remove(bundleMetadata)) {
-            throw new IllegalStateException("No such bundle registered: "+bundleMetadata);
+        try {
+            try {
+                Iterable<RegisteredType> itemsRemoved = uninstallCatalogItemsFromBundle( bundleMetadata.getVersionedName() );
+                for (RegisteredType t: itemsRemoved) result.addType(t);
+                uninstalledItems = true;
+            } catch (Exception e) {
+                Exceptions.propagateIfFatal(e);
+                if (!force) Exceptions.propagate(e);
+                log.warn("Error uninstalling catalog items of "+bundleMetadata+": "+e);
+                errors.add(e);
+            }
+            
+            if (!managedBundlesRecord.remove(bundleMetadata)) {
+                Exception e = new IllegalStateException("No such bundle registered with Brooklyn when uninstalling: "+bundleMetadata);
+                if (!force) Exceptions.propagate(e);
+                log.warn(e.getMessage());
+                errors.add(e);
+            }
+            try {
+                mgmt.getRebindManager().getChangeListener().onUnmanaged(bundleMetadata);
+            } catch (Exception e) {
+                Exceptions.propagateIfFatal(e);
+                if (!force) Exceptions.propagate(e);
+                log.warn("Error handling unmanagement of "+bundleMetadata+": "+e);
+                errors.add(e);            
+            }
+            
+            Bundle bundle = framework.getBundleContext().getBundle(bundleMetadata.getOsgiUniqueUrl());
+            result.bundle = bundle;
+            if (bundle==null) {
+                Exception e = new IllegalStateException("No such bundle installed in OSGi when uninstalling: "+bundleMetadata);
+                if (!force) Exceptions.propagate(e);
+                log.warn(e.getMessage());
+                errors.add(e);
+            } else {
+                try {
+                    bundle.stop();
+                    bundle.uninstall();
+                } catch (BundleException e) {
+                    Exceptions.propagateIfFatal(e);
+                    if (!force) Exceptions.propagate(e);
+                    log.warn("Error stopping and uninstalling "+bundleMetadata+": "+e);
+                    errors.add(e);            
+                }
+            }
+        } catch (Exception e) {
+            Exceptions.propagateIfFatal(e);
+            if (!force) Exceptions.propagate(e);
+            log.warn("Error removing "+bundleMetadata+": "+e);
+            errors.add(e);            
         }
-        mgmt.getRebindManager().getChangeListener().onUnmanaged(bundleMetadata);
-
         
-        Bundle bundle = framework.getBundleContext().getBundle(bundleMetadata.getOsgiUniqueUrl());
-        if (bundle==null) {
-            throw new IllegalStateException("No such bundle installed: "+bundleMetadata);
-        }
-        try {
-            bundle.stop();
-            bundle.uninstall();
-        } catch (BundleException e) {
-            throw Exceptions.propagate(e);
+        if (errors.isEmpty()) {
+            result.message = "Uninstalled "+bundleMetadata+" (type count "+result.typesInstalled.size()+", OSGi "+result.bundle+")";
+            result.code = ResultCode.BUNDLE_REMOVED;
+            return ReferenceWithError.newInstanceWithoutError(result);
         }
+        
+        RuntimeException e = Exceptions.create("Error removing bundle "+bundleMetadata, errors);
+        result.message = Exceptions.collapseText(e);
+        result.code = uninstalledItems ? ResultCode.ERROR_REMOVING_BUNDLE_OTHER : ResultCode.ERROR_REMOVING_BUNDLE_IN_USE;
+        return ReferenceWithError.newInstanceThrowingError(result, e);
     }
 
     @Beta

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/9b337035/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypePredicates.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypePredicates.java b/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypePredicates.java
index 0bf3490..f0189e2 100644
--- a/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypePredicates.java
+++ b/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypePredicates.java
@@ -175,6 +175,31 @@ public class RegisteredTypePredicates {
         }
     }
 
+    /** Filters for the symbolic name or alias matching the given typeName. */
+    public static Predicate<RegisteredType> nameOrAlias(final String typeName) {
+        return nameOrAlias(Predicates.equalTo(typeName));
+    }
+    public static Predicate<RegisteredType> nameOrAlias(final Predicate<? super String> filter) {
+        return new NameOrAliasMatches(filter);
+    }
+    
+    private static class NameOrAliasMatches implements Predicate<RegisteredType> {
+        private final Predicate<? super String> filter;
+        
+        public NameOrAliasMatches(Predicate<? super String> filter) {
+            this.filter = filter;
+        }
+        @Override
+        public boolean apply(@Nullable RegisteredType item) {
+            if (item==null) return false;
+            if (filter.apply(item.getSymbolicName())) return true;
+            for (String alias: item.getAliases()) {
+                if (filter.apply(alias)) return true;
+            }
+            return false;
+        }
+    }
+
     public static Predicate<RegisteredType> tag(final Object tag) {
         return tags(CollectionFunctionals.any(Predicates.equalTo(tag)));
     }
@@ -194,27 +219,42 @@ public class RegisteredTypePredicates {
         }
     }
 
-    public static <T> Predicate<RegisteredType> anySuperType(final Predicate<Class<T>> filter) {
+    public static <T> Predicate<RegisteredType> anySuperType(final Predicate<Object> filter) {
         return new AnySuperTypeMatches(filter);
     }
-    @SuppressWarnings({ "unchecked", "rawtypes" })
     public static Predicate<RegisteredType> subtypeOf(final Class<?> filter) {
         // the assignableFrom predicate checks if this class is assignable from the subsequent *input*.
         // in other words, we're checking if any input is a subtype of this class
-        return anySuperType((Predicate)Predicates.assignableFrom(filter));
+        return anySuperType(new Predicate<Object>() {
+            @Override
+            public boolean apply(Object input) {
+                if (!(input instanceof Class)) return false;
+                return filter.isAssignableFrom((Class<?>)input);
+            }
+        });
+    }
+    public static Predicate<RegisteredType> subtypeOf(final String filter) {
+        // the assignableFrom predicate checks if this class is assignable from the subsequent *input*.
+        // in other words, we're checking if any input is a subtype of this class
+        return anySuperType(new Predicate<Object>() {
+            @Override
+            public boolean apply(Object input) {
+                if (input instanceof Class) input = ((Class<?>)input).getName();
+                return filter.equals(input);
+            }
+        });
     }
     
     private static class AnySuperTypeMatches implements Predicate<RegisteredType> {
-        private final Predicate<Class<?>> filter;
+        private final Predicate<Object> filter;
         
-        @SuppressWarnings({ "rawtypes", "unchecked" })
-        private AnySuperTypeMatches(Predicate filter) {
+        private AnySuperTypeMatches(Predicate<Object> filter) {
             this.filter = filter;
         }
         @Override
         public boolean apply(@Nullable RegisteredType item) {
             if (item==null) return false;
-            return RegisteredTypes.isAnyTypeOrSuperSatisfying(item.getSuperTypes(), filter);
+            return RegisteredTypes.isAnyTypeOrSuper(item.getSuperTypes(), filter);
         }
     }
     

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/9b337035/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypes.java
----------------------------------------------------------------------
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 6ec5983..7ee137c 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
@@ -65,7 +65,6 @@ import com.google.common.annotations.Beta;
 import com.google.common.base.Function;
 import com.google.common.base.Objects;
 import com.google.common.base.Predicate;
-import com.google.common.base.Predicates;
 import com.google.common.collect.ComparisonChain;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Ordering;
@@ -406,12 +405,50 @@ public class RegisteredTypes {
      * to see whether any inherit from the given {@link Class} */
     public static boolean isAnyTypeSubtypeOf(Set<Object> candidateTypes, Class<?> superType) {
         if (superType == Object.class) return true;
-        return isAnyTypeOrSuperSatisfying(candidateTypes, Predicates.assignableFrom(superType));
+        return isAnyTypeOrSuper(candidateTypes, new Predicate<Object>() {
+            @Override
+            public boolean apply(Object input) {
+                return input instanceof Class && superType.isAssignableFrom( (Class<?>)input );
+            }
+        });
+    }
+
+    /** 
+     * Queries recursively the given types (either {@link Class} or {@link RegisteredType}) 
+     * to see whether any inherit from the given type either in the registry or a java class  */
+    public static boolean isAnyTypeSubtypeOf(Set<Object> candidateTypes, String superType) {
+        if (Object.class.getName().equals(superType)) return true;
+        return isAnyTypeOrSuper(candidateTypes, new Predicate<Object>() {
+            @Override
+            public boolean apply(Object input) {
+                if (input instanceof Class) input = ((Class<?>)input).getName();
+                return superType.equals(input);
+            }
+        });
+    }
+
+    /** 
+     * Queries recursively the given types (either {@link Class} or {@link RegisteredType}) 
+     * to see whether any superclasses satisfy the given {@link Predicate} comparing as string or class */
+    public static boolean isAnyTypeOrSuper(Set<Object> candidateTypes, Predicate<Object> filter) {
+        for (Object st: candidateTypes) {
+            if (filter.apply(st)) return true;
+        }
+        for (Object st: candidateTypes) {
+            if (st instanceof RegisteredType) {
+                if (isAnyTypeOrSuper(((RegisteredType)st).getSuperTypes(), filter)) return true;
+            }
+        }
+        return false;
     }
 
     /** 
      * Queries recursively the given types (either {@link Class} or {@link RegisteredType}) 
-     * to see whether any java superclasses satisfy the given {@link Predicate} */
+     * to see whether any java superclasses satisfy the given {@link Predicate} on the {@link Class} 
+     * @deprecated since 0.12.0 use {@link #isAnyTypeOrSuper(Set, Predicate)} accepting any object in the predicate,
+     * typically allowing string equivalence although it is valid to restrict to {@link Class} comparison
+     * (might be stricter in some OSGi cases) */
+    @Deprecated
     public static boolean isAnyTypeOrSuperSatisfying(Set<Object> candidateTypes, Predicate<Class<?>> filter) {
         for (Object st: candidateTypes) {
             if (st instanceof Class) {

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/9b337035/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/BundleApi.java
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/BundleApi.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/BundleApi.java
new file mode 100644
index 0000000..2c2eeae
--- /dev/null
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/BundleApi.java
@@ -0,0 +1,158 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.api;
+
+import java.util.List;
+
+import javax.validation.Valid;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.apache.brooklyn.rest.domain.BundleInstallationRestResult;
+import org.apache.brooklyn.rest.domain.BundleSummary;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+
+@Path("/bundles")
+@Api("Bundles")
+@Consumes(MediaType.APPLICATION_JSON)
+@Produces(MediaType.APPLICATION_JSON)
+public interface BundleApi {
+
+    @GET
+    @ApiOperation(value = "List bundles registered in the system including their types", 
+            response = BundleSummary.class,
+            responseContainer = "List")
+    public List<BundleSummary> list(
+        @ApiParam(name = "versions", value = "Whether to list 'latest' for each symbolic-name or 'all' versions", 
+            required = false, defaultValue = "latest")
+        @QueryParam("versions")
+        String versions);
+
+    @Path("/{symbolicName}")
+    @GET
+    @ApiOperation(value = "Get summaries for all versions of the given bundle, with more recent ones first (preferring non-SNAPSHOTs)", 
+            response = BundleSummary.class,
+            responseContainer = "List")
+    public List<BundleSummary> listVersions(
+        @ApiParam(name = "symbolicName", value = "Bundle name to query", required = true)
+        @PathParam("symbolicName")
+        String symbolicName);
+
+    @Path("/{symbolicName}/{version}")
+    @GET
+    @ApiOperation(value = "Get detail on a specific bundle given its symbolic name and version", 
+            response = BundleSummary.class)
+    public BundleSummary detail(
+        @ApiParam(name = "symbolicName", value = "Bundle name to query", required = true)
+        @PathParam("symbolicName")
+        String symbolicName,
+        @ApiParam(name = "version", value = "Version to query", required = true)
+        @PathParam("version")
+        String version);
+
+    
+    @Path("/{symbolicName}/{version}")
+    @DELETE
+    @ApiOperation(value = "Removes a bundle, unregistering all the types it declares", 
+            response = BundleInstallationRestResult.class)
+    public BundleInstallationRestResult remove(
+        @ApiParam(name = "symbolicName", value = "Bundle name to query", required = true)
+        @PathParam("symbolicName")
+        String symbolicName,
+        @ApiParam(name = "version", value = "Version to query", required = true)
+        @PathParam("version")
+        String version,
+        @ApiParam(name = "force", value = "Whether to forcibly remove it, even if in use and/or errors", required = false, defaultValue = "false")
+        @QueryParam("force") @DefaultValue("false")
+        Boolean force);
+
+    @POST
+    @Consumes({MediaType.APPLICATION_JSON, "application/x-yaml",
+        // see http://stackoverflow.com/questions/332129/yaml-mime-type
+        "text/yaml", "text/x-yaml", "application/yaml"})
+    @ApiOperation(
+            value = "Adds types to the registry from a given BOM YAML/JSON descriptor (creating a bundle with just this file in it)",
+            response = BundleInstallationRestResult.class
+    )
+    @ApiResponses(value = {
+            @ApiResponse(code = 400, message = "Error processing the given YAML"),
+            @ApiResponse(code = 201, message = "Items added successfully")
+    })
+    public Response createFromYaml(
+            @ApiParam(name = "yaml", value = "BOM YAML declaring the types to be installed", required = true)
+            @Valid String yaml,
+            @ApiParam(name="force", value="Force installation including replacing any different bundle of the same name and version")
+            @QueryParam("force") @DefaultValue("false")
+            Boolean forceUpdate);
+
+    @POST
+    @Consumes({"application/x-zip", "application/x-jar"})
+    @ApiOperation(
+            value = "Adds types to the registry from a given JAR or ZIP",
+            notes = "Accepts either an OSGi bundle JAR, or ZIP which will be turned into bundle JAR. Either format must "
+                    + "contain a catalog.bom at the root of the archive, which must contain the bundle and version key.",
+            response = BundleInstallationRestResult.class)
+    @ApiResponses(value = {
+            @ApiResponse(code = 400, message = "Error processing the given archive, or the catalog.bom is invalid"),
+            @ApiResponse(code = 201, message = "Catalog items added successfully")
+    })
+    public Response createFromArchive(
+            @ApiParam(
+                    name = "archive",
+                    value = "Bundle to install, in ZIP or JAR format, requiring catalog.bom containing bundle name and version",
+                    required = true)
+            byte[] archive,
+            @ApiParam(name = "force", value = "Whether to forcibly remove it, even if in use and/or errors", required = false, defaultValue = "false")
+            @QueryParam("force") @DefaultValue("false")
+            Boolean force);
+
+    @POST
+    @Consumes // anything (if doesn't match other methods with specific content types
+    @ApiOperation(
+            value = "Adds types to the registry from the given item, autodetecting type as ZIP/JAR or BOM YAML",
+            response = BundleInstallationRestResult.class
+    )
+    @ApiResponses(value = {
+            @ApiResponse(code = 400, message = "Error processing the given archive, or the catalog.bom is invalid"),
+            @ApiResponse(code = 201, message = "Catalog items added successfully")
+    })
+    public Response createAutodetecting(
+            @ApiParam(
+                    name = "item",
+                    value = "Item to install, as JAR/ZIP or Catalog YAML (autodetected)",
+                    required = true)
+                    byte[] item,
+            @ApiParam(name = "force", value = "Whether to forcibly remove it, even if in use and/or errors", required = false, defaultValue = "false")
+            @QueryParam("force") @DefaultValue("false")
+            Boolean force);
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/9b337035/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/CatalogApi.java
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/CatalogApi.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/CatalogApi.java
index 698f97f..3cbae5b 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/CatalogApi.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/CatalogApi.java
@@ -47,6 +47,9 @@ import io.swagger.annotations.ApiParam;
 import io.swagger.annotations.ApiResponse;
 import io.swagger.annotations.ApiResponses;
 
+/** @deprecated since 0.12.0 use /bundle, /type, and /subtype */
+// but we will probably keep this around for a while as many places use it
+@Deprecated
 @Path("/catalog")
 @Api("Catalog")
 @Consumes(MediaType.APPLICATION_JSON)

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/9b337035/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/SubtypeApi.java
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/SubtypeApi.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/SubtypeApi.java
new file mode 100644
index 0000000..01890c5
--- /dev/null
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/SubtypeApi.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.api;
+
+import java.util.List;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+
+import org.apache.brooklyn.rest.domain.TypeSummary;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+
+@Path("/subtypes")
+@Api("Subtypes")
+@Consumes(MediaType.APPLICATION_JSON)
+@Produces(MediaType.APPLICATION_JSON)
+public interface SubtypeApi {
+
+    @Path("/{supertype}")
+    @GET
+    @ApiOperation(value = "Get all known types which declare the given argument as a supertype", 
+            response = TypeSummary.class, responseContainer = "List")
+    public List<TypeSummary> list(
+        @ApiParam(name = "supertype", value = "Supertype to query", required = true)
+        @PathParam("supertype")
+        String supertype,
+        @ApiParam(name = "versions", value = "Whether to list 'latest' of each symbolic-name or 'all' versions", required = false, defaultValue = "latest")
+        @QueryParam("versions")
+        String versions,
+        @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);
+
+    // conveniences for common items where internally it uses java class name
+    // caller can of course use /subtypes/org.apache.brooklyn.api.Entity
+    
+    @GET @Path("/application")
+    @ApiOperation(value = "Get all applications", response = TypeSummary.class, responseContainer = "List")
+    public List<TypeSummary> listApplications(@ApiParam(name = "versions", value = "Whether to list 'latest' of each symbolic-name or 'all' versions", required = false, defaultValue = "latest") @QueryParam("versions") String versions,
+        @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);
+    
+    @GET @Path("/entity")
+    @ApiOperation(value = "Get all entities", response = TypeSummary.class, responseContainer = "List")
+    public List<TypeSummary> listEntities(@ApiParam(name = "versions", value = "Whether to list 'latest' of each symbolic-name or 'all' versions", required = false, defaultValue = "latest") @QueryParam("versions") String versions,
+        @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);
+    
+    @GET @Path("/policy")
+    @ApiOperation(value = "Get all policies", response = TypeSummary.class, responseContainer = "List")
+    public List<TypeSummary> listPolicies(@ApiParam(name = "versions", value = "Whether to list 'latest' of each symbolic-name or 'all' versions", required = false, defaultValue = "latest") @QueryParam("versions") String versions,
+        @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);
+    
+    @GET @Path("/enricher")
+    @ApiOperation(value = "Get all enrichers", response = TypeSummary.class, responseContainer = "List")
+    public List<TypeSummary> listEnrichers(@ApiParam(name = "versions", value = "Whether to list 'latest' of each symbolic-name or 'all' versions", required = false, defaultValue = "latest") @QueryParam("versions") String versions,
+        @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);
+
+    @GET @Path("/location")
+    // note some (deprecated) locations are only available on location manager 
+    @ApiOperation(value = "Get all locations stored in the registry", response = TypeSummary.class, responseContainer = "List")
+    public List<TypeSummary> listLocations(@ApiParam(name = "versions", value = "Whether to list 'latest' of each symbolic-name or 'all' versions", required = false, defaultValue = "latest") @QueryParam("versions") String versions,
+        @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);
+
+    // in future could have others, eg: tasks, feeds, etc
+    
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/9b337035/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/TypeApi.java
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/TypeApi.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/TypeApi.java
new file mode 100644
index 0000000..c39d5ef
--- /dev/null
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/TypeApi.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.api;
+
+import java.util.List;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.apache.brooklyn.rest.domain.TypeDetail;
+import org.apache.brooklyn.rest.domain.TypeSummary;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+
+@Path("/types")
+@Api("Types")
+@Consumes(MediaType.APPLICATION_JSON)
+@Produces(MediaType.APPLICATION_JSON)
+public interface TypeApi {
+
+    @GET
+    @ApiOperation(value = "List types registered in the system", 
+            response = TypeSummary.class,
+            responseContainer = "List")
+    public List<TypeSummary> list(
+        @ApiParam(name = "versions", value = "Whether to list 'latest' of each symbolic-name or 'all' versions", 
+            required = false, defaultValue = "latest")
+        @QueryParam("versions")
+        String versions,
+        @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);
+
+    @Path("/{nameOrAlias}")
+    @GET
+    @ApiOperation(value = "Get summaries for all versions and instances of a given type or alias, with best match first", 
+            response = TypeSummary.class,
+            responseContainer = "List")
+    public List<TypeSummary> listVersions(
+        @ApiParam(name = "nameOrAlias", value = "Type name to query", required = true)
+        @PathParam("nameOrAlias")
+        String nameOrAlias);
+
+    @Path("/{symbolicName}/{version}")
+    @GET
+    @ApiOperation(value = "Get detail on a given type and version, allowing 'latest' to match the most recent version (preferring non-SNAPSHOTs)", 
+            response = TypeDetail.class)
+    public TypeDetail detail(
+        @ApiParam(name = "symbolicName", value = "Type name to query", required = true)
+        @PathParam("symbolicName")
+        String symbolicName,
+        @ApiParam(name = "version", value = "Version to query", required = true)
+        @PathParam("version")
+        String version);
+    
+    @Path("/{symbolicName}/{version}/icon")
+    @GET
+    @ApiOperation(value = "Returns the icon image registered for this item")
+    @Produces("application/image")
+    public Response icon(
+        @ApiParam(name = "symbolicName", value = "Type name to query", required = true)
+        @PathParam("symbolicName")
+        String symbolicName,
+        @ApiParam(name = "version", value = "Version to query", required = true)
+        @PathParam("version")
+        String version);
+    
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/9b337035/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/AdjunctConfigSummary.java
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/AdjunctConfigSummary.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/AdjunctConfigSummary.java
new file mode 100644
index 0000000..4bba4b2
--- /dev/null
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/AdjunctConfigSummary.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.domain;
+
+import java.net.URI;
+import java.util.Map;
+import java.util.Objects;
+
+import org.apache.brooklyn.config.ConfigKey;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.collect.ImmutableMap;
+
+public class AdjunctConfigSummary extends ConfigSummary {
+
+    private static final long serialVersionUID = 4339330833863794513L;
+
+    @JsonInclude(Include.NON_NULL)
+    private final Map<String, URI> links;
+
+    // json deserialization
+    AdjunctConfigSummary() {
+        links = null;
+    }
+
+    public AdjunctConfigSummary(
+            @JsonProperty("name") String name,
+            @JsonProperty("type") String type,
+            @JsonProperty("description") String description,
+            @JsonProperty("defaultValue") Object defaultValue,
+            @JsonProperty("reconfigurable") boolean reconfigurable,
+            @JsonProperty("links") Map<String, URI> links) {
+        super(name, type, description, defaultValue, reconfigurable, null, null, null);
+        this.links = (links == null) ? ImmutableMap.<String, URI>of() : ImmutableMap.copyOf(links);
+    }
+
+    public AdjunctConfigSummary(ConfigKey<?> config, String label, Double priority, Map<String, URI> links) {
+        super(config, label, priority);
+        this.links = links != null ? ImmutableMap.copyOf(links) : null;
+    }
+
+    @Override
+    public Map<String, URI> getLinks() {
+        return links;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof AdjunctConfigSummary)) return false;
+        if (!super.equals(o)) return false;
+        AdjunctConfigSummary that = (AdjunctConfigSummary) o;
+        return Objects.equals(links, that.links);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(super.hashCode(), links);
+    }
+
+    @Override
+    public String toString() {
+        return "EnricherConfigSummary{" +
+                "links=" + links +
+                '}';
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/9b337035/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/BundleInstallationRestResult.java
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/BundleInstallationRestResult.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/BundleInstallationRestResult.java
new file mode 100644
index 0000000..48f15ee
--- /dev/null
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/BundleInstallationRestResult.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.domain;
+
+import java.util.Map;
+
+import org.apache.brooklyn.core.mgmt.ha.OsgiBundleInstallationResult;
+import org.apache.brooklyn.core.mgmt.ha.OsgiBundleInstallationResult.ResultCode;
+import org.apache.brooklyn.util.collections.MutableMap;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+
+public class BundleInstallationRestResult {
+    // as Osgi result, but without bundle, and with maps of catalog items installed
+    
+    private final String message;
+    private final String bundle;
+    private final OsgiBundleInstallationResult.ResultCode code;
+    @JsonInclude(Include.NON_EMPTY)
+    private Map<String,TypeSummary> types = MutableMap.of();
+    
+    /** json internal only */
+    @SuppressWarnings("unused")
+    private BundleInstallationRestResult() {
+        this.message = null;
+        this.bundle = null;
+        this.code = null;
+    }
+    
+    public BundleInstallationRestResult(String message, String bundle, ResultCode code) {
+        this.message = message;
+        this.bundle = bundle;
+        this.code = code;
+    }
+    
+    public String getMessage() {
+        return message;
+    }
+    public String getBundle() {
+        return bundle;
+    }
+    public OsgiBundleInstallationResult.ResultCode getCode() {
+        return code;
+    }
+    
+    public Map<String, TypeSummary> getTypes() {
+        return types;
+    }
+    
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/9b337035/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/BundleSummary.java
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/BundleSummary.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/BundleSummary.java
new file mode 100644
index 0000000..f14ee07
--- /dev/null
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/BundleSummary.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.domain;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.brooklyn.api.typereg.OsgiBundleWithUrl;
+import org.apache.brooklyn.util.collections.MutableList;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.javalang.JavaClassNames;
+import org.apache.brooklyn.util.text.NaturalOrderComparator;
+import org.apache.brooklyn.util.text.VersionComparator;
+
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.google.common.collect.ComparisonChain;
+
+public class BundleSummary implements Comparable<BundleSummary> {
+
+    private final String symbolicName;
+    private final String version;
+
+    @JsonInclude(value=Include.ALWAYS)
+    private final List<TypeSummary> types = MutableList.of();
+    
+    // not exported directly, but used to provide other top-level json fields
+    // for specific types
+    @JsonIgnore
+    private final Map<String,Object> others = MutableMap.of();
+    
+    /** for json deserialization */
+    BundleSummary() {
+        symbolicName = null;
+        version = null;
+    }
+    
+    public BundleSummary(OsgiBundleWithUrl bundle) {
+        symbolicName = bundle.getSymbolicName();
+        version = bundle.getSuppliedVersionString();
+    }
+    
+    /** Mutable map of other top-level metadata included on this DTO (eg listing config keys or effectors) */ 
+    @JsonAnyGetter
+    public Map<String,Object> getExtraFields() {
+        return others;
+    }
+    @JsonAnySetter
+    public void setExtraField(String name, Object value) {
+        others.put(name, value);
+    }
+    
+    public void addType(TypeSummary type) { types.add(type); }
+    
+    @Override
+    public int compareTo(BundleSummary o2) {
+        BundleSummary o1 = this;
+        return ComparisonChain.start()
+            .compare(o1.symbolicName, o2.symbolicName, NaturalOrderComparator.INSTANCE)
+            .compare(o2.version, o1.version, VersionComparator.INSTANCE)
+            .result();
+    }
+    
+    public String getSymbolicName() {
+        return symbolicName;
+    }
+    
+    public String getVersion() {
+        return version;
+    }
+    
+    public List<TypeSummary> getTypes() {
+        return types;
+    }
+    
+    @Override
+    public String toString() {
+        return JavaClassNames.cleanSimpleClassName(this)+"["+symbolicName+":"+version+"]";
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/9b337035/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/ConfigSummary.java
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/ConfigSummary.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/ConfigSummary.java
index 22e88d6..6d9ceb3 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/ConfigSummary.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/ConfigSummary.java
@@ -30,6 +30,8 @@ import javax.annotation.Nullable;
 import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.util.collections.Jsonya;
 
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 import com.google.common.base.Function;
@@ -42,19 +44,24 @@ public abstract class ConfigSummary implements HasName, Serializable {
 
     private final String name;
     private final String type;
-    @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
+    @JsonInclude(Include.NON_NULL)
     private final Object defaultValue;
-    @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
+    @JsonInclude(Include.NON_NULL)
     private final String description;
     @JsonSerialize
     private final boolean reconfigurable;
-    @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
+    @JsonInclude(Include.NON_NULL)
     private final String label;
-    @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
+    @JsonInclude(Include.NON_NULL)
     private final Double priority;
-    @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
+    @JsonInclude(Include.NON_NULL)
     private final List<Map<String, String>> possibleValues;
 
+    // json deserialization
+    ConfigSummary() {
+        this(null, null, null, null, false, null, null, null);
+    }
+    
     protected ConfigSummary(
             @JsonProperty("name") String name,
             @JsonProperty("type") String type,

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/9b337035/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EnricherConfigSummary.java
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EnricherConfigSummary.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EnricherConfigSummary.java
index 276cd6b..c868fb8 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EnricherConfigSummary.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EnricherConfigSummary.java
@@ -20,60 +20,24 @@ package org.apache.brooklyn.rest.domain;
 
 import java.net.URI;
 import java.util.Map;
-import java.util.Objects;
 
 import org.apache.brooklyn.config.ConfigKey;
 
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.databind.annotation.JsonSerialize;
-import com.google.common.collect.ImmutableMap;
-
-public class EnricherConfigSummary extends ConfigSummary {
+// TODO remove? this class has no value over its super
+public class EnricherConfigSummary extends AdjunctConfigSummary {
 
     private static final long serialVersionUID = 4339330833863794513L;
 
-    @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
-    private final Map<String, URI> links;
-
-    public EnricherConfigSummary(
-            @JsonProperty("name") String name,
-            @JsonProperty("type") String type,
-            @JsonProperty("description") String description,
-            @JsonProperty("defaultValue") Object defaultValue,
-            @JsonProperty("reconfigurable") boolean reconfigurable,
-            @JsonProperty("links") Map<String, URI> links) {
-        super(name, type, description, defaultValue, reconfigurable, null, null, null);
-        this.links = (links == null) ? ImmutableMap.<String, URI>of() : ImmutableMap.copyOf(links);
-    }
-
+    @SuppressWarnings("unused") // json deserialization
+    private EnricherConfigSummary() {}
+    
     public EnricherConfigSummary(ConfigKey<?> config, String label, Double priority, Map<String, URI> links) {
-        super(config, label, priority);
-        this.links = links != null ? ImmutableMap.copyOf(links) : null;
+        super(config, label, priority, links);
     }
 
-    @Override
-    public Map<String, URI> getLinks() {
-        return links;
+    public EnricherConfigSummary(String name, String type, String description, Object defaultValue, boolean reconfigurable,
+        Map<String, URI> links) {
+        super(name, type, description, defaultValue, reconfigurable, links);
     }
 
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (!(o instanceof EnricherConfigSummary)) return false;
-        if (!super.equals(o)) return false;
-        EnricherConfigSummary that = (EnricherConfigSummary) o;
-        return Objects.equals(links, that.links);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(super.hashCode(), links);
-    }
-
-    @Override
-    public String toString() {
-        return "EnricherConfigSummary{" +
-                "links=" + links +
-                '}';
-    }
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/9b337035/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/PolicyConfigSummary.java
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/PolicyConfigSummary.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/PolicyConfigSummary.java
index 8115ab9..5dfb898 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/PolicyConfigSummary.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/PolicyConfigSummary.java
@@ -20,59 +20,24 @@ package org.apache.brooklyn.rest.domain;
 
 import java.net.URI;
 import java.util.Map;
-import java.util.Objects;
 
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.databind.annotation.JsonSerialize;
-import com.google.common.collect.ImmutableMap;
 import org.apache.brooklyn.config.ConfigKey;
 
-public class PolicyConfigSummary extends ConfigSummary {
+//TODO remove? this class has no value over its super
+public class PolicyConfigSummary extends AdjunctConfigSummary {
 
     private static final long serialVersionUID = 4339330833863794513L;
 
-    @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
-    private final Map<String, URI> links;
-
-    public PolicyConfigSummary(
-            @JsonProperty("name") String name,
-            @JsonProperty("type") String type,
-            @JsonProperty("description") String description,
-            @JsonProperty("defaultValue") Object defaultValue,
-            @JsonProperty("reconfigurable") boolean reconfigurable,
-            @JsonProperty("links") Map<String, URI> links) {
-        super(name, type, description, defaultValue, reconfigurable, null, null, null);
-        this.links = (links == null) ? ImmutableMap.<String, URI>of() : ImmutableMap.copyOf(links);
-    }
-
+    @SuppressWarnings("unused") // json deserialization
+    private PolicyConfigSummary() {}
+    
     public PolicyConfigSummary(ConfigKey<?> config, String label, Double priority, Map<String, URI> links) {
-        super(config, label, priority);
-        this.links = links != null ? ImmutableMap.copyOf(links) : null;
-    }
-
-    @Override
-    public Map<String, URI> getLinks() {
-        return links;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (!(o instanceof PolicyConfigSummary)) return false;
-        if (!super.equals(o)) return false;
-        PolicyConfigSummary that = (PolicyConfigSummary) o;
-        return Objects.equals(links, that.links);
+        super(config, label, priority, links);
     }
 
-    @Override
-    public int hashCode() {
-        return Objects.hash(super.hashCode(), links);
+    public PolicyConfigSummary(String name, String type, String description, Object defaultValue, boolean reconfigurable,
+        Map<String, URI> links) {
+        super(name, type, description, defaultValue, reconfigurable, links);
     }
 
-    @Override
-    public String toString() {
-        return "PolicyConfigSummary{" +
-                "links=" + links +
-                '}';
-    }
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/9b337035/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/TaskSummary.java
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/TaskSummary.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/TaskSummary.java
index eef57e6..e3b0e95 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/TaskSummary.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/TaskSummary.java
@@ -30,8 +30,9 @@ import java.util.Set;
 import org.apache.brooklyn.util.collections.Jsonya;
 
 import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
 import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 
@@ -58,14 +59,14 @@ public class TaskSummary implements HasId, Serializable {
     private final List<LinkWithMetadata> children;
     private final LinkWithMetadata submittedByTask;
 
-    @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
+    @JsonInclude(Include.NON_NULL)
     private final LinkWithMetadata blockingTask;
-    @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
+    @JsonInclude(Include.NON_NULL)
     private final String blockingDetails;
 
     private final String detailedStatus;
 
-    @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
+    @JsonInclude(Include.NON_NULL)
     private final Map<String, LinkWithMetadata> streams;
 
     private final Map<String, URI> links;

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/9b337035/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/TypeDetail.java
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/TypeDetail.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/TypeDetail.java
new file mode 100644
index 0000000..2f9e32f
--- /dev/null
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/TypeDetail.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.domain;
+
+import org.apache.brooklyn.api.typereg.RegisteredType;
+import org.apache.brooklyn.api.typereg.RegisteredType.TypeImplementationPlan;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+
+public class TypeDetail extends TypeSummary {
+
+    public static class TypeImplementationPlanSummary {
+        @JsonInclude(value=Include.NON_EMPTY)
+        private String format;
+        private Object data;
+        private TypeImplementationPlanSummary() {}
+        private TypeImplementationPlanSummary(TypeImplementationPlan p) {
+            format = p.getPlanFormat();
+            data = p.getPlanData();
+        }
+        public String getFormat() {
+            return format;
+        }
+        public Object getData() {
+            return data;
+        }
+    }
+    private final TypeImplementationPlanSummary plan;
+    
+    /** Constructor for JSON deserialization use only. */
+    TypeDetail() {
+        plan = null;
+    }
+    
+    public TypeDetail(RegisteredType t) {
+        super(t);
+        plan = new TypeImplementationPlanSummary(t.getPlan());
+    }
+    
+    public TypeImplementationPlanSummary getPlan() {
+        return plan;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = super.hashCode();
+        result = prime * result + ((plan == null) ? 0 : plan.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (!super.equals(obj))
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        TypeDetail other = (TypeDetail) obj;
+        if (plan == null) {
+            if (other.plan != null)
+                return false;
+        } else if (!plan.equals(other.plan))
+            return false;
+        return true;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/9b337035/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/TypeSummary.java
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/TypeSummary.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/TypeSummary.java
new file mode 100644
index 0000000..0a045d4
--- /dev/null
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/TypeSummary.java
@@ -0,0 +1,287 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.domain;
+
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry.RegisteredTypeKind;
+import org.apache.brooklyn.api.typereg.RegisteredType;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.javalang.JavaClassNames;
+import org.apache.brooklyn.util.text.NaturalOrderComparator;
+import org.apache.brooklyn.util.text.VersionComparator;
+
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.google.common.collect.ComparisonChain;
+
+public class TypeSummary implements Comparable<TypeSummary> {
+
+    private final String symbolicName;
+    private final String version;
+    private final String containingBundle;
+    private final RegisteredTypeKind kind;
+    
+    @JsonInclude(value=Include.NON_EMPTY)
+    private final String displayName;
+    @JsonInclude(value=Include.NON_EMPTY)
+    private final String description;
+    @JsonInclude(value=Include.NON_EMPTY)
+    private String iconUrl;
+
+    @JsonInclude(value=Include.NON_EMPTY)
+    private Set<String> aliases;
+    @JsonInclude(value=Include.NON_EMPTY)
+    private Set<Object> supertypes;
+    @JsonInclude(value=Include.NON_EMPTY)
+    private Set<Object> tags;
+
+    @JsonInclude(value=Include.NON_DEFAULT)
+    private boolean disabled = false;
+    @JsonInclude(value=Include.NON_DEFAULT)
+    private boolean deprecated = false;
+    
+    // not exported directly, but used to provide other top-level json fields
+    // for specific types
+    @JsonIgnore
+    private final Map<String,Object> others = MutableMap.of();
+    
+    /** Constructor for JSON deserialization use only. */
+    TypeSummary() {
+        symbolicName = null;
+        version = null;
+        containingBundle = null;
+        kind = null;
+        
+        displayName = null;
+        description = null;
+    }
+    
+    public TypeSummary(RegisteredType t) {
+        symbolicName = t.getSymbolicName();
+        version = t.getVersion();
+        containingBundle = t.getContainingBundle();
+        kind = t.getKind();
+        
+        displayName = t.getDisplayName();
+        description = t.getDescription();
+        iconUrl = t.getIconUrl();
+        
+        aliases = t.getAliases();
+        supertypes = t.getSuperTypes();
+        tags = t.getTags();
+
+        deprecated = t.isDeprecated();
+        disabled = t.isDisabled();
+    }
+    
+    public TypeSummary(TypeSummary t) {
+        symbolicName = t.getSymbolicName();
+        version = t.getVersion();
+        containingBundle = t.getContainingBundle();
+        kind = t.getKind();
+        
+        displayName = t.getDisplayName();
+        description = t.getDescription();
+        iconUrl = t.getIconUrl();
+        
+        aliases = t.getAliases();
+        supertypes = t.getSupertypes();
+        tags = t.getTags();
+
+        deprecated = t.isDeprecated();
+        disabled = t.isDisabled();
+        
+        others.putAll(t.getExtraFields());
+    }
+
+    /** Mutable map of other top-level metadata included on this DTO (eg listing config keys or effectors) */ 
+    @JsonAnyGetter 
+    public Map<String,Object> getExtraFields() {
+        return others;
+    }
+    @JsonAnySetter
+    public void setExtraField(String name, Object value) {
+        others.put(name, value);
+    }
+    
+    public void setIconUrl(String iconUrl) {
+        this.iconUrl = iconUrl;
+    }
+    
+    @Override
+    public int compareTo(TypeSummary o2) {
+        TypeSummary o1 = this;
+        return ComparisonChain.start()
+            .compare(o1.symbolicName, o2.symbolicName, NaturalOrderComparator.INSTANCE)
+            .compareFalseFirst(o1.disabled, o2.disabled)
+            .compareFalseFirst(o1.deprecated, o2.deprecated)
+            .compare(o2.version, o1.version, VersionComparator.INSTANCE)
+            .result();
+    }
+
+    public String getSymbolicName() {
+        return symbolicName;
+    }
+
+    public String getVersion() {
+        return version;
+    }
+
+    public String getContainingBundle() {
+        return containingBundle;
+    }
+
+    public RegisteredTypeKind getKind() {
+        return kind;
+    }
+
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public String getIconUrl() {
+        return iconUrl;
+    }
+
+    public Set<String> getAliases() {
+        return aliases;
+    }
+
+    public Set<Object> getSupertypes() {
+        return supertypes;
+    }
+
+    public Set<Object> getTags() {
+        return tags;
+    }
+
+    public boolean isDisabled() {
+        return disabled;
+    }
+
+    public boolean isDeprecated() {
+        return deprecated;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((aliases == null) ? 0 : aliases.hashCode());
+        result = prime * result + ((containingBundle == null) ? 0 : containingBundle.hashCode());
+        result = prime * result + (deprecated ? 1231 : 1237);
+        result = prime * result + ((description == null) ? 0 : description.hashCode());
+        result = prime * result + (disabled ? 1231 : 1237);
+        result = prime * result + ((displayName == null) ? 0 : displayName.hashCode());
+        result = prime * result + ((iconUrl == null) ? 0 : iconUrl.hashCode());
+        result = prime * result + ((kind == null) ? 0 : kind.hashCode());
+        // don't use 'others' - see equals comment
+        // result = prime * result + ((others == null) ? 0 : others.hashCode());
+        result = prime * result + ((supertypes == null) ? 0 : supertypes.hashCode());
+        result = prime * result + ((symbolicName == null) ? 0 : symbolicName.hashCode());
+        result = prime * result + ((tags == null) ? 0 : tags.hashCode());
+        result = prime * result + ((version == null) ? 0 : version.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        TypeSummary other = (TypeSummary) obj;
+        if (aliases == null) {
+            if (other.aliases != null)
+                return false;
+        } else if (!aliases.equals(other.aliases))
+            return false;
+        if (containingBundle == null) {
+            if (other.containingBundle != null)
+                return false;
+        } else if (!containingBundle.equals(other.containingBundle))
+            return false;
+        if (deprecated != other.deprecated)
+            return false;
+        if (description == null) {
+            if (other.description != null)
+                return false;
+        } else if (!description.equals(other.description))
+            return false;
+        if (disabled != other.disabled)
+            return false;
+        if (displayName == null) {
+            if (other.displayName != null)
+                return false;
+        } else if (!displayName.equals(other.displayName))
+            return false;
+        if (iconUrl == null) {
+            if (other.iconUrl != null)
+                return false;
+        } else if (!iconUrl.equals(other.iconUrl))
+            return false;
+        if (kind != other.kind)
+            return false;
+        // don't compare "others" -- eg if "links" are set, we don't care
+//        if (others == null) {
+//            if (other.others != null)
+//                return false;
+//        } else if (!others.equals(other.others))
+//            return false;
+        if (supertypes == null) {
+            if (other.supertypes != null)
+                return false;
+        } else if (!supertypes.equals(other.supertypes))
+            return false;
+        if (symbolicName == null) {
+            if (other.symbolicName != null)
+                return false;
+        } else if (!symbolicName.equals(other.symbolicName))
+            return false;
+        if (tags == null) {
+            if (other.tags != null)
+                return false;
+        } else if (!tags.equals(other.tags))
+            return false;
+        if (version == null) {
+            if (other.version != null)
+                return false;
+        } else if (!version.equals(other.version))
+            return false;
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return JavaClassNames.cleanSimpleClassName(this)+"["+symbolicName+":"+version+
+            ", containingBundle=" + containingBundle + ", kind=" + kind + ", displayName=" + displayName + "]";
+    }
+    
+}