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:33 UTC

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

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;
+    }
+
+}