brooklyn-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From henev...@apache.org
Subject [16/34] brooklyn-server git commit: [BROOKLYN-183] REST API using CXF JAX-RS 2.0 implementation
Date Thu, 18 Feb 2016 15:47:34 GMT
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/ExplicitUsersSecurityProvider.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/ExplicitUsersSecurityProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/ExplicitUsersSecurityProvider.java
new file mode 100644
index 0000000..a0795cb
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/ExplicitUsersSecurityProvider.java
@@ -0,0 +1,117 @@
+/*
+ * 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.security.provider;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+import javax.servlet.http.HttpSession;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.config.StringConfigMap;
+import org.apache.brooklyn.core.internal.BrooklynProperties;
+import org.apache.brooklyn.rest.BrooklynWebConfig;
+import org.apache.brooklyn.rest.security.PasswordHasher;
+
+/**
+ * Security provider which validates users against passwords according to property keys,
+ * as set in {@link BrooklynWebConfig#USERS} and {@link BrooklynWebConfig#PASSWORD_FOR_USER(String)}
+ */
+public class ExplicitUsersSecurityProvider extends AbstractSecurityProvider implements SecurityProvider {
+
+    public static final Logger LOG = LoggerFactory.getLogger(ExplicitUsersSecurityProvider.class);
+    
+    protected final ManagementContext mgmt;
+    private boolean allowAnyUserWithValidPass;
+    private Set<String> allowedUsers = null;
+
+    public ExplicitUsersSecurityProvider(ManagementContext mgmt) {
+        this.mgmt = mgmt;
+        initialize();
+    }
+
+    private synchronized void initialize() {
+        if (allowedUsers != null) return;
+
+        StringConfigMap properties = mgmt.getConfig();
+
+        allowedUsers = new LinkedHashSet<String>();
+        String users = properties.getConfig(BrooklynWebConfig.USERS);
+        if (users == null) {
+            LOG.warn("REST has no users configured; no one will be able to log in!");
+        } else if ("*".equals(users)) {
+            LOG.info("REST allowing any user (so long as valid password is set)");
+            allowAnyUserWithValidPass = true;
+        } else {
+            StringTokenizer t = new StringTokenizer(users, ",");
+            while (t.hasMoreElements()) {
+                allowedUsers.add(("" + t.nextElement()).trim());
+            }
+            LOG.info("REST allowing users: " + allowedUsers);
+        }
+    }
+    
+    @Override
+    public boolean authenticate(HttpSession session, String user, String password) {
+        if (session==null || user==null) return false;
+        
+        if (!allowAnyUserWithValidPass) {
+            if (!allowedUsers.contains(user)) {
+                LOG.debug("REST rejecting unknown user "+user);
+                return false;                
+            }
+        }
+
+        if (checkExplicitUserPassword(mgmt, user, password)) {
+            return allow(session, user);
+        }
+        return false;
+    }
+
+    /** checks the supplied candidate user and password against the
+     * expect password (or SHA-256 + SALT thereof) defined as brooklyn properties.
+     */
+    public static boolean checkExplicitUserPassword(ManagementContext mgmt, String user, String password) {
+        BrooklynProperties properties = (BrooklynProperties) mgmt.getConfig();
+        String expectedPassword = properties.getConfig(BrooklynWebConfig.PASSWORD_FOR_USER(user));
+        String salt = properties.getConfig(BrooklynWebConfig.SALT_FOR_USER(user));
+        String expectedSha256 = properties.getConfig(BrooklynWebConfig.SHA256_FOR_USER(user));
+        
+        return checkPassword(password, expectedPassword, expectedSha256, salt);
+    }
+    /** 
+     * checks a candidate password against the expected credential defined for a given user.
+     * the expected credentials can be supplied as an expectedPassword OR as
+     * a combination of the SHA-256 hash of the expected password plus a defined salt.
+     * the combination of the SHA+SALT allows credentials to be supplied in a non-plaintext manner.
+     */
+    public static boolean checkPassword(String candidatePassword, String expectedPassword, String expectedPasswordSha256, String salt) {
+        if (expectedPassword != null) {
+            return expectedPassword.equals(candidatePassword);
+        } else if (expectedPasswordSha256 != null) {
+            String hashedCandidatePassword = PasswordHasher.sha256(salt, candidatePassword);
+            return expectedPasswordSha256.equals(hashedCandidatePassword);
+        }
+
+        return false;
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/LdapSecurityProvider.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/LdapSecurityProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/LdapSecurityProvider.java
new file mode 100644
index 0000000..d3636e9
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/LdapSecurityProvider.java
@@ -0,0 +1,132 @@
+/*
+ * 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.security.provider;
+
+import java.util.Hashtable;
+
+import javax.naming.Context;
+import javax.naming.NamingException;
+import javax.naming.directory.InitialDirContext;
+import javax.servlet.http.HttpSession;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.CharMatcher;
+
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.config.StringConfigMap;
+import org.apache.brooklyn.rest.BrooklynWebConfig;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.text.Strings;
+
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.collect.Lists;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A {@link SecurityProvider} implementation that relies on LDAP to authenticate.
+ *
+ * @author Peter Veentjer.
+ */
+public class LdapSecurityProvider extends AbstractSecurityProvider implements SecurityProvider {
+
+    public static final Logger LOG = LoggerFactory.getLogger(LdapSecurityProvider.class);
+
+    public static final String LDAP_CONTEXT_FACTORY = "com.sun.jndi.ldap.LdapCtxFactory";
+
+    private final String ldapUrl;
+    private final String ldapRealm;
+    private final String organizationUnit;
+
+    public LdapSecurityProvider(ManagementContext mgmt) {
+        StringConfigMap properties = mgmt.getConfig();
+        ldapUrl = properties.getConfig(BrooklynWebConfig.LDAP_URL);
+        Strings.checkNonEmpty(ldapUrl, "LDAP security provider configuration missing required property "+BrooklynWebConfig.LDAP_URL);
+        ldapRealm = CharMatcher.isNot('"').retainFrom(properties.getConfig(BrooklynWebConfig.LDAP_REALM));
+        Strings.checkNonEmpty(ldapRealm, "LDAP security provider configuration missing required property "+BrooklynWebConfig.LDAP_REALM);
+
+        if(Strings.isBlank(properties.getConfig(BrooklynWebConfig.LDAP_OU))) {
+            LOG.info("Setting LDAP ou attribute to: Users");
+            organizationUnit = "Users";
+        } else {
+            organizationUnit = CharMatcher.isNot('"').retainFrom(properties.getConfig(BrooklynWebConfig.LDAP_OU));
+        }
+        Strings.checkNonEmpty(ldapRealm, "LDAP security provider configuration missing required property "+BrooklynWebConfig.LDAP_OU);
+    }
+
+    public LdapSecurityProvider(String ldapUrl, String ldapRealm, String organizationUnit) {
+        this.ldapUrl = ldapUrl;
+        this.ldapRealm = ldapRealm;
+        this.organizationUnit = organizationUnit;
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Override
+    public boolean authenticate(HttpSession session, String user, String password) {
+        if (session==null || user==null) return false;
+        checkCanLoad();
+
+        Hashtable env = new Hashtable();
+        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
+        env.put(Context.PROVIDER_URL, ldapUrl);
+        env.put(Context.SECURITY_AUTHENTICATION, "simple");
+        env.put(Context.SECURITY_PRINCIPAL, getUserDN(user));
+        env.put(Context.SECURITY_CREDENTIALS, password);
+
+        try {
+            new InitialDirContext(env);
+            return allow(session, user);
+        } catch (NamingException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Returns the LDAP path for the user
+     *
+     * @param user
+     * @return String
+     */
+    protected String getUserDN(String user) {
+        List<String> domain = Lists.transform(Arrays.asList(ldapRealm.split("\\.")), new Function<String, String>() {
+            @Override
+            public String apply(String input) {
+                return "dc=" + input;
+            }
+        });
+
+        String dc = Joiner.on(",").join(domain).toLowerCase();
+        return "cn=" + user + ",ou=" + organizationUnit + "," + dc;
+    }
+
+    static boolean triedLoading = false;
+    public synchronized static void checkCanLoad() {
+        if (triedLoading) return;
+        try {
+            Class.forName(LDAP_CONTEXT_FACTORY);
+            triedLoading = true;
+        } catch (Throwable e) {
+            throw Exceptions.propagate(new ClassNotFoundException("Unable to load LDAP classes ("+LDAP_CONTEXT_FACTORY+") required for Brooklyn LDAP security provider"));
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/SecurityProvider.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/SecurityProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/SecurityProvider.java
new file mode 100644
index 0000000..57d1400
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/SecurityProvider.java
@@ -0,0 +1,35 @@
+/*
+ * 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.security.provider;
+
+import javax.servlet.http.HttpSession;
+
+/**
+ * The SecurityProvider is responsible for doing authentication.
+ *
+ * A class should either have a constructor receiving a BrooklynProperties or it should have a no-arg constructor.
+ */
+public interface SecurityProvider {
+
+    public boolean isAuthenticated(HttpSession session);
+    
+    public boolean authenticate(HttpSession session, String user, String password);
+    
+    public boolean logout(HttpSession session);
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/AccessTransformer.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/AccessTransformer.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/AccessTransformer.java
new file mode 100644
index 0000000..652b31c
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/AccessTransformer.java
@@ -0,0 +1,42 @@
+/*
+ * 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.apache.brooklyn.core.mgmt.internal.AccessManager;
+import org.apache.brooklyn.rest.domain.AccessSummary;
+
+import com.google.common.collect.ImmutableMap;
+import javax.ws.rs.core.UriBuilder;
+import org.apache.brooklyn.rest.api.AccessApi;
+import static org.apache.brooklyn.rest.util.WebResourceUtils.resourceUriBuilder;
+
+/**
+ * @author Adam Lowe
+ */
+public class AccessTransformer {
+
+    public static AccessSummary accessSummary(AccessManager manager, UriBuilder ub) {
+        URI selfUri = resourceUriBuilder(ub, AccessApi.class).build();
+        ImmutableMap<String, URI> links = ImmutableMap.of("self", selfUri);
+
+        return new AccessSummary(manager.isLocationProvisioningAllowed(), links);
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/ApplicationTransformer.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/ApplicationTransformer.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/ApplicationTransformer.java
new file mode 100644
index 0000000..27f0f4f
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/ApplicationTransformer.java
@@ -0,0 +1,125 @@
+/*
+ * 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.domain.Status.ACCEPTED;
+import static org.apache.brooklyn.rest.domain.Status.RUNNING;
+import static org.apache.brooklyn.rest.domain.Status.STARTING;
+import static org.apache.brooklyn.rest.domain.Status.STOPPED;
+import static org.apache.brooklyn.rest.domain.Status.STOPPING;
+import static org.apache.brooklyn.rest.domain.Status.UNKNOWN;
+import static org.apache.brooklyn.rest.domain.Status.DESTROYED;
+import static org.apache.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 org.apache.brooklyn.api.entity.Application;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.core.entity.Attributes;
+import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
+import org.apache.brooklyn.core.entity.trait.Startable;
+import org.apache.brooklyn.rest.domain.ApplicationSpec;
+import org.apache.brooklyn.rest.domain.ApplicationSummary;
+import org.apache.brooklyn.rest.domain.Status;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableMap;
+import javax.ws.rs.core.UriBuilder;
+import org.apache.brooklyn.rest.api.ApplicationApi;
+import org.apache.brooklyn.rest.api.EntityApi;
+import static org.apache.brooklyn.rest.util.WebResourceUtils.resourceUriBuilder;
+import static org.apache.brooklyn.rest.util.WebResourceUtils.serviceUriBuilder;
+
+public class ApplicationTransformer {
+
+    public static Function<? super Application, ApplicationSummary> fromApplication(final UriBuilder ub) {
+        return new Function<Application, ApplicationSummary>() {
+            @Override
+            public ApplicationSummary apply(Application application) {
+                return summaryFromApplication(application, ub);
+            }
+        };
+    };
+
+    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, UriBuilder ub) {
+        Map<String, URI> links;
+        if (application.getId() == null) {
+            links = Collections.emptyMap();
+        } else {
+            URI selfUri = serviceUriBuilder(ub, ApplicationApi.class, "get").build(application.getId());
+            URI entitiesUri = resourceUriBuilder(ub, EntityApi.class).build(application.getId());
+            links = ImmutableMap.of(
+                    "self", selfUri,
+                    "entities", entitiesUri);
+        }
+
+        return new ApplicationSummary(application.getId(), specFromApplication(application), statusFromApplication(application), links);
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/BrooklynFeatureTransformer.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/BrooklynFeatureTransformer.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/BrooklynFeatureTransformer.java
new file mode 100644
index 0000000..c8477cf
--- /dev/null
+++ b/rest/rest-resources/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 org.apache.brooklyn.core.BrooklynVersion.BrooklynFeature;
+import org.apache.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/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/CatalogTransformer.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/CatalogTransformer.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/CatalogTransformer.java
new file mode 100644
index 0000000..e584a9a
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/CatalogTransformer.java
@@ -0,0 +1,188 @@
+/*
+ * 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.apache.brooklyn.api.catalog.CatalogItem;
+import org.apache.brooklyn.api.catalog.CatalogItem.CatalogItemType;
+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.location.Location;
+import org.apache.brooklyn.api.location.LocationSpec;
+import org.apache.brooklyn.api.objs.SpecParameter;
+import org.apache.brooklyn.api.policy.Policy;
+import org.apache.brooklyn.api.policy.PolicySpec;
+import org.apache.brooklyn.api.sensor.Sensor;
+import org.apache.brooklyn.core.entity.EntityDynamicType;
+import org.apache.brooklyn.core.mgmt.BrooklynTags;
+import org.apache.brooklyn.core.objs.BrooklynTypes;
+import org.apache.brooklyn.rest.domain.CatalogEntitySummary;
+import org.apache.brooklyn.rest.domain.CatalogItemSummary;
+import org.apache.brooklyn.rest.domain.CatalogLocationSummary;
+import org.apache.brooklyn.rest.domain.CatalogPolicySummary;
+import org.apache.brooklyn.rest.domain.EffectorSummary;
+import org.apache.brooklyn.rest.domain.EntityConfigSummary;
+import org.apache.brooklyn.rest.domain.LocationConfigSummary;
+import org.apache.brooklyn.rest.domain.PolicyConfigSummary;
+import org.apache.brooklyn.rest.domain.SensorSummary;
+import org.apache.brooklyn.rest.domain.SummaryComparators;
+import org.apache.brooklyn.rest.util.BrooklynRestResourceUtils;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.collections.MutableSet;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.javalang.Reflections;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import javax.ws.rs.core.UriBuilder;
+import org.apache.brooklyn.rest.api.CatalogApi;
+import static org.apache.brooklyn.rest.util.WebResourceUtils.serviceUriBuilder;
+
+public class CatalogTransformer {
+
+    private static final org.slf4j.Logger log = LoggerFactory.getLogger(CatalogTransformer.class);
+    
+    public static <T extends Entity> CatalogEntitySummary catalogEntitySummary(BrooklynRestResourceUtils b, CatalogItem<T,EntitySpec<? extends T>> item, UriBuilder ub) {
+        Set<EntityConfigSummary> config = Sets.newLinkedHashSet();
+        Set<SensorSummary> sensors = Sets.newTreeSet(SummaryComparators.nameComparator());
+        Set<EffectorSummary> effectors = Sets.newTreeSet(SummaryComparators.nameComparator());
+
+        EntitySpec<?> spec = null;
+
+        try {
+            spec = (EntitySpec<?>) b.getCatalog().createSpec((CatalogItem) item);
+            EntityDynamicType typeMap = BrooklynTypes.getDefinedEntityType(spec.getType());
+            EntityType type = typeMap.getSnapshot();
+
+            for (SpecParameter<?> input: spec.getParameters())
+                config.add(EntityTransformer.entityConfigSummary(input));
+            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(), ub),
+            makeTags(spec, item), config, sensors, effectors,
+            item.isDeprecated(), makeLinks(item, ub));
+    }
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    public static CatalogItemSummary catalogItemSummary(BrooklynRestResourceUtils b, CatalogItem item, UriBuilder ub) {
+        try {
+            switch (item.getCatalogItemType()) {
+            case TEMPLATE:
+            case ENTITY:
+                return catalogEntitySummary(b, item, ub);
+            case POLICY:
+                return catalogPolicySummary(b, item, ub);
+            case LOCATION:
+                return catalogLocationSummary(b, item, ub);
+            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(), ub), item.tags().getTags(), item.isDeprecated(), makeLinks(item, ub));
+    }
+
+    public static CatalogPolicySummary catalogPolicySummary(BrooklynRestResourceUtils b, CatalogItem<? extends Policy,PolicySpec<?>> item, UriBuilder ub) {
+        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(), ub), config,
+                item.tags().getTags(), item.isDeprecated(), makeLinks(item, ub));
+    }
+
+    public static CatalogLocationSummary catalogLocationSummary(BrooklynRestResourceUtils b, CatalogItem<? extends Location,LocationSpec<?>> item, UriBuilder ub) {
+        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(), ub), config,
+                item.tags().getTags(), item.isDeprecated(), makeLinks(item, ub));
+    }
+
+    protected static Map<String, URI> makeLinks(CatalogItem<?,?> item, UriBuilder ub) {
+        return MutableMap.<String, URI>of().addIfNotNull("self", getSelfLink(item, ub));
+    }
+
+    protected static URI getSelfLink(CatalogItem<?,?> item, UriBuilder ub) {
+        String itemId = item.getId();
+        switch (item.getCatalogItemType()) {
+        case TEMPLATE:
+            return serviceUriBuilder(ub, CatalogApi.class, "getApplication").build(itemId, item.getVersion());
+        case ENTITY:
+            return serviceUriBuilder(ub, CatalogApi.class, "getEntity").build(itemId, item.getVersion());
+        case POLICY:
+            return serviceUriBuilder(ub, CatalogApi.class, "getPolicy").build(itemId, item.getVersion());
+        case LOCATION:
+            return serviceUriBuilder(ub, CatalogApi.class, "getLocation").build(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, UriBuilder ub) {
+        if (b.isUrlServerSideAndSafe(iconUrl)) {
+            return serviceUriBuilder(ub, CatalogApi.class, "getIcon").build(item.getSymbolicName(), item.getVersion()).toString();
+        }
+        return iconUrl;
+    }
+
+    private static Set<Object> makeTags(EntitySpec<?> spec, CatalogItem<?, ?> item) {
+        // Combine tags on item with an InterfacesTag.
+        Set<Object> tags = MutableSet.copyOf(item.tags().getTags());
+        if (spec != null) {
+            Class<?> type;
+            if (spec.getImplementation() != null) {
+                type = spec.getImplementation();
+            } else {
+                type = spec.getType();
+            }
+            if (type != null) {
+                tags.add(new BrooklynTags.TraitsTag(Reflections.getAllInterfaces(type)));
+            }
+        }
+        return tags;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/EffectorTransformer.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/EffectorTransformer.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/EffectorTransformer.java
new file mode 100644
index 0000000..b374bb3
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/EffectorTransformer.java
@@ -0,0 +1,91 @@
+/*
+ * 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 org.apache.brooklyn.api.effector.Effector;
+import org.apache.brooklyn.api.effector.ParameterType;
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.rest.domain.EffectorSummary;
+import org.apache.brooklyn.rest.domain.EffectorSummary.ParameterSummary;
+import org.apache.brooklyn.rest.util.WebResourceUtils;
+import org.apache.brooklyn.util.core.task.Tasks;
+import org.apache.brooklyn.util.core.task.ValueResolver;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.guava.Maybe;
+
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import javax.ws.rs.core.UriBuilder;
+import org.apache.brooklyn.rest.api.ApplicationApi;
+import org.apache.brooklyn.rest.api.EffectorApi;
+import org.apache.brooklyn.rest.api.EntityApi;
+import static org.apache.brooklyn.rest.util.WebResourceUtils.serviceUriBuilder;
+
+public class EffectorTransformer {
+
+    public static EffectorSummary effectorSummary(final Entity entity, Effector<?> effector, UriBuilder ub) {
+        URI applicationUri = serviceUriBuilder(ub, ApplicationApi.class, "get").build(entity.getApplicationId());
+        URI entityUri = serviceUriBuilder(ub, EntityApi.class, "get").build(entity.getApplicationId(), entity.getId());
+        URI selfUri = serviceUriBuilder(ub, EffectorApi.class, "invoke").build(entity.getApplicationId(), entity.getId(), effector.getName());
+        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", selfUri,
+                "entity", entityUri,
+                "application", 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/brooklyn-server/blob/6f624c78/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
new file mode 100644
index 0000000..f77bf81
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/EntityTransformer.java
@@ -0,0 +1,182 @@
+/*
+ * 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.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.config.render.RendererHints;
+import org.apache.brooklyn.rest.domain.EntityConfigSummary;
+import org.apache.brooklyn.rest.domain.EntitySummary;
+import org.apache.brooklyn.util.collections.MutableMap;
+
+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
+ */
+public class EntityTransformer {
+
+    public static final Function<? super Entity, EntitySummary> fromEntity(final UriBuilder ub) {
+        return new Function<Entity, EntitySummary>() {
+            @Override
+            public EntitySummary apply(Entity entity) {
+                return EntityTransformer.entitySummary(entity, ub);
+            }
+        };
+    };
+
+    public static EntitySummary entitySummary(Entity entity, UriBuilder ub) {
+        URI applicationUri = serviceUriBuilder(ub, ApplicationApi.class, "get").build(entity.getApplicationId());
+        URI entityUri = serviceUriBuilder(ub, EntityApi.class, "get").build(entity.getApplicationId(), entity.getId());
+        ImmutableMap.Builder<String, URI> lb = ImmutableMap.<String, URI>builder()
+                .put("self", entityUri);
+        if (entity.getParent()!=null) {
+            URI parentUri = serviceUriBuilder(ub, EntityApi.class, "get").build(entity.getApplicationId(), entity.getParent().getId());
+            lb.put("parent", parentUri);
+        }
+
+//        UriBuilder urib = serviceUriBuilder(ub, EntityApi.class, "getChildren").build(entity.getApplicationId(), entity.getId());
+        // TODO: change all these as well :S
+        lb.put("application", 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.getIconUrl()!=null)
+            lb.put("iconUrl", URI.create(entityUri + "/icon"));
+
+        if (entity.getCatalogItemId() != null) {
+            String versionedId = entity.getCatalogItemId();
+            URI catalogUri;
+            if (CatalogUtils.looksLikeVersionedId(versionedId)) {
+                String symbolicName = CatalogUtils.getSymbolicNameFromVersionedId(versionedId);
+                String version = CatalogUtils.getVersionFromVersionedId(versionedId);
+                catalogUri = serviceUriBuilder(ub, CatalogApi.class, "getEntity").build(symbolicName, version);
+            } else {
+                catalogUri = serviceUriBuilder(ub, CatalogApi.class, "getEntity_0_7_0").build(versionedId);
+            }
+            lb.put("catalog", catalogUri);
+        }
+
+        String type = entity.getEntityType().getName();
+        return new EntitySummary(entity.getId(), entity.getDisplayName(), type, entity.getCatalogItemId(), lb.build());
+    }
+
+    public static List<EntitySummary> entitySummaries(Iterable<? extends Entity> entities, final UriBuilder ub) {
+        return Lists.newArrayList(transform(
+            entities,
+            new Function<Entity, EntitySummary>() {
+                @Override
+                public EntitySummary apply(Entity entity) {
+                    return EntityTransformer.entitySummary(entity, ub);
+                }
+            }));
+    }
+
+    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(Entity entity, ConfigKey<?> config, UriBuilder ub) {
+      /*
+       * 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;
+
+        URI applicationUri = serviceUriBuilder(ub, ApplicationApi.class, "get").build(entity.getApplicationId());
+        URI entityUri = serviceUriBuilder(ub, EntityApi.class, "get").build(entity.getApplicationId(), entity.getId());
+        URI selfUri = serviceUriBuilder(ub, EntityConfigApi.class, "get").build(entity.getApplicationId(), entity.getId(), config.getName());
+        
+        MutableMap.Builder<String, URI> lb = MutableMap.<String, URI>builder()
+            .put("self", selfUri)
+            .put("application", applicationUri)
+            .put("entity", entityUri)
+            .put("action:json", 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 URI applicationUri(Application entity, UriBuilder ub) {
+        return serviceUriBuilder(ub, ApplicationApi.class, "get").build(entity.getApplicationId());
+    }
+    
+    public static URI entityUri(Entity entity, UriBuilder ub) {
+        return serviceUriBuilder(ub, EntityApi.class, "get").build(entity.getApplicationId(), 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);
+    }
+
+    public static EntityConfigSummary entityConfigSummary(SpecParameter<?> input) {
+        Double priority = input.isPinned() ? Double.valueOf(1d) : null;
+        return entityConfigSummary(input.getConfigKey(), input.getLabel(), priority, null);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/HighAvailabilityTransformer.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/HighAvailabilityTransformer.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/HighAvailabilityTransformer.java
new file mode 100644
index 0000000..41b0f22
--- /dev/null
+++ b/rest/rest-resources/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 org.apache.brooklyn.api.mgmt.ha.ManagementNodeSyncRecord;
+import org.apache.brooklyn.api.mgmt.ha.ManagementPlaneSyncRecord;
+import org.apache.brooklyn.rest.domain.HighAvailabilitySummary;
+import org.apache.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/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/LocationTransformer.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/LocationTransformer.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/LocationTransformer.java
new file mode 100644
index 0000000..db3c4f1
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/LocationTransformer.java
@@ -0,0 +1,202 @@
+/*
+ * 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 org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.location.LocationDefinition;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.core.config.Sanitizer;
+import org.apache.brooklyn.core.location.BasicLocationDefinition;
+import org.apache.brooklyn.core.location.LocationConfigKeys;
+import org.apache.brooklyn.core.location.internal.LocationInternal;
+import org.apache.brooklyn.rest.domain.LocationSummary;
+import org.apache.brooklyn.rest.util.WebResourceUtils;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.guava.Maybe;
+import org.apache.brooklyn.util.text.Strings;
+
+import com.google.common.collect.ImmutableMap;
+import javax.ws.rs.core.UriBuilder;
+import org.apache.brooklyn.rest.api.LocationApi;
+import static org.apache.brooklyn.rest.util.WebResourceUtils.serviceUriBuilder;
+
+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, org.apache.brooklyn.rest.domain.LocationSpec locationSpec, UriBuilder ub) {
+        return newInstance(null, id, locationSpec, LocationDetailLevel.LOCAL_EXCLUDING_SECRET, ub);
+    }
+    @SuppressWarnings("deprecation")
+    public static LocationSummary newInstance(ManagementContext mgmt, String id, org.apache.brooklyn.rest.domain.LocationSpec locationSpec, LocationDetailLevel level, UriBuilder ub) {
+        // 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();
+                    }
+                }
+            }
+        }
+
+        URI selfUri = serviceUriBuilder(ub, LocationApi.class, "get").build(id);
+        return new LocationSummary(
+                id,
+                locationSpec.getName(),
+                locationSpec.getSpec(),
+                null,
+                copyConfig(config, level),
+                ImmutableMap.of("self", selfUri));
+    }
+
+    /** @deprecated since 0.7.0 use method taking management context and detail specifier */
+    @Deprecated
+    public static LocationSummary newInstance(LocationDefinition l, UriBuilder ub) {
+        return newInstance(null, l, LocationDetailLevel.LOCAL_EXCLUDING_SECRET, ub);
+    }
+
+    public static LocationSummary newInstance(ManagementContext mgmt, LocationDefinition l, LocationDetailLevel level, UriBuilder ub) {
+        // 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();
+                    }
+                }
+            }
+        }
+
+        URI selfUri = serviceUriBuilder(ub, LocationApi.class, "get").build(l.getId());
+        return new LocationSummary(
+                l.getId(),
+                l.getName(),
+                l.getSpec(),
+                null,
+                copyConfig(config, level),
+                ImmutableMap.of("self", selfUri));
+    }
+
+    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, UriBuilder ub) {
+        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);
+        
+        URI selfUri = serviceUriBuilder(ub, LocationApi.class, "get").build(l.getId());
+        URI parentUri = l.getParent() == null ? null : serviceUriBuilder(ub, LocationApi.class, "get").build(l.getParent().getId());
+        URI specUri = specId == null ? null : serviceUriBuilder(ub, LocationApi.class, "get").build(specId);
+        return new LocationSummary(
+            l.getId(),
+            l.getDisplayName(),
+            spec,
+            l.getClass().getName(),
+            config,
+            MutableMap.of("self", selfUri)
+                .addIfNotNull("parent", parentUri)
+                .addIfNotNull("spec", specUri)
+                .asUnmodifiable() );
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/PolicyTransformer.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/PolicyTransformer.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/PolicyTransformer.java
new file mode 100644
index 0000000..18e7d57
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/PolicyTransformer.java
@@ -0,0 +1,97 @@
+/*
+ * 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.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.policy.Policy;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.policy.Policies;
+import org.apache.brooklyn.rest.domain.ApplicationSummary;
+import org.apache.brooklyn.rest.domain.PolicyConfigSummary;
+import org.apache.brooklyn.rest.domain.PolicySummary;
+import org.apache.brooklyn.rest.resources.PolicyConfigResource;
+import org.apache.brooklyn.rest.util.BrooklynRestResourceUtils;
+
+import com.google.common.collect.ImmutableMap;
+import javax.ws.rs.core.UriBuilder;
+import org.apache.brooklyn.rest.api.ApplicationApi;
+import org.apache.brooklyn.rest.api.EntityApi;
+import org.apache.brooklyn.rest.api.PolicyApi;
+import org.apache.brooklyn.rest.api.PolicyConfigApi;
+import static org.apache.brooklyn.rest.util.WebResourceUtils.resourceUriBuilder;
+import static org.apache.brooklyn.rest.util.WebResourceUtils.serviceUriBuilder;
+
+/**
+ * Converts from Brooklyn entities to restful API summary objects
+ */
+public class PolicyTransformer {
+
+    public static PolicySummary policySummary(Entity entity, Policy policy, UriBuilder ub) {
+        URI applicationUri = serviceUriBuilder(ub, ApplicationApi.class, "get").build(entity.getApplicationId());
+        URI entityUri = serviceUriBuilder(ub, EntityApi.class, "get").build(entity.getApplicationId(), entity.getId());
+        URI configUri = resourceUriBuilder(ub, PolicyConfigApi.class).build(entity.getApplicationId(), entity.getId(), policy.getId());
+
+        URI selfUri = serviceUriBuilder(ub, PolicyApi.class, "getStatus").build(entity.getApplicationId(), entity.getId(), policy.getId());
+        URI startUri = serviceUriBuilder(ub, PolicyApi.class, "start").build(entity.getApplicationId(), entity.getId(), policy.getId());
+        URI stopUri = serviceUriBuilder(ub, PolicyApi.class, "stop").build(entity.getApplicationId(), entity.getId(), policy.getId());
+        URI destroyUri = serviceUriBuilder(ub, PolicyApi.class, "destroy").build(entity.getApplicationId(), entity.getId(), policy.getId());
+
+        Map<String, URI> links = ImmutableMap.<String, URI>builder()
+                .put("self", selfUri)
+                .put("config", configUri)
+                .put("start", startUri)
+                .put("stop", stopUri)
+                .put("destroy", destroyUri)
+                .put("application", applicationUri)
+                .put("entity", 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, Entity entity, Policy policy, ConfigKey<?> config, UriBuilder ub) {
+        PolicyConfigSummary summary = policyConfigSummary(utils, entity, policy, config, ub);
+//        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, Entity entity, Policy policy, ConfigKey<?> config, UriBuilder ub) {
+        URI applicationUri = serviceUriBuilder(ub, ApplicationApi.class, "get").build(entity.getApplicationId());
+        URI entityUri = serviceUriBuilder(ub, EntityApi.class, "get").build(entity.getApplicationId(), entity.getId());
+        URI policyUri = serviceUriBuilder(ub, PolicyApi.class, "getStatus").build(entity.getApplicationId(), entity.getId(), policy.getId());
+        URI configUri = serviceUriBuilder(ub, PolicyConfigApi.class, "get").build(entity.getApplicationId(), entity.getId(), policy.getId(), config.getName());
+
+        Map<String, URI> links = ImmutableMap.<String, URI>builder()
+                .put("self", configUri)
+                .put("application", applicationUri)
+                .put("entity", entityUri)
+                .put("policy", 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/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/SensorTransformer.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/SensorTransformer.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/SensorTransformer.java
new file mode 100644
index 0000000..19820d0
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/SensorTransformer.java
@@ -0,0 +1,88 @@
+/*
+ * 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 org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.api.sensor.Sensor;
+import org.apache.brooklyn.core.config.render.RendererHints;
+import org.apache.brooklyn.rest.domain.SensorSummary;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.text.Strings;
+
+import com.google.common.collect.Iterables;
+import javax.ws.rs.core.UriBuilder;
+import org.apache.brooklyn.rest.api.ApplicationApi;
+import org.apache.brooklyn.rest.api.EntityApi;
+import org.apache.brooklyn.rest.api.SensorApi;
+import static org.apache.brooklyn.rest.util.WebResourceUtils.serviceUriBuilder;
+
+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, UriBuilder ub) {
+        URI applicationUri = serviceUriBuilder(ub, ApplicationApi.class, "get").build(entity.getApplicationId());
+        URI entityUri = serviceUriBuilder(ub, EntityApi.class, "get").build(entity.getApplicationId(), entity.getId());
+        URI selfUri = serviceUriBuilder(ub, SensorApi.class, "get").build(entity.getApplicationId(), entity.getId(), sensor.getName());
+
+        MutableMap.Builder<String, URI> lb = MutableMap.<String, URI>builder()
+                .put("self", selfUri)
+                .put("application", applicationUri)
+                .put("entity", entityUri)
+                .put("action:json", 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/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/TaskTransformer.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/TaskTransformer.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/TaskTransformer.java
new file mode 100644
index 0000000..cb74164
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/transform/TaskTransformer.java
@@ -0,0 +1,153 @@
+/*
+ * 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 org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.mgmt.HasTaskChildren;
+import org.apache.brooklyn.api.mgmt.Task;
+import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
+import org.apache.brooklyn.core.mgmt.BrooklynTaskTags.WrappedStream;
+import org.apache.brooklyn.rest.domain.LinkWithMetadata;
+import org.apache.brooklyn.rest.domain.TaskSummary;
+import org.apache.brooklyn.rest.util.WebResourceUtils;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.core.task.TaskInternal;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.text.Strings;
+
+import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
+import javax.ws.rs.core.UriBuilder;
+import org.apache.brooklyn.rest.api.ActivityApi;
+import org.apache.brooklyn.rest.api.EntityApi;
+import static org.apache.brooklyn.rest.util.WebResourceUtils.serviceUriBuilder;
+
+public class TaskTransformer {
+
+    @SuppressWarnings("unused")
+    private static final Logger log = LoggerFactory.getLogger(TaskTransformer.class);
+
+    public static final Function<Task<?>, TaskSummary> fromTask(final UriBuilder ub) {
+        return new Function<Task<?>, TaskSummary>() {
+            @Override
+            public TaskSummary apply(@Nullable Task<?> input) {
+                return taskSummary(input, ub);
+            }
+        };
+    };
+
+    public static TaskSummary taskSummary(Task<?> task, UriBuilder ub) {
+      try {
+        Preconditions.checkNotNull(task);
+        Entity entity = BrooklynTaskTags.getContextEntity(task);
+        String entityId;
+        String entityDisplayName;
+        URI entityLink;
+        
+        String selfLink = asLink(task, ub).getLink();
+
+        if (entity != null) {
+            entityId = entity.getId();
+            entityDisplayName = entity.getDisplayName();
+            entityLink = serviceUriBuilder(ub, EntityApi.class, "get").build(entity.getApplicationId(), 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, ub));
+            }
+        }
+        
+        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(), ub),
+                task.isDone() ? null : task instanceof TaskInternal ? asLink(((TaskInternal<?>)task).getBlockingTask(), ub) : 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, UriBuilder ub) {
+        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());
+        }
+        URI taskUri = serviceUriBuilder(ub, ActivityApi.class, "get").build(t.getId());
+        return new LinkWithMetadata(taskUri.toString(), data);
+    }
+}


Mime
View raw message