http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/9b337035/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/BrooklynRestApi.java
----------------------------------------------------------------------
diff --git 
a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/BrooklynRestApi.java
 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/BrooklynRestApi.java
index 01e8ad5..b3eb4e4 100644
--- 
a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/BrooklynRestApi.java
+++ 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/BrooklynRestApi.java
@@ -26,6 +26,7 @@ import org.apache.brooklyn.rest.resources.AccessResource;
 import org.apache.brooklyn.rest.resources.ActivityResource;
 import org.apache.brooklyn.rest.resources.ApidocResource;
 import org.apache.brooklyn.rest.resources.ApplicationResource;
+import org.apache.brooklyn.rest.resources.BundleResource;
 import org.apache.brooklyn.rest.resources.CatalogResource;
 import org.apache.brooklyn.rest.resources.EffectorResource;
 import org.apache.brooklyn.rest.resources.EntityConfigResource;
@@ -37,6 +38,8 @@ import org.apache.brooklyn.rest.resources.PolicyResource;
 import org.apache.brooklyn.rest.resources.ScriptResource;
 import org.apache.brooklyn.rest.resources.SensorResource;
 import org.apache.brooklyn.rest.resources.ServerResource;
+import org.apache.brooklyn.rest.resources.SubtypeResource;
+import org.apache.brooklyn.rest.resources.TypeResource;
 import org.apache.brooklyn.rest.resources.UsageResource;
 import org.apache.brooklyn.rest.util.DefaultExceptionMapper;
 import org.apache.brooklyn.rest.util.FormMapProvider;
@@ -46,14 +49,15 @@ import com.google.common.collect.Iterables;
 
 import io.swagger.jaxrs.listing.SwaggerSerializers;
 
-
-@SuppressWarnings("deprecation")
 public class BrooklynRestApi {
 
     public static Iterable<AbstractBrooklynRestResource> 
getBrooklynRestResources() {
         List<AbstractBrooklynRestResource> resources = new ArrayList<>();
         resources.add(new LocationResource());
         resources.add(new CatalogResource());
+        resources.add(new TypeResource());
+        resources.add(new SubtypeResource());
+        resources.add(new BundleResource());
         resources.add(new ApplicationResource());
         resources.add(new EntityResource());
         resources.add(new EntityConfigResource());

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/9b337035/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/BundleResource.java
----------------------------------------------------------------------
diff --git 
a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/BundleResource.java
 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/BundleResource.java
new file mode 100644
index 0000000..a2a6532
--- /dev/null
+++ 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/BundleResource.java
@@ -0,0 +1,186 @@
+/*
+ * 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.resources;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStreamReader;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
+import org.apache.brooklyn.api.typereg.ManagedBundle;
+import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog;
+import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
+import org.apache.brooklyn.core.mgmt.ha.OsgiBundleInstallationResult;
+import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
+import org.apache.brooklyn.rest.api.BundleApi;
+import org.apache.brooklyn.rest.domain.ApiError;
+import org.apache.brooklyn.rest.domain.BundleInstallationRestResult;
+import org.apache.brooklyn.rest.domain.BundleSummary;
+import org.apache.brooklyn.rest.filter.HaHotStateRequired;
+import org.apache.brooklyn.rest.transform.TypeTransformer;
+import org.apache.brooklyn.rest.util.WebResourceUtils;
+import org.apache.brooklyn.util.collections.MutableList;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.exceptions.ReferenceWithError;
+import org.apache.brooklyn.util.osgi.VersionedName;
+import org.apache.brooklyn.util.osgi.VersionedName.VersionedNameComparator;
+import org.apache.brooklyn.util.yaml.Yamls;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+
+@HaHotStateRequired
+public class BundleResource extends AbstractBrooklynRestResource implements 
BundleApi {
+
+    private static final Logger log = 
LoggerFactory.getLogger(BundleResource.class);
+    private static final String LATEST = "latest";
+
+    @Override
+    public List<BundleSummary> list(String versions) {
+        return list(TypeResource.isLatestOnly(versions, true), 
Predicates.alwaysTrue());
+    }
+    
+    private List<BundleSummary> list(boolean onlyLatest, Predicate<String> 
symbolicNameFilter) {
+        
+        Map<VersionedName,ManagedBundle> bundles = new 
TreeMap<>(VersionedNameComparator.INSTANCE);
+        for (ManagedBundle b: 
((ManagementContextInternal)mgmt()).getOsgiManager().get().getManagedBundles().values())
 {
+            if (symbolicNameFilter.apply(b.getSymbolicName())) {
+                // TODO entitlements for bundles
+                VersionedName key = onlyLatest ? new 
VersionedName(b.getSymbolicName(), LATEST) : b.getVersionedName();
+                ManagedBundle oldBundle = bundles.get(key);
+                if (oldBundle==null || 
oldBundle.getVersionedName().compareTo(b.getVersionedName()) > 0) {
+                    bundles.put(key, b);
+                }
+            }
+        }
+        return toBundleSummary(bundles.values());
+    }
+
+    private List<BundleSummary> toBundleSummary(Iterable<ManagedBundle> 
sortedItems) {
+        List<BundleSummary> result = MutableList.of();
+        for (ManagedBundle t: sortedItems) {
+            result.add(TypeTransformer.bundleSummary(brooklyn(), t, 
ui.getBaseUriBuilder(), mgmt()));
+        }
+        return result;
+    }
+
+    @Override
+    public List<BundleSummary> listVersions(String symbolicName) {
+        return list(false, Predicates.equalTo(symbolicName));
+    }
+
+    @Override
+    public BundleSummary detail(String symbolicName, String version) {
+        ManagedBundle b = lookup(symbolicName, version);
+        return TypeTransformer.bundleDetails(brooklyn(), b, 
ui.getBaseUriBuilder(), mgmt());
+    }
+
+    protected ManagedBundle lookup(String symbolicName, String version) {
+        // TODO entitlements for bundles
+
+        ManagedBundle b = 
((ManagementContextInternal)mgmt()).getOsgiManager().get().getManagedBundle(new 
VersionedName(symbolicName, version));
+        if (b==null) {
+            throw WebResourceUtils.notFound("Bundle with id '%s:%s' not 
found", symbolicName, version);
+        }
+        return b;
+    }
+
+    @Override
+    public BundleInstallationRestResult remove(String symbolicName, String 
version, Boolean force) {
+        ManagedBundle b = lookup(symbolicName, version);
+        log.info("REST removing "+symbolicName+":"+version);
+        if (force==null) force = false;
+        ReferenceWithError<OsgiBundleInstallationResult> r = 
((ManagementContextInternal)mgmt()).getOsgiManager().get().uninstallUploadedBundle(b,
 force);
+        return TypeTransformer.bundleInstallationResult(r.getWithoutError(), 
mgmt(), brooklyn(), ui);
+    }
+
+
+    @Override
+    public Response createFromYaml(String yaml, Boolean force) {
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), 
Entitlements.ADD_CATALOG_ITEM, yaml)) {
+            throw WebResourceUtils.forbidden("User '%s' is not authorized to 
add catalog items",
+                Entitlements.getEntitlementContext().user());
+        }
+        if (force==null) force = false;
+
+        try {
+            return Response.status(Status.CREATED).entity(
+                    TypeTransformer.bundleInstallationResult(
+                        
((BasicBrooklynCatalog)brooklyn().getCatalog()).addItemsBundleResult(yaml, 
force), mgmt(), brooklyn(), ui)).build();
+        } catch (Exception e) {
+            Exceptions.propagateIfFatal(e);
+            return badRequest(e);
+        }
+    }
+    
+    @Override
+    public Response createFromArchive(byte[] zipInput, Boolean force) {
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), 
Entitlements.ROOT, null)) {
+            throw WebResourceUtils.forbidden("User '%s' is not authorized to 
add catalog items",
+                Entitlements.getEntitlementContext().user());
+        }
+        if (force==null) force = false;
+
+        ReferenceWithError<OsgiBundleInstallationResult> result = 
((ManagementContextInternal)mgmt()).getOsgiManager().get()
+            .install(null, new ByteArrayInputStream(zipInput), true, true, 
force);
+
+        if 
(OsgiBundleInstallationResult.ResultCode.IGNORING_BUNDLE_AREADY_INSTALLED.equals(result.getWithoutError().getCode()))
 {
+            result = 
ReferenceWithError.newInstanceThrowingError(result.getWithoutError(), new 
IllegalStateException(
+                    "Cannot add bundle" + 
result.getWithoutError().getMetadata().getVersionedName() +
+                    "; different bundle with same name already installed"));
+        }
+        
+        if (result.hasError()) {
+            // (rollback already done as part of install, if necessary)
+            if (log.isTraceEnabled()) {
+                log.trace("Unable to create from archive, returning 400: 
"+result.getError().getMessage(), result.getError());
+            }
+            return 
ApiError.builder().errorCode(Status.BAD_REQUEST).message(result.getWithoutError().getMessage())
+                
.data(TypeTransformer.bundleInstallationResult(result.getWithoutError(), 
mgmt(), brooklyn(), ui)).build().asJsonResponse();
+        }
+
+        BundleInstallationRestResult resultR = 
TypeTransformer.bundleInstallationResult(result.get(), mgmt(), brooklyn(), ui);
+        return Response.status(Status.CREATED).entity( resultR ).build();
+    }
+
+    @Override
+    public Response createAutodetecting(byte[] item, Boolean force) {
+        Throwable yamlException = null;
+        try {
+            MutableList.copyOf( Yamls.parseAll(new InputStreamReader(new 
ByteArrayInputStream(item))) );
+        } catch (Exception e) {
+            Exceptions.propagateIfFatal(e);
+            yamlException = e;
+        }
+        
+        if (yamlException==null) {
+            // treat as yaml if it parsed
+            return createFromYaml(new String(item), force);
+        }
+        
+        return createFromArchive(item, force);
+    }
+}
+

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/9b337035/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java
----------------------------------------------------------------------
diff --git 
a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java
 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java
index d327e40..65e6956 100644
--- 
a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java
+++ 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java
@@ -35,7 +35,6 @@ import javax.ws.rs.core.Response.Status;
 import javax.ws.rs.core.UriInfo;
 
 import org.apache.brooklyn.api.catalog.CatalogItem;
-import org.apache.brooklyn.api.mgmt.ManagementContext;
 import org.apache.brooklyn.api.typereg.RegisteredType;
 import org.apache.brooklyn.core.catalog.internal.CatalogUtils;
 import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
@@ -46,6 +45,7 @@ import 
org.apache.brooklyn.core.typereg.RegisteredTypePredicates;
 import org.apache.brooklyn.core.typereg.RegisteredTypes;
 import org.apache.brooklyn.rest.api.CatalogApi;
 import org.apache.brooklyn.rest.domain.ApiError;
+import org.apache.brooklyn.rest.domain.BundleInstallationRestResult;
 import org.apache.brooklyn.rest.domain.CatalogEnricherSummary;
 import org.apache.brooklyn.rest.domain.CatalogEntitySummary;
 import org.apache.brooklyn.rest.domain.CatalogItemSummary;
@@ -53,7 +53,7 @@ import org.apache.brooklyn.rest.domain.CatalogLocationSummary;
 import org.apache.brooklyn.rest.domain.CatalogPolicySummary;
 import org.apache.brooklyn.rest.filter.HaHotStateRequired;
 import org.apache.brooklyn.rest.transform.CatalogTransformer;
-import org.apache.brooklyn.rest.util.BrooklynRestResourceUtils;
+import org.apache.brooklyn.rest.transform.TypeTransformer;
 import org.apache.brooklyn.rest.util.WebResourceUtils;
 import org.apache.brooklyn.util.collections.MutableList;
 import org.apache.brooklyn.util.collections.MutableMap;
@@ -149,36 +149,6 @@ public class CatalogResource extends 
AbstractBrooklynRestResource implements Cat
         }
     }
 
-    public static class BundleInstallationRestResult {
-        // as Osgi result, but without bundle, and with maps of catalog items 
installed
-        
-        String message;
-        String bundle;
-        OsgiBundleInstallationResult.ResultCode code;
-        
-        Map<String,Object> types;
-        
-        public String getMessage() {
-            return message;
-        }
-        
-        public static BundleInstallationRestResult 
of(OsgiBundleInstallationResult in, ManagementContext mgmt, 
BrooklynRestResourceUtils brooklynU, UriInfo ui) {
-            BundleInstallationRestResult result = new 
BundleInstallationRestResult();
-            result.message = in.getMessage();
-            result.bundle = in.getVersionedName() != null ? 
in.getVersionedName().toString() : "";
-            result.code = in.getCode();
-            if (in.getCatalogItemsInstalled()!=null) {
-                result.types = MutableMap.of();
-                for (String id: in.getCatalogItemsInstalled()) {
-                    RegisteredType ci = mgmt.getTypeRegistry().get(id);
-                    CatalogItemSummary summary = 
CatalogTransformer.catalogItemSummary(brooklynU, ci, ui.getBaseUriBuilder());
-                    result.types.put(id, summary);
-                }
-            }
-            return result;
-        }
-    }
-    
     @Override
     @Beta
     public Response createFromArchive(byte[] zipInput, boolean detail, boolean 
forceUpdate) {
@@ -202,11 +172,11 @@ public class CatalogResource extends 
AbstractBrooklynRestResource implements Cat
                 log.trace("Unable to create from archive, returning 400: 
"+result.getError().getMessage(), result.getError());
             }
             return 
ApiError.builder().errorCode(Status.BAD_REQUEST).message(result.getWithoutError().getMessage())
-                
.data(BundleInstallationRestResult.of(result.getWithoutError(), mgmt(), 
brooklyn(), ui)).build().asJsonResponse();
+                
.data(TypeTransformer.bundleInstallationResult(result.getWithoutError(), 
mgmt(), brooklyn(), ui)).build().asJsonResponse();
         }
 
-        BundleInstallationRestResult resultR = 
BundleInstallationRestResult.of(result.get(), mgmt(), brooklyn(), ui);
-        return Response.status(Status.CREATED).entity( detail ? resultR : 
resultR.types ).build();
+        BundleInstallationRestResult resultR = 
TypeTransformer.bundleInstallationResult(result.get(), mgmt(), brooklyn(), ui);
+        return Response.status(Status.CREATED).entity( detail ? resultR : 
resultR.getTypes() ).build();
     }
 
     private Response buildCreateResponse(Iterable<RegisteredType> 
catalogItems) {

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/9b337035/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/SubtypeResource.java
----------------------------------------------------------------------
diff --git 
a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/SubtypeResource.java
 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/SubtypeResource.java
new file mode 100644
index 0000000..12716b3
--- /dev/null
+++ 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/SubtypeResource.java
@@ -0,0 +1,82 @@
+/*
+ * 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.resources;
+
+import java.util.List;
+
+import org.apache.brooklyn.api.entity.Application;
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.policy.Policy;
+import org.apache.brooklyn.api.sensor.Enricher;
+import org.apache.brooklyn.api.typereg.RegisteredType;
+import org.apache.brooklyn.core.typereg.RegisteredTypePredicates;
+import org.apache.brooklyn.core.typereg.RegisteredTypes;
+import org.apache.brooklyn.rest.api.SubtypeApi;
+import org.apache.brooklyn.rest.domain.TypeSummary;
+import org.apache.brooklyn.rest.filter.HaHotStateRequired;
+import org.apache.brooklyn.util.collections.MutableList;
+import org.apache.brooklyn.util.text.StringPredicates;
+import org.apache.brooklyn.util.text.Strings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+
+@HaHotStateRequired
+public class SubtypeResource extends AbstractBrooklynRestResource implements 
SubtypeApi {
+
+    @SuppressWarnings("unused")
+    private static final Logger log = 
LoggerFactory.getLogger(SubtypeResource.class);
+
+    @Override
+    public List<TypeSummary> list(String supertype, String versions, String 
regex, String fragment) {
+        List<Predicate<RegisteredType>> filters = 
MutableList.<Predicate<RegisteredType>>of()
+            .append(RegisteredTypePredicates.entitledToSee(mgmt()))
+            .append(RegisteredTypePredicates.subtypeOf(supertype));
+        if (TypeResource.isLatestOnly(versions, true)) {
+            // TODO inefficient - does n^2 comparisons where n is sufficient
+            // create RegisteredTypes.filterBestVersions to do a list after 
the initial parse
+            // (and javadoc in predicate method below)
+            filters.add(RegisteredTypePredicates.isBestVersion(mgmt()));
+        }
+        if (Strings.isNonEmpty(regex)) {
+            
filters.add(RegisteredTypePredicates.nameOrAlias(StringPredicates.containsRegex(regex)));
+        }
+        if (Strings.isNonEmpty(fragment)) {
+            
filters.add(RegisteredTypePredicates.nameOrAlias(StringPredicates.containsLiteralIgnoreCase(fragment)));
+        }
+        Predicate<RegisteredType> filter = Predicates.and(filters);
+        ImmutableList<RegisteredType> sortedItems =
+            
FluentIterable.from(brooklyn().getTypeRegistry().getMatching(filter))
+                
.toSortedList(RegisteredTypes.RegisteredTypeNameThenBestFirstComparator.INSTANCE);
+        return TypeResource.toTypeSummary(brooklyn(), sortedItems, 
ui.getBaseUriBuilder());
+    }
+    
+    @Override public List<TypeSummary> listApplications(String versions, 
String regex, String fragment) { return list(Application.class.getName(), 
versions, regex, fragment); }
+    @Override public List<TypeSummary> listEntities(String versions, String 
regex, String fragment) { return list(Entity.class.getName(), versions, regex, 
fragment); }
+    @Override public List<TypeSummary> listPolicies(String versions, String 
regex, String fragment) { return list(Policy.class.getName(), versions, regex, 
fragment); }
+    @Override public List<TypeSummary> listEnrichers(String versions, String 
regex, String fragment) { return list(Enricher.class.getName(), versions, 
regex, fragment); }
+    @Override public List<TypeSummary> listLocations(String versions, String 
regex, String fragment) { return list(Location.class.getName(), versions, 
regex, fragment); }
+    
+}
+

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/9b337035/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/TypeResource.java
----------------------------------------------------------------------
diff --git 
a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/TypeResource.java
 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/TypeResource.java
new file mode 100644
index 0000000..86de57d
--- /dev/null
+++ 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/TypeResource.java
@@ -0,0 +1,186 @@
+/*
+ * 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.resources;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Set;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.core.UriBuilder;
+
+import org.apache.brooklyn.api.typereg.RegisteredType;
+import org.apache.brooklyn.core.catalog.internal.CatalogUtils;
+import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
+import org.apache.brooklyn.core.typereg.RegisteredTypePredicates;
+import org.apache.brooklyn.core.typereg.RegisteredTypes;
+import org.apache.brooklyn.rest.api.TypeApi;
+import org.apache.brooklyn.rest.domain.TypeDetail;
+import org.apache.brooklyn.rest.domain.TypeSummary;
+import org.apache.brooklyn.rest.filter.HaHotStateRequired;
+import org.apache.brooklyn.rest.transform.TypeTransformer;
+import org.apache.brooklyn.rest.util.BrooklynRestResourceUtils;
+import org.apache.brooklyn.rest.util.WebResourceUtils;
+import org.apache.brooklyn.util.collections.MutableList;
+import org.apache.brooklyn.util.collections.MutableSet;
+import org.apache.brooklyn.util.core.ResourceUtils;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.text.StringPredicates;
+import org.apache.brooklyn.util.text.Strings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.Files;
+
+@HaHotStateRequired
+public class TypeResource extends AbstractBrooklynRestResource implements 
TypeApi {
+
+    private static final Logger log = 
LoggerFactory.getLogger(TypeResource.class);
+    private static final String LATEST = "latest";
+    private static final String ALL = "all";
+
+    private static Set<String> missingIcons = MutableSet.of();
+
+    static boolean isLatestOnly(String versions, boolean defaultValue) {
+        if (ALL.equalsIgnoreCase(versions)) return false;
+        if (LATEST.equalsIgnoreCase(versions)) return true;
+        if (Strings.isNonBlank(versions)) {
+            log.warn("Invalid 'versions' argument '"+versions+"' when listing 
types; should be 'all' or 'latest'");
+        }
+        return defaultValue;
+    }
+    
+    @Override
+    public List<TypeSummary> list(String versions, String regex, String 
fragment) {
+        List<Predicate<RegisteredType>> filters = 
MutableList.<Predicate<RegisteredType>>of()
+            .append(RegisteredTypePredicates.entitledToSee(mgmt()));
+        if (TypeResource.isLatestOnly(versions, true)) {
+            // TODO inefficient - does n^2 comparisons where n is sufficient
+            // create RegisteredTypes.filterBestVersions to do a list after 
the initial parse
+            // (and javadoc in predicate method below)
+            filters.add(RegisteredTypePredicates.isBestVersion(mgmt()));
+        }
+        if (Strings.isNonEmpty(regex)) {
+            
filters.add(RegisteredTypePredicates.nameOrAlias(StringPredicates.containsRegex(regex)));
+        }
+        if (Strings.isNonEmpty(fragment)) {
+            
filters.add(RegisteredTypePredicates.nameOrAlias(StringPredicates.containsLiteralIgnoreCase(fragment)));
+        }
+        Predicate<RegisteredType> filter = Predicates.and(filters);
+
+        ImmutableList<RegisteredType> sortedItems =
+            
FluentIterable.from(brooklyn().getTypeRegistry().getMatching(filter))
+                
.toSortedList(RegisteredTypes.RegisteredTypeNameThenBestFirstComparator.INSTANCE);
+        return toTypeSummary(brooklyn(), sortedItems, ui.getBaseUriBuilder());
+    }
+
+    static List<TypeSummary> toTypeSummary(BrooklynRestResourceUtils brooklyn, 
Iterable<RegisteredType> sortedItems, UriBuilder uriBuilder) {
+        List<TypeSummary> result = MutableList.of();
+        for (RegisteredType t: sortedItems) {
+            result.add(TypeTransformer.summary(brooklyn, t, uriBuilder));
+        }
+        return result;
+    }
+
+    @Override
+    public List<TypeSummary> listVersions(String nameOrAlias) {
+        Predicate<RegisteredType> filter = 
Predicates.and(RegisteredTypePredicates.entitledToSee(mgmt()), 
+            RegisteredTypePredicates.nameOrAlias(nameOrAlias));
+        ImmutableList<RegisteredType> sortedItems =
+            
FluentIterable.from(brooklyn().getTypeRegistry().getMatching(filter))
+                
.toSortedList(RegisteredTypes.RegisteredTypeNameThenBestFirstComparator.INSTANCE);
+        return toTypeSummary(brooklyn(), sortedItems, ui.getBaseUriBuilder());
+    }
+
+    @Override
+    public TypeDetail detail(String symbolicName, String version) {
+        RegisteredType item = lookup(symbolicName, version);
+        return TypeTransformer.detail(brooklyn(), item, 
ui.getBaseUriBuilder());
+    }
+
+    protected RegisteredType lookup(String symbolicName, String version) {
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), 
Entitlements.SEE_CATALOG_ITEM, symbolicName+":"+version)) {
+            // TODO best to default to "not found" - unless maybe they have 
permission to "see null"
+            throw WebResourceUtils.forbidden("User '%s' not permitted to see 
info on this type (including whether or not installed)",
+                Entitlements.getEntitlementContext().user());
+        }
+        RegisteredType item;
+        if (LATEST.equalsIgnoreCase(version)) {
+            item = brooklyn().getTypeRegistry().get(symbolicName);
+        } else {
+            item = brooklyn().getTypeRegistry().get(symbolicName, version);
+        }
+        if (item==null) {
+            throw WebResourceUtils.notFound("Entity with id '%s:%s' not 
found", symbolicName, version);
+        }
+        return item;
+    }
+
+    @Override
+    public Response icon(String symbolicName, String version) {
+        RegisteredType item = lookup(symbolicName, version);
+        return produceIcon(item);
+    }
+
+    private Response produceIcon(RegisteredType result) {
+        String url = result.getIconUrl();
+        if (url==null) {
+            log.debug("No icon available for "+result+"; returning 
"+Status.NO_CONTENT);
+            return Response.status(Status.NO_CONTENT).build();
+        }
+        
+        if (brooklyn().isUrlServerSideAndSafe(url)) {
+            // classpath URL's we will serve IF they end with a recognised 
image format;
+            // paths (ie non-protocol) and 
+            // NB, for security, file URL's are NOT served
+            log.debug("Loading and returning "+url+" as icon for "+result);
+            
+            MediaType mime = 
WebResourceUtils.getImageMediaTypeFromExtension(Files.getFileExtension(url));
+            try {
+                Object content = 
ResourceUtils.create(CatalogUtils.newClassLoadingContext(mgmt(), 
result)).getResourceFromUrl(url);
+                return Response.ok(content, mime).build();
+            } catch (Exception e) {
+                Exceptions.propagateIfFatal(e);
+                synchronized (missingIcons) {
+                    if (missingIcons.add(url)) {
+                        // note: this can be quite common when running from an 
IDE, as resources may not be copied;
+                        // a mvn build should sort it out (the IDE will then 
find the resources, until you clean or maybe refresh...)
+                        log.warn("Missing icon data for "+result.getId()+", 
expected at: "+url+" (subsequent messages will log debug only)");
+                        log.debug("Trace for missing icon data at "+url+": 
"+e, e);
+                    } else {
+                        log.debug("Missing icon data for "+result.getId()+", 
expected at: "+url+" (already logged WARN and error details)");
+                    }
+                }
+                throw WebResourceUtils.notFound("Icon unavailable for %s", 
result.getId());
+            }
+        }
+        
+        log.debug("Returning redirect to "+url+" as icon for "+result);
+        
+        // for anything else we do a redirect (e.g. http / https; perhaps ftp)
+        return Response.temporaryRedirect(URI.create(url)).build();
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/9b337035/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/EntityTransformer.java
----------------------------------------------------------------------
diff --git 
a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/EntityTransformer.java
 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/EntityTransformer.java
index 8a15020..6bf3f19 100644
--- 
a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/EntityTransformer.java
+++ 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/EntityTransformer.java
@@ -19,6 +19,7 @@
 package org.apache.brooklyn.rest.transform;
 
 import static com.google.common.collect.Iterables.transform;
+import static org.apache.brooklyn.rest.util.WebResourceUtils.serviceUriBuilder;
 
 import java.lang.reflect.Field;
 import java.net.URI;
@@ -26,13 +27,21 @@ import java.util.List;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
 
+import javax.ws.rs.core.UriBuilder;
+
 import org.apache.brooklyn.api.catalog.CatalogConfig;
 import org.apache.brooklyn.api.entity.Application;
 import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.objs.SpecParameter;
 import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.catalog.internal.CatalogUtils;
 import org.apache.brooklyn.core.config.render.RendererHints;
 import org.apache.brooklyn.core.typereg.RegisteredTypes;
+import org.apache.brooklyn.rest.api.ApplicationApi;
+import org.apache.brooklyn.rest.api.CatalogApi;
+import org.apache.brooklyn.rest.api.EntityApi;
+import org.apache.brooklyn.rest.api.EntityConfigApi;
+import org.apache.brooklyn.rest.domain.AdjunctConfigSummary;
 import org.apache.brooklyn.rest.domain.EnricherConfigSummary;
 import org.apache.brooklyn.rest.domain.EntityConfigSummary;
 import org.apache.brooklyn.rest.domain.EntitySummary;
@@ -43,13 +52,6 @@ import com.google.common.base.Function;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
-import javax.ws.rs.core.UriBuilder;
-import org.apache.brooklyn.core.catalog.internal.CatalogUtils;
-import org.apache.brooklyn.rest.api.ApplicationApi;
-import org.apache.brooklyn.rest.api.CatalogApi;
-import org.apache.brooklyn.rest.api.EntityApi;
-import org.apache.brooklyn.rest.api.EntityConfigApi;
-import static org.apache.brooklyn.rest.util.WebResourceUtils.serviceUriBuilder;
 
 /**
  * @author Adam Lowe
@@ -123,6 +125,10 @@ public class EntityTransformer {
         return new EntityConfigSummary(config, label, priority, pinned, 
mapOfLinks);
     }
 
+    public static AdjunctConfigSummary adjunctConfigSummary(ConfigKey<?> 
config, String label, Double priority, Map<String, URI> links) {
+        return new AdjunctConfigSummary(config, label, priority, links);
+    }
+
     public static PolicyConfigSummary policyConfigSummary(ConfigKey<?> config, 
String label, Double priority, Map<String, URI> links) {
         return new PolicyConfigSummary(config, label, priority, links);
     }
@@ -192,6 +198,11 @@ public class EntityTransformer {
         return entityConfigSummary(input.getConfigKey(), input.getLabel(), 
priority, input.isPinned(), null);
     }
 
+    public static AdjunctConfigSummary adjunctConfigSummary(SpecParameter<?> 
input) {
+        Double priority = input.isPinned() ? Double.valueOf(1d) : null;
+        return policyConfigSummary(input.getConfigKey(), input.getLabel(), 
priority, null);
+    }
+
     public static PolicyConfigSummary policyConfigSummary(SpecParameter<?> 
input) {
         Double priority = input.isPinned() ? Double.valueOf(1d) : null;
         return policyConfigSummary(input.getConfigKey(), input.getLabel(), 
priority, null);

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/9b337035/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/TypeTransformer.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..fb3fdd3
--- /dev/null
+++ 
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/TypeTransformer.java
@@ -0,0 +1,194 @@
+/*
+ * 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.transform;
+
+import static org.apache.brooklyn.rest.util.WebResourceUtils.serviceUriBuilder;
+
+import java.net.URI;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.brooklyn.api.effector.Effector;
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.entity.EntityType;
+import org.apache.brooklyn.api.internal.AbstractBrooklynObjectSpec;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.api.objs.EntityAdjunct;
+import org.apache.brooklyn.api.objs.SpecParameter;
+import org.apache.brooklyn.api.policy.Policy;
+import org.apache.brooklyn.api.sensor.Enricher;
+import org.apache.brooklyn.api.sensor.Feed;
+import org.apache.brooklyn.api.sensor.Sensor;
+import org.apache.brooklyn.api.typereg.ManagedBundle;
+import org.apache.brooklyn.api.typereg.RegisteredType;
+import org.apache.brooklyn.core.entity.EntityDynamicType;
+import org.apache.brooklyn.core.mgmt.ha.OsgiBundleInstallationResult;
+import org.apache.brooklyn.core.objs.BrooklynTypes;
+import org.apache.brooklyn.core.typereg.RegisteredTypePredicates;
+import org.apache.brooklyn.core.typereg.RegisteredTypes;
+import org.apache.brooklyn.rest.api.TypeApi;
+import org.apache.brooklyn.rest.domain.AdjunctConfigSummary;
+import org.apache.brooklyn.rest.domain.BundleInstallationRestResult;
+import org.apache.brooklyn.rest.domain.BundleSummary;
+import org.apache.brooklyn.rest.domain.EffectorSummary;
+import org.apache.brooklyn.rest.domain.EntityConfigSummary;
+import org.apache.brooklyn.rest.domain.SensorSummary;
+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.MutableMap;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.Sets;
+
+public class TypeTransformer {
+
+    private static final org.slf4j.Logger log = 
LoggerFactory.getLogger(TypeTransformer.class);
+    
+    public static <T extends Entity> TypeSummary 
summary(BrooklynRestResourceUtils b, RegisteredType item, UriBuilder ub) {
+        return embellish(new TypeSummary(item), item, false, b, ub);
+    }
+
+    public static TypeDetail detail(BrooklynRestResourceUtils b, 
RegisteredType item, UriBuilder ub) {
+        return embellish(new TypeDetail(item), item, true, b, ub);
+    }
+
+    private static <T extends TypeSummary> T embellish(T result, 
RegisteredType item, boolean detail, BrooklynRestResourceUtils b, UriBuilder 
ub) {
+        result.getExtraFields().put("links", makeLinks(item, ub));
+        
+        if (RegisteredTypes.isTemplate(item)) {
+            result.getExtraFields().put("template", true);
+        }
+        if (item.getIconUrl()!=null) {
+            result.setIconUrl(tidyIconLink(b, item, item.getIconUrl(), ub));
+        }
+        
+        if (detail) {
+            if (RegisteredTypes.isSubtypeOf(item, Entity.class)) {
+                embellishEntity(result, item, b);
+            } else if (RegisteredTypes.isSubtypeOf(item, EntityAdjunct.class) 
||
+                    // when implied supertypes are used we won't need the code 
below
+                    RegisteredTypes.isSubtypeOf(item, Policy.class) || 
RegisteredTypes.isSubtypeOf(item, Enricher.class) || 
RegisteredTypes.isSubtypeOf(item, Feed.class)
+                    ) {
+                try {
+                    Set<AdjunctConfigSummary> config = Sets.newLinkedHashSet();
+                    
+                    AbstractBrooklynObjectSpec<?,?> spec = 
b.getTypeRegistry().createSpec(item, null, null);
+                    for (final SpecParameter<?> input : spec.getParameters()){
+                        
config.add(EntityTransformer.adjunctConfigSummary(input));
+                    }
+                    
+                    result.getExtraFields().put("config", config);
+                } catch (Exception e) {
+                    Exceptions.propagateIfFatal(e);
+                    log.trace("Unable to create spec for "+item+": "+e, e);
+                }
+                
+            } else if (RegisteredTypes.isSubtypeOf(item, Location.class)) {
+                // TODO include config on location specs?  (wasn't done 
previously so not needed, but good for completeness)
+                result.getExtraFields().put("config", Collections.emptyMap());
+            }
+        }
+        return result;
+    }
+
+    protected static <T extends TypeSummary> void embellishEntity(T result, 
RegisteredType item, BrooklynRestResourceUtils b) {
+        try {
+            Set<EntityConfigSummary> config = Sets.newLinkedHashSet();
+            Set<SensorSummary> sensors = 
Sets.newTreeSet(SummaryComparators.nameComparator());
+            Set<EffectorSummary> effectors = 
Sets.newTreeSet(SummaryComparators.nameComparator());
+      
+            EntitySpec<?> spec = b.getTypeRegistry().createSpec(item, null, 
EntitySpec.class);
+            EntityDynamicType typeMap = 
BrooklynTypes.getDefinedEntityType(spec.getType());
+            EntityType type = typeMap.getSnapshot();
+   
+            AtomicInteger paramPriorityCnt = new AtomicInteger();
+            for (SpecParameter<?> input: spec.getParameters())
+                config.add(EntityTransformer.entityConfigSummary(input, 
paramPriorityCnt));
+            for (Sensor<?> x: type.getSensors())
+                sensors.add(SensorTransformer.sensorSummaryForCatalog(x));
+            for (Effector<?> x: type.getEffectors())
+                
effectors.add(EffectorTransformer.effectorSummaryForCatalog(x));
+            
+            result.getExtraFields().put("config", config);
+            result.getExtraFields().put("sensors", sensors);
+            result.getExtraFields().put("effectors", effectors);
+        
+        } catch (Exception e) {
+            Exceptions.propagateIfFatal(e);
+
+            // templates with multiple entities can't have spec created in the 
manner above; just ignore
+            if (item.getSuperTypes().contains(Entity.class)) {
+                log.warn("Unable to create spec for "+item+": "+e, e);
+            }
+            if (log.isTraceEnabled()) {
+                log.trace("Unable to create spec for "+item+": "+e, e);
+            }
+        }
+    }
+
+    public static BundleSummary bundleSummary(BrooklynRestResourceUtils 
brooklyn, ManagedBundle b, UriBuilder baseUriBuilder, ManagementContext mgmt) {
+        BundleSummary result = new BundleSummary(b);
+        for (RegisteredType t: 
mgmt.getTypeRegistry().getMatching(RegisteredTypePredicates.containingBundle(b)))
 {
+            result.addType(summary(brooklyn, t, baseUriBuilder));
+        }
+        return result;
+    }
+    
+    public static BundleSummary bundleDetails(BrooklynRestResourceUtils 
brooklyn, ManagedBundle b, UriBuilder baseUriBuilder, ManagementContext mgmt) {
+        BundleSummary result = bundleSummary(brooklyn, b, baseUriBuilder, 
mgmt);
+        result.getExtraFields().put("osgiVersion", b.getOsgiVersionString());
+        result.getExtraFields().put("checksum", b.getChecksum());
+        return result;
+    }
+
+    public static BundleInstallationRestResult 
bundleInstallationResult(OsgiBundleInstallationResult in, ManagementContext 
mgmt, BrooklynRestResourceUtils brooklynU, UriInfo ui) {
+        BundleInstallationRestResult result = new BundleInstallationRestResult(
+            in.getMessage(), in.getVersionedName() != null ? 
in.getVersionedName().toString() : "", in.getCode());
+        for (RegisteredType t: in.getTypesInstalled()) {
+            TypeSummary summary = TypeTransformer.summary(brooklynU, t, 
ui.getBaseUriBuilder());
+            result.getTypes().put(t.getId(), summary);
+        }
+        return result;
+    }
+
+    protected static Map<String, URI> makeLinks(RegisteredType item, 
UriBuilder ub) {
+        return MutableMap.<String, URI>of().addIfNotNull("self", 
getSelfLink(item, ub));
+    }
+    
+    private static URI getSelfLink(RegisteredType item, UriBuilder ub) {
+        return serviceUriBuilder(ub, TypeApi.class, 
"detail").build(item.getSymbolicName(), item.getVersion());
+    }
+    private static String tidyIconLink(BrooklynRestResourceUtils b, 
RegisteredType item, String iconUrl, UriBuilder ub) {
+        if (b.isUrlServerSideAndSafe(iconUrl)) {
+            return serviceUriBuilder(ub, TypeApi.class, 
"icon").build(item.getSymbolicName(), item.getVersion()).toString();
+        }
+        return iconUrl;
+    }
+
+}

Reply via email to