You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by ha...@apache.org on 2015/08/09 04:55:17 UTC

[10/28] incubator-brooklyn git commit: brooklyn-rest-server: add org.apache package prefix

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/ApplicationTransformer.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/ApplicationTransformer.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/ApplicationTransformer.java
new file mode 100644
index 0000000..2ba84d9
--- /dev/null
+++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/ApplicationTransformer.java
@@ -0,0 +1,116 @@
+/*
+ * 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 brooklyn.rest.domain.Status.ACCEPTED;
+import static brooklyn.rest.domain.Status.RUNNING;
+import static brooklyn.rest.domain.Status.STARTING;
+import static brooklyn.rest.domain.Status.STOPPED;
+import static brooklyn.rest.domain.Status.STOPPING;
+import static brooklyn.rest.domain.Status.UNKNOWN;
+import static brooklyn.rest.domain.Status.DESTROYED;
+import static brooklyn.rest.domain.Status.ERROR;
+
+import java.net.URI;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+
+import javax.annotation.Nullable;
+
+import brooklyn.entity.Application;
+import brooklyn.entity.basic.Attributes;
+import brooklyn.entity.basic.Lifecycle;
+import brooklyn.entity.trait.Startable;
+import brooklyn.location.Location;
+import brooklyn.rest.domain.ApplicationSpec;
+import brooklyn.rest.domain.ApplicationSummary;
+import brooklyn.rest.domain.Status;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableMap;
+
+public class ApplicationTransformer {
+
+    public static final Function<? super Application, ApplicationSummary> FROM_APPLICATION = new Function<Application, ApplicationSummary>() {
+        @Override
+        public ApplicationSummary apply(Application input) {
+            return summaryFromApplication(input);
+        }
+    };
+
+    public static Status statusFromApplication(Application application) {
+        if (application == null) return UNKNOWN;
+        Lifecycle state = application.getAttribute(Attributes.SERVICE_STATE_ACTUAL);
+        if (state != null) return statusFromLifecycle(state);
+        Boolean up = application.getAttribute(Startable.SERVICE_UP);
+        if (up != null && up.booleanValue()) return RUNNING;
+        return UNKNOWN;
+    }
+
+
+    public static Status statusFromLifecycle(Lifecycle state) {
+        if (state == null) return UNKNOWN;
+        switch (state) {
+            case CREATED:
+                return ACCEPTED;
+            case STARTING:
+                return STARTING;
+            case RUNNING:
+                return RUNNING;
+            case STOPPING:
+                return STOPPING;
+            case STOPPED:
+                return STOPPED;
+            case DESTROYED:
+                return DESTROYED;
+            case ON_FIRE:
+                return ERROR;
+            default:
+                return UNKNOWN;
+        }
+    }
+
+    public static ApplicationSpec specFromApplication(Application application) {
+        Collection<String> locations = Collections2.transform(application.getLocations(), new Function<Location, String>() {
+            @Override
+            @Nullable
+            public String apply(@Nullable Location input) {
+                return input.getId();
+            }
+        });
+        // okay to have entities and config as null, as this comes from a real instance
+        return new ApplicationSpec(application.getDisplayName(), application.getEntityType().getName(),
+                null, locations, null);
+    }
+
+    public static ApplicationSummary summaryFromApplication(Application application) {
+        Map<String, URI> links;
+        if (application.getId() == null) {
+            links = Collections.emptyMap();
+        } else {
+            links = ImmutableMap.of(
+                    "self", URI.create("/v1/applications/" + application.getId()),
+                    "entities", URI.create("/v1/applications/" + application.getId() + "/entities"));
+        }
+
+        return new ApplicationSummary(application.getId(), specFromApplication(application), statusFromApplication(application), links);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/BrooklynFeatureTransformer.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/BrooklynFeatureTransformer.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/BrooklynFeatureTransformer.java
new file mode 100644
index 0000000..37d1643
--- /dev/null
+++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/BrooklynFeatureTransformer.java
@@ -0,0 +1,45 @@
+/*
+ * 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 com.google.common.base.Function;
+
+import brooklyn.BrooklynVersion.BrooklynFeature;
+import brooklyn.rest.domain.BrooklynFeatureSummary;
+
+public class BrooklynFeatureTransformer {
+
+    public static final Function<BrooklynFeature, BrooklynFeatureSummary> FROM_FEATURE = new Function<BrooklynFeature, BrooklynFeatureSummary>() {
+        @Override
+        public BrooklynFeatureSummary apply(BrooklynFeature feature) {
+            return featureSummary(feature);
+        }
+    };
+
+    public static BrooklynFeatureSummary featureSummary(BrooklynFeature feature) {
+        return new BrooklynFeatureSummary(
+                feature.getName(),
+                feature.getSymbolicName(),
+                feature.getVersion(),
+                feature.getLastModified(),
+                feature.getAdditionalData());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/CatalogTransformer.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/CatalogTransformer.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/CatalogTransformer.java
new file mode 100644
index 0000000..8620142
--- /dev/null
+++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/CatalogTransformer.java
@@ -0,0 +1,163 @@
+/*
+ * 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 java.net.URI;
+import java.util.Map;
+import java.util.Set;
+
+import org.slf4j.LoggerFactory;
+
+import brooklyn.basic.BrooklynTypes;
+import org.apache.brooklyn.catalog.CatalogItem;
+import org.apache.brooklyn.catalog.CatalogItem.CatalogItemType;
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.Effector;
+import brooklyn.entity.Entity;
+import brooklyn.entity.EntityType;
+import brooklyn.entity.basic.EntityDynamicType;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.event.Sensor;
+import brooklyn.location.Location;
+import brooklyn.location.LocationSpec;
+import brooklyn.policy.Policy;
+import brooklyn.policy.PolicySpec;
+import brooklyn.rest.domain.CatalogEntitySummary;
+import brooklyn.rest.domain.CatalogItemSummary;
+import brooklyn.rest.domain.CatalogLocationSummary;
+import brooklyn.rest.domain.CatalogPolicySummary;
+import brooklyn.rest.domain.EffectorSummary;
+import brooklyn.rest.domain.EntityConfigSummary;
+import brooklyn.rest.domain.LocationConfigSummary;
+import brooklyn.rest.domain.PolicyConfigSummary;
+import brooklyn.rest.domain.SensorSummary;
+import brooklyn.rest.domain.SummaryComparators;
+import org.apache.brooklyn.rest.util.BrooklynRestResourceUtils;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.exceptions.Exceptions;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+
+public class CatalogTransformer {
+
+    private static final org.slf4j.Logger log = LoggerFactory.getLogger(CatalogTransformer.class);
+    
+    public static CatalogEntitySummary catalogEntitySummary(BrooklynRestResourceUtils b, CatalogItem<? extends Entity,EntitySpec<?>> item) {
+        Set<EntityConfigSummary> config = Sets.newTreeSet(SummaryComparators.nameComparator());
+        Set<SensorSummary> sensors = Sets.newTreeSet(SummaryComparators.nameComparator());
+        Set<EffectorSummary> effectors = Sets.newTreeSet(SummaryComparators.nameComparator());
+
+        try {
+            EntitySpec<?> spec = b.getCatalog().createSpec(item);
+            EntityDynamicType typeMap = BrooklynTypes.getDefinedEntityType(spec.getType());
+            EntityType type = typeMap.getSnapshot();
+
+            for (ConfigKey<?> x: type.getConfigKeys())
+                config.add(EntityTransformer.entityConfigSummary(x, typeMap.getConfigKeyField(x.getName())));
+            for (Sensor<?> x: type.getSensors())
+                sensors.add(SensorTransformer.sensorSummaryForCatalog(x));
+            for (Effector<?> x: type.getEffectors())
+                effectors.add(EffectorTransformer.effectorSummaryForCatalog(x));
+            
+        } catch (Exception e) {
+            Exceptions.propagateIfFatal(e);
+            
+            // templates with multiple entities can't have spec created in the manner above; just ignore
+            if (item.getCatalogItemType()==CatalogItemType.ENTITY) {
+                log.warn("Unable to create spec for "+item+": "+e, e);
+            }
+            if (log.isTraceEnabled()) {
+                log.trace("Unable to create spec for "+item+": "+e, e);
+            }
+        }
+        
+        return new CatalogEntitySummary(item.getSymbolicName(), item.getVersion(), item.getDisplayName(),
+            item.getJavaType(), item.getPlanYaml(),
+            item.getDescription(), tidyIconLink(b, item, item.getIconUrl()),
+            config, sensors, effectors,
+            item.isDeprecated(), makeLinks(item));
+    }
+
+    @SuppressWarnings("unchecked")
+    public static CatalogItemSummary catalogItemSummary(BrooklynRestResourceUtils b, CatalogItem<?,?> item) {
+        try {
+            switch (item.getCatalogItemType()) {
+            case TEMPLATE:
+            case ENTITY:
+                return catalogEntitySummary(b, (CatalogItem<? extends Entity, EntitySpec<?>>) item);
+            case POLICY:
+                return catalogPolicySummary(b, (CatalogItem<? extends Policy, PolicySpec<?>>) item);
+            case LOCATION:
+                return catalogLocationSummary(b, (CatalogItem<? extends Location, LocationSpec<?>>) item);
+            default:
+                log.warn("Unexpected catalog item type when getting self link (supplying generic item): "+item.getCatalogItemType()+" "+item);
+            }
+        } catch (Exception e) {
+            Exceptions.propagateIfFatal(e);
+            log.warn("Invalid item in catalog when converting REST summaries (supplying generic item), at "+item+": "+e, e);
+        }
+        return new CatalogItemSummary(item.getSymbolicName(), item.getVersion(), item.getDisplayName(),
+            item.getJavaType(), item.getPlanYaml(),
+            item.getDescription(), tidyIconLink(b, item, item.getIconUrl()), item.isDeprecated(), makeLinks(item));
+    }
+
+    public static CatalogPolicySummary catalogPolicySummary(BrooklynRestResourceUtils b, CatalogItem<? extends Policy,PolicySpec<?>> item) {
+        Set<PolicyConfigSummary> config = ImmutableSet.of();
+        return new CatalogPolicySummary(item.getSymbolicName(), item.getVersion(), item.getDisplayName(),
+                item.getJavaType(), item.getPlanYaml(),
+                item.getDescription(), tidyIconLink(b, item, item.getIconUrl()), config,
+                item.isDeprecated(), makeLinks(item));
+    }
+
+    public static CatalogLocationSummary catalogLocationSummary(BrooklynRestResourceUtils b, CatalogItem<? extends Location,LocationSpec<?>> item) {
+        Set<LocationConfigSummary> config = ImmutableSet.of();
+        return new CatalogLocationSummary(item.getSymbolicName(), item.getVersion(), item.getDisplayName(),
+                item.getJavaType(), item.getPlanYaml(),
+                item.getDescription(), tidyIconLink(b, item, item.getIconUrl()), config,
+                item.isDeprecated(), makeLinks(item));
+    }
+
+    protected static Map<String, URI> makeLinks(CatalogItem<?,?> item) {
+        return MutableMap.<String, URI>of().addIfNotNull("self", URI.create(getSelfLink(item)));
+    }
+
+    protected static String getSelfLink(CatalogItem<?,?> item) {
+        String itemId = item.getId();
+        switch (item.getCatalogItemType()) {
+        case TEMPLATE:
+            return "/v1/applications/" + itemId + "/" + item.getVersion();
+        case ENTITY:
+            return "/v1/entities/" + itemId + "/" + item.getVersion();
+        case POLICY:
+            return "/v1/policies/" + itemId + "/" + item.getVersion();
+        case LOCATION:
+            return "/v1/locations/" + itemId + "/" + item.getVersion();
+        default:
+            log.warn("Unexpected catalog item type when getting self link (not supplying self link): "+item.getCatalogItemType()+" "+item);
+            return null;
+        }
+    }
+    private static String tidyIconLink(BrooklynRestResourceUtils b, CatalogItem<?,?> item, String iconUrl) {
+        if (b.isUrlServerSideAndSafe(iconUrl))
+            return "/v1/catalog/icon/"+item.getSymbolicName() + "/" + item.getVersion();
+        return iconUrl;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/EffectorTransformer.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/EffectorTransformer.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/EffectorTransformer.java
new file mode 100644
index 0000000..3489942
--- /dev/null
+++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/EffectorTransformer.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.transform;
+
+import java.net.URI;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+
+import brooklyn.entity.Effector;
+import brooklyn.entity.Entity;
+import brooklyn.entity.ParameterType;
+import brooklyn.entity.basic.EntityLocal;
+import brooklyn.rest.domain.EffectorSummary;
+import brooklyn.rest.domain.EffectorSummary.ParameterSummary;
+import org.apache.brooklyn.rest.util.WebResourceUtils;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.guava.Maybe;
+import brooklyn.util.task.Tasks;
+import brooklyn.util.task.ValueResolver;
+
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+
+public class EffectorTransformer {
+
+    public static EffectorSummary effectorSummary(final EntityLocal entity, Effector<?> effector) {
+        String applicationUri = "/v1/applications/" + entity.getApplicationId();
+        String entityUri = applicationUri + "/entities/" + entity.getId();
+        return new EffectorSummary(effector.getName(), effector.getReturnTypeName(),
+                 ImmutableSet.copyOf(Iterables.transform(effector.getParameters(),
+                new Function<ParameterType<?>, EffectorSummary.ParameterSummary<?>>() {
+                    @Override
+                    public EffectorSummary.ParameterSummary<?> apply(@Nullable ParameterType<?> parameterType) {
+                        return parameterSummary(entity, parameterType);
+                    }
+                })), effector.getDescription(), ImmutableMap.of(
+                "self", URI.create(entityUri + "/effectors/" + effector.getName()),
+                "entity", URI.create(entityUri),
+                "application", URI.create(applicationUri)
+        ));
+    }
+
+    public static EffectorSummary effectorSummaryForCatalog(Effector<?> effector) {
+        Set<EffectorSummary.ParameterSummary<?>> parameters = ImmutableSet.copyOf(Iterables.transform(effector.getParameters(),
+                new Function<ParameterType<?>, EffectorSummary.ParameterSummary<?>>() {
+                    @Override
+                    public EffectorSummary.ParameterSummary<?> apply(ParameterType<?> parameterType) {
+                        return parameterSummary(null, parameterType);
+                    }
+                }));
+        return new EffectorSummary(effector.getName(),
+                effector.getReturnTypeName(), parameters, effector.getDescription(), null);
+    }
+    
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    protected static EffectorSummary.ParameterSummary<?> parameterSummary(Entity entity, ParameterType<?> parameterType) {
+        try {
+            Maybe<?> defaultValue = Tasks.resolving(parameterType.getDefaultValue()).as(parameterType.getParameterClass())
+                .context(entity).timeout(ValueResolver.REAL_QUICK_WAIT).getMaybe();
+            return new ParameterSummary(parameterType.getName(), parameterType.getParameterClassName(), 
+                parameterType.getDescription(), 
+                WebResourceUtils.getValueForDisplay(defaultValue.orNull(), true, false));
+        } catch (Exception e) {
+            throw Exceptions.propagate(e);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/EntityTransformer.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/EntityTransformer.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/EntityTransformer.java
new file mode 100644
index 0000000..3ba7cd8
--- /dev/null
+++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/EntityTransformer.java
@@ -0,0 +1,155 @@
+/*
+ * 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 com.google.common.collect.Iterables.transform;
+
+import java.lang.reflect.Field;
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.brooklyn.catalog.CatalogConfig;
+import brooklyn.config.ConfigKey;
+import brooklyn.config.render.RendererHints;
+import brooklyn.entity.Application;
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.EntityLocal;
+import brooklyn.rest.domain.EntityConfigSummary;
+import brooklyn.rest.domain.EntitySummary;
+import org.apache.brooklyn.rest.util.WebResourceUtils;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.net.URLParamEncoder;
+
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+
+/**
+ * @author Adam Lowe
+ */
+public class EntityTransformer {
+
+    public static final Function<? super Entity, EntitySummary> FROM_ENTITY = new Function<Entity, EntitySummary>() {
+        @Override
+        public EntitySummary apply(Entity entity) {
+            return EntityTransformer.entitySummary(entity);
+        }
+    };
+
+    public static EntitySummary entitySummary(Entity entity) {
+        String applicationUri = "/v1/applications/" + entity.getApplicationId();
+        String entityUri = applicationUri + "/entities/" + entity.getId();
+        ImmutableMap.Builder<String, URI> lb = ImmutableMap.<String, URI>builder()
+                .put("self", URI.create(entityUri));
+        if (entity.getParent()!=null)
+            lb.put("parent", URI.create(applicationUri+"/entities/"+entity.getParent().getId()));
+        String type = entity.getEntityType().getName();
+        lb.put("application", URI.create(applicationUri))
+                .put("children", URI.create(entityUri + "/children"))
+                .put("config", URI.create(entityUri + "/config"))
+                .put("sensors", URI.create(entityUri + "/sensors"))
+                .put("effectors", URI.create(entityUri + "/effectors"))
+                .put("policies", URI.create(entityUri + "/policies"))
+                .put("activities", URI.create(entityUri + "/activities"))
+                .put("locations", URI.create(entityUri + "/locations"))
+                .put("tags", URI.create(entityUri + "/tags"))
+                .put("expunge", URI.create(entityUri + "/expunge"))
+                .put("rename", URI.create(entityUri + "/name"))
+                .put("spec", URI.create(entityUri + "/spec"))
+            ;
+
+        if (entity.getCatalogItemId() != null) {
+            lb.put("catalog", URI.create("/v1/catalog/entities/" + WebResourceUtils.getPathFromVersionedId(entity.getCatalogItemId())));
+        }
+
+        if (entity.getIconUrl()!=null)
+            lb.put("iconUrl", URI.create(entityUri + "/icon"));
+
+        return new EntitySummary(entity.getId(), entity.getDisplayName(), type, entity.getCatalogItemId(), lb.build());
+    }
+
+    public static List<EntitySummary> entitySummaries(Iterable<? extends Entity> entities) {
+        return Lists.newArrayList(transform(
+            entities,
+            new Function<Entity, EntitySummary>() {
+                @Override
+                public EntitySummary apply(Entity entity) {
+                    return EntityTransformer.entitySummary(entity);
+                }
+            }));
+    }
+
+    protected static EntityConfigSummary entityConfigSummary(ConfigKey<?> config, String label, Double priority, Map<String, URI> links) {
+        Map<String, URI> mapOfLinks =  links==null ? null : ImmutableMap.copyOf(links);
+        return new EntityConfigSummary(config, label, priority, mapOfLinks);
+    }
+    /** generates a representation for a given config key, 
+     * with label inferred from annoation in the entity class,
+     * and links pointing to the entity and the applicaiton */
+    public static EntityConfigSummary entityConfigSummary(EntityLocal entity, ConfigKey<?> config) {
+      /*
+       * following code nearly there to get the @CatalogConfig annotation
+       * in the class and use that to populate a label
+       */
+
+//    EntityDynamicType typeMap = 
+//            ((AbstractEntity)entity).getMutableEntityType();
+//      // above line works if we can cast; line below won't work, but there should some way
+//      // to get back the handle to the spec from an entity local, which then *would* work
+//            EntityTypes.getDefinedEntityType(entity.getClass());
+
+//    String label = typeMap.getConfigKeyField(config.getName());
+        String label = null;
+        Double priority = null;
+
+        String applicationUri = "/v1/applications/" + entity.getApplicationId();
+        String entityUri = applicationUri + "/entities/" + entity.getId();
+        String selfUri = entityUri + "/config/" + URLParamEncoder.encode(config.getName());
+        
+        MutableMap.Builder<String, URI> lb = MutableMap.<String, URI>builder()
+            .put("self", URI.create(selfUri))
+            .put("application", URI.create(applicationUri))
+            .put("entity", URI.create(entityUri))
+            .put("action:json", URI.create(selfUri));
+
+        Iterable<RendererHints.NamedAction> hints = Iterables.filter(RendererHints.getHintsFor(config), RendererHints.NamedAction.class);
+        for (RendererHints.NamedAction na : hints) {
+            SensorTransformer.addNamedAction(lb, na, entity.getConfig(config), config, entity);
+        }
+    
+        return entityConfigSummary(config, label, priority, lb.build());
+    }
+
+    public static String applicationUri(Application entity) {
+        return "/v1/applications/" + entity.getApplicationId();
+    }
+    
+    public static String entityUri(Entity entity) {
+        return applicationUri(entity.getApplication()) + "/entities/" + entity.getId();
+    }
+    
+    public static EntityConfigSummary entityConfigSummary(ConfigKey<?> config, Field configKeyField) {
+        CatalogConfig catalogConfig = configKeyField!=null ? configKeyField.getAnnotation(CatalogConfig.class) : null;
+        String label = catalogConfig==null ? null : catalogConfig.label();
+        Double priority = catalogConfig==null ? null : catalogConfig.priority();
+        return entityConfigSummary(config, label, priority, null);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/HighAvailabilityTransformer.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/HighAvailabilityTransformer.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/HighAvailabilityTransformer.java
new file mode 100644
index 0000000..61d0f49
--- /dev/null
+++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/HighAvailabilityTransformer.java
@@ -0,0 +1,50 @@
+/*
+ * 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 java.net.URI;
+import java.util.Map;
+
+import brooklyn.management.ha.ManagementPlaneSyncRecord;
+import brooklyn.management.ha.ManagementNodeSyncRecord;
+import brooklyn.rest.domain.HighAvailabilitySummary;
+import brooklyn.rest.domain.HighAvailabilitySummary.HaNodeSummary;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+
+public class HighAvailabilityTransformer {
+
+    public static HighAvailabilitySummary highAvailabilitySummary(String ownNodeId, ManagementPlaneSyncRecord memento) {
+        Map<String, HaNodeSummary> nodes = Maps.newLinkedHashMap();
+        for (Map.Entry<String, ManagementNodeSyncRecord> entry : memento.getManagementNodes().entrySet()) {
+            nodes.put(entry.getKey(), haNodeSummary(entry.getValue()));
+        }
+        
+        // TODO What links?
+        ImmutableMap.Builder<String, URI> lb = ImmutableMap.<String, URI>builder();
+
+        return new HighAvailabilitySummary(ownNodeId, memento.getMasterNodeId(), nodes, lb.build());
+    }
+
+    public static HaNodeSummary haNodeSummary(ManagementNodeSyncRecord memento) {
+        String status = memento.getStatus() == null ? null : memento.getStatus().toString();
+        return new HaNodeSummary(memento.getNodeId(), memento.getUri(), status, memento.getLocalTimestamp(), memento.getRemoteTimestamp());
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/LocationTransformer.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/LocationTransformer.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/LocationTransformer.java
new file mode 100644
index 0000000..240845e
--- /dev/null
+++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/LocationTransformer.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 java.net.URI;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.entity.basic.Sanitizer;
+import brooklyn.location.Location;
+import brooklyn.location.LocationDefinition;
+import brooklyn.location.basic.BasicLocationDefinition;
+import brooklyn.location.basic.LocationConfigKeys;
+import brooklyn.location.basic.LocationInternal;
+import brooklyn.management.ManagementContext;
+import brooklyn.rest.domain.LocationSummary;
+import org.apache.brooklyn.rest.util.WebResourceUtils;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.config.ConfigBag;
+import brooklyn.util.guava.Maybe;
+import brooklyn.util.text.Strings;
+
+import com.google.common.collect.ImmutableMap;
+
+public class LocationTransformer {
+
+    @SuppressWarnings("unused")
+    private static final Logger log = LoggerFactory.getLogger(LocationTransformer.LocationDetailLevel.class);
+    
+    public static enum LocationDetailLevel { NONE, LOCAL_EXCLUDING_SECRET, FULL_EXCLUDING_SECRET, FULL_INCLUDING_SECRET }
+    
+    /** @deprecated since 0.7.0 use method taking management context and detail specifier */
+    @Deprecated
+    public static LocationSummary newInstance(String id, brooklyn.rest.domain.LocationSpec locationSpec) {
+        return newInstance(null, id, locationSpec, LocationDetailLevel.LOCAL_EXCLUDING_SECRET);
+    }
+    @SuppressWarnings("deprecation")
+    public static LocationSummary newInstance(ManagementContext mgmt, String id, brooklyn.rest.domain.LocationSpec locationSpec, LocationDetailLevel level) {
+        // TODO: Remove null checks on mgmt when newInstance(String, LocationSpec) is deleted
+        Map<String, ?> config = locationSpec.getConfig();
+        if (mgmt != null && (level==LocationDetailLevel.FULL_EXCLUDING_SECRET || level==LocationDetailLevel.FULL_INCLUDING_SECRET)) {
+            LocationDefinition ld = new BasicLocationDefinition(id, locationSpec.getName(), locationSpec.getSpec(), locationSpec.getConfig());
+            Location ll = mgmt.getLocationRegistry().resolve(ld, false, null).orNull();
+            if (ll!=null) config = ((LocationInternal)ll).config().getBag().getAllConfig();
+        } else if (level==LocationDetailLevel.LOCAL_EXCLUDING_SECRET) {
+            // get displayName
+            if (!config.containsKey(LocationConfigKeys.DISPLAY_NAME.getName()) && mgmt!=null) {
+                LocationDefinition ld = new BasicLocationDefinition(id, locationSpec.getName(), locationSpec.getSpec(), locationSpec.getConfig());
+                Location ll = mgmt.getLocationRegistry().resolve(ld, false, null).orNull();
+                if (ll!=null) {
+                    Map<String, Object> configExtra = ((LocationInternal)ll).config().getBag().getAllConfig();
+                    if (configExtra.containsKey(LocationConfigKeys.DISPLAY_NAME.getName())) {
+                        ConfigBag configNew = ConfigBag.newInstance(config);
+                        configNew.configure(LocationConfigKeys.DISPLAY_NAME, (String)configExtra.get(LocationConfigKeys.DISPLAY_NAME.getName()));
+                        config = configNew.getAllConfig();
+                    }
+                }
+            }
+        }
+        return new LocationSummary(
+                id,
+                locationSpec.getName(),
+                locationSpec.getSpec(),
+                null,
+                copyConfig(config, level),
+                ImmutableMap.of("self", URI.create("/v1/locations/" + id)));
+    }
+
+    /** @deprecated since 0.7.0 use method taking management context and detail specifier */
+    @Deprecated
+    public static LocationSummary newInstance(LocationDefinition l) {
+        return newInstance(null, l, LocationDetailLevel.LOCAL_EXCLUDING_SECRET);
+    }
+
+    public static LocationSummary newInstance(ManagementContext mgmt, LocationDefinition l, LocationDetailLevel level) {
+        // TODO: Can remove null checks on mgmt when newInstance(LocationDefinition) is deleted
+        Map<String, Object> config = l.getConfig();
+        if (mgmt != null && (level==LocationDetailLevel.FULL_EXCLUDING_SECRET || level==LocationDetailLevel.FULL_INCLUDING_SECRET)) {
+            Location ll = mgmt.getLocationRegistry().resolve(l, false, null).orNull();
+            if (ll!=null) config = ((LocationInternal)ll).config().getBag().getAllConfig();
+        } else if (level==LocationDetailLevel.LOCAL_EXCLUDING_SECRET) {
+            // get displayName
+            if (mgmt != null && !config.containsKey(LocationConfigKeys.DISPLAY_NAME.getName())) {
+                Location ll = mgmt.getLocationRegistry().resolve(l, false, null).orNull();
+                if (ll!=null) {
+                    Map<String, Object> configExtra = ((LocationInternal)ll).config().getBag().getAllConfig();
+                    if (configExtra.containsKey(LocationConfigKeys.DISPLAY_NAME.getName())) {
+                        ConfigBag configNew = ConfigBag.newInstance(config);
+                        configNew.configure(LocationConfigKeys.DISPLAY_NAME, (String)configExtra.get(LocationConfigKeys.DISPLAY_NAME.getName()));
+                        config = configNew.getAllConfig();
+                    }
+                }
+            }
+        }
+
+        return new LocationSummary(
+                l.getId(),
+                l.getName(),
+                l.getSpec(),
+                null,
+                copyConfig(config, level),
+                ImmutableMap.of("self", URI.create("/v1/locations/" + l.getId())));
+    }
+
+    private static Map<String, ?> copyConfig(Map<String,?> entries, LocationDetailLevel level) {
+        ImmutableMap.Builder<String, Object> builder = ImmutableMap.builder();
+        if (level!=LocationDetailLevel.NONE) {
+            for (Map.Entry<String,?> entry : entries.entrySet()) {
+                if (level==LocationDetailLevel.FULL_INCLUDING_SECRET || !Sanitizer.IS_SECRET_PREDICATE.apply(entry.getKey())) {
+                    builder.put(entry.getKey(), WebResourceUtils.getValueForDisplay(entry.getValue(), true, false));
+                }
+            }
+        }
+        return builder.build();
+    }
+
+    public static LocationSummary newInstance(ManagementContext mgmt, Location l, LocationDetailLevel level) {
+        String spec = null;
+        String specId = null;
+        Location lp = l;
+        while (lp!=null && (spec==null || specId==null)) {
+            // walk parent locations
+            // TODO not sure this is the best strategy, or if it's needed, as the spec config is inherited anyway... 
+            if (spec==null) {
+                Maybe<Object> originalSpec = ((LocationInternal)lp).config().getRaw(LocationInternal.ORIGINAL_SPEC);
+                if (originalSpec.isPresent())
+                    spec = Strings.toString(originalSpec.get());
+            }
+            if (specId==null) {
+                LocationDefinition ld = null;
+                // prefer looking it up by name as this loads the canonical definition
+                if (spec!=null) ld = mgmt.getLocationRegistry().getDefinedLocationByName(spec);
+                if (ld==null && spec!=null && spec.startsWith("named:")) 
+                    ld = mgmt.getLocationRegistry().getDefinedLocationByName(Strings.removeFromStart(spec, "named:"));
+                if (ld==null) ld = mgmt.getLocationRegistry().getDefinedLocationById(lp.getId());
+                if (ld!=null) {
+                    if (spec==null) spec = ld.getSpec();
+                    specId = ld.getId();
+                }
+            }
+            lp = lp.getParent();
+        }
+        if (specId==null && spec!=null) {
+            // fall back to attempting to lookup it
+            Location ll = mgmt.getLocationRegistry().resolve(spec, false, null).orNull();
+            if (ll!=null) specId = ll.getId();
+        }
+        
+        Map<String, Object> configOrig;
+        if (level == LocationDetailLevel.LOCAL_EXCLUDING_SECRET) {
+            configOrig = MutableMap.copyOf(((LocationInternal)l).config().getLocalBag().getAllConfig());
+        } else {
+            configOrig = MutableMap.copyOf(((LocationInternal)l).config().getBag().getAllConfig());
+        }
+        if (level==LocationDetailLevel.LOCAL_EXCLUDING_SECRET) {
+            // for LOCAL, also get the display name
+            if (!configOrig.containsKey(LocationConfigKeys.DISPLAY_NAME.getName())) {
+                Map<String, Object> configExtra = ((LocationInternal)l).config().getBag().getAllConfig();
+                if (configExtra.containsKey(LocationConfigKeys.DISPLAY_NAME.getName()))
+                    configOrig.put(LocationConfigKeys.DISPLAY_NAME.getName(), configExtra.get(LocationConfigKeys.DISPLAY_NAME.getName()));
+            }
+        }
+        Map<String, ?> config = level==LocationDetailLevel.NONE ? null : copyConfig(configOrig, level);
+        
+        return new LocationSummary(
+            l.getId(),
+            l.getDisplayName(),
+            spec,
+            l.getClass().getName(),
+            config,
+            MutableMap.of("self", URI.create("/v1/locations/" + l.getId()))
+                .addIfNotNull("parent", l.getParent()!=null ? URI.create("/v1/locations/"+l.getParent().getId()) : null)
+                .addIfNotNull("spec", specId!=null ? URI.create("/v1/locations/"+specId) : null)
+                .asUnmodifiable() );
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/PolicyTransformer.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/PolicyTransformer.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/PolicyTransformer.java
new file mode 100644
index 0000000..6893633
--- /dev/null
+++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/PolicyTransformer.java
@@ -0,0 +1,84 @@
+/*
+ * 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 java.net.URI;
+import java.util.Map;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.EntityLocal;
+import brooklyn.policy.Policy;
+import brooklyn.policy.basic.Policies;
+import brooklyn.rest.domain.ApplicationSummary;
+import brooklyn.rest.domain.PolicyConfigSummary;
+import brooklyn.rest.domain.PolicySummary;
+import org.apache.brooklyn.rest.resources.PolicyConfigResource;
+import org.apache.brooklyn.rest.util.BrooklynRestResourceUtils;
+
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * Converts from Brooklyn entities to restful API summary objects
+ */
+public class PolicyTransformer {
+
+    public static PolicySummary policySummary(Entity entity, Policy policy) {
+        String applicationUri = "/v1/applications/" + entity.getApplicationId();
+        String entityUri = applicationUri + "/entities/" + entity.getId();
+
+        Map<String, URI> links = ImmutableMap.<String, URI>builder()
+                .put("self", URI.create(entityUri + "/policies/" + policy.getId()))
+                .put("config", URI.create(entityUri + "/policies/" + policy.getId() + "/config"))
+                .put("start", URI.create(entityUri + "/policies/" + policy.getId() + "/start"))
+                .put("stop", URI.create(entityUri + "/policies/" + policy.getId() + "/stop"))
+                .put("destroy", URI.create(entityUri + "/policies/" + policy.getId() + "/destroy"))
+                .put("application", URI.create(applicationUri))
+                .put("entity", URI.create(entityUri))
+                .build();
+
+        return new PolicySummary(policy.getId(), policy.getDisplayName(), policy.getCatalogItemId(), ApplicationTransformer.statusFromLifecycle(Policies.getPolicyStatus(policy)), links);
+    }
+
+    public static PolicyConfigSummary policyConfigSummary(BrooklynRestResourceUtils utils, ApplicationSummary application, EntityLocal entity, Policy policy, ConfigKey<?> config) {
+        PolicyConfigSummary summary = policyConfigSummary(utils, entity, policy, config);
+//        TODO
+//        if (!entity.getApplicationId().equals(application.getInstance().getId()))
+//            throw new IllegalStateException("Application "+application+" does not match app "+entity.getApplication()+" of "+entity);
+        return summary;
+    }
+
+    public static PolicyConfigSummary policyConfigSummary(BrooklynRestResourceUtils utils, EntityLocal entity, Policy policy, ConfigKey<?> config) {
+        String applicationUri = "/v1/applications/" + entity.getApplicationId();
+        String entityUri = applicationUri + "/entities/" + entity.getId();
+        String policyUri = entityUri + "/policies/" + policy.getId();
+
+        Map<String, URI> links = ImmutableMap.<String, URI>builder()
+                .put("self", URI.create(policyUri + "/config/" + config.getName()))
+                .put("application", URI.create(applicationUri))
+                .put("entity", URI.create(entityUri))
+                .put("policy", URI.create(policyUri))
+                .build();
+
+        return new PolicyConfigSummary(config.getName(), config.getTypeName(), config.getDescription(), 
+                PolicyConfigResource.getStringValueForDisplay(utils, policy, config.getDefaultValue()), 
+                config.isReconfigurable(), 
+                links);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/SensorTransformer.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/SensorTransformer.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/SensorTransformer.java
new file mode 100644
index 0000000..dfa6de0
--- /dev/null
+++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/SensorTransformer.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.transform;
+
+import java.net.URI;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.config.render.RendererHints;
+import brooklyn.entity.Entity;
+import brooklyn.event.AttributeSensor;
+import brooklyn.event.Sensor;
+import brooklyn.rest.domain.SensorSummary;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.net.URLParamEncoder;
+import brooklyn.util.text.Strings;
+
+import com.google.common.collect.Iterables;
+
+public class SensorTransformer {
+
+    private static final Logger log = LoggerFactory.getLogger(SensorTransformer.class);
+
+    public static SensorSummary sensorSummaryForCatalog(Sensor<?> sensor) {
+        return new SensorSummary(sensor.getName(), sensor.getTypeName(),
+                sensor.getDescription(), null);
+    }
+
+    public static SensorSummary sensorSummary(Entity entity, Sensor<?> sensor) {
+        String applicationUri = "/v1/applications/" + entity.getApplicationId();
+        String entityUri = applicationUri + "/entities/" + entity.getId();
+        String selfUri = entityUri + "/sensors/" + URLParamEncoder.encode(sensor.getName());
+
+        MutableMap.Builder<String, URI> lb = MutableMap.<String, URI>builder()
+                .put("self", URI.create(selfUri))
+                .put("application", URI.create(applicationUri))
+                .put("entity", URI.create(entityUri))
+                .put("action:json", URI.create(selfUri));
+
+        if (sensor instanceof AttributeSensor) {
+            Iterable<RendererHints.NamedAction> hints = Iterables.filter(RendererHints.getHintsFor((AttributeSensor<?>)sensor), RendererHints.NamedAction.class);
+            for (RendererHints.NamedAction na : hints) addNamedAction(lb, na , entity, sensor);
+        }
+
+        return new SensorSummary(sensor.getName(), sensor.getTypeName(), sensor.getDescription(), lb.build());
+    }
+
+    private static <T> void addNamedAction(MutableMap.Builder<String, URI> lb, RendererHints.NamedAction na , Entity entity, Sensor<T> sensor) {
+        addNamedAction(lb, na, entity.getAttribute( ((AttributeSensor<T>) sensor) ), sensor, entity);
+    }
+    
+    @SuppressWarnings("unchecked")
+    static <T> void addNamedAction(MutableMap.Builder<String, URI> lb, RendererHints.NamedAction na, T value, Object context, Entity contextEntity) {
+        if (na instanceof RendererHints.NamedActionWithUrl) {
+            try {
+                String v = ((RendererHints.NamedActionWithUrl<T>) na).getUrlFromValue(value);
+                if (Strings.isNonBlank(v)) {
+                    String action = na.getActionName().toLowerCase();
+                    lb.putIfAbsent("action:"+action, URI.create(v));
+                }
+            } catch (Exception e) {
+                Exceptions.propagateIfFatal(e);
+                log.warn("Unable to make action "+na+" from "+context+" on "+contextEntity+": "+e, e);
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/TaskTransformer.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/TaskTransformer.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/TaskTransformer.java
new file mode 100644
index 0000000..50b07e8
--- /dev/null
+++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/TaskTransformer.java
@@ -0,0 +1,147 @@
+/*
+ * 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 java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.BrooklynTaskTags;
+import brooklyn.entity.basic.BrooklynTaskTags.WrappedStream;
+import brooklyn.management.HasTaskChildren;
+import brooklyn.management.Task;
+import brooklyn.rest.domain.LinkWithMetadata;
+import brooklyn.rest.domain.TaskSummary;
+import org.apache.brooklyn.rest.util.WebResourceUtils;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.task.TaskInternal;
+import brooklyn.util.text.Strings;
+
+import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
+
+public class TaskTransformer {
+
+    @SuppressWarnings("unused")
+    private static final Logger log = LoggerFactory.getLogger(TaskTransformer.class);
+
+    public static final Function<Task<?>, TaskSummary> FROM_TASK = new Function<Task<?>, TaskSummary>() {
+        @Override
+        public TaskSummary apply(@Nullable Task<?> input) {
+            return taskSummary(input);
+        }
+    };
+
+    public static TaskSummary taskSummary(Task<?> task) {
+      try {
+        Preconditions.checkNotNull(task);
+        Entity entity = BrooklynTaskTags.getContextEntity(task);
+        String entityId;
+        String entityDisplayName;
+        URI entityLink;
+        
+        String selfLink = asLink(task).getLink();
+
+        if (entity != null) {
+            entityId = entity.getId();
+            entityDisplayName = entity.getDisplayName();
+            entityLink = new URI("/v1/applications/"+entity.getApplicationId()+"/"+"entities"+"/"+entity.getId());
+        } else {
+            entityId = null;
+            entityDisplayName = null;
+            entityLink = null;
+        }
+
+        List<LinkWithMetadata> children = Collections.emptyList();
+        if (task instanceof HasTaskChildren) {
+            children = new ArrayList<LinkWithMetadata>();
+            for (Task<?> t: ((HasTaskChildren)task).getChildren()) {
+                children.add(asLink(t));
+            }
+        }
+        
+        Map<String,LinkWithMetadata> streams = new MutableMap<String, LinkWithMetadata>();
+        for (WrappedStream stream: BrooklynTaskTags.streams(task)) {
+            MutableMap<String, Object> metadata = MutableMap.<String,Object>of("name", stream.streamType);
+            if (stream.streamSize.get()!=null) {
+                metadata.add("size", stream.streamSize.get());
+                metadata.add("sizeText", Strings.makeSizeString(stream.streamSize.get()));
+            }
+            String link = selfLink+"/stream/"+stream.streamType;
+            streams.put(stream.streamType, new LinkWithMetadata(link, metadata));
+        }
+        
+        Map<String,URI> links = MutableMap.of("self", new URI(selfLink),
+                "children", new URI(selfLink+"/"+"children"));
+        if (entityLink!=null) links.put("entity", entityLink);
+        
+        Object result;
+        try {
+            if (task.isDone()) {
+                result = WebResourceUtils.getValueForDisplay(task.get(), true, false);
+            } else {
+                result = null;
+            }
+        } catch (Throwable t) {
+            result = Exceptions.collapseText(t);
+        }
+        
+        return new TaskSummary(task.getId(), task.getDisplayName(), task.getDescription(), entityId, entityDisplayName, 
+                task.getTags(), ifPositive(task.getSubmitTimeUtc()), ifPositive(task.getStartTimeUtc()), ifPositive(task.getEndTimeUtc()),
+                task.getStatusSummary(), result, task.isError(), task.isCancelled(),
+                children, asLink(task.getSubmittedByTask()), 
+                task.isDone() ? null : task instanceof TaskInternal ? asLink(((TaskInternal<?>)task).getBlockingTask()) : null, 
+                task.isDone() ? null : task instanceof TaskInternal ? ((TaskInternal<?>)task).getBlockingDetails() : null, 
+                task.getStatusDetail(true),
+                streams,
+                links);
+      } catch (URISyntaxException e) {
+          // shouldn't happen
+          throw Exceptions.propagate(e);
+      }
+    }
+
+    private static Long ifPositive(Long time) {
+        if (time==null || time<=0) return null;
+        return time;
+    }
+
+    public static LinkWithMetadata asLink(Task<?> t) {
+        if (t==null) return null;
+        MutableMap<String,Object> data = new MutableMap<String,Object>();
+        data.put("id", t.getId());
+        if (t.getDisplayName()!=null) data.put("taskName", t.getDisplayName());
+        Entity entity = BrooklynTaskTags.getContextEntity(t);
+        if (entity!=null) {
+            data.put("entityId", entity.getId());
+            if (entity.getDisplayName()!=null) data.put("entityDisplayName", entity.getDisplayName());
+        }
+        return new LinkWithMetadata("/v1/activities/"+t.getId(), data);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/util/BrooklynRestResourceUtils.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/util/BrooklynRestResourceUtils.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/util/BrooklynRestResourceUtils.java
new file mode 100644
index 0000000..11df7b5
--- /dev/null
+++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/util/BrooklynRestResourceUtils.java
@@ -0,0 +1,564 @@
+/*
+ * 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.util;
+
+import static org.apache.brooklyn.rest.util.WebResourceUtils.notFound;
+import static com.google.common.collect.Iterables.transform;
+import groovy.lang.GroovyClassLoader;
+
+import java.lang.reflect.Constructor;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.concurrent.Future;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.basic.BrooklynTypes;
+import org.apache.brooklyn.catalog.BrooklynCatalog;
+import org.apache.brooklyn.catalog.CatalogItem;
+import brooklyn.catalog.internal.CatalogUtils;
+import brooklyn.config.ConfigKey;
+import brooklyn.enricher.Enrichers;
+import brooklyn.entity.Application;
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.AbstractEntity;
+import brooklyn.entity.basic.ApplicationBuilder;
+import brooklyn.entity.basic.Attributes;
+import brooklyn.entity.basic.BasicApplication;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.basic.EntityInternal;
+import brooklyn.entity.basic.EntityLocal;
+import brooklyn.entity.trait.Startable;
+import brooklyn.location.Location;
+import brooklyn.location.LocationRegistry;
+import brooklyn.management.ManagementContext;
+import brooklyn.management.Task;
+import brooklyn.management.entitlement.Entitlements;
+import brooklyn.management.entitlement.Entitlements.StringAndArgument;
+import brooklyn.policy.Policy;
+import brooklyn.policy.basic.AbstractPolicy;
+import brooklyn.rest.domain.ApplicationSpec;
+import brooklyn.rest.domain.EntitySpec;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.flags.TypeCoercions;
+import brooklyn.util.javalang.Reflections;
+import brooklyn.util.net.Urls;
+import brooklyn.util.text.Strings;
+
+import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.io.Files;
+
+public class BrooklynRestResourceUtils {
+
+    private static final Logger log = LoggerFactory.getLogger(BrooklynRestResourceUtils.class);
+
+    private final ManagementContext mgmt;
+    
+    public BrooklynRestResourceUtils(ManagementContext mgmt) {
+        Preconditions.checkNotNull(mgmt, "mgmt");
+        this.mgmt = mgmt;
+    }
+
+    public BrooklynCatalog getCatalog() {
+        return mgmt.getCatalog();
+    }
+    
+    public ClassLoader getCatalogClassLoader() {
+        return mgmt.getCatalogClassLoader();
+    }
+    
+    public LocationRegistry getLocationRegistry() {
+        return mgmt.getLocationRegistry();
+    }
+
+    /** finds the policy indicated by the given ID or name.
+     * @see {@link #getEntity(String,String)}; it then searches the policies of that
+     * entity for one whose ID or name matches that given.
+     * <p>
+     * 
+     * @throws 404 or 412 (unless input is null in which case output is null) */
+    public Policy getPolicy(String application, String entity, String policy) {
+        return getPolicy(getEntity(application, entity), policy);
+    }
+
+    /** finds the policy indicated by the given ID or name.
+     * @see {@link #getPolicy(String,String,String)}.
+     * <p>
+     * 
+     * @throws 404 or 412 (unless input is null in which case output is null) */
+    public Policy getPolicy(Entity entity, String policy) {
+        if (policy==null) return null;
+
+        for (Policy p: entity.getPolicies()) {
+            if (policy.equals(p.getId())) return p;
+        }
+        for (Policy p: entity.getPolicies()) {
+            if (policy.equals(p.getDisplayName())) return p;
+        }
+        
+        throw WebResourceUtils.notFound("Cannot find policy '%s' in entity '%s'", policy, entity);
+    }
+
+    /** finds the entity indicated by the given ID or name
+     * <p>
+     * prefers ID based lookup in which case appId is optional, and if supplied will be enforced.
+     * optionally the name can be supplied, for cases when paths should work across versions,
+     * in which case names will be searched recursively (and the application is required). 
+     * 
+     * @throws 404 or 412 (unless input is null in which case output is null) */
+    public EntityLocal getEntity(String application, String entity) {
+        if (entity==null) return null;
+        Application app = application!=null ? getApplication(application) : null;
+        EntityLocal e = (EntityLocal) mgmt.getEntityManager().getEntity(entity);
+        
+        if (e!=null) {
+            if (!Entitlements.isEntitled(mgmt.getEntitlementManager(), Entitlements.SEE_ENTITY, e)) {
+                throw WebResourceUtils.notFound("Cannot find entity '%s': no known ID and application not supplied for searching", entity);
+            }
+            
+            if (app==null || app.equals(findTopLevelApplication(e))) return e;
+            throw WebResourceUtils.preconditionFailed("Application '%s' specified does not match application '%s' to which entity '%s' (%s) is associated", 
+                    application, e.getApplication()==null ? null : e.getApplication().getId(), entity, e);
+        }
+        if (application==null)
+            throw WebResourceUtils.notFound("Cannot find entity '%s': no known ID and application not supplied for searching", entity);
+        
+        assert app!=null : "null app should not be returned from getApplication";
+        e = searchForEntityNamed(app, entity);
+        if (e!=null) return e;
+        throw WebResourceUtils.notFound("Cannot find entity '%s' in application '%s' (%s)", entity, application, app);
+    }
+    
+    private Application findTopLevelApplication(Entity e) {
+        // For nested apps, e.getApplication() can return its direct parent-app rather than the root app
+        // (particularly if e.getApplication() was called before the parent-app was wired up to its parent,
+        // because that call causes the application to be cached).
+        // Therefore we continue to walk the hierarchy until we find an "orphaned" application at the top.
+        
+        Application app = e.getApplication();
+        while (app != null && !app.equals(app.getApplication())) {
+            app = app.getApplication();
+        }
+        return app;
+    }
+
+    /** looks for the given application instance, first by ID then by name
+     * 
+     * @throws 404 if not found, or not entitled
+     */
+    public Application getApplication(String application) {
+        Entity e = mgmt.getEntityManager().getEntity(application);
+        if (!Entitlements.isEntitled(mgmt.getEntitlementManager(), Entitlements.SEE_ENTITY, e)) {
+            throw notFound("Application '%s' not found", application);
+        }
+        
+        if (e != null && e instanceof Application) return (Application) e;
+        for (Application app : mgmt.getApplications()) {
+            if (app.getId().equals(application)) return app;
+            if (application.equalsIgnoreCase(app.getDisplayName())) return app;
+        }
+        
+        throw notFound("Application '%s' not found", application);
+    }
+
+    /** walks the hierarchy (depth-first) at root (often an Application) looking for
+     * an entity matching the given ID or name; returns the first such entity, or null if none found
+     **/
+    public EntityLocal searchForEntityNamed(Entity root, String entity) {
+        if (root.getId().equals(entity) || entity.equals(root.getDisplayName())) return (EntityLocal) root;
+        for (Entity child: root.getChildren()) {
+            Entity result = searchForEntityNamed(child, entity);
+            if (result!=null) return (EntityLocal) result;
+        }
+        return null;
+    }
+
+    @SuppressWarnings({ "unchecked", "deprecation" })
+    public Application create(ApplicationSpec spec) {
+        log.debug("REST creating application instance for {}", spec);
+        
+        if (!Entitlements.isEntitled(mgmt.getEntitlementManager(), Entitlements.DEPLOY_APPLICATION, spec)) {
+            throw WebResourceUtils.unauthorized("User '%s' is not authorized to deploy application %s",
+                Entitlements.getEntitlementContext().user(), spec);
+        }
+        
+        final String type = spec.getType();
+        final String name = spec.getName();
+        final Map<String,String> configO = spec.getConfig();
+        final Set<EntitySpec> entities = (spec.getEntities() == null) ? ImmutableSet.<EntitySpec>of() : spec.getEntities();
+        
+        final Application instance;
+
+        // Load the class; first try to use the appropriate catalog item; but then allow anything that is on the classpath
+        final Class<? extends Entity> clazz;
+        final String catalogItemId;
+        if (Strings.isEmpty(type)) {
+            clazz = BasicApplication.class;
+            catalogItemId = null;
+        } else {
+            Class<? extends Entity> tempclazz;
+            BrooklynCatalog catalog = getCatalog();
+            CatalogItem<?, ?> item = catalog.getCatalogItemForType(type);
+            if (item != null) {
+                catalogItemId = item.getId();
+                tempclazz = (Class<? extends Entity>) catalog.loadClass(item);
+            } else {
+                catalogItemId = null;
+                try {
+                    tempclazz = (Class<? extends Entity>) catalog.getRootClassLoader().loadClass(type);
+                    log.info("Catalog does not contain item for type {}; loaded class directly instead", type);
+                } catch (ClassNotFoundException e2) {
+                    log.warn("No catalog item for type {}, and could not load class directly; rethrowing", type);
+                    throw new NoSuchElementException("Unable to find catalog item for type "+type);
+                }
+            }
+            clazz = tempclazz;
+        }
+        if (Entitlements.isEntitled(mgmt.getEntitlementManager(), Entitlements.INVOKE_EFFECTOR, null)) {
+
+            try {
+                if (ApplicationBuilder.class.isAssignableFrom(clazz)) {
+                    Constructor<?> constructor = clazz.getConstructor();
+                    ApplicationBuilder appBuilder = (ApplicationBuilder) constructor.newInstance();
+                    if (!Strings.isEmpty(name)) appBuilder.appDisplayName(name);
+                    if (entities.size() > 0)
+                        log.warn("Cannot supply additional entities when using an ApplicationBuilder; ignoring in spec {}", spec);
+
+                    log.info("REST placing '{}' under management", spec.getName());
+                    appBuilder.configure(convertFlagsToKeys(appBuilder.getType(), configO));
+                    configureRenderingMetadata(spec, appBuilder);
+                    instance = appBuilder.manage(mgmt);
+
+                } else if (Application.class.isAssignableFrom(clazz)) {
+                    brooklyn.entity.proxying.EntitySpec<?> coreSpec = toCoreEntitySpec(clazz, name, configO, catalogItemId);
+                    configureRenderingMetadata(spec, coreSpec);
+                    instance = (Application) mgmt.getEntityManager().createEntity(coreSpec);
+                    for (EntitySpec entitySpec : entities) {
+                        log.info("REST creating instance for entity {}", entitySpec.getType());
+                        instance.addChild(mgmt.getEntityManager().createEntity(toCoreEntitySpec(entitySpec)));
+                    }
+
+                    log.info("REST placing '{}' under management", spec.getName() != null ? spec.getName() : spec);
+                    Entities.startManagement(instance, mgmt);
+
+                } else if (Entity.class.isAssignableFrom(clazz)) {
+                    if (entities.size() > 0)
+                        log.warn("Cannot supply additional entities when using a non-application entity; ignoring in spec {}", spec);
+
+                    brooklyn.entity.proxying.EntitySpec<?> coreSpec = toCoreEntitySpec(BasicApplication.class, name, configO, catalogItemId);
+                    configureRenderingMetadata(spec, coreSpec);
+
+                    instance = (Application) mgmt.getEntityManager().createEntity(coreSpec);
+
+                    Entity soleChild = mgmt.getEntityManager().createEntity(toCoreEntitySpec(clazz, name, configO, catalogItemId));
+                    instance.addChild(soleChild);
+                    instance.addEnricher(Enrichers.builder()
+                            .propagatingAllBut(Attributes.SERVICE_UP, Attributes.SERVICE_NOT_UP_INDICATORS, 
+                                    Attributes.SERVICE_STATE_ACTUAL, Attributes.SERVICE_STATE_EXPECTED, 
+                                    Attributes.SERVICE_PROBLEMS)
+                            .from(soleChild)
+                            .build());
+
+                    log.info("REST placing '{}' under management", spec.getName());
+                    Entities.startManagement(instance, mgmt);
+
+                } else {
+                    throw new IllegalArgumentException("Class " + clazz + " must extend one of ApplicationBuilder, Application or Entity");
+                }
+
+                return instance;
+
+            } catch (Exception e) {
+                log.error("REST failed to create application: " + e, e);
+                throw Exceptions.propagate(e);
+            }
+        }
+        throw WebResourceUtils.unauthorized("User '%s' is not authorized to create application from applicationSpec %s",
+                Entitlements.getEntitlementContext().user(), spec);
+    }
+    
+    public Task<?> start(Application app, ApplicationSpec spec) {
+        return start(app, getLocations(spec));
+    }
+
+    public Task<?> start(Application app, List<? extends Location> locations) {
+        return Entities.invokeEffector((EntityLocal)app, app, Startable.START,
+                MutableMap.of("locations", locations));
+    }
+
+    public List<Location> getLocations(ApplicationSpec spec) {
+        // Start all the managed entities by asking the app instance to start in background
+        Function<String, Location> buildLocationFromId = new Function<String, Location>() {
+            @Override
+            public Location apply(String id) {
+                id = fixLocation(id);
+                return getLocationRegistry().resolve(id);
+            }
+        };
+
+        ArrayList<Location> locations = Lists.newArrayList(transform(spec.getLocations(), buildLocationFromId));
+        return locations;
+    }
+
+    @SuppressWarnings({ "unchecked", "deprecation" })
+    private brooklyn.entity.proxying.EntitySpec<? extends Entity> toCoreEntitySpec(brooklyn.rest.domain.EntitySpec spec) {
+        String type = spec.getType();
+        String name = spec.getName();
+        Map<String, String> config = (spec.getConfig() == null) ? Maps.<String,String>newLinkedHashMap() : Maps.newLinkedHashMap(spec.getConfig());
+
+        BrooklynCatalog catalog = getCatalog();
+        CatalogItem<?, ?> item = catalog.getCatalogItemForType(type);
+        Class<? extends Entity> tempclazz;
+        final String catalogItemId;
+        if (item != null) {
+            tempclazz = (Class<? extends Entity>) catalog.loadClass(item);
+            catalogItemId = item.getId();
+        } else {
+            catalogItemId = null;
+            try {
+                tempclazz = (Class<? extends Entity>) catalog.getRootClassLoader().loadClass(type);
+                log.info("Catalog does not contain item for type {}; loaded class directly instead", type);
+            } catch (ClassNotFoundException e2) {
+                log.warn("No catalog item for type {}, and could not load class directly; rethrowing", type);
+                throw new NoSuchElementException("Unable to find catalog item for type "+type);
+            }
+        }
+        final Class<? extends Entity> clazz = tempclazz;
+        brooklyn.entity.proxying.EntitySpec<? extends Entity> result;
+        if (clazz.isInterface()) {
+            result = brooklyn.entity.proxying.EntitySpec.create(clazz);
+        } else {
+            result = brooklyn.entity.proxying.EntitySpec.create(Entity.class).impl(clazz).additionalInterfaces(Reflections.getAllInterfaces(clazz));
+        }
+        result.catalogItemId(catalogItemId);
+        if (!Strings.isEmpty(name)) result.displayName(name);
+        result.configure( convertFlagsToKeys(result.getType(), config) );
+        configureRenderingMetadata(spec, result);
+        return result;
+    }
+    
+    protected void configureRenderingMetadata(ApplicationSpec spec, ApplicationBuilder appBuilder) {
+        appBuilder.configure(getRenderingConfigurationFor(spec.getType()));
+    }
+
+    protected void configureRenderingMetadata(ApplicationSpec input, brooklyn.entity.proxying.EntitySpec<?> entity) {
+        entity.configure(getRenderingConfigurationFor(input.getType()));
+    }
+
+    protected void configureRenderingMetadata(EntitySpec input, brooklyn.entity.proxying.EntitySpec<?> entity) {
+        entity.configure(getRenderingConfigurationFor(input.getType()));
+    }
+
+    protected Map<?, ?> getRenderingConfigurationFor(String catalogId) {
+        MutableMap<Object, Object> result = MutableMap.of();
+        CatalogItem<?,?> item = CatalogUtils.getCatalogItemOptionalVersion(mgmt, catalogId);
+        if (item==null) return result;
+        
+        result.addIfNotNull("iconUrl", item.getIconUrl());
+        return result;
+    }
+    
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    private <T extends Entity> brooklyn.entity.proxying.EntitySpec<?> toCoreEntitySpec(Class<T> clazz, String name, Map<String,String> configO, String catalogItemId) {
+        Map<String, String> config = (configO == null) ? Maps.<String,String>newLinkedHashMap() : Maps.newLinkedHashMap(configO);
+        
+        brooklyn.entity.proxying.EntitySpec<? extends Entity> result;
+        if (clazz.isInterface()) {
+            result = brooklyn.entity.proxying.EntitySpec.create(clazz);
+        } else {
+            // If this is a concrete class, particularly for an Application class, we want the proxy
+            // to expose all interfaces it implements.
+            Class interfaceclazz = (Application.class.isAssignableFrom(clazz)) ? Application.class : Entity.class;
+            Set<Class<?>> additionalInterfaceClazzes = Reflections.getInterfacesIncludingClassAncestors(clazz);
+            result = brooklyn.entity.proxying.EntitySpec.create(interfaceclazz).impl(clazz).additionalInterfaces(additionalInterfaceClazzes);
+        }
+        
+        result.catalogItemId(catalogItemId);
+        if (!Strings.isEmpty(name)) result.displayName(name);
+        result.configure( convertFlagsToKeys(result.getImplementation(), config) );
+        return result;
+    }
+
+    private Map<?,?> convertFlagsToKeys(Class<? extends Entity> javaType, Map<?, ?> config) {
+        if (config==null || config.isEmpty() || javaType==null) return config;
+        
+        Map<String, ConfigKey<?>> configKeys = BrooklynTypes.getDefinedConfigKeys(javaType);
+        Map<Object,Object> result = new LinkedHashMap<Object,Object>();
+        for (Map.Entry<?,?> entry: config.entrySet()) {
+            log.debug("Setting key {} to {} for REST creation of {}", new Object[] { entry.getKey(), entry.getValue(), javaType});
+            Object key = configKeys.get(entry.getKey());
+            if (key==null) {
+                log.warn("Unrecognised config key {} passed to {}; will be treated as flag (and likely ignored)", entry.getKey(), javaType);
+                key = entry.getKey();
+            }
+            result.put(key, entry.getValue());
+        }
+        return result;
+    }
+    
+    public Task<?> destroy(final Application application) {
+        return mgmt.getExecutionManager().submit(
+                MutableMap.of("displayName", "destroying "+application,
+                        "description", "REST call to destroy application "+application.getDisplayName()+" ("+application+")"),
+                new Runnable() {
+            @Override
+            public void run() {
+                ((EntityInternal)application).destroy();
+                mgmt.getEntityManager().unmanage(application);
+            }
+        });
+    }
+    
+    public Task<?> expunge(final Entity entity, final boolean release) {
+        if (mgmt.getEntitlementManager().isEntitled(Entitlements.getEntitlementContext(),
+                Entitlements.INVOKE_EFFECTOR, Entitlements.EntityAndItem.of(entity, 
+                    StringAndArgument.of("expunge", MutableMap.of("release", release))))) {
+            return mgmt.getExecutionManager().submit(
+                    MutableMap.of("displayName", "expunging " + entity, "description", "REST call to expunge entity "
+                            + entity.getDisplayName() + " (" + entity + ")"), new Runnable() {
+                        @Override
+                        public void run() {
+                            if (release)
+                                Entities.destroyCatching(entity);
+                            else
+                                mgmt.getEntityManager().unmanage(entity);
+                        }
+                    });
+        }
+        throw WebResourceUtils.unauthorized("User '%s' is not authorized to expunge entity %s",
+                    Entitlements.getEntitlementContext().user(), entity);
+    }
+
+
+    @Deprecated
+    @SuppressWarnings({ "rawtypes" })
+    public Response createCatalogEntryFromGroovyCode(String groovyCode) {
+        ClassLoader parent = getCatalog().getRootClassLoader();
+        @SuppressWarnings("resource")
+        GroovyClassLoader loader = new GroovyClassLoader(parent);
+
+        Class clazz = loader.parseClass(groovyCode);
+
+        if (AbstractEntity.class.isAssignableFrom(clazz)) {
+            CatalogItem<?,?> item = getCatalog().addItem(clazz);
+            log.info("REST created "+item);
+            return Response.created(URI.create("entities/" + clazz.getName())).build();
+
+        } else if (AbstractPolicy.class.isAssignableFrom(clazz)) {
+            CatalogItem<?,?> item = getCatalog().addItem(clazz);
+            log.info("REST created "+item);
+            return Response.created(URI.create("policies/" + clazz.getName())).build();
+        }
+
+        throw WebResourceUtils.preconditionFailed("Unsupported type superclass "+clazz.getSuperclass()+"; expects Entity or Policy");
+    }
+
+    @Deprecated
+    public static String fixLocation(String locationId) {
+        if (locationId.startsWith("/v1/locations/")) {
+            log.warn("REST API using legacy URI syntax for location: "+locationId);
+            locationId = Strings.removeFromStart(locationId, "/v1/locations/");
+        }
+        return locationId;
+    }
+
+    public Object getObjectValueForDisplay(Object value) {
+        if (value==null) return null;
+        // currently everything converted to string, expanded if it is a "done" future
+        if (value instanceof Future) {
+            if (((Future<?>)value).isDone()) {
+                try {
+                    value = ((Future<?>)value).get();
+                } catch (Exception e) {
+                    value = ""+value+" (error evaluating: "+e+")";
+                }
+            }
+        }
+        
+        if (TypeCoercions.isPrimitiveOrBoxer(value.getClass())) return value;
+        return value.toString();
+    }
+
+    // currently everything converted to string, expanded if it is a "done" future
+    public String getStringValueForDisplay(Object value) {
+        if (value==null) return null;
+        return ""+getObjectValueForDisplay(value);
+    }
+
+    /** true if the URL points to content which must be resolved on the server-side (i.e. classpath)
+     *  and which is safe to do so (currently just images, though in future perhaps also javascript and html plugins)
+     *  <p>
+     *  note we do not let caller access classpath through this mechanism, 
+     *  just those which are supplied by the platform administrator e.g. as an icon url */
+    public boolean isUrlServerSideAndSafe(String url) {
+        if (Strings.isEmpty(url)) return false;
+        String ext = Files.getFileExtension(url);
+        if (Strings.isEmpty(ext)) return false;
+        MediaType mime = WebResourceUtils.getImageMediaTypeFromExtension(ext);
+        if (mime==null) return false;
+        
+        return !Urls.isUrlWithProtocol(url) || url.startsWith("classpath:");
+    }
+
+    
+    public Iterable<Entity> descendantsOfAnyType(String application, String entity) {
+        List<Entity> result = Lists.newArrayList();
+        EntityLocal e = getEntity(application, entity);
+        gatherAllDescendants(e, result);
+        return result;
+    }
+    
+    private static void gatherAllDescendants(Entity e, List<Entity> result) {
+        if (result.add(e)) {
+            for (Entity ee: e.getChildren())
+                gatherAllDescendants(ee, result);
+        }
+    }
+
+    public Iterable<Entity> descendantsOfType(String application, String entity, final String typeRegex) {
+        Iterable<Entity> result = descendantsOfAnyType(application, entity);
+        return Iterables.filter(result, new Predicate<Entity>() {
+            @Override
+            public boolean apply(Entity entity) {
+                if (entity==null) return false;
+                return (entity.getEntityType().getName().matches(typeRegex));
+            }
+        });
+    }
+
+    public void reloadBrooklynProperties() {
+        mgmt.reloadBrooklynProperties();
+    }
+}