You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2016/02/18 16:47:19 UTC

[01/34] brooklyn-server git commit: [BROOKLYN-183] REST API using CXF JAX-RS 2.0 implementation

Repository: brooklyn-server
Updated Branches:
  refs/heads/master a2820e5f6 -> fa19e8f5f


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/util/NullHttpServletRequestProvider.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/util/NullHttpServletRequestProvider.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/util/NullHttpServletRequestProvider.java
deleted file mode 100644
index 7a24f31..0000000
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/util/NullHttpServletRequestProvider.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * 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 java.lang.reflect.Type;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.ws.rs.core.Context;
-import javax.ws.rs.ext.Provider;
-
-import com.sun.jersey.core.spi.component.ComponentContext;
-import com.sun.jersey.core.spi.component.ComponentScope;
-import com.sun.jersey.spi.inject.Injectable;
-import com.sun.jersey.spi.inject.InjectableProvider;
-
-@Provider
-public class NullHttpServletRequestProvider implements InjectableProvider<Context, Type> { 
-    public Injectable<HttpServletRequest> getInjectable(ComponentContext ic, 
-            Context a, Type c) { 
-        if (HttpServletRequest.class == c) { 
-            return new Injectable<HttpServletRequest>() {
-                public HttpServletRequest getValue() { return null; }
-            }; 
-        } else 
-            return null; 
-    } 
-    public ComponentScope getScope() { 
-        return ComponentScope.Singleton; 
-    } 
-} 

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/util/NullServletConfigProvider.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/util/NullServletConfigProvider.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/util/NullServletConfigProvider.java
deleted file mode 100644
index 106780d..0000000
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/util/NullServletConfigProvider.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * 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 java.lang.reflect.Type;
-
-import javax.servlet.ServletContext;
-import javax.ws.rs.core.Context;
-import javax.ws.rs.ext.Provider;
-
-import com.sun.jersey.core.spi.component.ComponentContext;
-import com.sun.jersey.core.spi.component.ComponentScope;
-import com.sun.jersey.spi.container.servlet.WebConfig;
-import com.sun.jersey.spi.inject.Injectable;
-import com.sun.jersey.spi.inject.InjectableProvider;
-
-@Provider
-public class NullServletConfigProvider implements InjectableProvider<Context, Type> { 
-    public Injectable<ServletContext> getInjectable(ComponentContext ic, 
-            Context a, Type c) { 
-        if (ServletContext.class == c) { 
-            return new Injectable<ServletContext>() {
-                public ServletContext getValue() { return null; }
-            }; 
-        } else if (WebConfig.class == c) {
-            return new Injectable<ServletContext>() {
-                public ServletContext getValue() { return null; }
-            }; 
-        } else 
-            return null; 
-    } 
-    public ComponentScope getScope() { 
-        return ComponentScope.Singleton; 
-    } 
-} 

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonSerializerIntegrationTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonSerializerIntegrationTest.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonSerializerIntegrationTest.java
new file mode 100644
index 0000000..1cf79e8
--- /dev/null
+++ b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonSerializerIntegrationTest.java
@@ -0,0 +1,173 @@
+/*
+ * 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.json;
+
+import java.io.NotSerializableException;
+import java.net.URI;
+import java.util.Map;
+
+import javax.ws.rs.core.MediaType;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
+import org.apache.brooklyn.core.test.entity.TestApplication;
+import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.apache.brooklyn.rest.BrooklynRestApiLauncher;
+import org.apache.brooklyn.rest.util.json.BrooklynJacksonSerializerTest.SelfRefNonSerializableClass;
+import org.apache.brooklyn.util.http.HttpTool;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.utils.URIBuilder;
+import org.eclipse.jetty.server.NetworkConnector;
+import org.eclipse.jetty.server.Server;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.gson.Gson;
+
+public class BrooklynJacksonSerializerIntegrationTest {
+
+    private static final Logger log = LoggerFactory.getLogger(BrooklynJacksonSerializerIntegrationTest.class);
+    
+    // Ensure TEXT_PLAIN just returns toString for ManagementContext instance.
+    // Strangely, testWithLauncherSerializingListsContainingEntitiesAndOtherComplexStuff ended up in the 
+    // EntityConfigResource.getPlain code, throwing a ClassCastException.
+    // 
+    // TODO This tests the fix for that ClassCastException, but does not explain why 
+    // testWithLauncherSerializingListsContainingEntitiesAndOtherComplexStuff was calling it.
+    @Test(groups="Integration") //because of time
+    public void testWithAcceptsPlainText() throws Exception {
+        ManagementContext mgmt = LocalManagementContextForTests.newInstance();
+        Server server = null;
+        try {
+            server = BrooklynRestApiLauncher.launcher().managementContext(mgmt).start();
+            HttpClient client = HttpTool.httpClientBuilder().build();
+
+            TestApplication app = TestApplication.Factory.newManagedInstanceForTests(mgmt);
+
+            String serverAddress = "http://localhost:"+((NetworkConnector)server.getConnectors()[0]).getLocalPort();
+            String appUrl = serverAddress + "/v1/applications/" + app.getId();
+            String entityUrl = appUrl + "/entities/" + app.getId();
+            URI configUri = new URIBuilder(entityUrl + "/config/" + TestEntity.CONF_OBJECT.getName())
+                    .addParameter("raw", "true")
+                    .build();
+
+            // assert config here is just mgmt.toString()
+            app.config().set(TestEntity.CONF_OBJECT, mgmt);
+            String content = get(client, configUri, ImmutableMap.of("Accept", MediaType.TEXT_PLAIN));
+            log.info("CONFIG MGMT is:\n"+content);
+            Assert.assertEquals(content, mgmt.toString(), "content="+content);
+            
+        } finally {
+            try {
+                if (server != null) server.stop();
+            } catch (Exception e) {
+                log.warn("failed to stop server: "+e);
+            }
+            Entities.destroyAll(mgmt);
+        }
+    }
+        
+    @Test(groups="Integration") //because of time
+    public void testWithLauncherSerializingListsContainingEntitiesAndOtherComplexStuff() throws Exception {
+        ManagementContext mgmt = LocalManagementContextForTests.newInstance();
+        Server server = null;
+        try {
+            server = BrooklynRestApiLauncher.launcher().managementContext(mgmt).start();
+            HttpClient client = HttpTool.httpClientBuilder().build();
+
+            TestApplication app = TestApplication.Factory.newManagedInstanceForTests(mgmt);
+
+            String serverAddress = "http://localhost:"+((NetworkConnector)server.getConnectors()[0]).getLocalPort();
+            String appUrl = serverAddress + "/v1/applications/" + app.getId();
+            String entityUrl = appUrl + "/entities/" + app.getId();
+            URI configUri = new URIBuilder(entityUrl + "/config/" + TestEntity.CONF_OBJECT.getName())
+                    .addParameter("raw", "true")
+                    .build();
+
+            // assert config here is just mgmt
+            app.config().set(TestEntity.CONF_OBJECT, mgmt);
+            String content = get(client, configUri, ImmutableMap.of("Accept", MediaType.APPLICATION_JSON));
+            log.info("CONFIG MGMT is:\n"+content);
+            @SuppressWarnings("rawtypes")
+            Map values = new Gson().fromJson(content, Map.class);
+            Assert.assertEquals(values, ImmutableMap.of("type", LocalManagementContextForTests.class.getCanonicalName()), "values="+values);
+
+            // assert normal API returns the same, containing links
+            content = get(client, entityUrl, ImmutableMap.of("Accept", MediaType.APPLICATION_JSON));
+            log.info("ENTITY is: \n"+content);
+            values = new Gson().fromJson(content, Map.class);
+            Assert.assertTrue(values.size()>=3, "Map is too small: "+values);
+            Assert.assertTrue(values.size()<=6, "Map is too big: "+values);
+            Assert.assertEquals(values.get("type"), TestApplication.class.getCanonicalName(), "values="+values);
+            Assert.assertNotNull(values.get("links"), "Map should have contained links: values="+values);
+
+            // but config etc returns our nicely json serialized
+            app.config().set(TestEntity.CONF_OBJECT, app);
+            content = get(client, configUri, ImmutableMap.of("Accept", MediaType.APPLICATION_JSON));
+            log.info("CONFIG ENTITY is:\n"+content);
+            values = new Gson().fromJson(content, Map.class);
+            Assert.assertEquals(values, ImmutableMap.of("type", Entity.class.getCanonicalName(), "id", app.getId()), "values="+values);
+
+            // and self-ref gives error + toString
+            SelfRefNonSerializableClass angry = new SelfRefNonSerializableClass();
+            app.config().set(TestEntity.CONF_OBJECT, angry);
+            content = get(client, configUri, ImmutableMap.of("Accept", MediaType.APPLICATION_JSON));
+            log.info("CONFIG ANGRY is:\n"+content);
+            assertErrorObjectMatchingToString(content, angry);
+            
+            // as does Server
+            app.config().set(TestEntity.CONF_OBJECT, server);
+            content = get(client, configUri, ImmutableMap.of("Accept", MediaType.APPLICATION_JSON));
+            // NOTE, if using the default visibility / object mapper, the getters of the object are invoked
+            // resulting in an object which is huge, 7+MB -- and it wreaks havoc w eclipse console regex parsing!
+            // (but with our custom VisibilityChecker server just gives us the nicer error!)
+            log.info("CONFIG SERVER is:\n"+content);
+            assertErrorObjectMatchingToString(content, server);
+            Assert.assertTrue(content.contains(NotSerializableException.class.getCanonicalName()), "server should have contained things which are not serializable");
+            Assert.assertTrue(content.length() < 1024, "content should not have been very long; instead was: "+content.length());
+            
+        } finally {
+            try {
+                if (server != null) server.stop();
+            } catch (Exception e) {
+                log.warn("failed to stop server: "+e);
+            }
+            Entities.destroyAll(mgmt);
+        }
+    }
+
+    private void assertErrorObjectMatchingToString(String content, Object expected) {
+        Object value = new Gson().fromJson(content, Object.class);
+        Assert.assertTrue(value instanceof Map, "Expected map, got: "+value);
+        Assert.assertEquals(((Map<?,?>)value).get("toString"), expected.toString());
+    }
+
+    private String get(HttpClient client, String uri, Map<String, String> headers) {
+        return get(client, URI.create(uri), headers);
+    }
+
+    private String get(HttpClient client, URI uri, Map<String, String> headers) {
+        return HttpTool.httpGet(client, uri, headers).getContentAsString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonSerializerTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonSerializerTest.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonSerializerTest.java
deleted file mode 100644
index 9542eda..0000000
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonSerializerTest.java
+++ /dev/null
@@ -1,399 +0,0 @@
-/*
- * 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.json;
-
-import java.io.NotSerializableException;
-import java.net.URI;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-
-import javax.ws.rs.core.MediaType;
-
-import org.apache.http.client.HttpClient;
-import org.apache.http.client.utils.URIBuilder;
-import org.eclipse.jetty.server.Server;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.Assert;
-import org.testng.annotations.Test;
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.mgmt.ManagementContext;
-import org.apache.brooklyn.core.entity.Attributes;
-import org.apache.brooklyn.core.entity.Entities;
-import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
-import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
-import org.apache.brooklyn.core.test.entity.TestApplication;
-import org.apache.brooklyn.core.test.entity.TestEntity;
-import org.apache.brooklyn.rest.BrooklynRestApiLauncher;
-import org.apache.brooklyn.util.collections.MutableList;
-import org.apache.brooklyn.util.collections.MutableMap;
-import org.apache.brooklyn.util.http.HttpTool;
-import org.apache.brooklyn.util.exceptions.Exceptions;
-import org.apache.brooklyn.util.stream.Streams;
-import org.apache.brooklyn.util.text.Strings;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.annotation.JsonSerialize;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Multimap;
-import com.google.common.collect.MultimapBuilder;
-import com.google.gson.Gson;
-import org.eclipse.jetty.server.NetworkConnector;
-
-public class BrooklynJacksonSerializerTest {
-
-    private static final Logger log = LoggerFactory.getLogger(BrooklynJacksonSerializerTest.class);
-    
-    public static class SillyClassWithManagementContext {
-        @JsonProperty
-        ManagementContext mgmt;
-        @JsonProperty
-        String id;
-        
-        public SillyClassWithManagementContext() { }
-        
-        public SillyClassWithManagementContext(String id, ManagementContext mgmt) {
-            this.id = id;
-            this.mgmt = mgmt;
-        }
-
-        @Override
-        public String toString() {
-            return super.toString()+"[id="+id+";mgmt="+mgmt+"]";
-        }
-    }
-
-    @Test
-    public void testCustomSerializerWithSerializableSillyManagementExample() throws Exception {
-        ManagementContext mgmt = LocalManagementContextForTests.newInstance();
-        try {
-
-            ObjectMapper mapper = BrooklynJacksonJsonProvider.newPrivateObjectMapper(mgmt);
-
-            SillyClassWithManagementContext silly = new SillyClassWithManagementContext("123", mgmt);
-            log.info("silly is: "+silly);
-
-            String sillyS = mapper.writeValueAsString(silly);
-
-            log.info("silly json is: "+sillyS);
-
-            SillyClassWithManagementContext silly2 = mapper.readValue(sillyS, SillyClassWithManagementContext.class);
-            log.info("silly2 is: "+silly2);
-
-            Assert.assertEquals(silly.id, silly2.id);
-            
-        } finally {
-            Entities.destroyAll(mgmt);
-        }
-    }
-    
-    public static class SelfRefNonSerializableClass {
-        @JsonProperty
-        Object bogus = this;
-    }
-
-    @Test
-    public void testSelfReferenceFailsWhenStrict() {
-        checkNonSerializableWhenStrict(new SelfRefNonSerializableClass());
-    }
-    @Test
-    public void testSelfReferenceGeneratesErrorMapObject() throws Exception {
-        checkSerializesAsMapWithErrorAndToString(new SelfRefNonSerializableClass());
-    }
-    @Test
-    public void testNonSerializableInListIsShownInList() throws Exception {
-        List<?> result = checkSerializesAs(MutableList.of(1, new SelfRefNonSerializableClass()), List.class);
-        Assert.assertEquals( result.get(0), 1 );
-        Assert.assertEquals( ((Map<?,?>)result.get(1)).get("errorType"), NotSerializableException.class.getName() );
-    }
-    @Test
-    public void testNonSerializableInMapIsShownInMap() throws Exception {
-        Map<?,?> result = checkSerializesAs(MutableMap.of("x", new SelfRefNonSerializableClass()), Map.class);
-        Assert.assertEquals( ((Map<?,?>)result.get("x")).get("errorType"), NotSerializableException.class.getName() );
-    }
-    static class TupleWithNonSerializable {
-        String good = "bon";
-        SelfRefNonSerializableClass bad = new SelfRefNonSerializableClass();
-    }
-    @Test
-    public void testNonSerializableInObjectIsShownInMap() throws Exception {
-        String resultS = checkSerializesAs(new TupleWithNonSerializable(), null);
-        log.info("nested non-serializable json is "+resultS);
-        Assert.assertTrue(resultS.startsWith("{\"good\":\"bon\",\"bad\":{"), "expected a nested map for the error field, not "+resultS);
-        
-        Map<?,?> result = checkSerializesAs(new TupleWithNonSerializable(), Map.class);
-        Assert.assertEquals( result.get("good"), "bon" );
-        Assert.assertTrue( result.containsKey("bad"), "Should have had a key for field 'bad'" );
-        Assert.assertEquals( ((Map<?,?>)result.get("bad")).get("errorType"), NotSerializableException.class.getName() );
-    }
-    
-    public static class EmptyClass {
-    }
-
-    @Test
-    public void testEmptySerializesAsEmpty() throws Exception {
-        // deliberately, a class with no fields and no annotations serializes as an error,
-        // because the alternative, {}, is useless.  however if it *is* annotated, as below, then it will serialize fine.
-        checkSerializesAsMapWithErrorAndToString(new SelfRefNonSerializableClass());
-    }
-    @Test
-    public void testEmptyNonSerializableFailsWhenStrict() {
-        checkNonSerializableWhenStrict(new EmptyClass());
-    }
-
-    @JsonSerialize
-    public static class EmptyClassWithSerialize {
-    }
-
-    @Test
-    public void testEmptyAnnotatedSerializesAsEmptyEvenWhenStrict() throws Exception {
-        try {
-            BidiSerialization.setStrictSerialization(true);
-            testEmptyAnnotatedSerializesAsEmpty();
-        } finally {
-            BidiSerialization.clearStrictSerialization();
-        }
-    }
-    
-    @Test
-    public void testEmptyAnnotatedSerializesAsEmpty() throws Exception {
-        Map<?, ?> map = checkSerializesAs( new EmptyClassWithSerialize(), Map.class );
-        Assert.assertTrue(map.isEmpty(), "Expected an empty map; instead got: "+map);
-
-        String result = checkSerializesAs( MutableList.of(new EmptyClassWithSerialize()), null );
-        result = result.replaceAll(" ", "").trim();
-        Assert.assertEquals(result, "[{}]");
-    }
-
-    @Test
-    public void testSensorFailsWhenStrict() {
-        checkNonSerializableWhenStrict(MutableList.of(Attributes.HTTP_PORT));
-    }
-    @Test
-    public void testSensorSensible() throws Exception {
-        Map<?,?> result = checkSerializesAs(Attributes.HTTP_PORT, Map.class);
-        log.info("SENSOR json is: "+result);
-        Assert.assertFalse(result.toString().contains("error"), "Shouldn't have had an error, instead got: "+result);
-    }
-
-    @Test
-    public void testLinkedListSerialization() throws Exception {
-        LinkedList<Object> ll = new LinkedList<Object>();
-        ll.add(1); ll.add("two");
-        String result = checkSerializesAs(ll, null);
-        log.info("LLIST json is: "+result);
-        Assert.assertFalse(result.contains("error"), "Shouldn't have had an error, instead got: "+result);
-        Assert.assertEquals(Strings.collapseWhitespace(result, ""), "[1,\"two\"]");
-    }
-
-    @Test
-    public void testMultiMapSerialization() throws Exception {
-        Multimap<String, Integer> m = MultimapBuilder.hashKeys().arrayListValues().build();
-        m.put("bob", 24);
-        m.put("bob", 25);
-        String result = checkSerializesAs(m, null);
-        log.info("multimap serialized as: " + result);
-        Assert.assertFalse(result.contains("error"), "Shouldn't have had an error, instead got: "+result);
-        Assert.assertEquals(Strings.collapseWhitespace(result, ""), "{\"bob\":[24,25]}");
-    }
-
-    @Test
-    public void testSupplierSerialization() throws Exception {
-        String result = checkSerializesAs(Strings.toStringSupplier(Streams.byteArrayOfString("x")), null);
-        log.info("SUPPLIER json is: "+result);
-        Assert.assertFalse(result.contains("error"), "Shouldn't have had an error, instead got: "+result);
-    }
-
-    @Test
-    public void testWrappedStreamSerialization() throws Exception {
-        String result = checkSerializesAs(BrooklynTaskTags.tagForStream("TEST", Streams.byteArrayOfString("x")), null);
-        log.info("WRAPPED STREAM json is: "+result);
-        Assert.assertFalse(result.contains("error"), "Shouldn't have had an error, instead got: "+result);
-    }
-
-    @SuppressWarnings("unchecked")
-    protected <T> T checkSerializesAs(Object x, Class<T> type) {
-        ManagementContext mgmt = LocalManagementContextForTests.newInstance();
-        try {
-            ObjectMapper mapper = BrooklynJacksonJsonProvider.newPrivateObjectMapper(mgmt);
-            String tS = mapper.writeValueAsString(x);
-            log.debug("serialized "+x+" as "+tS);
-            Assert.assertTrue(tS.length() < 1000, "Data too long, size "+tS.length()+" for "+x);
-            if (type==null) return (T) tS;
-            return mapper.readValue(tS, type);
-        } catch (Exception e) {
-            throw Exceptions.propagate(e);
-        } finally {
-            Entities.destroyAll(mgmt);
-        }
-    }
-    protected Map<?,?> checkSerializesAsMapWithErrorAndToString(Object x) {
-        Map<?,?> rt = checkSerializesAs(x, Map.class);
-        Assert.assertEquals(rt.get("toString"), x.toString());
-        Assert.assertEquals(rt.get("error"), Boolean.TRUE);
-        return rt;
-    }
-    protected void checkNonSerializableWhenStrict(Object x) {
-        checkNonSerializable(x, true);
-    }
-    protected void checkNonSerializable(Object x, boolean strict) {
-        ManagementContext mgmt = LocalManagementContextForTests.newInstance();
-        try {
-            ObjectMapper mapper = BrooklynJacksonJsonProvider.newPrivateObjectMapper(mgmt);
-            if (strict)
-                BidiSerialization.setStrictSerialization(true);
-            
-            String tS = mapper.writeValueAsString(x);
-            Assert.fail("Should not have serialized "+x+"; instead gave: "+tS);
-            
-        } catch (Exception e) {
-            Exceptions.propagateIfFatal(e);
-            log.info("Got expected error, when serializing "+x+": "+e);
-            
-        } finally {
-            if (strict)
-                BidiSerialization.clearStrictSerialization();
-            Entities.destroyAll(mgmt);
-        }
-    }
-    
-    // Ensure TEXT_PLAIN just returns toString for ManagementContext instance.
-    // Strangely, testWithLauncherSerializingListsContainingEntitiesAndOtherComplexStuff ended up in the 
-    // EntityConfigResource.getPlain code, throwing a ClassCastException.
-    // 
-    // TODO This tests the fix for that ClassCastException, but does not explain why 
-    // testWithLauncherSerializingListsContainingEntitiesAndOtherComplexStuff was calling it.
-    @Test(groups="Integration") //because of time
-    public void testWithAcceptsPlainText() throws Exception {
-        ManagementContext mgmt = LocalManagementContextForTests.newInstance();
-        Server server = null;
-        try {
-            server = BrooklynRestApiLauncher.launcher().managementContext(mgmt).start();
-            HttpClient client = HttpTool.httpClientBuilder().build();
-
-            TestApplication app = TestApplication.Factory.newManagedInstanceForTests(mgmt);
-
-            String serverAddress = "http://localhost:"+((NetworkConnector)server.getConnectors()[0]).getLocalPort();
-            String appUrl = serverAddress + "/v1/applications/" + app.getId();
-            String entityUrl = appUrl + "/entities/" + app.getId();
-            URI configUri = new URIBuilder(entityUrl + "/config/" + TestEntity.CONF_OBJECT.getName())
-                    .addParameter("raw", "true")
-                    .build();
-
-            // assert config here is just mgmt.toString()
-            app.config().set(TestEntity.CONF_OBJECT, mgmt);
-            String content = get(client, configUri, ImmutableMap.of("Accept", MediaType.TEXT_PLAIN));
-            log.info("CONFIG MGMT is:\n"+content);
-            Assert.assertEquals(content, mgmt.toString(), "content="+content);
-            
-        } finally {
-            try {
-                if (server != null) server.stop();
-            } catch (Exception e) {
-                log.warn("failed to stop server: "+e);
-            }
-            Entities.destroyAll(mgmt);
-        }
-    }
-        
-    @Test(groups="Integration") //because of time
-    public void testWithLauncherSerializingListsContainingEntitiesAndOtherComplexStuff() throws Exception {
-        ManagementContext mgmt = LocalManagementContextForTests.newInstance();
-        Server server = null;
-        try {
-            server = BrooklynRestApiLauncher.launcher().managementContext(mgmt).start();
-            HttpClient client = HttpTool.httpClientBuilder().build();
-
-            TestApplication app = TestApplication.Factory.newManagedInstanceForTests(mgmt);
-
-            String serverAddress = "http://localhost:"+((NetworkConnector)server.getConnectors()[0]).getLocalPort();
-            String appUrl = serverAddress + "/v1/applications/" + app.getId();
-            String entityUrl = appUrl + "/entities/" + app.getId();
-            URI configUri = new URIBuilder(entityUrl + "/config/" + TestEntity.CONF_OBJECT.getName())
-                    .addParameter("raw", "true")
-                    .build();
-
-            // assert config here is just mgmt
-            app.config().set(TestEntity.CONF_OBJECT, mgmt);
-            String content = get(client, configUri, ImmutableMap.of("Accept", MediaType.APPLICATION_JSON));
-            log.info("CONFIG MGMT is:\n"+content);
-            @SuppressWarnings("rawtypes")
-            Map values = new Gson().fromJson(content, Map.class);
-            Assert.assertEquals(values, ImmutableMap.of("type", LocalManagementContextForTests.class.getCanonicalName()), "values="+values);
-
-            // assert normal API returns the same, containing links
-            content = get(client, entityUrl, ImmutableMap.of("Accept", MediaType.APPLICATION_JSON));
-            log.info("ENTITY is: \n"+content);
-            values = new Gson().fromJson(content, Map.class);
-            Assert.assertTrue(values.size()>=3, "Map is too small: "+values);
-            Assert.assertTrue(values.size()<=6, "Map is too big: "+values);
-            Assert.assertEquals(values.get("type"), TestApplication.class.getCanonicalName(), "values="+values);
-            Assert.assertNotNull(values.get("links"), "Map should have contained links: values="+values);
-
-            // but config etc returns our nicely json serialized
-            app.config().set(TestEntity.CONF_OBJECT, app);
-            content = get(client, configUri, ImmutableMap.of("Accept", MediaType.APPLICATION_JSON));
-            log.info("CONFIG ENTITY is:\n"+content);
-            values = new Gson().fromJson(content, Map.class);
-            Assert.assertEquals(values, ImmutableMap.of("type", Entity.class.getCanonicalName(), "id", app.getId()), "values="+values);
-
-            // and self-ref gives error + toString
-            SelfRefNonSerializableClass angry = new SelfRefNonSerializableClass();
-            app.config().set(TestEntity.CONF_OBJECT, angry);
-            content = get(client, configUri, ImmutableMap.of("Accept", MediaType.APPLICATION_JSON));
-            log.info("CONFIG ANGRY is:\n"+content);
-            assertErrorObjectMatchingToString(content, angry);
-            
-            // as does Server
-            app.config().set(TestEntity.CONF_OBJECT, server);
-            content = get(client, configUri, ImmutableMap.of("Accept", MediaType.APPLICATION_JSON));
-            // NOTE, if using the default visibility / object mapper, the getters of the object are invoked
-            // resulting in an object which is huge, 7+MB -- and it wreaks havoc w eclipse console regex parsing!
-            // (but with our custom VisibilityChecker server just gives us the nicer error!)
-            log.info("CONFIG SERVER is:\n"+content);
-            assertErrorObjectMatchingToString(content, server);
-            Assert.assertTrue(content.contains(NotSerializableException.class.getCanonicalName()), "server should have contained things which are not serializable");
-            Assert.assertTrue(content.length() < 1024, "content should not have been very long; instead was: "+content.length());
-            
-        } finally {
-            try {
-                if (server != null) server.stop();
-            } catch (Exception e) {
-                log.warn("failed to stop server: "+e);
-            }
-            Entities.destroyAll(mgmt);
-        }
-    }
-
-    private void assertErrorObjectMatchingToString(String content, Object expected) {
-        Object value = new Gson().fromJson(content, Object.class);
-        Assert.assertTrue(value instanceof Map, "Expected map, got: "+value);
-        Assert.assertEquals(((Map<?,?>)value).get("toString"), expected.toString());
-    }
-
-    private String get(HttpClient client, String uri, Map<String, String> headers) {
-        return get(client, URI.create(uri), headers);
-    }
-
-    private String get(HttpClient client, URI uri, Map<String, String> headers) {
-        return HttpTool.httpGet(client, uri, headers).getContentAsString();
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/resources/brooklyn/scanning.catalog.bom
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/resources/brooklyn/scanning.catalog.bom b/rest/rest-server/src/test/resources/brooklyn/scanning.catalog.bom
deleted file mode 100644
index cddb832..0000000
--- a/rest/rest-server/src/test/resources/brooklyn/scanning.catalog.bom
+++ /dev/null
@@ -1,19 +0,0 @@
-# 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.
-#
-brooklyn.catalog:
-  scanJavaAnnotations: true

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/utils/rest-swagger/pom.xml
----------------------------------------------------------------------
diff --git a/utils/rest-swagger/pom.xml b/utils/rest-swagger/pom.xml
index 83849f9..3afa7e0 100644
--- a/utils/rest-swagger/pom.xml
+++ b/utils/rest-swagger/pom.xml
@@ -36,21 +36,11 @@
     </parent>
 
     <dependencies>
-    
-        <!-- ATTN: this moves jersey-server from 1.7 to 1.12 -->
-        <dependency>
-            <groupId>com.sun.jersey</groupId>
-            <artifactId>jersey-servlet</artifactId>
-        </dependency>
         <dependency>
             <groupId>com.google.guava</groupId>
             <artifactId>guava</artifactId>
         </dependency>
-        <dependency>
-            <groupId>com.google.code.findbugs</groupId>
-            <artifactId>jsr305</artifactId>
-        </dependency>
-        
+
         <dependency>
             <groupId>org.apache.brooklyn</groupId>
             <artifactId>brooklyn-utils-common</artifactId>
@@ -58,6 +48,11 @@
         </dependency>
 
         <dependency>
+            <groupId>javax.ws.rs</groupId>
+            <artifactId>javax.ws.rs-api</artifactId>
+        </dependency>
+
+        <dependency>
             <groupId>org.apache.brooklyn</groupId>
             <artifactId>brooklyn-test-support</artifactId>
             <version>${project.version}</version>
@@ -86,6 +81,10 @@
                     <groupId>com.sun.jersey</groupId>
                     <artifactId>jersey-client</artifactId>
                 </exclusion>
+                <exclusion>
+                    <groupId>javax.ws.rs</groupId>
+                    <artifactId>jsr311-api</artifactId>
+                </exclusion>
             </exclusions>
         </dependency>
         <dependency>
@@ -151,10 +150,15 @@
                             io.swagger.jaxrs.*,
                             !*
                         </Export-Package>
+                        <Import-Package>
+                            javax.ws.rs;version="[1.1,2.0]",
+                            javax.ws.rs.core;version="[1.1,2.0]",
+                            javax.ws.rs.ext;version="[1.1,2.0]",
+                            *
+                        </Import-Package>
                     </instructions>
                 </configuration>
             </plugin>
         </plugins>
     </build>
-    
 </project>

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/utils/rest-swagger/src/main/java/org/apache/brooklyn/rest/apidoc/ApiListingResource.java
----------------------------------------------------------------------
diff --git a/utils/rest-swagger/src/main/java/org/apache/brooklyn/rest/apidoc/ApiListingResource.java b/utils/rest-swagger/src/main/java/org/apache/brooklyn/rest/apidoc/ApiListingResource.java
index 84c52f8..2d08329 100644
--- a/utils/rest-swagger/src/main/java/org/apache/brooklyn/rest/apidoc/ApiListingResource.java
+++ b/utils/rest-swagger/src/main/java/org/apache/brooklyn/rest/apidoc/ApiListingResource.java
@@ -15,246 +15,6 @@
  */
 package org.apache.brooklyn.rest.apidoc;
 
-import com.sun.jersey.spi.container.servlet.WebConfig;
-import io.swagger.annotations.ApiOperation;
-import io.swagger.config.FilterFactory;
-import io.swagger.config.Scanner;
-import io.swagger.config.ScannerFactory;
-import io.swagger.config.SwaggerConfig;
-import io.swagger.core.filter.SpecFilter;
-import io.swagger.core.filter.SwaggerSpecFilter;
-import io.swagger.jaxrs.Reader;
-import io.swagger.jaxrs.config.JaxrsScanner;
-import io.swagger.jaxrs.config.ReaderConfigUtils;
-import io.swagger.jaxrs.listing.SwaggerSerializers;
-import io.swagger.models.Swagger;
-import io.swagger.util.Yaml;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import javax.servlet.ServletConfig;
-import javax.servlet.ServletContext;
-import javax.ws.rs.GET;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
-import javax.ws.rs.core.Application;
-import javax.ws.rs.core.Context;
-import javax.ws.rs.core.Cookie;
-import javax.ws.rs.core.HttpHeaders;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.MultivaluedMap;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.UriInfo;
-
-import org.apache.brooklyn.util.text.Strings;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * ApiListingResource usable within a jersey servlet filter.
- *
- * Taken from io.swagger:swagger-jaxrs, class
- * io.swagger.jaxrs.listing.ApiListingResource, which can only be used within a
- * servlet context. We are here using a filter, but jersey has a WebConfig class
- * that can substitute ServletConfig and FilterConfig.
- *
- * @todo Remove when the rest-server is no longer running within a filter (e.g.
- * as a standalone OSGi http service)
- *
- * @author Ciprian Ciubotariu <ch...@gmx.net>
- */
-public class ApiListingResource {
-
-    static Logger LOGGER = LoggerFactory.getLogger(ApiListingResource.class);
-
-    @Context
-    ServletContext context;
-
-    boolean initialized = false;
-
-    private static class ServletConfigAdapter implements ServletConfig {
-
-        private final WebConfig webConfig;
-
-        private ServletConfigAdapter(WebConfig webConfig) {
-            this.webConfig = webConfig;
-        }
-
-        @Override
-        public String getServletName() {
-            return webConfig.getName();
-        }
-
-        @Override
-        public ServletContext getServletContext() {
-            return webConfig.getServletContext();
-        }
-
-        @Override
-        public String getInitParameter(String name) {
-            return webConfig.getInitParameter(name);
-        }
-
-        @Override
-        public Enumeration<String> getInitParameterNames() {
-            return webConfig.getInitParameterNames();
-        }
-
-    }
-
-    protected synchronized Swagger scan(Application app, WebConfig sc) {
-        Swagger swagger = null;
-        Scanner scanner = ScannerFactory.getScanner();
-        LOGGER.debug("using scanner " + scanner);
-
-        if (scanner != null) {
-            SwaggerSerializers.setPrettyPrint(scanner.getPrettyPrint());
-            swagger = (Swagger) context.getAttribute("swagger");
-
-            Set<Class<?>> classes;
-            if (scanner instanceof JaxrsScanner) {
-                JaxrsScanner jaxrsScanner = (JaxrsScanner) scanner;
-                classes = jaxrsScanner.classesFromContext(app, new ServletConfigAdapter(sc));
-            } else {
-                classes = scanner.classes();
-            }
-            if (classes != null) {
-                Reader reader = new Reader(swagger, ReaderConfigUtils.getReaderConfig(context));
-                swagger = reader.read(classes);
-                if (scanner instanceof SwaggerConfig) {
-                    swagger = ((SwaggerConfig) scanner).configure(swagger);
-                } else {
-                    SwaggerConfig configurator = (SwaggerConfig) context.getAttribute("reader");
-                    if (configurator != null) {
-                        LOGGER.debug("configuring swagger with " + configurator);
-                        configurator.configure(swagger);
-                    } else {
-                        LOGGER.debug("no configurator");
-                    }
-                }
-                context.setAttribute("swagger", swagger);
-            }
-        }
-        initialized = true;
-        return swagger;
-    }
-
-    private Swagger process(
-            Application app,
-            WebConfig sc,
-            HttpHeaders headers,
-            UriInfo uriInfo) {
-        Swagger swagger = (Swagger) context.getAttribute("swagger");
-        if (!initialized) {
-            swagger = scan(app, sc);
-        }
-        if (swagger != null) {
-            SwaggerSpecFilter filterImpl = FilterFactory.getFilter();
-            if (filterImpl != null) {
-                SpecFilter f = new SpecFilter();
-                swagger = f.filter(swagger, filterImpl, getQueryParams(uriInfo.getQueryParameters()), getCookies(headers),
-                        getHeaders(headers));
-            }
-        }
-        return swagger;
-    }
-
-    @GET
-    @Produces({MediaType.APPLICATION_JSON, "application/yaml"})
-    @ApiOperation(value = "The swagger definition in either JSON or YAML", hidden = true)
-    @Path("/swagger.{type:json|yaml}")
-    public Response getListing(
-            @Context Application app,
-            @Context WebConfig sc,
-            @Context HttpHeaders headers,
-            @Context UriInfo uriInfo,
-            @PathParam("type") String type) {
-        if (Strings.isNonBlank(type) && type.trim().equalsIgnoreCase("yaml")) {
-            return getListingYaml(app, sc, headers, uriInfo);
-        } else {
-            return getListingJson(app, sc, headers, uriInfo);
-        }
-    }
-
-    @GET
-    @Produces({MediaType.APPLICATION_JSON})
-    @Path("/swagger")
-    @ApiOperation(value = "The swagger definition in JSON", hidden = true)
-    public Response getListingJson(
-            @Context Application app,
-            @Context WebConfig sc,
-            @Context HttpHeaders headers,
-            @Context UriInfo uriInfo) {
-        Swagger swagger = process(app, sc, headers, uriInfo);
-
-        if (swagger != null) {
-            return Response.ok().entity(swagger).build();
-        } else {
-            return Response.status(404).build();
-        }
-    }
-
-    @GET
-    @Produces("application/yaml")
-    @Path("/swagger")
-    @ApiOperation(value = "The swagger definition in YAML", hidden = true)
-    public Response getListingYaml(
-            @Context Application app,
-            @Context WebConfig sc,
-            @Context HttpHeaders headers,
-            @Context UriInfo uriInfo) {
-        Swagger swagger = process(app, sc, headers, uriInfo);
-        try {
-            if (swagger != null) {
-                String yaml = Yaml.mapper().writeValueAsString(swagger);
-                StringBuilder b = new StringBuilder();
-                String[] parts = yaml.split("\n");
-                for (String part : parts) {
-                    b.append(part);
-                    b.append("\n");
-                }
-                return Response.ok().entity(b.toString()).type("application/yaml").build();
-            }
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
-        return Response.status(404).build();
-    }
-
-    protected Map<String, List<String>> getQueryParams(MultivaluedMap<String, String> params) {
-        Map<String, List<String>> output = new HashMap<>();
-        if (params != null) {
-            for (String key : params.keySet()) {
-                List<String> values = params.get(key);
-                output.put(key, values);
-            }
-        }
-        return output;
-    }
-
-    protected Map<String, String> getCookies(HttpHeaders headers) {
-        Map<String, String> output = new HashMap<>();
-        if (headers != null) {
-            for (String key : headers.getCookies().keySet()) {
-                Cookie cookie = headers.getCookies().get(key);
-                output.put(key, cookie.getValue());
-            }
-        }
-        return output;
-    }
-
-    protected Map<String, List<String>> getHeaders(HttpHeaders headers) {
-        Map<String, List<String>> output = new HashMap<>();
-        if (headers != null) {
-            for (String key : headers.getRequestHeaders().keySet()) {
-                List<String> values = headers.getRequestHeaders().get(key);
-                output.put(key, values);
-            }
-        }
-        return output;
-    }
+public class ApiListingResource extends io.swagger.jaxrs.listing.ApiListingResource {
 
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/utils/rest-swagger/src/main/java/org/apache/brooklyn/rest/apidoc/RestApiResourceScanner.java
----------------------------------------------------------------------
diff --git a/utils/rest-swagger/src/main/java/org/apache/brooklyn/rest/apidoc/RestApiResourceScanner.java b/utils/rest-swagger/src/main/java/org/apache/brooklyn/rest/apidoc/RestApiResourceScanner.java
index 96bd821..1bb86b9 100644
--- a/utils/rest-swagger/src/main/java/org/apache/brooklyn/rest/apidoc/RestApiResourceScanner.java
+++ b/utils/rest-swagger/src/main/java/org/apache/brooklyn/rest/apidoc/RestApiResourceScanner.java
@@ -16,16 +16,18 @@
 package org.apache.brooklyn.rest.apidoc;
 
 
-import io.swagger.annotations.Api;
-import io.swagger.jaxrs.config.AbstractScanner;
-import io.swagger.jaxrs.config.JaxrsScanner;
-
 import java.util.HashSet;
 import java.util.Set;
 
 import javax.servlet.ServletConfig;
 import javax.ws.rs.core.Application;
 
+import io.swagger.annotations.Api;
+import io.swagger.config.SwaggerConfig;
+import io.swagger.jaxrs.config.AbstractScanner;
+import io.swagger.jaxrs.config.JaxrsScanner;
+import io.swagger.models.Swagger;
+
 
 /**
  * Much like DefaultJaxrsScanner, but looks at annotations of ancestors as well.
@@ -34,7 +36,7 @@ import javax.ws.rs.core.Application;
  * that interface will be added as well.
  *
  */
-public class RestApiResourceScanner extends AbstractScanner implements JaxrsScanner {
+public class RestApiResourceScanner extends AbstractScanner implements JaxrsScanner, SwaggerConfig {
 
     private Set<Class<?>> apiClasses = null;
 
@@ -78,4 +80,15 @@ public class RestApiResourceScanner extends AbstractScanner implements JaxrsScan
         return new HashSet<>();
     }
 
+    @Override
+    public Swagger configure(Swagger swagger) {
+        swagger.setBasePath("/v1");
+        return swagger;
+    }
+
+    @Override
+    public String getFilterClass() {
+        return null;
+    }
+
 }


[32/34] brooklyn-server git commit: clarify when /v1/ is or is not required, fixing many integration tests

Posted by he...@apache.org.
clarify when /v1/ is or is not required, fixing many integration tests

and remove warnings


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

Branch: refs/heads/master
Commit: 96d804a75a660a618e0083fe1863006056406703
Parents: 3ab0040
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Thu Feb 18 13:40:15 2016 +0000
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Thu Feb 18 13:40:15 2016 +0000

----------------------------------------------------------------------
 .../rest/transform/CatalogTransformer.java      |  5 ++
 .../util/json/BrooklynJacksonJsonProvider.java  |  2 +-
 .../json/ConfigurableSerializerProvider.java    |  1 +
 .../rest/resources/ApplicationResourceTest.java |  4 +-
 .../SensorResourceIntegrationTest.java          |  6 +--
 .../rest/resources/ServerShutdownTest.java      |  2 +-
 .../brooklynnode/DeployBlueprintTest.java       |  8 +--
 .../rest/testing/mocks/RestMockAppBuilder.java  |  1 +
 .../testing/mocks/RestMockSimpleEntity.java     |  8 +--
 .../BrooklynPropertiesSecurityFilterTest.java   | 12 ++---
 .../rest/BrooklynRestApiLauncherTest.java       |  8 +--
 .../BrooklynRestApiLauncherTestFixture.java     | 19 ++++++--
 .../brooklyn/rest/HaMasterCheckFilterTest.java  | 51 ++++++++++----------
 .../AbstractRestApiEntitlementsTest.java        |  8 +--
 .../ServerResourceIntegrationTest.java          |  8 +--
 .../java/org/apache/brooklyn/test/Asserts.java  |  4 +-
 16 files changed, 84 insertions(+), 63 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/96d804a7/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
index e584a9a..7e0d8bc 100644
--- 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
@@ -56,14 +56,19 @@ 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;
 
+@SuppressWarnings("deprecation")
 public class CatalogTransformer {
 
     private static final org.slf4j.Logger log = LoggerFactory.getLogger(CatalogTransformer.class);
     
+    @SuppressWarnings({ "unchecked", "rawtypes" })
     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());

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/96d804a7/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonJsonProvider.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonJsonProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonJsonProvider.java
index 9c6704d..7083eb6 100644
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonJsonProvider.java
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonJsonProvider.java
@@ -158,7 +158,7 @@ public class BrooklynJacksonJsonProvider extends JacksonJsonProvider implements
         mapper.setSerializerProvider(sp);
         mapper.setVisibilityChecker(new PossiblyStrictPreferringFieldsVisibilityChecker());
 
-        SimpleModule mapperModule = new SimpleModule("Brooklyn", new Version(0, 0, 0, "ignored"));
+        SimpleModule mapperModule = new SimpleModule("Brooklyn", new Version(0, 0, 0, "ignored", null, null));
 
         new BidiSerialization.ManagementContextSerialization(mgmt).install(mapperModule);
         new BidiSerialization.EntitySerialization(mgmt).install(mapperModule);

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/96d804a7/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/ConfigurableSerializerProvider.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/ConfigurableSerializerProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/ConfigurableSerializerProvider.java
index 1b87e76..b77202f 100644
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/ConfigurableSerializerProvider.java
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/ConfigurableSerializerProvider.java
@@ -33,6 +33,7 @@ import com.fasterxml.jackson.databind.ser.SerializerFactory;
 /** allows the serializer-of-last-resort to be customized, ie used for unknown-types */
 final class ConfigurableSerializerProvider extends DefaultSerializerProvider {
 
+    private static final long serialVersionUID = 6094990395562170217L;
     protected JsonSerializer<Object> unknownTypeSerializer;
 
     public ConfigurableSerializerProvider() {}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/96d804a7/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java
index b9a6e9b..a6abe0f 100644
--- a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java
@@ -64,7 +64,6 @@ import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
 import org.apache.brooklyn.rest.testing.mocks.CapitalizePolicy;
 import org.apache.brooklyn.rest.testing.mocks.NameMatcherGroup;
 import org.apache.brooklyn.rest.testing.mocks.RestMockApp;
-import org.apache.brooklyn.rest.testing.mocks.RestMockAppBuilder;
 import org.apache.brooklyn.rest.testing.mocks.RestMockSimpleEntity;
 import org.apache.brooklyn.test.Asserts;
 import org.apache.brooklyn.util.collections.CollectionFunctionals;
@@ -209,10 +208,11 @@ public class ApplicationResourceTest extends BrooklynRestResourceTest {
         assertEquals(client().path(appUri).get(ApplicationSummary.class).getSpec().getName(), "simple-app-interface");
     }
 
+    @SuppressWarnings("deprecation")
     @Test(dependsOnMethods = { "testDeployApplication", "testLocatedLocation" })
     public void testDeployApplicationFromBuilder() throws Exception {
         ApplicationSpec spec = ApplicationSpec.builder()
-                .type(RestMockAppBuilder.class.getCanonicalName())
+                .type(org.apache.brooklyn.rest.testing.mocks.RestMockAppBuilder.class.getCanonicalName())
                 .name("simple-app-builder")
                 .locations(ImmutableSet.of("localhost"))
                 .build();

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/96d804a7/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/SensorResourceIntegrationTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/SensorResourceIntegrationTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/SensorResourceIntegrationTest.java
index 9b7214d..2a6b564 100644
--- a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/SensorResourceIntegrationTest.java
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/SensorResourceIntegrationTest.java
@@ -26,9 +26,10 @@ import org.apache.brooklyn.api.mgmt.ManagementContext;
 import org.apache.brooklyn.core.entity.EntityInternal;
 import org.apache.brooklyn.core.entity.EntityPredicates;
 import org.apache.brooklyn.entity.stock.BasicApplication;
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
 import org.apache.brooklyn.rest.testing.mocks.RestMockSimpleEntity;
-import org.apache.brooklyn.test.HttpTestUtils;
 import org.apache.brooklyn.util.collections.MutableList;
+import org.apache.brooklyn.util.http.HttpAsserts;
 import org.apache.brooklyn.util.http.HttpTool;
 import org.apache.brooklyn.util.http.HttpToolResponse;
 import org.apache.brooklyn.util.net.Urls;
@@ -39,7 +40,6 @@ import org.testng.annotations.Test;
 
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
-import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
 
 public class SensorResourceIntegrationTest extends BrooklynRestResourceTest {
 
@@ -67,7 +67,7 @@ public class SensorResourceIntegrationTest extends BrooklynRestResourceTest {
         // Uses explicit "application/json" because failed on jenkins as though "text/plain" was the default on Ubuntu jenkins!
         HttpClient client = HttpTool.httpClientBuilder().uri(baseUri).build();
         HttpToolResponse response = HttpTool.httpGet(client, url, ImmutableMap.<String, String>of("Accept", "application/json"));
-        HttpTestUtils.assertHealthyStatusCode(response.getResponseCode());
+        HttpAsserts.assertHealthyStatusCode(response.getResponseCode());
         Assert.assertEquals(response.getContentAsString(), "\"12345 frogs\"");
     }
 

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/96d804a7/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ServerShutdownTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ServerShutdownTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ServerShutdownTest.java
index 9d55482..18c3deb 100644
--- a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ServerShutdownTest.java
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ServerShutdownTest.java
@@ -65,7 +65,7 @@ public class ServerShutdownTest extends BrooklynRestResourceTest {
         assertTrue(getManagementContext().isRunning());
         assertFalse(shutdownListener.isRequested());
 
-        MultivaluedMap<String, String> formData = new MultivaluedHashMap();
+        MultivaluedMap<String, String> formData = new MultivaluedHashMap<String,String>();
         formData.add("requestTimeout", "0");
         formData.add("delayForHttpReturn", "0");
         client().path("/server/shutdown").type(MediaType.APPLICATION_FORM_URLENCODED).post(formData);

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/96d804a7/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/test/entity/brooklynnode/DeployBlueprintTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/test/entity/brooklynnode/DeployBlueprintTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/test/entity/brooklynnode/DeployBlueprintTest.java
index 0113d39..87d4604 100644
--- a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/test/entity/brooklynnode/DeployBlueprintTest.java
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/test/entity/brooklynnode/DeployBlueprintTest.java
@@ -30,8 +30,9 @@ import org.apache.brooklyn.entity.brooklynnode.BrooklynNode;
 import org.apache.brooklyn.entity.brooklynnode.BrooklynNode.DeployBlueprintEffector;
 import org.apache.brooklyn.entity.stock.BasicApplication;
 import org.apache.brooklyn.feed.http.JsonFunctions;
-import org.apache.brooklyn.test.HttpTestUtils;
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
 import org.apache.brooklyn.util.guava.Functionals;
+import org.apache.brooklyn.util.http.HttpTool;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.testng.annotations.Test;
@@ -39,7 +40,6 @@ import org.testng.annotations.Test;
 import com.google.common.base.Function;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
-import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
 
 public class DeployBlueprintTest extends BrooklynRestResourceTest {
 
@@ -77,11 +77,11 @@ public class DeployBlueprintTest extends BrooklynRestResourceTest {
 
         log.info("got: "+id);
 
-        String apps = HttpTestUtils.getContent(getEndpointAddress() + "/applications");
+        String apps = HttpTool.getContent(getEndpointAddress() + "/applications");
         List<String> appType = parseJsonList(apps, ImmutableList.of("spec", "type"), String.class);
         assertEquals(appType, ImmutableList.of(BasicApplication.class.getName()));
 
-        String status = HttpTestUtils.getContent(getEndpointAddress()+"/applications/"+id+"/entities/"+id+"/sensors/service.status");
+        String status = HttpTool.getContent(getEndpointAddress()+"/applications/"+id+"/entities/"+id+"/sensors/service.status");
         log.info("STATUS: "+status);
     }
 

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/96d804a7/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockAppBuilder.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockAppBuilder.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockAppBuilder.java
index 1ca10bd..e4ae595 100644
--- a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockAppBuilder.java
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockAppBuilder.java
@@ -24,6 +24,7 @@ import org.apache.brooklyn.core.entity.StartableApplication;
 import org.apache.brooklyn.core.entity.factory.ApplicationBuilder;
 import org.apache.brooklyn.util.javalang.Reflections;
 
+/** @deprecated since 0.9.0 don't use {@link ApplicationBuilder} */
 public class RestMockAppBuilder extends ApplicationBuilder {
 
     public RestMockAppBuilder() {

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/96d804a7/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockSimpleEntity.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockSimpleEntity.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockSimpleEntity.java
index 58d24aa..4a49e45 100644
--- a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockSimpleEntity.java
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockSimpleEntity.java
@@ -21,7 +21,6 @@ package org.apache.brooklyn.rest.testing.mocks;
 import java.util.Map;
 
 import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.entity.EntityLocal;
 import org.apache.brooklyn.api.sensor.AttributeSensor;
 import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.core.annotation.Effector;
@@ -31,10 +30,10 @@ import org.apache.brooklyn.core.effector.MethodEffector;
 import org.apache.brooklyn.core.sensor.BasicAttributeSensor;
 import org.apache.brooklyn.entity.software.base.AbstractSoftwareProcessSshDriver;
 import org.apache.brooklyn.entity.software.base.SoftwareProcessImpl;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import org.apache.brooklyn.location.ssh.SshMachineLocation;
 import org.apache.brooklyn.util.core.flags.SetFromFlag;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class RestMockSimpleEntity extends SoftwareProcessImpl {
 
@@ -87,7 +86,8 @@ public class RestMockSimpleEntity extends SoftwareProcessImpl {
     }
     
     public static class MockSshDriver extends AbstractSoftwareProcessSshDriver {
-        public MockSshDriver(EntityLocal entity, SshMachineLocation machine) {
+        @SuppressWarnings("deprecation")
+        public MockSshDriver(org.apache.brooklyn.api.entity.EntityLocal entity, SshMachineLocation machine) {
             super(entity, machine);
         }
         public boolean isRunning() { return true; }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/96d804a7/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynPropertiesSecurityFilterTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynPropertiesSecurityFilterTest.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynPropertiesSecurityFilterTest.java
index 6fe610b..f89acf0 100644
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynPropertiesSecurityFilterTest.java
+++ b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynPropertiesSecurityFilterTest.java
@@ -105,12 +105,12 @@ public class BrooklynPropertiesSecurityFilterTest extends BrooklynRestApiLaunche
             String appId = startAppAtNode(server);
             String entityId = getTestEntityInApp(server, appId);
             HttpClient client = HttpTool.httpClientBuilder()
-                    .uri(getBaseUri(server))
+                    .uri(getBaseUriRest())
                     .build();
             List<? extends NameValuePair> nvps = Lists.newArrayList(
                     new BasicNameValuePair("arg", "bar"));
             String effector = String.format("/applications/%s/entities/%s/effectors/identityEffector", appId, entityId);
-            HttpToolResponse response = HttpTool.httpPost(client, URI.create(getBaseUri() + effector),
+            HttpToolResponse response = HttpTool.httpPost(client, URI.create(getBaseUriRest() + effector),
                     ImmutableMap.of(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED.getMimeType()),
                     URLEncodedUtils.format(nvps, Charsets.UTF_8).getBytes());
 
@@ -127,9 +127,9 @@ public class BrooklynPropertiesSecurityFilterTest extends BrooklynRestApiLaunche
                 "services:\n" +
                 "- type: org.apache.brooklyn.test.entity.TestEntity";
         HttpClient client = HttpTool.httpClientBuilder()
-                .uri(getBaseUri(server))
+                .uri(getBaseUriRest(server))
                 .build();
-        HttpToolResponse response = HttpTool.httpPost(client, URI.create(getBaseUri() + "/applications"),
+        HttpToolResponse response = HttpTool.httpPost(client, URI.create(getBaseUriRest() + "applications"),
                 ImmutableMap.of(HttpHeaders.CONTENT_TYPE, "application/x-yaml"),
                 blueprint.getBytes());
         assertTrue(HttpTool.isStatusCodeHealthy(response.getResponseCode()), "error creating app. response code=" + response.getResponseCode());
@@ -141,10 +141,10 @@ public class BrooklynPropertiesSecurityFilterTest extends BrooklynRestApiLaunche
     @SuppressWarnings("rawtypes")
     private String getTestEntityInApp(Server server, String appId) throws Exception {
         HttpClient client = HttpTool.httpClientBuilder()
-                .uri(getBaseUri(server))
+                .uri(getBaseUriRest(server))
                 .build();
         List entities = new ObjectMapper().readValue(
-                HttpTool.httpGet(client, URI.create(getBaseUri() + "/applications/" + appId + "/entities"), MutableMap.<String, String>of()).getContent(), List.class);
+                HttpTool.httpGet(client, URI.create(getBaseUriRest(server) + "applications/" + appId + "/entities"), MutableMap.<String, String>of()).getContent(), List.class);
         LOG.info((String) ((Map) entities.get(0)).get("id"));
         return (String) ((Map) entities.get(0)).get("id");
     }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/96d804a7/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTest.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTest.java
index e73fab4..d64b9a6 100644
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTest.java
+++ b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTest.java
@@ -29,7 +29,6 @@ import org.apache.brooklyn.test.Asserts;
 import org.apache.brooklyn.util.http.HttpAsserts;
 import org.apache.brooklyn.util.http.HttpTool;
 import org.apache.http.HttpStatus;
-import org.eclipse.jetty.server.NetworkConnector;
 import org.eclipse.jetty.server.Server;
 import org.testng.annotations.Test;
 
@@ -41,6 +40,7 @@ public class BrooklynRestApiLauncherTest extends BrooklynRestApiLauncherTestFixt
     }
 
     @Test
+    // doesn't work from IDE
     public void testWebAppStart() throws Exception {
         checkRestCatalogEntities(useServerForTest(baseLauncher().mode(WEB_XML).start()));
     }
@@ -52,11 +52,11 @@ public class BrooklynRestApiLauncherTest extends BrooklynRestApiLauncherTestFixt
     }
     
     private static void checkRestCatalogEntities(Server server) throws Exception {
-        final String rootUrl = "http://localhost:"+((NetworkConnector)server.getConnectors()[0]).getLocalPort();
+        final String rootUrl = getBaseUriRest(server);
         int code = Asserts.succeedsEventually(new Callable<Integer>() {
             @Override
             public Integer call() throws Exception {
-                int code = HttpTool.getHttpStatusCode(rootUrl+"/v1/catalog/entities");
+                int code = HttpTool.getHttpStatusCode(rootUrl+"catalog/entities");
                 if (code == HttpStatus.SC_FORBIDDEN) {
                     throw new RuntimeException("Retry request");
                 } else {
@@ -65,7 +65,7 @@ public class BrooklynRestApiLauncherTest extends BrooklynRestApiLauncherTestFixt
             }
         });
         HttpAsserts.assertHealthyStatusCode(code);
-        HttpAsserts.assertContentContainsText(rootUrl+"/v1/catalog/entities", BrooklynNode.class.getSimpleName());
+        HttpAsserts.assertContentContainsText(rootUrl+"catalog/entities", BrooklynNode.class.getSimpleName());
     }
     
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/96d804a7/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTestFixture.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTestFixture.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTestFixture.java
index 4ca48df..1893d4a 100644
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTestFixture.java
+++ b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTestFixture.java
@@ -69,12 +69,25 @@ public abstract class BrooklynRestApiLauncherTestFixture {
         return server;
     }
     
-    protected String getBaseUri() {
-        return getBaseUri(server);
-    }
+    /** @deprecated since 0.9.0 use {@link #getBaseUriHostAndPost(Server)} or {@link #getBaseUriRest(Server)} */
     public static String getBaseUri(Server server) {
+        return getBaseUriHostAndPost(server);
+    }
+    
+    protected String getBaseUriHostAndPost() {
+        return getBaseUriHostAndPost(server);
+    }
+    /** returns base of server, without trailing slash - e.g. <code>http://localhost:8081</code> */
+    public static String getBaseUriHostAndPost(Server server) {
         return "http://localhost:"+((NetworkConnector)server.getConnectors()[0]).getLocalPort();
     }
+    protected String getBaseUriRest() {
+        return getBaseUriRest(server);
+    }
+    /** returns REST endpoint, with trailing slash */
+    public static String getBaseUriRest(Server server) {
+        return getBaseUriHostAndPost(server)+"/v1/";
+    }
     
     public static void forceUseOfDefaultCatalogWithJavaClassPath(Server server) {
         ManagementContext mgmt = getManagementContextFromJettyServerAttributes(server);

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/96d804a7/rest/rest-server/src/test/java/org/apache/brooklyn/rest/HaMasterCheckFilterTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/HaMasterCheckFilterTest.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/HaMasterCheckFilterTest.java
index 90e7957..db58f99 100644
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/HaMasterCheckFilterTest.java
+++ b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/HaMasterCheckFilterTest.java
@@ -35,17 +35,16 @@ import org.apache.brooklyn.camp.brooklyn.BrooklynCampPlatformLauncherNoServer;
 import org.apache.brooklyn.core.entity.Entities;
 import org.apache.brooklyn.core.mgmt.rebind.RebindTestUtils;
 import org.apache.brooklyn.entity.stock.BasicApplication;
-import org.apache.http.HttpStatus;
-import org.apache.http.client.HttpClient;
-import org.eclipse.jetty.server.Server;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.Test;
 import org.apache.brooklyn.rest.security.provider.AnyoneSecurityProvider;
 import org.apache.brooklyn.test.Asserts;
 import org.apache.brooklyn.util.http.HttpTool;
 import org.apache.brooklyn.util.http.HttpToolResponse;
 import org.apache.brooklyn.util.os.Os;
 import org.apache.brooklyn.util.time.Duration;
+import org.apache.http.client.HttpClient;
+import org.eclipse.jetty.server.Server;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.Test;
 
 import com.google.common.base.Predicates;
 import com.google.common.base.Supplier;
@@ -63,7 +62,6 @@ public class HaMasterCheckFilterTest extends BrooklynRestApiLauncherTestFixture
 
     @AfterMethod(alwaysRun=true)
     public void tearDown() throws Exception {
-System.err.println("TEAR DOWN");
         server.stop();
         Entities.destroyAll(writeMgmt);
         Entities.destroyAll(readMgmt);
@@ -74,23 +72,25 @@ System.err.println("TEAR DOWN");
     public void testEntitiesExistOnDisabledHA() throws Exception {
         initHaCluster(HighAvailabilityMode.DISABLED, HighAvailabilityMode.DISABLED);
         assertReadIsMaster();
-        assertEntityExists(new ReturnCodeCheck());
+        assertEntityExists(new ReturnCodeNotRetry());
     }
 
     @Test(groups = "Integration")
     public void testEntitiesExistOnMasterPromotion() throws Exception {
         initHaCluster(HighAvailabilityMode.AUTO, HighAvailabilityMode.AUTO);
+        assertEntityNotFound(new ReturnCodeNotRetry());
         stopWriteNode();
-        assertEntityExists(new ReturnCodeCheck());
+        assertEntityExists(new ReturnCodeNotRetryAndNodeIsMaster());
         assertReadIsMaster();
     }
 
     @Test(groups = "Integration")
     public void testEntitiesExistOnHotStandbyAndPromotion() throws Exception {
         initHaCluster(HighAvailabilityMode.AUTO, HighAvailabilityMode.HOT_STANDBY);
-        assertEntityExists(new ReturnCodeCheck());
+        assertEntityExists(new ReturnCodeNotRetry());
         stopWriteNode();
-        assertEntityExists(new ReturnCodeAndNodeState());
+        // once the node claims master we should get a 200
+        assertEntityExists(new ReturnCodeNotRetryAndNodeIsMaster());
         assertReadIsMaster();
     }
 
@@ -98,23 +98,23 @@ System.err.println("TEAR DOWN");
     public void testEntitiesExistOnHotBackup() throws Exception {
         initHaCluster(HighAvailabilityMode.AUTO, HighAvailabilityMode.HOT_BACKUP);
         Asserts.continually(
-                ImmutableMap.<String,Object>of(
-                        "timeout", Duration.THIRTY_SECONDS,
-                        "period", Duration.ZERO),
                 new ReturnCodeSupplier(),
-                Predicates.or(Predicates.equalTo(200), Predicates.equalTo(403)));
+                Predicates.or(Predicates.equalTo(200), Predicates.equalTo(403)),
+                Duration.THIRTY_SECONDS, 
+                null,
+                null);
     }
 
     private HttpClient getClient(Server server) {
         HttpClient client = HttpTool.httpClientBuilder()
-                .uri(getBaseUri(server))
+                .uri(getBaseUriRest(server))
                 .build();
         return client;
     }
 
     private int getAppResponseCode() {
         HttpToolResponse response = HttpTool.httpGet(
-                client, URI.create(getBaseUri(server) + "/applications/" + appId),
+                client, URI.create(getBaseUriRest() + "applications/" + appId),
                 ImmutableMap.<String,String>of());
         return response.getResponseCode();
     }
@@ -173,6 +173,10 @@ System.err.println("TEAR DOWN");
     private void assertEntityExists(Callable<Integer> c) {
         assertEquals((int)Asserts.succeedsEventually(c), 200);
     }
+    
+    private void assertEntityNotFound(Callable<Integer> c) {
+        assertEquals((int)Asserts.succeedsEventually(c), 404);
+    }
 
     private void assertReadIsMaster() {
         assertEquals(readMgmt.getHighAvailabilityManager().getNodeState(), ManagementNodeState.MASTER);
@@ -182,7 +186,7 @@ System.err.println("TEAR DOWN");
         writeMgmt.getHighAvailabilityManager().stop();
     }
 
-    private class ReturnCodeCheck implements Callable<Integer> {
+    private class ReturnCodeNotRetry implements Callable<Integer> {
         @Override
         public Integer call() {
             int retCode = getAppResponseCode();
@@ -194,17 +198,14 @@ System.err.println("TEAR DOWN");
         }
     }
 
-    private class ReturnCodeAndNodeState extends ReturnCodeCheck {
+    private class ReturnCodeNotRetryAndNodeIsMaster extends ReturnCodeNotRetry {
         @Override
         public Integer call() {
-            Integer ret = super.call();
-            if (ret == HttpStatus.SC_OK) {
-                ManagementNodeState state = readMgmt.getHighAvailabilityManager().getNodeState();
-                if (state != ManagementNodeState.MASTER) {
-                    throw new RuntimeException("Not master yet " + state);
-                }
+            ManagementNodeState state = readMgmt.getHighAvailabilityManager().getNodeState();
+            if (state != ManagementNodeState.MASTER) {
+                throw new RuntimeException("Not master yet " + state);
             }
-            return ret;
+            return super.call();
         }
     }
 

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/96d804a7/rest/rest-server/src/test/java/org/apache/brooklyn/rest/entitlement/AbstractRestApiEntitlementsTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/entitlement/AbstractRestApiEntitlementsTest.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/entitlement/AbstractRestApiEntitlementsTest.java
index 73449ea..7331b39 100644
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/entitlement/AbstractRestApiEntitlementsTest.java
+++ b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/entitlement/AbstractRestApiEntitlementsTest.java
@@ -84,13 +84,13 @@ public abstract class AbstractRestApiEntitlementsTest extends BrooklynRestApiLau
     
     protected HttpClient newClient(String user) throws Exception {
         return httpClientBuilder()
-                .uri(getBaseUri())
+                .uri(getBaseUriRest())
                 .credentials(new UsernamePasswordCredentials(user, "ignoredPassword"))
                 .build();
     }
 
     protected String httpGet(String user, String path) throws Exception {
-        HttpToolResponse response = HttpTool.httpGet(newClient(user), URI.create(getBaseUri()).resolve(path), ImmutableMap.<String, String>of());
+        HttpToolResponse response = HttpTool.httpGet(newClient(user), URI.create(getBaseUriRest()).resolve(path), ImmutableMap.<String, String>of());
         assertTrue(HttpAsserts.isHealthyStatusCode(response.getResponseCode()), "code="+response.getResponseCode()+"; reason="+response.getReasonPhrase());
         return response.getContentAsString();
     }
@@ -100,12 +100,12 @@ public abstract class AbstractRestApiEntitlementsTest extends BrooklynRestApiLau
     }
 
     protected void assertForbidden(String user, String path) throws Exception {
-        HttpToolResponse response = HttpTool.httpGet(newClient(user), URI.create(getBaseUri()).resolve(path), ImmutableMap.<String, String>of());
+        HttpToolResponse response = HttpTool.httpGet(newClient(user), URI.create(getBaseUriRest()).resolve(path), ImmutableMap.<String, String>of());
         assertEquals(response.getResponseCode(), 403, "code="+response.getResponseCode()+"; reason="+response.getReasonPhrase()+"; content="+response.getContentAsString());
     }
 
     protected void assert404(String user, String path) throws Exception {
-        HttpToolResponse response = HttpTool.httpGet(newClient(user), URI.create(getBaseUri()).resolve(path), ImmutableMap.<String, String>of());
+        HttpToolResponse response = HttpTool.httpGet(newClient(user), URI.create(getBaseUriRest()).resolve(path), ImmutableMap.<String, String>of());
         assertEquals(response.getResponseCode(), 404, "code="+response.getResponseCode()+"; reason="+response.getReasonPhrase()+"; content="+response.getContentAsString());
     }
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/96d804a7/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ServerResourceIntegrationTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ServerResourceIntegrationTest.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ServerResourceIntegrationTest.java
index 74fbe3c..604d1eb 100644
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ServerResourceIntegrationTest.java
+++ b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ServerResourceIntegrationTest.java
@@ -68,10 +68,10 @@ public class ServerResourceIntegrationTest extends BrooklynRestApiLauncherTestFi
                     .withoutJsgui()
                     .securityProvider(TestSecurityProvider.class)
                     .start());
-            String baseUri = getBaseUri(server);
+            String baseUri = getBaseUriRest(server);
     
             HttpToolResponse response;
-            final URI uri = URI.create(getBaseUri() + "/server/properties/reload");
+            final URI uri = URI.create(getBaseUriRest() + "server/properties/reload");
             final Map<String, String> args = Collections.emptyMap();
     
             // Unauthorised when no credentials, and when default credentials.
@@ -112,11 +112,11 @@ public class ServerResourceIntegrationTest extends BrooklynRestApiLauncherTestFi
 
     private String getServerUser(Server server) throws Exception {
         HttpClient client = httpClientBuilder()
-                .uri(getBaseUri(server))
+                .uri(getBaseUriRest(server))
                 .credentials(TestSecurityProvider.CREDENTIAL)
                 .build();
         
-        HttpToolResponse response = HttpTool.httpGet(client, URI.create(getBaseUri(server) + "/server/user"),
+        HttpToolResponse response = HttpTool.httpGet(client, URI.create(getBaseUriRest(server) + "server/user"),
                 ImmutableMap.<String, String>of());
         HttpAsserts.assertHealthyStatusCode(response.getResponseCode());
         return response.getContentAsString();

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/96d804a7/utils/common/src/main/java/org/apache/brooklyn/test/Asserts.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/test/Asserts.java b/utils/common/src/main/java/org/apache/brooklyn/test/Asserts.java
index fac3142..3ad5bda 100644
--- a/utils/common/src/main/java/org/apache/brooklyn/test/Asserts.java
+++ b/utils/common/src/main/java/org/apache/brooklyn/test/Asserts.java
@@ -798,12 +798,12 @@ public class Asserts {
         continually(ImmutableMap.<String,Object>of(), supplier, predicate);
     }
 
-    /** @deprecated since 0.9.0 use {@link #eventually(Supplier, Predicate, Duration, Duration, String)} */ @Deprecated
+    /** @deprecated since 0.9.0 use {@link #continually(Supplier, Predicate, Duration, Duration, String)} */ @Deprecated
     public static <T> void continually(Map<String,?> flags, Supplier<? extends T> supplier, Predicate<? super T> predicate) {
         continually(flags, supplier, predicate, null);
     }
 
-    /** @deprecated since 0.9.0 use {@link #eventually(Supplier, Predicate, Duration, Duration, String)} */ @Deprecated
+    /** @deprecated since 0.9.0 use {@link #continually(Supplier, Predicate, Duration, Duration, String)} */ @Deprecated
     public static <T> void continually(Map<String,?> flags, Supplier<? extends T> supplier, Predicate<T> predicate, String errMsg) {
         continually(supplier, predicate, toDuration(flags.get("timeout"), toDuration(flags.get("duration"), null)), 
             toDuration(flags.get("period"), null), null);


[12/34] brooklyn-server git commit: [BROOKLYN-183] REST API using CXF JAX-RS 2.0 implementation

Posted by he...@apache.org.
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/EntityResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/EntityResourceTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/EntityResourceTest.java
new file mode 100644
index 0000000..decbcef
--- /dev/null
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/EntityResourceTest.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.resources;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nullable;
+import javax.ws.rs.core.MediaType;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.entity.EntityInternal;
+import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.apache.brooklyn.entity.stock.BasicApplication;
+import org.apache.brooklyn.rest.domain.ApplicationSpec;
+import org.apache.brooklyn.rest.domain.EntitySpec;
+import org.apache.brooklyn.rest.domain.TaskSummary;
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+import org.apache.brooklyn.rest.testing.mocks.RestMockSimpleEntity;
+import org.apache.brooklyn.util.collections.MutableList;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.http.HttpAsserts;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import javax.ws.rs.core.Response;
+
+@Test(singleThreaded = true,
+        // by using a different suite name we disallow interleaving other tests between the methods of this test class, which wrecks the test fixtures
+        suiteName = "EntityResourceTest")
+public class EntityResourceTest extends BrooklynRestResourceTest {
+
+    private static final Logger log = LoggerFactory.getLogger(EntityResourceTest.class);
+    
+    private final ApplicationSpec simpleSpec = ApplicationSpec.builder()
+            .name("simple-app")
+            .entities(ImmutableSet.of(new EntitySpec("simple-ent", RestMockSimpleEntity.class.getName())))
+            .locations(ImmutableSet.of("localhost"))
+            .build();
+
+    private EntityInternal entity;
+
+    private static final String entityEndpoint = "/applications/simple-app/entities/simple-ent";
+
+    @BeforeClass(alwaysRun = true)
+    public void setUp() throws Exception {
+        // Deploy application
+        startServer();
+        Response deploy = clientDeploy(simpleSpec);
+        waitForApplicationToBeRunning(deploy.getLocation());
+
+        // Add tag
+        entity = (EntityInternal) Iterables.find(getManagementContext().getEntityManager().getEntities(), new Predicate<Entity>() {
+            @Override
+            public boolean apply(@Nullable Entity input) {
+                return "RestMockSimpleEntity".equals(input.getEntityType().getSimpleName());
+            }
+        });
+    }
+
+    @Test
+    public void testTagsSanity() throws Exception {
+        entity.tags().addTag("foo");
+        
+        Response response = client().path(entityEndpoint + "/tags")
+                .accept(MediaType.APPLICATION_JSON_TYPE)
+                .get();
+        String data = response.readEntity(String.class);
+        
+        try {
+            List<Object> tags = new ObjectMapper().readValue(data, new TypeReference<List<Object>>() {});
+            Assert.assertTrue(tags.contains("foo"));
+            Assert.assertFalse(tags.contains("bar"));
+        } catch (Exception e) {
+            Exceptions.propagateIfFatal(e);
+            throw new IllegalStateException("Error with deserialization of tags list: "+e+"\n"+data, e);
+        }
+    }
+    
+    @Test
+    public void testRename() throws Exception {
+        try {
+            Response response = client().path(entityEndpoint + "/name")
+                .query("name", "New Name")
+                .post(null);
+
+            HttpAsserts.assertHealthyStatusCode(response.getStatus());
+            Assert.assertTrue(entity.getDisplayName().equals("New Name"));
+        } finally {
+            // restore it for other tests!
+            entity.setDisplayName("simple-ent");
+        }
+    }
+    
+    @Test
+    public void testAddChild() throws Exception {
+        try {
+            // to test in GUI: 
+            // services: [ { type: org.apache.brooklyn.entity.stock.BasicEntity }]
+            Response response = client().path(entityEndpoint + "/children").query("timeout", "10s")
+                .post(javax.ws.rs.client.Entity.entity("services: [ { type: "+TestEntity.class.getName()+" }]", "application/yaml"));
+
+            HttpAsserts.assertHealthyStatusCode(response.getStatus());
+            Assert.assertEquals(entity.getChildren().size(), 1);
+            Entity child = Iterables.getOnlyElement(entity.getChildren());
+            Assert.assertTrue(Entities.isManaged(child));
+            
+            TaskSummary task = response.readEntity(TaskSummary.class);
+            Assert.assertEquals(task.getResult(), MutableList.of(child.getId()));
+            
+        } finally {
+            // restore it for other tests
+            Collection<Entity> children = entity.getChildren();
+            if (!children.isEmpty()) Entities.unmanage(Iterables.getOnlyElement(children));
+        }
+    }
+    
+    @Test
+    public void testTagsDoNotSerializeTooMuch() throws Exception {
+        entity.tags().addTag("foo");
+        entity.tags().addTag(entity.getParent());
+
+        Response response = client().path(entityEndpoint + "/tags")
+                .accept(MediaType.APPLICATION_JSON)
+                .get();
+        String raw = response.readEntity(String.class);
+        log.info("TAGS raw: "+raw);
+        HttpAsserts.assertHealthyStatusCode(response.getStatus());
+        
+        Assert.assertTrue(raw.contains(entity.getParent().getId()), "unexpected app tag, does not include ID: "+raw);
+        
+        Assert.assertTrue(raw.length() < 1000, "unexpected app tag, includes too much mgmt info (len "+raw.length()+"): "+raw);
+        
+        Assert.assertFalse(raw.contains(entity.getManagementContext().getManagementNodeId()), "unexpected app tag, includes too much mgmt info: "+raw);
+        Assert.assertFalse(raw.contains("managementContext"), "unexpected app tag, includes too much mgmt info: "+raw);
+        Assert.assertFalse(raw.contains("localhost"), "unexpected app tag, includes too much mgmt info: "+raw);
+        Assert.assertFalse(raw.contains("catalog"), "unexpected app tag, includes too much mgmt info: "+raw);
+
+        @SuppressWarnings("unchecked")
+        List<Object> tags = mapper().readValue(raw, List.class);
+        log.info("TAGS are: "+tags);
+        
+        Assert.assertEquals(tags.size(), 2, "tags are: "+tags);
+
+        Assert.assertTrue(tags.contains("foo"));
+        Assert.assertFalse(tags.contains("bar"));
+        
+        MutableList<Object> appTags = MutableList.copyOf(tags);
+        appTags.remove("foo");
+        Object appTag = Iterables.getOnlyElement( appTags );
+        
+        // it's a map at this point, because there was no way to make it something stronger than Object
+        Assert.assertTrue(appTag instanceof Map, "Should have deserialized an entity: "+appTag);
+        // let's re-serialize it as an entity
+        appTag = mapper().readValue(mapper().writeValueAsString(appTag), Entity.class);
+        
+        Assert.assertTrue(appTag instanceof Entity, "Should have deserialized an entity: "+appTag);
+        Assert.assertEquals( ((Entity)appTag).getId(), entity.getApplicationId(), "Wrong ID: "+appTag);
+        Assert.assertTrue(appTag instanceof BasicApplication, "Should have deserialized BasicApplication: "+appTag);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ErrorResponseTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ErrorResponseTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ErrorResponseTest.java
new file mode 100644
index 0000000..15f3fcf
--- /dev/null
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ErrorResponseTest.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import com.google.common.collect.ImmutableMap;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response.Status;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import org.apache.brooklyn.rest.domain.ApiError;
+import org.apache.brooklyn.rest.domain.ApplicationSpec;
+import org.apache.brooklyn.rest.domain.EntitySpec;
+import org.apache.brooklyn.rest.domain.PolicySummary;
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+import org.apache.brooklyn.rest.testing.mocks.RestMockSimpleEntity;
+import org.apache.brooklyn.rest.testing.mocks.RestMockSimplePolicy;
+
+import com.google.common.collect.ImmutableSet;
+import javax.ws.rs.core.Response;
+
+@Test( // by using a different suite name we disallow interleaving other tests between the methods of this test class, which wrecks the test fixtures
+        suiteName = "ErrorResponseTest")
+public class ErrorResponseTest extends BrooklynRestResourceTest {
+
+    private final ApplicationSpec simpleSpec = ApplicationSpec.builder().name("simple-app").entities(
+            ImmutableSet.of(new EntitySpec("simple-ent", RestMockSimpleEntity.class.getName()))).locations(
+            ImmutableSet.of("localhost")).build();
+    private String policyId;
+
+    @BeforeClass(alwaysRun = true)
+    public void setUp() throws Exception {
+        startServer();
+        Response aResponse = clientDeploy(simpleSpec);
+        waitForApplicationToBeRunning(aResponse.getLocation());
+
+        String policiesEndpoint = "/applications/simple-app/entities/simple-ent/policies";
+
+        Response pResponse = client().path(policiesEndpoint)
+                .query("type", RestMockSimplePolicy.class.getCanonicalName())
+                .type(MediaType.APPLICATION_JSON_TYPE)
+                .post(toJsonEntity(ImmutableMap.of()));
+        PolicySummary response = pResponse.readEntity(PolicySummary.class);
+        assertNotNull(response.getId());
+        policyId = response.getId();
+    }
+
+    @Test
+    public void testResponseToBadRequest() {
+        String resource = "/applications/simple-app/entities/simple-ent/policies/"+policyId+"/config/"
+                + RestMockSimplePolicy.INTEGER_CONFIG.getName() + "/set";
+
+        Response response = client().path(resource)
+                .query("value", "notanumber")
+                .post(null);
+
+        assertEquals(response.getStatus(), Status.BAD_REQUEST.getStatusCode());
+        assertEquals(response.getHeaders().getFirst("Content-Type"), MediaType.APPLICATION_JSON);
+
+        ApiError error = response.readEntity(ApiError.class);
+        assertTrue(error.getMessage().toLowerCase().contains("cannot coerce"));
+    }
+
+    @Test
+    public void testResponseToWrongMethod() {
+        String resource = "/applications/simple-app/entities/simple-ent/policies/"+policyId+"/config/"
+                + RestMockSimplePolicy.INTEGER_CONFIG.getName() + "/set";
+
+        // Should be POST, not GET
+        Response response = client().path(resource)
+                .query("value", "4")
+                .get();
+
+        assertEquals(response.getStatus(), 405);
+        // Can we assert anything about the content type?
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/LocationResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/LocationResourceTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/LocationResourceTest.java
new file mode 100644
index 0000000..9cf3892
--- /dev/null
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/LocationResourceTest.java
@@ -0,0 +1,190 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.net.URI;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.google.api.client.repackaged.com.google.common.base.Joiner;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import javax.ws.rs.core.GenericType;
+
+import org.apache.brooklyn.api.location.LocationSpec;
+import org.apache.brooklyn.core.location.SimulatedLocation;
+import org.apache.brooklyn.location.jclouds.JcloudsLocation;
+import org.apache.brooklyn.rest.domain.CatalogLocationSummary;
+import org.apache.brooklyn.rest.domain.LocationSummary;
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+import org.apache.brooklyn.test.Asserts;
+
+
+@Test(singleThreaded = true, 
+        // by using a different suite name we disallow interleaving other tests between the methods of this test class, which wrecks the test fixtures
+        suiteName = "LocationResourceTest")
+public class LocationResourceTest extends BrooklynRestResourceTest {
+
+    private static final Logger log = LoggerFactory.getLogger(LocationResourceTest.class);
+    private String legacyLocationName = "my-jungle-legacy";
+    private String legacyLocationVersion = "0.0.0.SNAPSHOT";
+    
+    private String locationName = "my-jungle";
+    private String locationVersion = "0.1.2";
+
+    @Test
+    @Deprecated
+    public void testAddLegacyLocationDefinition() {
+        Map<String, String> expectedConfig = ImmutableMap.of(
+                "identity", "bob",
+                "credential", "CR3dential");
+        Response response = client().path("/locations")
+                .type(MediaType.APPLICATION_JSON_TYPE)
+                .post(new org.apache.brooklyn.rest.domain.LocationSpec(legacyLocationName, "aws-ec2:us-east-1", expectedConfig));
+
+        URI addedLegacyLocationUri = response.getLocation();
+        log.info("added legacy, at: " + addedLegacyLocationUri);
+        LocationSummary location = client().path(response.getLocation()).get(LocationSummary.class);
+        log.info(" contents: " + location);
+        assertEquals(location.getSpec(), "brooklyn.catalog:"+legacyLocationName+":"+legacyLocationVersion);
+        assertTrue(addedLegacyLocationUri.getPath().startsWith("/locations/"));
+
+        JcloudsLocation l = (JcloudsLocation) getManagementContext().getLocationRegistry().resolve(legacyLocationName);
+        Assert.assertEquals(l.getProvider(), "aws-ec2");
+        Assert.assertEquals(l.getRegion(), "us-east-1");
+        Assert.assertEquals(l.getIdentity(), "bob");
+        Assert.assertEquals(l.getCredential(), "CR3dential");
+    }
+
+    @SuppressWarnings("deprecation")
+    @Test
+    public void testAddNewLocationDefinition() {
+        String yaml = Joiner.on("\n").join(ImmutableList.of(
+                "brooklyn.catalog:",
+                "  symbolicName: "+locationName,
+                "  version: " + locationVersion,
+                "",
+                "brooklyn.locations:",
+                "- type: "+"aws-ec2:us-east-1",
+                "  brooklyn.config:",
+                "    identity: bob",
+                "    credential: CR3dential"));
+
+        
+        Response response = client().path("/catalog")
+                .post(yaml);
+
+        assertEquals(response.getStatus(), Response.Status.CREATED.getStatusCode());
+        
+
+        URI addedCatalogItemUri = response.getLocation();
+        log.info("added, at: " + addedCatalogItemUri);
+        
+        // Ensure location definition exists
+        CatalogLocationSummary locationItem = client().path("/catalog/locations/"+locationName + "/" + locationVersion)
+                .get(CatalogLocationSummary.class);
+        log.info(" item: " + locationItem);
+        LocationSummary locationSummary = client().path(URI.create("/locations/"+locationName+"/")).get(LocationSummary.class);
+        log.info(" summary: " + locationSummary);
+        Assert.assertEquals(locationSummary.getSpec(), "brooklyn.catalog:"+locationName+":"+locationVersion);
+
+        // Ensure location is usable - can instantiate, and has right config
+        JcloudsLocation l = (JcloudsLocation) getManagementContext().getLocationRegistry().resolve(locationName);
+        Assert.assertEquals(l.getProvider(), "aws-ec2");
+        Assert.assertEquals(l.getRegion(), "us-east-1");
+        Assert.assertEquals(l.getIdentity(), "bob");
+        Assert.assertEquals(l.getCredential(), "CR3dential");
+    }
+
+    @SuppressWarnings("deprecation")
+    @Test(dependsOnMethods = { "testAddNewLocationDefinition" })
+    public void testListAllLocationDefinitions() {
+        Set<LocationSummary> locations = client().path("/locations")
+                .get(new GenericType<Set<LocationSummary>>() {});
+        Iterable<LocationSummary> matching = Iterables.filter(locations, new Predicate<LocationSummary>() {
+            @Override
+            public boolean apply(@Nullable LocationSummary l) {
+                return locationName.equals(l.getName());
+            }
+        });
+        LocationSummary location = Iterables.getOnlyElement(matching);
+        
+        URI expectedLocationUri = URI.create(getEndpointAddress() + "/locations/"+locationName).normalize();
+        Assert.assertEquals(location.getSpec(), "brooklyn.catalog:"+locationName+":"+locationVersion);
+        Assert.assertEquals(location.getLinks().get("self").toString(), expectedLocationUri.getPath());
+    }
+
+    @SuppressWarnings("deprecation")
+    @Test(dependsOnMethods = { "testListAllLocationDefinitions" })
+    public void testGetSpecificLocation() {
+        URI expectedLocationUri = URI.create("/locations/"+locationName);
+        LocationSummary location = client().path(expectedLocationUri).get(LocationSummary.class);
+        assertEquals(location.getSpec(), "brooklyn.catalog:"+locationName+":"+locationVersion);
+    }
+
+    @SuppressWarnings("deprecation")
+    @Test
+    public void testGetLocationConfig() {
+        SimulatedLocation parentLoc = (SimulatedLocation) getManagementContext().getLocationManager().createLocation(LocationSpec.create(SimulatedLocation.class)
+                .configure("myParentKey", "myParentVal"));
+        SimulatedLocation loc = (SimulatedLocation) getManagementContext().getLocationManager().createLocation(LocationSpec.create(SimulatedLocation.class)
+                .parent(parentLoc)
+                .configure("mykey", "myval")
+                .configure("password", "mypassword"));
+    
+        // "full" means including-inherited, filtered to exclude secrets
+        LocationSummary summaryFull = client().path("/locations/"+loc.getId()).query("full","true").get(LocationSummary.class);
+        assertEquals(summaryFull.getConfig(), ImmutableMap.of("mykey", "myval", "myParentKey", "myParentVal"), "conf="+summaryFull.getConfig());
+        
+        // Default is local-only, filtered to exclude secrets
+        URI uriDefault = URI.create("/locations/"+loc.getId());
+        LocationSummary summaryDefault = client().path(uriDefault).get(LocationSummary.class);
+        assertEquals(summaryDefault.getConfig(), ImmutableMap.of("mykey", "myval"), "conf="+summaryDefault.getConfig());
+    }
+
+    @Test(dependsOnMethods = { "testAddLegacyLocationDefinition" })
+    @Deprecated
+    public void testDeleteLocation() {
+        final int size = getLocationRegistry().getDefinedLocations().size();
+        URI expectedLocationUri = URI.create("/locations/"+legacyLocationName);
+
+        Response response = client().path(expectedLocationUri).delete();
+        assertEquals(response.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
+        Asserts.succeedsEventually(new Runnable() {
+            @Override
+            public void run() {
+                assertEquals(getLocationRegistry().getDefinedLocations().size(), size - 1);
+            }
+        });
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/PolicyResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/PolicyResourceTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/PolicyResourceTest.java
new file mode 100644
index 0000000..21f1210
--- /dev/null
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/PolicyResourceTest.java
@@ -0,0 +1,143 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.fail;
+
+import java.util.Map;
+import java.util.Set;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import org.apache.brooklyn.rest.domain.ApplicationSpec;
+import org.apache.brooklyn.rest.domain.EntitySpec;
+import org.apache.brooklyn.rest.domain.PolicyConfigSummary;
+import org.apache.brooklyn.rest.domain.PolicySummary;
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+import org.apache.brooklyn.rest.testing.mocks.RestMockSimpleEntity;
+import org.apache.brooklyn.rest.testing.mocks.RestMockSimplePolicy;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import javax.ws.rs.core.GenericType;
+
+@Test(singleThreaded = true,
+        // by using a different suite name we disallow interleaving other tests between the methods of this test class, which wrecks the test fixtures
+        suiteName = "PolicyResourceTest")
+public class PolicyResourceTest extends BrooklynRestResourceTest {
+
+    @SuppressWarnings("unused")
+    private static final Logger log = LoggerFactory.getLogger(PolicyResourceTest.class);
+
+    private static final String ENDPOINT = "/applications/simple-app/entities/simple-ent/policies/";
+
+    private final ApplicationSpec simpleSpec = ApplicationSpec.builder().name("simple-app").entities(
+            ImmutableSet.of(new EntitySpec("simple-ent", RestMockSimpleEntity.class.getName()))).locations(
+            ImmutableSet.of("localhost")).build();
+
+    private String policyId;
+    
+    @BeforeClass(alwaysRun = true)
+    public void setUp() throws Exception {
+        startServer();
+        Response aResponse = clientDeploy(simpleSpec);
+        waitForApplicationToBeRunning(aResponse.getLocation());
+
+        Response pResponse = client().path(ENDPOINT)
+                .query("type", RestMockSimplePolicy.class.getCanonicalName())
+                .type(MediaType.APPLICATION_JSON_TYPE)
+                .post(toJsonEntity(ImmutableMap.of()));
+
+        PolicySummary response = pResponse.readEntity(PolicySummary.class);
+        assertNotNull(response.getId());
+        policyId = response.getId();
+
+    }
+
+    @Test
+    public void testListConfig() throws Exception {
+        Set<PolicyConfigSummary> config = client().path(ENDPOINT + policyId + "/config")
+                .get(new GenericType<Set<PolicyConfigSummary>>() {});
+        
+        Set<String> configNames = Sets.newLinkedHashSet();
+        for (PolicyConfigSummary conf : config) {
+            configNames.add(conf.getName());
+        }
+
+        assertEquals(configNames, ImmutableSet.of(
+                RestMockSimplePolicy.SAMPLE_CONFIG.getName(),
+                RestMockSimplePolicy.INTEGER_CONFIG.getName()));
+    }
+
+    @Test
+    public void testGetNonExistantConfigReturns404() throws Exception {
+        String invalidConfigName = "doesnotexist";
+        try {
+            PolicyConfigSummary summary = client().path(ENDPOINT + policyId + "/config/" + invalidConfigName)
+                    .get(PolicyConfigSummary.class);
+            fail("Should have thrown 404, but got "+summary);
+        } catch (Exception e) {
+            if (!e.toString().contains("404")) throw e;
+        }
+    }
+
+    @Test
+    public void testGetDefaultValue() throws Exception {
+        String configName = RestMockSimplePolicy.SAMPLE_CONFIG.getName();
+        String expectedVal = RestMockSimplePolicy.SAMPLE_CONFIG.getDefaultValue();
+        
+        String configVal = client().path(ENDPOINT + policyId + "/config/" + configName)
+                .get(String.class);
+        assertEquals(configVal, expectedVal);
+    }
+    
+    @Test(dependsOnMethods = "testGetDefaultValue")
+    public void testReconfigureConfig() throws Exception {
+        String configName = RestMockSimplePolicy.SAMPLE_CONFIG.getName();
+        
+        Response response = client().path(ENDPOINT + policyId + "/config/" + configName + "/set")
+                .query("value", "newval")
+                .post(null);
+
+        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
+    }
+    
+    @Test(dependsOnMethods = "testReconfigureConfig")
+    public void testGetConfigValue() throws Exception {
+        String configName = RestMockSimplePolicy.SAMPLE_CONFIG.getName();
+        String expectedVal = "newval";
+        
+        Map<String, Object> allState = client().path(ENDPOINT + policyId + "/config/current-state")
+                .get(new GenericType<Map<String, Object>>() {});
+        assertEquals(allState, ImmutableMap.of(configName, expectedVal));
+        
+        String configVal = client().path(ENDPOINT + policyId + "/config/" + configName)
+                .get(String.class);
+        assertEquals(configVal, expectedVal);
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ScriptResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ScriptResourceTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ScriptResourceTest.java
new file mode 100644
index 0000000..20e504c
--- /dev/null
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ScriptResourceTest.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import java.util.Collections;
+
+import org.apache.brooklyn.api.entity.Application;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
+import org.apache.brooklyn.rest.domain.ScriptExecutionSummary;
+import org.apache.brooklyn.rest.testing.mocks.RestMockApp;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+@Test( // by using a different suite name we disallow interleaving other tests between the methods of this test class, which wrecks the test fixtures
+        suiteName = "ScriptResourceTest")
+public class ScriptResourceTest {
+
+    @Test
+    public void testGroovy() {
+        ManagementContext mgmt = LocalManagementContextForTests.newInstance();
+        Application app = mgmt.getEntityManager().createEntity( EntitySpec.create(Application.class, RestMockApp.class) );
+        try {
+        
+            Entities.start(app, Collections.<Location>emptyList());
+
+            ScriptResource s = new ScriptResource();
+            s.setManagementContext(mgmt);
+
+            ScriptExecutionSummary result = s.groovy(null, "def apps = []; mgmt.applications.each { println 'app:'+it; apps << it.id }; apps");
+            Assert.assertEquals(Collections.singletonList(app.getId()).toString(), result.getResult());
+            Assert.assertTrue(result.getStdout().contains("app:RestMockApp"));
+        
+        } finally { Entities.destroyAll(mgmt); }
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/SensorResourceIntegrationTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/SensorResourceIntegrationTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/SensorResourceIntegrationTest.java
new file mode 100644
index 0000000..9b7214d
--- /dev/null
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/SensorResourceIntegrationTest.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import java.net.URI;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.core.entity.EntityInternal;
+import org.apache.brooklyn.core.entity.EntityPredicates;
+import org.apache.brooklyn.entity.stock.BasicApplication;
+import org.apache.brooklyn.rest.testing.mocks.RestMockSimpleEntity;
+import org.apache.brooklyn.test.HttpTestUtils;
+import org.apache.brooklyn.util.collections.MutableList;
+import org.apache.brooklyn.util.http.HttpTool;
+import org.apache.brooklyn.util.http.HttpToolResponse;
+import org.apache.brooklyn.util.net.Urls;
+import org.apache.http.client.HttpClient;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+
+public class SensorResourceIntegrationTest extends BrooklynRestResourceTest {
+
+    private ManagementContext mgmt;
+    private BasicApplication app;
+
+    @BeforeClass(alwaysRun = true)
+    protected void setUp() {
+        mgmt = getManagementContext();
+        app = mgmt.getEntityManager().createEntity(EntitySpec.create(BasicApplication.class).displayName("simple-app")
+            .child(EntitySpec.create(Entity.class, RestMockSimpleEntity.class).displayName("simple-ent")));
+        mgmt.getEntityManager().manage(app);
+        app.start(MutableList.of(mgmt.getLocationRegistry().resolve("localhost")));
+    }
+
+    // marked integration because of time
+    @Test(groups = "Integration")
+    public void testSensorBytes() throws Exception {
+        EntityInternal entity = (EntityInternal) Iterables.find(mgmt.getEntityManager().getEntities(), EntityPredicates.displayNameEqualTo("simple-ent"));
+        SensorResourceTest.addAmphibianSensor(entity);
+
+        String baseUri = getEndpointAddress();
+        URI url = URI.create(Urls.mergePaths(baseUri, SensorResourceTest.SENSORS_ENDPOINT, SensorResourceTest.SENSOR_NAME));
+
+        // Uses explicit "application/json" because failed on jenkins as though "text/plain" was the default on Ubuntu jenkins!
+        HttpClient client = HttpTool.httpClientBuilder().uri(baseUri).build();
+        HttpToolResponse response = HttpTool.httpGet(client, url, ImmutableMap.<String, String>of("Accept", "application/json"));
+        HttpTestUtils.assertHealthyStatusCode(response.getResponseCode());
+        Assert.assertEquals(response.getContentAsString(), "\"12345 frogs\"");
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/SensorResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/SensorResourceTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/SensorResourceTest.java
new file mode 100644
index 0000000..01b3546
--- /dev/null
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/SensorResourceTest.java
@@ -0,0 +1,267 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import static org.testng.Assert.assertEquals;
+
+import java.util.Map;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.core.config.render.RendererHints;
+import org.apache.brooklyn.core.entity.EntityInternal;
+import org.apache.brooklyn.core.entity.EntityPredicates;
+import org.apache.brooklyn.core.sensor.Sensors;
+import org.apache.brooklyn.rest.api.SensorApi;
+import org.apache.brooklyn.rest.domain.ApplicationSpec;
+import org.apache.brooklyn.rest.domain.EntitySpec;
+import org.apache.brooklyn.rest.test.config.render.TestRendererHints;
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+import org.apache.brooklyn.rest.testing.mocks.RestMockSimpleEntity;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.http.HttpAsserts;
+import org.apache.brooklyn.util.stream.Streams;
+import org.apache.brooklyn.util.text.StringFunctions;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Functions;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import java.io.InputStream;
+import javax.ws.rs.core.GenericType;
+import org.apache.cxf.jaxrs.client.WebClient;
+
+/**
+ * Test the {@link SensorApi} implementation.
+ * <p>
+ * Check that {@link SensorResource} correctly renders {@link AttributeSensor}
+ * values, including {@link RendererHints.DisplayValue} hints.
+ */
+@Test(singleThreaded = true,
+        // by using a different suite name we disallow interleaving other tests between the methods of this test class, which wrecks the test fixtures
+        suiteName = "SensorResourceTest")
+public class SensorResourceTest extends BrooklynRestResourceTest {
+
+    final static ApplicationSpec SIMPLE_SPEC = ApplicationSpec.builder()
+            .name("simple-app")
+            .entities(ImmutableSet.of(new EntitySpec("simple-ent", RestMockSimpleEntity.class.getName())))
+            .locations(ImmutableSet.of("localhost"))
+            .build();
+
+    static final String SENSORS_ENDPOINT = "/applications/simple-app/entities/simple-ent/sensors";
+    static final String SENSOR_NAME = "amphibian.count";
+    static final AttributeSensor<Integer> SENSOR = Sensors.newIntegerSensor(SENSOR_NAME);
+
+    EntityInternal entity;
+
+    /**
+     * Sets up the application and entity.
+     * <p>
+     * Adds a sensor and sets its value to {@code 12345}. Configures a display value
+     * hint that appends {@code frogs} to the value of the sensor.
+     */
+    @BeforeClass(alwaysRun = true)
+    public void setUp() throws Exception {
+        // Deploy application
+        startServer();
+        Response deploy = clientDeploy(SIMPLE_SPEC);
+        waitForApplicationToBeRunning(deploy.getLocation());
+
+        entity = (EntityInternal) Iterables.find(getManagementContext().getEntityManager().getEntities(), EntityPredicates.displayNameEqualTo("simple-ent"));
+        addAmphibianSensor(entity);
+    }
+
+    static void addAmphibianSensor(EntityInternal entity) {
+        // Add new sensor
+        entity.getMutableEntityType().addSensor(SENSOR);
+        entity.sensors().set(SENSOR, 12345);
+
+        // Register display value hint
+        RendererHints.register(SENSOR, RendererHints.displayValue(Functions.compose(StringFunctions.append(" frogs"), Functions.toStringFunction())));
+    }
+
+    @AfterClass(alwaysRun = true)
+    public void tearDown() throws Exception {
+        TestRendererHints.clearRegistry();
+    }
+
+    /** Check default is to use display value hint. */
+    @Test
+    public void testBatchSensorRead() throws Exception {
+        Response response = client().path(SENSORS_ENDPOINT + "/current-state")
+                .accept(MediaType.APPLICATION_JSON)
+                .get();
+        Map<String, ?> currentState = response.readEntity(new GenericType<Map<String,?>>(Map.class) {});
+
+        for (String sensor : currentState.keySet()) {
+            if (sensor.equals(SENSOR_NAME)) {
+                assertEquals(currentState.get(sensor), "12345 frogs");
+            }
+        }
+    }
+
+    /** Check setting {@code raw} to {@code true} ignores display value hint. */
+    @Test(dependsOnMethods = "testBatchSensorRead")
+    public void testBatchSensorReadRaw() throws Exception {
+        Response response = client().path(SENSORS_ENDPOINT + "/current-state")
+                .query("raw", "true")
+                .accept(MediaType.APPLICATION_JSON)
+                .get();
+        Map<String, ?> currentState = response.readEntity(new GenericType<Map<String,?>>(Map.class) {});
+
+        for (String sensor : currentState.keySet()) {
+            if (sensor.equals(SENSOR_NAME)) {
+                assertEquals(currentState.get(sensor), Integer.valueOf(12345));
+            }
+        }
+    }
+
+    protected Response doSensorTest(Boolean raw, MediaType acceptsType, Object expectedValue) {
+        return doSensorTestUntyped(
+            raw==null ? null : (""+raw).toLowerCase(), 
+            acceptsType==null ? null : new String[] { acceptsType.toString() },
+            expectedValue);
+    }
+    protected Response doSensorTestUntyped(String raw, String[] acceptsTypes, Object expectedValue) {
+        WebClient req = client().path(SENSORS_ENDPOINT + "/" + SENSOR_NAME);
+        if (raw!=null) req = req.query("raw", raw);
+        Response response;
+        if (acceptsTypes!=null) {
+            response = req.accept(acceptsTypes).get();
+        } else {
+            response = req.get();
+        }
+        if (expectedValue!=null) {
+            HttpAsserts.assertHealthyStatusCode(response.getStatus());
+            Object value = response.readEntity(expectedValue.getClass());
+            assertEquals(value, expectedValue);
+        }
+        return response;
+    }
+    
+    /**
+     * Check we can get a sensor, explicitly requesting json; gives a string picking up the rendering hint.
+     * 
+     * If no "Accepts" header is given, then we don't control whether json or plain text comes back.
+     * It is dependent on the method order, which is compiler-specific.
+     */
+    @Test
+    public void testGetJson() throws Exception {
+        doSensorTest(null, MediaType.APPLICATION_JSON_TYPE, "\"12345 frogs\"");
+    }
+    
+    @Test
+    public void testGetJsonBytes() throws Exception {
+        Response response = doSensorTest(null, MediaType.APPLICATION_JSON_TYPE, null);
+        byte[] bytes = Streams.readFully(response.readEntity(InputStream.class));
+        // assert we have one set of surrounding quotes
+        assertEquals(bytes.length, 13);
+    }
+
+    /** Check that plain returns a string without quotes, with the rendering hint */
+    @Test
+    public void testGetPlain() throws Exception {
+        doSensorTest(null, MediaType.TEXT_PLAIN_TYPE, "12345 frogs");
+    }
+
+    /** 
+     * Check that when we set {@code raw = true}, the result ignores the display value hint.
+     *
+     * If no "Accepts" header is given, then we don't control whether json or plain text comes back.
+     * It is dependent on the method order, which is compiler-specific.
+     */
+    @Test
+    public void testGetRawJson() throws Exception {
+        doSensorTest(true, MediaType.APPLICATION_JSON_TYPE, 12345);
+    }
+    
+    /** As {@link #testGetRaw()} but with plain set, returns the number */
+    @Test
+    public void testGetPlainRaw() throws Exception {
+        // have to pass a string because that's how PLAIN is deserialized
+        doSensorTest(true, MediaType.TEXT_PLAIN_TYPE, "12345");
+    }
+
+    /** Check explicitly setting {@code raw} to {@code false} is as before */
+    @Test
+    public void testGetPlainRawFalse() throws Exception {
+        doSensorTest(false, MediaType.TEXT_PLAIN_TYPE, "12345 frogs");
+    }
+
+    /** Check empty vaue for {@code raw} will revert to using default. */
+    @Test
+    public void testGetPlainRawEmpty() throws Exception {
+        doSensorTestUntyped("", new String[] { MediaType.TEXT_PLAIN }, "12345 frogs");
+    }
+
+    /** Check unparseable vaue for {@code raw} will revert to using default. */
+    @Test
+    public void testGetPlainRawError() throws Exception {
+        doSensorTestUntyped("biscuits", new String[] { MediaType.TEXT_PLAIN }, "12345 frogs");
+    }
+    
+    /** Check we can set a value */
+    @Test
+    public void testSet() throws Exception {
+        try {
+            Response response = client().path(SENSORS_ENDPOINT + "/" + SENSOR_NAME)
+                .type(MediaType.APPLICATION_JSON_TYPE)
+                .post(67890);
+            assertEquals(response.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
+
+            assertEquals(entity.getAttribute(SENSOR), (Integer)67890);
+            
+            String value = client().path(SENSORS_ENDPOINT + "/" + SENSOR_NAME).accept(MediaType.TEXT_PLAIN_TYPE).get(String.class);
+            assertEquals(value, "67890 frogs");
+
+        } finally { addAmphibianSensor(entity); }
+    }
+
+    @Test
+    public void testSetFromMap() throws Exception {
+        try {
+            Response response = client().path(SENSORS_ENDPOINT)
+                .type(MediaType.APPLICATION_JSON_TYPE)
+                .post(MutableMap.of(SENSOR_NAME, 67890));
+            assertEquals(response.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
+            
+            assertEquals(entity.getAttribute(SENSOR), (Integer)67890);
+
+        } finally { addAmphibianSensor(entity); }
+    }
+    
+    /** Check we can delete a value */
+    @Test
+    public void testDelete() throws Exception {
+        try {
+            Response response = client().path(SENSORS_ENDPOINT + "/" + SENSOR_NAME)
+                .delete();
+            assertEquals(response.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
+
+            String value = client().path(SENSORS_ENDPOINT + "/" + SENSOR_NAME).accept(MediaType.TEXT_PLAIN_TYPE).get(String.class);
+            assertEquals(value, "");
+
+        } finally { addAmphibianSensor(entity); }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ServerResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ServerResourceTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ServerResourceTest.java
new file mode 100644
index 0000000..24f9e3d
--- /dev/null
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ServerResourceTest.java
@@ -0,0 +1,168 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.brooklyn.api.entity.ImplementedBy;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.core.BrooklynVersion;
+import org.apache.brooklyn.core.internal.BrooklynProperties;
+import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
+import org.apache.brooklyn.entity.software.base.EmptySoftwareProcess;
+import org.apache.brooklyn.entity.software.base.EmptySoftwareProcessDriver;
+import org.apache.brooklyn.entity.software.base.EmptySoftwareProcessImpl;
+import org.apache.brooklyn.rest.domain.HighAvailabilitySummary;
+import org.apache.brooklyn.rest.domain.VersionSummary;
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableSet;
+import javax.ws.rs.core.Response;
+import org.apache.cxf.jaxrs.client.WebClient;
+
+@Test(singleThreaded = true,
+        // by using a different suite name we disallow interleaving other tests between the methods of this test class, which wrecks the test fixtures
+        suiteName = "ServerResourceTest")
+public class ServerResourceTest extends BrooklynRestResourceTest {
+
+    private static final Logger log = LoggerFactory.getLogger(ServerResourceTest.class);
+    
+    @Test
+    public void testGetVersion() throws Exception {
+        VersionSummary version = client().path("/server/version").get(VersionSummary.class);
+        assertEquals(version.getVersion(), BrooklynVersion.get());
+    }
+
+    @Test
+    public void testGetStatus() throws Exception {
+        String status = client().path("/server/status").get(String.class);
+        assertEquals(status, "MASTER");
+    }
+
+    @Test
+    public void testGetHighAvailability() throws Exception {
+        // Note by default management context from super is started without HA enabled.
+        // Therefore can only assert a minimal amount of stuff.
+        HighAvailabilitySummary summary = client().path("/server/highAvailability").get(HighAvailabilitySummary.class);
+        log.info("HA summary is: "+summary);
+        
+        String ownNodeId = getManagementContext().getManagementNodeId();
+        assertEquals(summary.getOwnId(), ownNodeId);
+        assertEquals(summary.getMasterId(), ownNodeId);
+        assertEquals(summary.getNodes().keySet(), ImmutableSet.of(ownNodeId));
+        assertEquals(summary.getNodes().get(ownNodeId).getNodeId(), ownNodeId);
+        assertEquals(summary.getNodes().get(ownNodeId).getStatus(), "MASTER");
+        assertNotNull(summary.getNodes().get(ownNodeId).getLocalTimestamp());
+        // remote will also be non-null if there is no remote backend (local is re-used)
+        assertNotNull(summary.getNodes().get(ownNodeId).getRemoteTimestamp());
+        assertEquals(summary.getNodes().get(ownNodeId).getLocalTimestamp(), summary.getNodes().get(ownNodeId).getRemoteTimestamp());
+    }
+
+    @SuppressWarnings("serial")
+    @Test
+    public void testReloadsBrooklynProperties() throws Exception {
+        final AtomicInteger reloadCount = new AtomicInteger();
+        getManagementContext().addPropertiesReloadListener(new ManagementContext.PropertiesReloadListener() {
+            @Override public void reloaded() {
+                reloadCount.incrementAndGet();
+            }});
+        client().path("/server/properties/reload").post(null);
+        assertEquals(reloadCount.get(), 1);
+    }
+
+    @Test
+    void testGetConfig() throws Exception {
+        ((ManagementContextInternal)getManagementContext()).getBrooklynProperties().put("foo.bar.baz", "quux");
+        try {
+            assertEquals(client().path("/server/config/foo.bar.baz").get(String.class), "quux");
+        } finally {
+            ((ManagementContextInternal)getManagementContext()).getBrooklynProperties().remove("foo.bar.baz");
+        }
+    }
+
+    @Test
+    void testGetMissingConfigThrowsException() throws Exception {
+        final String key = "foo.bar.baz";
+        BrooklynProperties properties = ((ManagementContextInternal)getManagementContext()).getBrooklynProperties();
+        Object existingValue = null;
+        boolean keyAlreadyPresent = false;
+        if (properties.containsKey(key)) {
+            existingValue = properties.remove(key);
+            keyAlreadyPresent = true;
+        }
+        try {
+            final WebClient webClient = client().path("/server/config/" + key);
+            Response response = webClient.get();
+            assertEquals(response.getStatus(), 204);
+        } finally {
+            if (keyAlreadyPresent) {
+                properties.put(key, existingValue);
+            }
+        }
+    }
+
+    // Alternatively could reuse a blocking location, see org.apache.brooklyn.entity.software.base.SoftwareProcessEntityTest.ReleaseLatchLocation
+    @ImplementedBy(StopLatchEntityImpl.class)
+    public interface StopLatchEntity extends EmptySoftwareProcess {
+        public void unblock();
+        public boolean isBlocked();
+    }
+
+    public static class StopLatchEntityImpl extends EmptySoftwareProcessImpl implements StopLatchEntity {
+        private CountDownLatch lock = new CountDownLatch(1);
+        private volatile boolean isBlocked;
+
+        @Override
+        public void unblock() {
+            lock.countDown();
+        }
+
+        @Override
+        protected void postStop() {
+            super.preStop();
+            try {
+                isBlocked = true;
+                lock.await();
+                isBlocked = false;
+            } catch (InterruptedException e) {
+                throw Exceptions.propagate(e);
+            }
+        }
+
+        @Override
+        public Class<?> getDriverInterface() {
+            return EmptySoftwareProcessDriver.class;
+        }
+
+        @Override
+        public boolean isBlocked() {
+            return isBlocked;
+        }
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ServerShutdownTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ServerShutdownTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ServerShutdownTest.java
new file mode 100644
index 0000000..9d55482
--- /dev/null
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ServerShutdownTest.java
@@ -0,0 +1,164 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedHashMap;
+import javax.ws.rs.core.MultivaluedMap;
+
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.mgmt.EntityManager;
+import org.apache.brooklyn.api.mgmt.Task;
+import org.apache.brooklyn.core.entity.Attributes;
+import org.apache.brooklyn.core.entity.EntityAsserts;
+import org.apache.brooklyn.core.entity.drivers.BasicEntityDriverManager;
+import org.apache.brooklyn.core.entity.drivers.ReflectiveEntityDriverFactory;
+import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
+import org.apache.brooklyn.core.entity.trait.Startable;
+import org.apache.brooklyn.core.test.entity.TestApplication;
+import org.apache.brooklyn.rest.resources.ServerResourceTest.StopLatchEntity;
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+import org.apache.brooklyn.test.Asserts;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+@Test( // by using a different suite name we disallow interleaving other tests between the methods of this test class, which wrecks the test fixtures
+        suiteName = "ServerShutdownTest")
+public class ServerShutdownTest extends BrooklynRestResourceTest {
+    private static final Logger log = LoggerFactory.getLogger(ServerResourceTest.class);
+
+    @Override
+    protected boolean isMethodInit() {
+        return true;
+    }
+
+    @Test
+    public void testShutdown() throws Exception {
+        assertTrue(getManagementContext().isRunning());
+        assertFalse(shutdownListener.isRequested());
+
+        MultivaluedMap<String, String> formData = new MultivaluedHashMap();
+        formData.add("requestTimeout", "0");
+        formData.add("delayForHttpReturn", "0");
+        client().path("/server/shutdown").type(MediaType.APPLICATION_FORM_URLENCODED).post(formData);
+
+        Asserts.succeedsEventually(new Runnable() {
+            @Override
+            public void run() {
+                assertTrue(shutdownListener.isRequested());
+            }
+        });
+        Asserts.succeedsEventually(new Runnable() {
+            @Override public void run() {
+                assertFalse(getManagementContext().isRunning());
+            }});
+    }
+
+    @Test
+    public void testStopAppThenShutdownAndStopAppsWaitsForFirstStop() throws InterruptedException {
+        ReflectiveEntityDriverFactory f = ((BasicEntityDriverManager)getManagementContext().getEntityDriverManager()).getReflectiveDriverFactory();
+        f.addClassFullNameMapping("org.apache.brooklyn.entity.software.base.EmptySoftwareProcessDriver", "org.apache.brooklyn.rest.resources.ServerResourceTest$EmptySoftwareProcessTestDriver");
+
+        // Second stop on SoftwareProcess could return early, while the first stop is still in progress
+        // This causes the app to shutdown prematurely, leaking machines.
+        EntityManager emgr = getManagementContext().getEntityManager();
+        EntitySpec<TestApplication> appSpec = EntitySpec.create(TestApplication.class);
+        TestApplication app = emgr.createEntity(appSpec);
+        EntitySpec<StopLatchEntity> latchEntitySpec = EntitySpec.create(StopLatchEntity.class);
+        final StopLatchEntity entity = app.createAndManageChild(latchEntitySpec);
+        app.start(ImmutableSet.of(app.newLocalhostProvisioningLocation()));
+        EntityAsserts.assertAttributeEquals(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+
+        try {
+            final Task<Void> firstStop = app.invoke(Startable.STOP, ImmutableMap.<String, Object>of());
+            Asserts.succeedsEventually(new Runnable() {
+                @Override
+                public void run() {
+                    assertTrue(entity.isBlocked());
+                }
+            });
+
+            final AtomicReference<Exception> shutdownError = new AtomicReference<>();
+            // Can't use ExecutionContext as it will be stopped on shutdown
+            Thread shutdownThread = new Thread() {
+                @Override
+                public void run() {
+                    try {
+                        MultivaluedMap<String, String> formData = new MultivaluedHashMap<>();
+                        formData.add("stopAppsFirst", "true");
+                        formData.add("shutdownTimeout", "0");
+                        formData.add("requestTimeout", "0");
+                        formData.add("delayForHttpReturn", "0");
+                        client().path("/server/shutdown").type(MediaType.APPLICATION_FORM_URLENCODED).post(formData);
+                    } catch (Exception e) {
+                        log.error("Shutdown request error", e);
+                        shutdownError.set(e);
+                        throw Exceptions.propagate(e);
+                    }
+                }
+            };
+            shutdownThread.start();
+
+            //shutdown must wait until the first stop completes (or time out)
+            Asserts.succeedsContinually(new Runnable() {
+                @Override
+                public void run() {
+                    assertFalse(firstStop.isDone());
+                    assertEquals(getManagementContext().getApplications().size(), 1);
+                    assertFalse(shutdownListener.isRequested());
+                }
+            });
+
+            // NOTE test is not fully deterministic. Depending on thread scheduling this will
+            // execute before or after ServerResource.shutdown does the app stop loop. This
+            // means that the shutdown code might not see the app at all. In any case though
+            // the test must succeed.
+            entity.unblock();
+
+            Asserts.succeedsEventually(new Runnable() {
+                @Override
+                public void run() {
+                    assertTrue(firstStop.isDone());
+                    assertTrue(shutdownListener.isRequested());
+                    assertFalse(getManagementContext().isRunning());
+                }
+            });
+
+            shutdownThread.join();
+            assertNull(shutdownError.get(), "Shutdown request error, logged above");
+        } finally {
+            // Be sure we always unblock entity stop even in the case of an exception.
+            // In the success path the entity is already unblocked above.
+            entity.unblock();
+        }
+    }
+
+}


[30/34] brooklyn-server git commit: fix tests which expected a particular result

Posted by he...@apache.org.
fix tests which expected a particular result


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

Branch: refs/heads/master
Commit: 7f047baaa1e029b41977e3316cd990f1cc13eb7e
Parents: 79b98c6
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Thu Feb 18 11:16:37 2016 +0000
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Thu Feb 18 11:16:37 2016 +0000

----------------------------------------------------------------------
 .../apache/brooklyn/rest/resources/ApplicationResourceTest.java | 4 +++-
 .../org/apache/brooklyn/rest/resources/CatalogResourceTest.java | 5 ++++-
 2 files changed, 7 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/7f047baa/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java
index 6091096..b9a6e9b 100644
--- a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java
@@ -85,10 +85,12 @@ import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Maps;
+
 import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.client.Entity;
 import javax.ws.rs.core.GenericType;
 import javax.ws.rs.core.MultivaluedHashMap;
+
 import org.apache.cxf.jaxrs.client.WebClient;
 
 @Test(singleThreaded = true,
@@ -645,7 +647,7 @@ public class ApplicationResourceTest extends BrooklynRestResourceTest {
             Response response3 = client().path("/applications")
                     .post(Entity.entity(yaml3, "application/x-yaml"));
             HttpAsserts.assertClientErrorStatusCode(response3.getStatus());
-            assertTrue(response3.readEntity(String.class).contains("cannot be matched"));
+            assertTrue(response3.readEntity(String.class).toLowerCase().contains("unable to match"));
             waitForPageNotFoundResponse("/applications/my-app3", ApplicationSummary.class);
             
         } finally {

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/7f047baa/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/CatalogResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/CatalogResourceTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/CatalogResourceTest.java
index b6a77d5..8d5d017 100644
--- a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/CatalogResourceTest.java
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/CatalogResourceTest.java
@@ -47,6 +47,7 @@ import org.apache.brooklyn.test.support.TestResourceUnavailableException;
 import org.apache.brooklyn.util.javalang.Reflections;
 import org.apache.http.HttpHeaders;
 import org.apache.http.entity.ContentType;
+import org.eclipse.jetty.http.HttpStatus;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.testng.Assert;
@@ -55,7 +56,9 @@ import org.testng.reporters.Files;
 
 import com.google.common.base.Joiner;
 import com.google.common.collect.Iterables;
+
 import java.io.InputStream;
+
 import javax.ws.rs.core.GenericType;
 
 @Test( // by using a different suite name we disallow interleaving other tests between the methods of this test class, which wrecks the test fixtures
@@ -501,7 +504,7 @@ public class CatalogResourceTest extends BrooklynRestResourceTest {
         Response response = client().path("/catalog")
                 .post(yaml);
 
-        assertEquals(response.getStatus(), Response.Status.INTERNAL_SERVER_ERROR.getStatusCode());
+        assertEquals(response.getStatus(), HttpStatus.BAD_REQUEST_400);
     }
 
     private static String ver(String id) {


[18/34] brooklyn-server git commit: [BROOKLYN-183] REST API using CXF JAX-RS 2.0 implementation

Posted by he...@apache.org.
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ApplicationResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ApplicationResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ApplicationResource.java
new file mode 100644
index 0000000..986855e
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ApplicationResource.java
@@ -0,0 +1,451 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static javax.ws.rs.core.Response.created;
+import static javax.ws.rs.core.Response.status;
+import static javax.ws.rs.core.Response.Status.ACCEPTED;
+import static org.apache.brooklyn.rest.util.WebResourceUtils.serviceAbsoluteUriBuilder;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.ResponseBuilder;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.brooklyn.api.entity.Application;
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.entity.Group;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.mgmt.Task;
+import org.apache.brooklyn.api.objs.BrooklynObject;
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.api.sensor.Sensor;
+import org.apache.brooklyn.api.typereg.RegisteredType;
+import org.apache.brooklyn.core.config.ConstraintViolationException;
+import org.apache.brooklyn.core.entity.Attributes;
+import org.apache.brooklyn.core.entity.EntityPredicates;
+import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
+import org.apache.brooklyn.core.entity.trait.Startable;
+import org.apache.brooklyn.core.mgmt.EntityManagementUtils;
+import org.apache.brooklyn.core.mgmt.EntityManagementUtils.CreationResult;
+import org.apache.brooklyn.core.mgmt.entitlement.EntitlementPredicates;
+import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
+import org.apache.brooklyn.core.mgmt.entitlement.Entitlements.EntityAndItem;
+import org.apache.brooklyn.core.mgmt.entitlement.Entitlements.StringAndArgument;
+import org.apache.brooklyn.core.sensor.Sensors;
+import org.apache.brooklyn.core.typereg.RegisteredTypeLoadingContexts;
+import org.apache.brooklyn.core.typereg.RegisteredTypes;
+import org.apache.brooklyn.entity.group.AbstractGroup;
+import org.apache.brooklyn.rest.api.ApplicationApi;
+import org.apache.brooklyn.rest.domain.ApplicationSpec;
+import org.apache.brooklyn.rest.domain.ApplicationSummary;
+import org.apache.brooklyn.rest.domain.EntityDetail;
+import org.apache.brooklyn.rest.domain.EntitySummary;
+import org.apache.brooklyn.rest.domain.TaskSummary;
+import org.apache.brooklyn.rest.filter.HaHotStateRequired;
+import org.apache.brooklyn.rest.transform.ApplicationTransformer;
+import org.apache.brooklyn.rest.transform.EntityTransformer;
+import org.apache.brooklyn.rest.transform.TaskTransformer;
+import org.apache.brooklyn.rest.util.BrooklynRestResourceUtils;
+import org.apache.brooklyn.rest.util.WebResourceUtils;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.core.ResourceUtils;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.exceptions.UserFacingException;
+import org.apache.brooklyn.util.guava.Maybe;
+import org.apache.brooklyn.util.javalang.JavaClassNames;
+import org.apache.brooklyn.util.text.Strings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Throwables;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+
+@HaHotStateRequired
+public class ApplicationResource extends AbstractBrooklynRestResource implements ApplicationApi {
+
+    private static final Logger log = LoggerFactory.getLogger(ApplicationResource.class);
+
+    @Context
+    private UriInfo uriInfo;
+
+    private EntityDetail fromEntity(Entity entity) {
+        Boolean serviceUp = entity.getAttribute(Attributes.SERVICE_UP);
+
+        Lifecycle serviceState = entity.getAttribute(Attributes.SERVICE_STATE_ACTUAL);
+
+        String iconUrl = entity.getIconUrl();
+        if (iconUrl!=null) {
+            if (brooklyn().isUrlServerSideAndSafe(iconUrl))
+                // route to server if it is a server-side url
+                iconUrl = EntityTransformer.entityUri(entity, ui.getBaseUriBuilder())+"/icon";
+        }
+
+        List<EntitySummary> children = Lists.newArrayList();
+        if (!entity.getChildren().isEmpty()) {
+            for (Entity child : entity.getChildren()) {
+                children.add(fromEntity(child));
+            }
+        }
+
+        String parentId = null;
+        if (entity.getParent()!= null) {
+            parentId = entity.getParent().getId();
+        }
+
+        List<String> groupIds = Lists.newArrayList();
+        if (!entity.groups().isEmpty()) {
+            groupIds.addAll(entitiesIdAsArray(entity.groups()));
+        }
+
+        List<Map<String, String>> members = Lists.newArrayList();
+        if (entity instanceof Group) {
+            // use attribute instead of method in case it is read-only
+            Collection<Entity> memberEntities = entity.getAttribute(AbstractGroup.GROUP_MEMBERS);
+            if (memberEntities != null && !memberEntities.isEmpty())
+                members.addAll(entitiesIdAndNameAsList(memberEntities));
+        }
+
+        return new EntityDetail(
+                entity.getApplicationId(),
+                entity.getId(),
+                parentId,
+                entity.getDisplayName(),
+                entity.getEntityType().getName(),
+                serviceUp,
+                serviceState,
+                iconUrl,
+                entity.getCatalogItemId(),
+                children,
+                groupIds,
+                members);
+    }
+
+    private List<Map<String, String>> entitiesIdAndNameAsList(Collection<? extends Entity> entities) {
+        List<Map<String, String>> members = Lists.newArrayList();
+        for (Entity entity : entities) {
+            if (Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY, entity)) {
+                members.add(ImmutableMap.of("id", entity.getId(), "name", entity.getDisplayName()));
+            }
+        }
+        return members;
+    }
+
+    private List<String> entitiesIdAsArray(Iterable<? extends Entity> entities) {
+        List<String> ids = Lists.newArrayList();
+        for (Entity entity : entities) {
+            if (Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY, entity)) {
+                ids.add(entity.getId());
+            }
+        }
+        return ids;
+    }
+
+    @Override
+    public List<EntityDetail> fetch(String entityIds) {
+
+        List<EntityDetail> entitySummaries = Lists.newArrayList();
+        for (Entity application : mgmt().getApplications()) {
+            entitySummaries.add(fromEntity(application));
+        }
+
+        if (entityIds != null) {
+            for (String entityId: entityIds.split(",")) {
+                Entity entity = mgmt().getEntityManager().getEntity(entityId.trim());
+                while (entity != null && entity.getParent() != null) {
+                    if (Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY, entity)) {
+                        entitySummaries.add(fromEntity(entity));
+                    }
+                    entity = entity.getParent();
+                }
+            }
+        }
+        return entitySummaries;
+    }
+
+    @Override
+    public List<ApplicationSummary> list(String typeRegex) {
+        if (Strings.isBlank(typeRegex)) {
+            typeRegex = ".*";
+        }
+        return FluentIterable
+                .from(mgmt().getApplications())
+                .filter(EntitlementPredicates.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY))
+                .filter(EntityPredicates.hasInterfaceMatching(typeRegex))
+                .transform(ApplicationTransformer.fromApplication(ui.getBaseUriBuilder()))
+                .toList();
+    }
+
+    @Override
+    public ApplicationSummary get(String application) {
+        return ApplicationTransformer.summaryFromApplication(brooklyn().getApplication(application), ui.getBaseUriBuilder());
+    }
+
+    @Override
+    public Response create(ApplicationSpec applicationSpec) {
+        return createFromAppSpec(applicationSpec);
+    }
+
+    /** @deprecated since 0.7.0 see #create */ @Deprecated
+    protected Response createFromAppSpec(ApplicationSpec applicationSpec) {
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.DEPLOY_APPLICATION, applicationSpec)) {
+            throw WebResourceUtils.forbidden("User '%s' is not authorized to start application %s",
+                Entitlements.getEntitlementContext().user(), applicationSpec);
+        }
+
+        checkApplicationTypesAreValid(applicationSpec);
+        checkLocationsAreValid(applicationSpec);
+        // TODO duplicate prevention
+        List<Location> locations = brooklyn().getLocations(applicationSpec);
+        Application app = brooklyn().create(applicationSpec);
+        Task<?> t = brooklyn().start(app, locations);
+        TaskSummary ts = TaskTransformer.fromTask(ui.getBaseUriBuilder()).apply(t);
+        URI ref = serviceAbsoluteUriBuilder(uriInfo.getBaseUriBuilder(), ApplicationApi.class, "get")
+                .build(app.getApplicationId());
+        return created(ref).entity(ts).build();
+    }
+
+    @Override
+    public Response createFromYaml(String yaml) {
+        // First of all, see if it's a URL
+        URI uri;
+        try {
+            uri = new URI(yaml);
+        } catch (URISyntaxException e) {
+            // It's not a URI then...
+            uri = null;
+        }
+        if (uri != null) {
+            log.debug("Create app called with URI; retrieving contents: {}", uri);
+            yaml = ResourceUtils.create(mgmt()).getResourceAsString(uri.toString());
+        }
+
+        log.debug("Creating app from yaml:\n{}", yaml);
+        EntitySpec<? extends Application> spec = createEntitySpecForApplication(yaml);
+
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.DEPLOY_APPLICATION, spec)) {
+            throw WebResourceUtils.forbidden("User '%s' is not authorized to start application %s",
+                Entitlements.getEntitlementContext().user(), yaml);
+        }
+
+        return launch(yaml, spec);
+    }
+
+    private Response launch(String yaml, EntitySpec<? extends Application> spec) {
+        try {
+            Application app = EntityManagementUtils.createUnstarted(mgmt(), spec);
+            CreationResult<Application,Void> result = EntityManagementUtils.start(app);
+
+            boolean isEntitled = Entitlements.isEntitled(
+                    mgmt().getEntitlementManager(),
+                    Entitlements.INVOKE_EFFECTOR,
+                    EntityAndItem.of(app, StringAndArgument.of(Startable.START.getName(), null)));
+
+            if (!isEntitled) {
+                throw WebResourceUtils.forbidden("User '%s' is not authorized to start application %s",
+                    Entitlements.getEntitlementContext().user(), spec.getType());
+            }
+
+            log.info("Launched from YAML: " + yaml + " -> " + app + " (" + result.task() + ")");
+
+            URI ref = serviceAbsoluteUriBuilder(ui.getBaseUriBuilder(), ApplicationApi.class, "get").build(app.getApplicationId());
+            ResponseBuilder response = created(ref);
+            if (result.task() != null)
+                response.entity(TaskTransformer.fromTask(ui.getBaseUriBuilder()).apply(result.task()));
+            return response.build();
+        } catch (ConstraintViolationException e) {
+            throw new UserFacingException(e);
+        } catch (Exception e) {
+            throw Exceptions.propagate(e);
+        }
+    }
+
+    @Override
+    public Response createPoly(byte[] inputToAutodetectType) {
+        log.debug("Creating app from autodetecting input");
+
+        boolean looksLikeLegacy = false;
+        Exception legacyFormatException = null;
+        // attempt legacy format
+        try {
+            ApplicationSpec appSpec = mapper().readValue(inputToAutodetectType, ApplicationSpec.class);
+            if (appSpec.getType() != null || appSpec.getEntities() != null) {
+                looksLikeLegacy = true;
+            }
+            return createFromAppSpec(appSpec);
+        } catch (Exception e) {
+            Exceptions.propagateIfFatal(e);
+            legacyFormatException = e;
+            log.debug("Input is not legacy ApplicationSpec JSON (will try others): "+e, e);
+        }
+
+        //TODO infer encoding from request
+        String potentialYaml = new String(inputToAutodetectType);
+        EntitySpec<? extends Application> spec = createEntitySpecForApplication(potentialYaml);
+
+        // TODO not json - try ZIP, etc
+
+        if (spec != null) {
+            return launch(potentialYaml, spec);
+        } else if (looksLikeLegacy) {
+            throw Throwables.propagate(legacyFormatException);
+        } else {
+            return Response.serverError().entity("Unsupported format; not able to autodetect.").build();
+        }
+    }
+
+    @Override
+    public Response createFromForm(String contents) {
+        log.debug("Creating app from form");
+        return createPoly(contents.getBytes());
+    }
+
+    @Override
+    public Response delete(String application) {
+        Application app = brooklyn().getApplication(application);
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.INVOKE_EFFECTOR, Entitlements.EntityAndItem.of(app,
+            StringAndArgument.of(Entitlements.LifecycleEffectors.DELETE, null)))) {
+            throw WebResourceUtils.forbidden("User '%s' is not authorized to delete application %s",
+                Entitlements.getEntitlementContext().user(), app);
+        }
+        Task<?> t = brooklyn().destroy(app);
+        TaskSummary ts = TaskTransformer.fromTask(ui.getBaseUriBuilder()).apply(t);
+        return status(ACCEPTED).entity(ts).build();
+    }
+
+    private EntitySpec<? extends Application> createEntitySpecForApplication(String potentialYaml) {
+        try {
+            return EntityManagementUtils.createEntitySpecForApplication(mgmt(), potentialYaml);
+        } catch (Exception e) {
+            // An IllegalArgumentException for creating the entity spec gets wrapped in a ISE, and possibly a Compound.
+            // But we want to return a 400 rather than 500, so ensure we throw IAE.
+            IllegalArgumentException iae = (IllegalArgumentException) Exceptions.getFirstThrowableOfType(e, IllegalArgumentException.class);
+            if (iae != null) {
+                throw new IllegalArgumentException("Cannot create spec for app: "+iae.getMessage(), e);
+            } else {
+                throw Exceptions.propagate(e);
+            }
+        }
+    }
+
+    private void checkApplicationTypesAreValid(ApplicationSpec applicationSpec) {
+        String appType = applicationSpec.getType();
+        if (appType != null) {
+            checkEntityTypeIsValid(appType);
+
+            if (applicationSpec.getEntities() != null) {
+                throw WebResourceUtils.preconditionFailed("Application given explicit type '%s' must not define entities", appType);
+            }
+            return;
+        }
+
+        for (org.apache.brooklyn.rest.domain.EntitySpec entitySpec : applicationSpec.getEntities()) {
+            String entityType = entitySpec.getType();
+            checkEntityTypeIsValid(checkNotNull(entityType, "entityType"));
+        }
+    }
+
+    private void checkSpecTypeIsValid(String type, Class<? extends BrooklynObject> subType) {
+        Maybe<RegisteredType> typeV = RegisteredTypes.tryValidate(mgmt().getTypeRegistry().get(type), RegisteredTypeLoadingContexts.spec(subType));
+        if (!typeV.isNull()) {
+            // found, throw if any problem
+            typeV.get();
+            return;
+        }
+
+        // not found, try classloading
+        try {
+            brooklyn().getCatalogClassLoader().loadClass(type);
+        } catch (ClassNotFoundException e) {
+            log.debug("Class not found for type '" + type + "'; reporting 404", e);
+            throw WebResourceUtils.notFound("Undefined type '%s'", type);
+        }
+        log.info(JavaClassNames.simpleClassName(subType)+" type '{}' not defined in catalog but is on classpath; continuing", type);
+    }
+
+    private void checkEntityTypeIsValid(String type) {
+        checkSpecTypeIsValid(type, Entity.class);
+    }
+
+    @SuppressWarnings("deprecation")
+    private void checkLocationsAreValid(ApplicationSpec applicationSpec) {
+        for (String locationId : applicationSpec.getLocations()) {
+            locationId = BrooklynRestResourceUtils.fixLocation(locationId);
+            if (!brooklyn().getLocationRegistry().canMaybeResolve(locationId) && brooklyn().getLocationRegistry().getDefinedLocationById(locationId)==null) {
+                throw WebResourceUtils.notFound("Undefined location '%s'", locationId);
+            }
+        }
+    }
+
+    @Override
+    public List<EntitySummary> getDescendants(String application, String typeRegex) {
+        return EntityTransformer.entitySummaries(brooklyn().descendantsOfType(application, application, typeRegex), ui.getBaseUriBuilder());
+    }
+
+    @Override
+    public Map<String, Object> getDescendantsSensor(String application, String sensor, String typeRegex) {
+        Iterable<Entity> descs = brooklyn().descendantsOfType(application, application, typeRegex);
+        return getSensorMap(sensor, descs);
+    }
+
+    public static Map<String, Object> getSensorMap(String sensor, Iterable<Entity> descs) {
+        if (Iterables.isEmpty(descs))
+            return Collections.emptyMap();
+        Map<String, Object> result = MutableMap.of();
+        Iterator<Entity> di = descs.iterator();
+        Sensor<?> s = null;
+        while (di.hasNext()) {
+            Entity potentialSource = di.next();
+            s = potentialSource.getEntityType().getSensor(sensor);
+            if (s!=null) break;
+        }
+        if (s==null)
+            s = Sensors.newSensor(Object.class, sensor);
+        if (!(s instanceof AttributeSensor<?>)) {
+            log.warn("Cannot retrieve non-attribute sensor "+s+" for entities; returning empty map");
+            return result;
+        }
+        for (Entity e: descs) {
+            Object v = null;
+            try {
+                v = e.getAttribute((AttributeSensor<?>)s);
+            } catch (Exception exc) {
+                Exceptions.propagateIfFatal(exc);
+                log.warn("Error retrieving sensor "+s+" for "+e+" (ignoring): "+exc);
+            }
+            if (v!=null)
+                result.put(e.getId(), v);
+        }
+        return result;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java
new file mode 100644
index 0000000..d7b206a
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java
@@ -0,0 +1,509 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
+import org.apache.brooklyn.api.catalog.CatalogItem;
+import org.apache.brooklyn.api.entity.Application;
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.location.LocationSpec;
+import org.apache.brooklyn.api.policy.Policy;
+import org.apache.brooklyn.api.policy.PolicySpec;
+import org.apache.brooklyn.api.typereg.RegisteredType;
+import org.apache.brooklyn.core.catalog.CatalogPredicates;
+import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog;
+import org.apache.brooklyn.core.catalog.internal.CatalogDto;
+import org.apache.brooklyn.core.catalog.internal.CatalogItemComparator;
+import org.apache.brooklyn.core.catalog.internal.CatalogUtils;
+import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
+import org.apache.brooklyn.core.mgmt.entitlement.Entitlements.StringAndArgument;
+import org.apache.brooklyn.core.typereg.RegisteredTypeLoadingContexts;
+import org.apache.brooklyn.core.typereg.RegisteredTypePredicates;
+import org.apache.brooklyn.core.typereg.RegisteredTypes;
+import org.apache.brooklyn.rest.api.CatalogApi;
+import org.apache.brooklyn.rest.domain.ApiError;
+import org.apache.brooklyn.rest.domain.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.filter.HaHotStateRequired;
+import org.apache.brooklyn.rest.transform.CatalogTransformer;
+import org.apache.brooklyn.rest.util.WebResourceUtils;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.collections.MutableSet;
+import org.apache.brooklyn.util.core.ResourceUtils;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.text.StringPredicates;
+import org.apache.brooklyn.util.text.Strings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.io.Files;
+import javax.ws.rs.core.UriInfo;
+import org.apache.brooklyn.util.guava.Maybe;
+
+@HaHotStateRequired
+public class CatalogResource extends AbstractBrooklynRestResource implements CatalogApi {
+
+    private static final Logger log = LoggerFactory.getLogger(CatalogResource.class);
+    
+    @SuppressWarnings("rawtypes")
+    private Function<CatalogItem, CatalogItemSummary> toCatalogItemSummary(final UriInfo ui) {
+        return new Function<CatalogItem, CatalogItemSummary>() {
+            @Override
+            public CatalogItemSummary apply(@Nullable CatalogItem input) {
+                return CatalogTransformer.catalogItemSummary(brooklyn(), input, ui.getBaseUriBuilder());
+            }
+        };
+    };
+
+    static Set<String> missingIcons = MutableSet.of();
+    
+    @Override
+    public Response create(String yaml) {
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.ADD_CATALOG_ITEM, yaml)) {
+            throw WebResourceUtils.forbidden("User '%s' is not authorized to add catalog item",
+                Entitlements.getEntitlementContext().user());
+        }
+        
+        Iterable<? extends CatalogItem<?, ?>> items; 
+        try {
+            items = brooklyn().getCatalog().addItems(yaml);
+        } catch (IllegalArgumentException e) {
+            return Response.status(Status.BAD_REQUEST)
+                    .type(MediaType.APPLICATION_JSON)
+                    .entity(ApiError.of(e))
+                    .build();
+        }
+
+        log.info("REST created catalog items: "+items);
+
+        Map<String,Object> result = MutableMap.of();
+        
+        for (CatalogItem<?,?> item: items) {
+            result.put(item.getId(), CatalogTransformer.catalogItemSummary(brooklyn(), item, ui.getBaseUriBuilder()));
+        }
+        return Response.status(Status.CREATED).entity(result).build();
+    }
+
+    @SuppressWarnings("deprecation")
+    @Override
+    public Response resetXml(String xml, boolean ignoreErrors) {
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_CATALOG_ITEM, null) ||
+            !Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.ADD_CATALOG_ITEM, null)) {
+            throw WebResourceUtils.forbidden("User '%s' is not authorized to modify catalog",
+                Entitlements.getEntitlementContext().user());
+        }
+
+        ((BasicBrooklynCatalog)mgmt().getCatalog()).reset(CatalogDto.newDtoFromXmlContents(xml, "REST reset"), !ignoreErrors);
+        return Response.ok().build();
+    }
+    
+    @Override
+    @Deprecated
+    public void deleteEntity_0_7_0(String entityId) throws Exception {
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_CATALOG_ITEM, StringAndArgument.of(entityId, "delete"))) {
+            throw WebResourceUtils.forbidden("User '%s' is not authorized to modify catalog",
+                Entitlements.getEntitlementContext().user());
+        }
+        try {
+            Maybe<RegisteredType> item = RegisteredTypes.tryValidate(mgmt().getTypeRegistry().get(entityId), RegisteredTypeLoadingContexts.spec(Entity.class));
+            if (item.isNull()) {
+                throw WebResourceUtils.notFound("Entity with id '%s' not found", entityId);
+            }
+            if (item.isAbsent()) {
+                throw WebResourceUtils.notFound("Item with id '%s' is not an entity", entityId);
+            }
+
+            brooklyn().getCatalog().deleteCatalogItem(item.get().getSymbolicName(), item.get().getVersion());
+
+        } catch (NoSuchElementException e) {
+            // shouldn't come here
+            throw WebResourceUtils.notFound("Entity with id '%s' could not be deleted", entityId);
+
+        }
+    }
+
+    @Override
+    public void deleteApplication(String symbolicName, String version) throws Exception {
+        deleteEntity(symbolicName, version);
+    }
+
+    @Override
+    public void deleteEntity(String symbolicName, String version) throws Exception {
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_CATALOG_ITEM, StringAndArgument.of(symbolicName+(Strings.isBlank(version) ? "" : ":"+version), "delete"))) {
+            throw WebResourceUtils.forbidden("User '%s' is not authorized to modify catalog",
+                Entitlements.getEntitlementContext().user());
+        }
+        
+        RegisteredType item = mgmt().getTypeRegistry().get(symbolicName, version);
+        if (item == null) {
+            throw WebResourceUtils.notFound("Entity with id '%s:%s' not found", symbolicName, version);
+        } else if (!RegisteredTypePredicates.IS_ENTITY.apply(item) && !RegisteredTypePredicates.IS_APPLICATION.apply(item)) {
+            throw WebResourceUtils.preconditionFailed("Item with id '%s:%s' not an entity", symbolicName, version);
+        } else {
+            brooklyn().getCatalog().deleteCatalogItem(item.getSymbolicName(), item.getVersion());
+        }
+    }
+
+    @Override
+    public void deletePolicy(String policyId, String version) throws Exception {
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_CATALOG_ITEM, StringAndArgument.of(policyId+(Strings.isBlank(version) ? "" : ":"+version), "delete"))) {
+            throw WebResourceUtils.forbidden("User '%s' is not authorized to modify catalog",
+                Entitlements.getEntitlementContext().user());
+        }
+        
+        RegisteredType item = mgmt().getTypeRegistry().get(policyId, version);
+        if (item == null) {
+            throw WebResourceUtils.notFound("Policy with id '%s:%s' not found", policyId, version);
+        } else if (!RegisteredTypePredicates.IS_POLICY.apply(item)) {
+            throw WebResourceUtils.preconditionFailed("Item with id '%s:%s' not a policy", policyId, version);
+        } else {
+            brooklyn().getCatalog().deleteCatalogItem(item.getSymbolicName(), item.getVersion());
+        }
+    }
+
+    @Override
+    public void deleteLocation(String locationId, String version) throws Exception {
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_CATALOG_ITEM, StringAndArgument.of(locationId+(Strings.isBlank(version) ? "" : ":"+version), "delete"))) {
+            throw WebResourceUtils.forbidden("User '%s' is not authorized to modify catalog",
+                Entitlements.getEntitlementContext().user());
+        }
+        
+        RegisteredType item = mgmt().getTypeRegistry().get(locationId, version);
+        if (item == null) {
+            throw WebResourceUtils.notFound("Location with id '%s:%s' not found", locationId, version);
+        } else if (!RegisteredTypePredicates.IS_LOCATION.apply(item)) {
+            throw WebResourceUtils.preconditionFailed("Item with id '%s:%s' not a location", locationId, version);
+        } else {
+            brooklyn().getCatalog().deleteCatalogItem(item.getSymbolicName(), item.getVersion());
+        }
+    }
+
+    @Override
+    public List<CatalogEntitySummary> listEntities(String regex, String fragment, boolean allVersions) {
+        Predicate<CatalogItem<Entity, EntitySpec<?>>> filter =
+                Predicates.and(
+                        CatalogPredicates.IS_ENTITY,
+                        CatalogPredicates.<Entity, EntitySpec<?>>disabled(false));
+        List<CatalogItemSummary> result = getCatalogItemSummariesMatchingRegexFragment(filter, regex, fragment, allVersions);
+        return castList(result, CatalogEntitySummary.class);
+    }
+
+    @Override
+    public List<CatalogItemSummary> listApplications(String regex, String fragment, boolean allVersions) {
+        @SuppressWarnings("unchecked")
+        Predicate<CatalogItem<Application, EntitySpec<? extends Application>>> filter =
+                Predicates.and(
+                        CatalogPredicates.IS_TEMPLATE,
+                        CatalogPredicates.<Application,EntitySpec<? extends Application>>deprecated(false),
+                        CatalogPredicates.<Application,EntitySpec<? extends Application>>disabled(false));
+        return getCatalogItemSummariesMatchingRegexFragment(filter, regex, fragment, allVersions);
+    }
+
+    @Override
+    @Deprecated
+    public CatalogEntitySummary getEntity_0_7_0(String entityId) {
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CATALOG_ITEM, entityId)) {
+            throw WebResourceUtils.forbidden("User '%s' is not authorized to see catalog entry",
+                Entitlements.getEntitlementContext().user());
+        }
+
+        CatalogItem<Entity,EntitySpec<?>> result =
+                CatalogUtils.getCatalogItemOptionalVersion(mgmt(), Entity.class, entityId);
+
+        if (result==null) {
+            throw WebResourceUtils.notFound("Entity with id '%s' not found", entityId);
+        }
+
+        return CatalogTransformer.catalogEntitySummary(brooklyn(), result, ui.getBaseUriBuilder());
+    }
+    
+    @Override
+    public CatalogEntitySummary getEntity(String symbolicName, String version) {
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CATALOG_ITEM, symbolicName+(Strings.isBlank(version)?"":":"+version))) {
+            throw WebResourceUtils.forbidden("User '%s' is not authorized to see catalog entry",
+                Entitlements.getEntitlementContext().user());
+        }
+
+        //TODO These casts are not pretty, we could just provide separate get methods for the different types?
+        //Or we could provide asEntity/asPolicy cast methods on the CataloItem doing a safety check internally
+        @SuppressWarnings("unchecked")
+        CatalogItem<Entity, EntitySpec<?>> result =
+              (CatalogItem<Entity, EntitySpec<?>>) brooklyn().getCatalog().getCatalogItem(symbolicName, version);
+
+        if (result==null) {
+            throw WebResourceUtils.notFound("Entity with id '%s:%s' not found", symbolicName, version);
+        }
+
+        return CatalogTransformer.catalogEntitySummary(brooklyn(), result, ui.getBaseUriBuilder());
+    }
+
+    @Override
+    @Deprecated
+    public CatalogEntitySummary getApplication_0_7_0(String applicationId) throws Exception {
+        return getEntity_0_7_0(applicationId);
+    }
+
+    @Override
+    public CatalogEntitySummary getApplication(String symbolicName, String version) {
+        return getEntity(symbolicName, version);
+    }
+
+    @Override
+    public List<CatalogPolicySummary> listPolicies(String regex, String fragment, boolean allVersions) {
+        Predicate<CatalogItem<Policy, PolicySpec<?>>> filter =
+                Predicates.and(
+                        CatalogPredicates.IS_POLICY,
+                        CatalogPredicates.<Policy, PolicySpec<?>>disabled(false));
+        List<CatalogItemSummary> result = getCatalogItemSummariesMatchingRegexFragment(filter, regex, fragment, allVersions);
+        return castList(result, CatalogPolicySummary.class);
+    }
+
+    @Override
+    @Deprecated
+    public CatalogPolicySummary getPolicy_0_7_0(String policyId) {
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CATALOG_ITEM, policyId)) {
+            throw WebResourceUtils.forbidden("User '%s' is not authorized to see catalog entry",
+                Entitlements.getEntitlementContext().user());
+        }
+
+        CatalogItem<? extends Policy, PolicySpec<?>> result =
+            CatalogUtils.getCatalogItemOptionalVersion(mgmt(), Policy.class, policyId);
+
+        if (result==null) {
+            throw WebResourceUtils.notFound("Policy with id '%s' not found", policyId);
+        }
+
+        return CatalogTransformer.catalogPolicySummary(brooklyn(), result, ui.getBaseUriBuilder());
+    }
+
+    @Override
+    public CatalogPolicySummary getPolicy(String policyId, String version) throws Exception {
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CATALOG_ITEM, policyId+(Strings.isBlank(version)?"":":"+version))) {
+            throw WebResourceUtils.forbidden("User '%s' is not authorized to see catalog entry",
+                Entitlements.getEntitlementContext().user());
+        }
+
+        @SuppressWarnings("unchecked")
+        CatalogItem<? extends Policy, PolicySpec<?>> result =
+                (CatalogItem<? extends Policy, PolicySpec<?>>)brooklyn().getCatalog().getCatalogItem(policyId, version);
+
+        if (result==null) {
+          throw WebResourceUtils.notFound("Policy with id '%s:%s' not found", policyId, version);
+        }
+
+        return CatalogTransformer.catalogPolicySummary(brooklyn(), result, ui.getBaseUriBuilder());
+    }
+
+    @Override
+    public List<CatalogLocationSummary> listLocations(String regex, String fragment, boolean allVersions) {
+        Predicate<CatalogItem<Location, LocationSpec<?>>> filter =
+                Predicates.and(
+                        CatalogPredicates.IS_LOCATION,
+                        CatalogPredicates.<Location, LocationSpec<?>>disabled(false));
+        List<CatalogItemSummary> result = getCatalogItemSummariesMatchingRegexFragment(filter, regex, fragment, allVersions);
+        return castList(result, CatalogLocationSummary.class);
+    }
+
+    @Override
+    @Deprecated
+    public CatalogLocationSummary getLocation_0_7_0(String locationId) {
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CATALOG_ITEM, locationId)) {
+            throw WebResourceUtils.forbidden("User '%s' is not authorized to see catalog entry",
+                Entitlements.getEntitlementContext().user());
+        }
+
+        CatalogItem<? extends Location, LocationSpec<?>> result =
+            CatalogUtils.getCatalogItemOptionalVersion(mgmt(), Location.class, locationId);
+
+        if (result==null) {
+            throw WebResourceUtils.notFound("Location with id '%s' not found", locationId);
+        }
+
+        return CatalogTransformer.catalogLocationSummary(brooklyn(), result, ui.getBaseUriBuilder());
+    }
+
+    @Override
+    public CatalogLocationSummary getLocation(String locationId, String version) throws Exception {
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CATALOG_ITEM, locationId+(Strings.isBlank(version)?"":":"+version))) {
+            throw WebResourceUtils.forbidden("User '%s' is not authorized to see catalog entry",
+                Entitlements.getEntitlementContext().user());
+        }
+
+        @SuppressWarnings("unchecked")
+        CatalogItem<? extends Location, LocationSpec<?>> result =
+                (CatalogItem<? extends Location, LocationSpec<?>>)brooklyn().getCatalog().getCatalogItem(locationId, version);
+
+        if (result==null) {
+          throw WebResourceUtils.notFound("Location with id '%s:%s' not found", locationId, version);
+        }
+
+        return CatalogTransformer.catalogLocationSummary(brooklyn(), result, ui.getBaseUriBuilder());
+    }
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    private <T,SpecT> List<CatalogItemSummary> getCatalogItemSummariesMatchingRegexFragment(Predicate<CatalogItem<T,SpecT>> type, String regex, String fragment, boolean allVersions) {
+        List filters = new ArrayList();
+        filters.add(type);
+        if (Strings.isNonEmpty(regex))
+            filters.add(CatalogPredicates.xml(StringPredicates.containsRegex(regex)));
+        if (Strings.isNonEmpty(fragment))
+            filters.add(CatalogPredicates.xml(StringPredicates.containsLiteralIgnoreCase(fragment)));
+        if (!allVersions)
+            filters.add(CatalogPredicates.isBestVersion(mgmt()));
+        
+        filters.add(CatalogPredicates.entitledToSee(mgmt()));
+
+        ImmutableList<CatalogItem<Object, Object>> sortedItems =
+                FluentIterable.from(brooklyn().getCatalog().getCatalogItems())
+                    .filter(Predicates.and(filters))
+                    .toSortedList(CatalogItemComparator.getInstance());
+        return Lists.transform(sortedItems, toCatalogItemSummary(ui));
+    }
+
+    @Override
+    @Deprecated
+    public Response getIcon_0_7_0(String itemId) {
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CATALOG_ITEM, itemId)) {
+            throw WebResourceUtils.forbidden("User '%s' is not authorized to see catalog entry",
+                Entitlements.getEntitlementContext().user());
+        }
+
+        return getCatalogItemIcon( mgmt().getTypeRegistry().get(itemId) );
+    }
+
+    @Override
+    public Response getIcon(String itemId, String version) {
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CATALOG_ITEM, itemId+(Strings.isBlank(version)?"":":"+version))) {
+            throw WebResourceUtils.forbidden("User '%s' is not authorized to see catalog entry",
+                Entitlements.getEntitlementContext().user());
+        }
+        
+        return getCatalogItemIcon(mgmt().getTypeRegistry().get(itemId, version));
+    }
+
+    @Override
+    public void setDeprecatedLegacy(String itemId, boolean deprecated) {
+        log.warn("Use of deprecated \"/catalog/entities/{itemId}/deprecated/{deprecated}\" for "+itemId
+                +"; use \"/catalog/entities/{itemId}/deprecated\" with request body");
+        setDeprecated(itemId, deprecated);
+    }
+    
+    @SuppressWarnings("deprecation")
+    @Override
+    public void setDeprecated(String itemId, boolean deprecated) {
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_CATALOG_ITEM, StringAndArgument.of(itemId, "deprecated"))) {
+            throw WebResourceUtils.forbidden("User '%s' is not authorized to modify catalog",
+                    Entitlements.getEntitlementContext().user());
+        }
+        CatalogUtils.setDeprecated(mgmt(), itemId, deprecated);
+    }
+
+    @SuppressWarnings("deprecation")
+    @Override
+    public void setDisabled(String itemId, boolean disabled) {
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_CATALOG_ITEM, StringAndArgument.of(itemId, "disabled"))) {
+            throw WebResourceUtils.forbidden("User '%s' is not authorized to modify catalog",
+                    Entitlements.getEntitlementContext().user());
+        }
+        CatalogUtils.setDisabled(mgmt(), itemId, disabled);
+    }
+
+    private Response getCatalogItemIcon(RegisteredType result) {
+        String url = result.getIconUrl();
+        if (url==null) {
+            log.debug("No icon available for "+result+"; returning "+Status.NO_CONTENT);
+            return Response.status(Status.NO_CONTENT).build();
+        }
+        
+        if (brooklyn().isUrlServerSideAndSafe(url)) {
+            // classpath URL's we will serve IF they end with a recognised image format;
+            // paths (ie non-protocol) and 
+            // NB, for security, file URL's are NOT served
+            log.debug("Loading and returning "+url+" as icon for "+result);
+            
+            MediaType mime = WebResourceUtils.getImageMediaTypeFromExtension(Files.getFileExtension(url));
+            try {
+                Object content = ResourceUtils.create(CatalogUtils.newClassLoadingContext(mgmt(), result)).getResourceFromUrl(url);
+                return Response.ok(content, mime).build();
+            } catch (Exception e) {
+                Exceptions.propagateIfFatal(e);
+                synchronized (missingIcons) {
+                    if (missingIcons.add(url)) {
+                        // note: this can be quite common when running from an IDE, as resources may not be copied;
+                        // a mvn build should sort it out (the IDE will then find the resources, until you clean or maybe refresh...)
+                        log.warn("Missing icon data for "+result.getId()+", expected at: "+url+" (subsequent messages will log debug only)");
+                        log.debug("Trace for missing icon data at "+url+": "+e, e);
+                    } else {
+                        log.debug("Missing icon data for "+result.getId()+", expected at: "+url+" (already logged WARN and error details)");
+                    }
+                }
+                throw WebResourceUtils.notFound("Icon unavailable for %s", result.getId());
+            }
+        }
+        
+        log.debug("Returning redirect to "+url+" as icon for "+result);
+        
+        // for anything else we do a redirect (e.g. http / https; perhaps ftp)
+        return Response.temporaryRedirect(URI.create(url)).build();
+    }
+
+    // TODO Move to an appropriate utility class?
+    @SuppressWarnings("unchecked")
+    private static <T> List<T> castList(List<? super T> list, Class<T> elementType) {
+        List<T> result = Lists.newArrayList();
+        Iterator<? super T> li = list.iterator();
+        while (li.hasNext()) {
+            try {
+                result.add((T) li.next());
+            } catch (Throwable throwable) {
+                if (throwable instanceof NoClassDefFoundError) {
+                    // happens if class cannot be loaded for any reason during transformation - don't treat as fatal
+                } else {
+                    Exceptions.propagateIfFatal(throwable);
+                }
+                
+                // item cannot be transformed; we will have logged a warning earlier
+                log.debug("Ignoring invalid catalog item: "+throwable);
+            }
+        }
+        return result;
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/EffectorResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/EffectorResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/EffectorResource.java
new file mode 100644
index 0000000..22661bd
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/EffectorResource.java
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import javax.annotation.Nullable;
+import javax.ws.rs.core.Response;
+
+import org.apache.brooklyn.api.effector.Effector;
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.mgmt.Task;
+import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
+import org.apache.brooklyn.core.mgmt.entitlement.Entitlements.StringAndArgument;
+import org.apache.brooklyn.core.mgmt.internal.EffectorUtils;
+import org.apache.brooklyn.rest.api.EffectorApi;
+import org.apache.brooklyn.rest.domain.EffectorSummary;
+import org.apache.brooklyn.rest.domain.SummaryComparators;
+import org.apache.brooklyn.rest.filter.HaHotStateRequired;
+import org.apache.brooklyn.rest.transform.EffectorTransformer;
+import org.apache.brooklyn.rest.transform.TaskTransformer;
+import org.apache.brooklyn.rest.util.WebResourceUtils;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.guava.Maybe;
+import org.apache.brooklyn.util.time.Time;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.collect.FluentIterable;
+
+@HaHotStateRequired
+public class EffectorResource extends AbstractBrooklynRestResource implements EffectorApi {
+
+    private static final Logger log = LoggerFactory.getLogger(EffectorResource.class);
+
+    @Override
+    public List<EffectorSummary> list(final String application, final String entityToken) {
+        final Entity entity = brooklyn().getEntity(application, entityToken);
+        return FluentIterable
+                .from(entity.getEntityType().getEffectors())
+                .filter(new Predicate<Effector<?>>() {
+                    @Override
+                    public boolean apply(@Nullable Effector<?> input) {
+                        return Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.INVOKE_EFFECTOR,
+                                Entitlements.EntityAndItem.of(entity, StringAndArgument.of(input.getName(), null)));
+                    }
+                })
+                .transform(new Function<Effector<?>, EffectorSummary>() {
+                    @Override
+                    public EffectorSummary apply(Effector<?> effector) {
+                        return EffectorTransformer.effectorSummary(entity, effector, ui.getBaseUriBuilder());
+                    }
+                })
+                .toSortedList(SummaryComparators.nameComparator());
+    }
+
+    @Override
+    public Response invoke(String application, String entityToken, String effectorName,
+            String timeout, Map<String, Object> parameters) {
+        final Entity entity = brooklyn().getEntity(application, entityToken);
+
+        // TODO check effectors?
+        Maybe<Effector<?>> effector = EffectorUtils.findEffectorDeclared(entity, effectorName);
+        if (effector.isAbsentOrNull()) {
+            throw WebResourceUtils.notFound("Entity '%s' has no effector with name '%s'", entityToken, effectorName);
+        } else if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.INVOKE_EFFECTOR,
+                Entitlements.EntityAndItem.of(entity, StringAndArgument.of(effector.get().getName(), null)))) {
+            throw WebResourceUtils.forbidden("User '%s' is not authorized to invoke effector %s on entity %s",
+                    Entitlements.getEntitlementContext().user(), effector.get().getName(), entity);
+        }
+        log.info("REST invocation of " + entity + "." + effector.get() + " " + parameters);
+        Task<?> t = entity.invoke(effector.get(), parameters);
+
+        try {
+            Object result;
+            if (timeout == null || timeout.isEmpty() || "never".equalsIgnoreCase(timeout)) {
+                result = t.get();
+            } else {
+                long timeoutMillis = "always".equalsIgnoreCase(timeout) ? 0 : Time.parseElapsedTime(timeout);
+                try {
+                    if (timeoutMillis == 0) throw new TimeoutException();
+                    result = t.get(timeoutMillis, TimeUnit.MILLISECONDS);
+                } catch (TimeoutException e) {
+                    result = TaskTransformer.taskSummary(t, ui.getBaseUriBuilder());
+                }
+            }
+            return Response.status(Response.Status.ACCEPTED).entity(result).build();
+        } 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/resources/EntityConfigResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/EntityConfigResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/EntityConfigResource.java
new file mode 100644
index 0000000..4c3c9d2
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/EntityConfigResource.java
@@ -0,0 +1,206 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.BasicConfigKey;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.entity.EntityInternal;
+import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
+import org.apache.brooklyn.core.mgmt.entitlement.Entitlements.EntityAndItem;
+import org.apache.brooklyn.rest.api.EntityConfigApi;
+import org.apache.brooklyn.rest.domain.EntityConfigSummary;
+import org.apache.brooklyn.rest.filter.HaHotStateRequired;
+import org.apache.brooklyn.rest.transform.EntityTransformer;
+import org.apache.brooklyn.rest.util.WebResourceUtils;
+import org.apache.brooklyn.util.core.flags.TypeCoercions;
+import org.apache.brooklyn.util.core.task.Tasks;
+import org.apache.brooklyn.util.core.task.ValueResolver;
+import org.apache.brooklyn.util.text.Strings;
+import org.apache.brooklyn.util.time.Duration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Predicates;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+@HaHotStateRequired
+public class EntityConfigResource extends AbstractBrooklynRestResource implements EntityConfigApi {
+
+    private static final Logger LOG = LoggerFactory.getLogger(EntityConfigResource.class);
+
+    @Override
+    public List<EntityConfigSummary> list(final String application, final String entityToken) {
+        final Entity entity = brooklyn().getEntity(application, entityToken);
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY, entity)) {
+            throw WebResourceUtils.forbidden("User '%s' is not authorized to see entity '%s'",
+                    Entitlements.getEntitlementContext().user(), entity);
+        }
+
+        // TODO merge with keys which have values:
+        //      ((EntityInternal) entity).config().getBag().getAllConfigAsConfigKeyMap();
+        List<EntityConfigSummary> result = Lists.newArrayList();
+        
+        for (ConfigKey<?> key : entity.getEntityType().getConfigKeys()) {
+            // Exclude config that user is not allowed to see
+            if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CONFIG, new EntityAndItem<String>(entity, key.getName()))) {
+                LOG.trace("User {} not authorized to see config {} of entity {}; excluding from ConfigKey list results", 
+                        new Object[] {Entitlements.getEntitlementContext().user(), key.getName(), entity});
+                continue;
+            }
+            result.add(EntityTransformer.entityConfigSummary(entity, key, ui.getBaseUriBuilder()));
+        }
+        
+        return result;
+    }
+
+    // TODO support parameters  ?show=value,summary&name=xxx &format={string,json,xml}
+    // (and in sensors class)
+    @Override
+    public Map<String, Object> batchConfigRead(String application, String entityToken, Boolean raw) {
+        // TODO: add test
+        Entity entity = brooklyn().getEntity(application, entityToken);
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY, entity)) {
+            throw WebResourceUtils.forbidden("User '%s' is not authorized to see entity '%s'",
+                    Entitlements.getEntitlementContext().user(), entity);
+        }
+
+        // wrap in a task for better runtime view
+        return Entities.submit(entity, Tasks.<Map<String,Object>>builder().displayName("REST API batch config read").body(new BatchConfigRead(mgmt(), this, entity, raw)).build()).getUnchecked();
+    }
+    
+    private static class BatchConfigRead implements Callable<Map<String,Object>> {
+        private final ManagementContext mgmt;
+        private final EntityConfigResource resource;
+        private final Entity entity;
+        private final Boolean raw;
+
+        public BatchConfigRead(ManagementContext mgmt, EntityConfigResource resource, Entity entity, Boolean raw) {
+            this.mgmt = mgmt;
+            this.resource = resource;
+            this.entity = entity;
+            this.raw = raw;
+        }
+
+        @Override
+        public Map<String, Object> call() throws Exception {
+            Map<ConfigKey<?>, ?> source = ((EntityInternal) entity).config().getBag().getAllConfigAsConfigKeyMap();
+            Map<String, Object> result = Maps.newLinkedHashMap();
+            for (Map.Entry<ConfigKey<?>, ?> ek : source.entrySet()) {
+                ConfigKey<?> key = ek.getKey();
+                Object value = ek.getValue();
+                
+                // Exclude config that user is not allowed to see
+                if (!Entitlements.isEntitled(mgmt.getEntitlementManager(), Entitlements.SEE_CONFIG, new EntityAndItem<String>(entity, ek.getKey().getName()))) {
+                    LOG.trace("User {} not authorized to see sensor {} of entity {}; excluding from current-state results", 
+                            new Object[] {Entitlements.getEntitlementContext().user(), ek.getKey().getName(), entity});
+                    continue;
+                }
+                result.put(key.getName(), 
+                    resource.resolving(value).preferJson(true).asJerseyOutermostReturnValue(false).raw(raw).context(entity).timeout(Duration.ZERO).renderAs(key).resolve());
+            }
+            return result;
+        }
+    }
+
+    @Override
+    public Object get(String application, String entityToken, String configKeyName, Boolean raw) {
+        return get(true, application, entityToken, configKeyName, raw);
+    }
+
+    @Override
+    public String getPlain(String application, String entityToken, String configKeyName, Boolean raw) {
+        return Strings.toString(get(false, application, entityToken, configKeyName, raw));
+    }
+
+    public Object get(boolean preferJson, String application, String entityToken, String configKeyName, Boolean raw) {
+        Entity entity = brooklyn().getEntity(application, entityToken);
+        ConfigKey<?> ck = findConfig(entity, configKeyName);
+        
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY, entity)) {
+            throw WebResourceUtils.forbidden("User '%s' is not authorized to see entity '%s'",
+                    Entitlements.getEntitlementContext().user(), entity);
+        }
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CONFIG, new EntityAndItem<String>(entity, ck.getName()))) {
+            throw WebResourceUtils.forbidden("User '%s' is not authorized to see entity '%s' config '%s'",
+                    Entitlements.getEntitlementContext().user(), entity, ck.getName());
+        }
+        
+        Object value = ((EntityInternal)entity).config().getRaw(ck).orNull();
+        return resolving(value).preferJson(preferJson).asJerseyOutermostReturnValue(true).raw(raw).context(entity).timeout(ValueResolver.PRETTY_QUICK_WAIT).renderAs(ck).resolve();
+    }
+
+    private ConfigKey<?> findConfig(Entity entity, String configKeyName) {
+        ConfigKey<?> ck = entity.getEntityType().getConfigKey(configKeyName);
+        if (ck == null)
+            ck = new BasicConfigKey<Object>(Object.class, configKeyName);
+        return ck;
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Override
+    public void setFromMap(String application, String entityToken, Boolean recurse, Map newValues) {
+        final Entity entity = brooklyn().getEntity(application, entityToken);
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_ENTITY, entity)) {
+            throw WebResourceUtils.forbidden("User '%s' is not authorized to modify entity '%s'",
+                    Entitlements.getEntitlementContext().user(), entity);
+        }
+
+        if (LOG.isDebugEnabled())
+            LOG.debug("REST user " + Entitlements.getEntitlementContext() + " setting configs " + newValues);
+        for (Object entry : newValues.entrySet()) {
+            String configName = Strings.toString(((Map.Entry) entry).getKey());
+            Object newValue = ((Map.Entry) entry).getValue();
+
+            ConfigKey ck = findConfig(entity, configName);
+            ((EntityInternal) entity).config().set(ck, TypeCoercions.coerce(newValue, ck.getTypeToken()));
+            if (Boolean.TRUE.equals(recurse)) {
+                for (Entity e2 : Entities.descendants(entity, Predicates.alwaysTrue(), false)) {
+                    ((EntityInternal) e2).config().set(ck, newValue);
+                }
+            }
+        }
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Override
+    public void set(String application, String entityToken, String configName, Boolean recurse, Object newValue) {
+        final Entity entity = brooklyn().getEntity(application, entityToken);
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_ENTITY, entity)) {
+            throw WebResourceUtils.forbidden("User '%s' is not authorized to modify entity '%s'",
+                    Entitlements.getEntitlementContext().user(), entity);
+        }
+
+        ConfigKey ck = findConfig(entity, configName);
+        LOG.debug("REST setting config " + configName + " on " + entity + " to " + newValue);
+        ((EntityInternal) entity).config().set(ck, TypeCoercions.coerce(newValue, ck.getTypeToken()));
+        if (Boolean.TRUE.equals(recurse)) {
+            for (Entity e2 : Entities.descendants(entity, Predicates.alwaysTrue(), false)) {
+                ((EntityInternal) e2).config().set(ck, newValue);
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/EntityResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/EntityResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/EntityResource.java
new file mode 100644
index 0000000..4d263da
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/EntityResource.java
@@ -0,0 +1,223 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import static javax.ws.rs.core.Response.created;
+import static javax.ws.rs.core.Response.status;
+import static javax.ws.rs.core.Response.Status.ACCEPTED;
+
+import java.net.URI;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.ResponseBuilder;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.mgmt.Task;
+import org.apache.brooklyn.core.mgmt.BrooklynTags;
+import org.apache.brooklyn.core.mgmt.BrooklynTags.NamedStringTag;
+import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
+import org.apache.brooklyn.core.mgmt.EntityManagementUtils;
+import org.apache.brooklyn.core.mgmt.EntityManagementUtils.CreationResult;
+import org.apache.brooklyn.core.mgmt.entitlement.EntitlementPredicates;
+import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
+import org.apache.brooklyn.rest.api.EntityApi;
+import org.apache.brooklyn.rest.domain.EntitySummary;
+import org.apache.brooklyn.rest.domain.LocationSummary;
+import org.apache.brooklyn.rest.domain.TaskSummary;
+import org.apache.brooklyn.rest.filter.HaHotStateRequired;
+import org.apache.brooklyn.rest.transform.EntityTransformer;
+import org.apache.brooklyn.rest.transform.LocationTransformer;
+import org.apache.brooklyn.rest.transform.LocationTransformer.LocationDetailLevel;
+import org.apache.brooklyn.rest.transform.TaskTransformer;
+import org.apache.brooklyn.rest.util.WebResourceUtils;
+import org.apache.brooklyn.util.collections.MutableList;
+import org.apache.brooklyn.util.core.ResourceUtils;
+import org.apache.brooklyn.util.time.Duration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.Collections2;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.io.Files;
+import static org.apache.brooklyn.rest.util.WebResourceUtils.serviceAbsoluteUriBuilder;
+
+@HaHotStateRequired
+public class EntityResource extends AbstractBrooklynRestResource implements EntityApi {
+
+    private static final Logger log = LoggerFactory.getLogger(EntityResource.class);
+
+    @Context
+    private UriInfo uriInfo;
+    
+    @Override
+    public List<EntitySummary> list(final String application) {
+        return FluentIterable
+                .from(brooklyn().getApplication(application).getChildren())
+                .filter(EntitlementPredicates.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY))
+                .transform(EntityTransformer.fromEntity(ui.getBaseUriBuilder()))
+                .toList();
+    }
+
+    @Override
+    public EntitySummary get(String application, String entityName) {
+        Entity entity = brooklyn().getEntity(application, entityName);
+        if (Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY, entity)) {
+            return EntityTransformer.entitySummary(entity, ui.getBaseUriBuilder());
+        }
+        throw WebResourceUtils.forbidden("User '%s' is not authorized to get entity '%s'",
+                Entitlements.getEntitlementContext().user(), entity);
+    }
+
+    @Override
+    public List<EntitySummary> getChildren(final String application, final String entity) {
+        return FluentIterable
+                .from(brooklyn().getEntity(application, entity).getChildren())
+                .filter(EntitlementPredicates.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY))
+                .transform(EntityTransformer.fromEntity(ui.getBaseUriBuilder()))
+                .toList();
+    }
+
+    @Override
+    public List<EntitySummary> getChildrenOld(String application, String entity) {
+        log.warn("Using deprecated call to /entities when /children should be used");
+        return getChildren(application, entity);
+    }
+
+    @Override
+    public Response addChildren(String applicationToken, String entityToken, Boolean start, String timeoutS, String yaml) {
+        final Entity parent = brooklyn().getEntity(applicationToken, entityToken);
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_ENTITY, parent)) {
+            throw WebResourceUtils.forbidden("User '%s' is not authorized to modify entity '%s'",
+                    Entitlements.getEntitlementContext().user(), entityToken);
+        }
+        CreationResult<List<Entity>, List<String>> added = EntityManagementUtils.addChildren(parent, yaml, start)
+                .blockUntilComplete(timeoutS==null ? Duration.millis(20) : Duration.of(timeoutS));
+        ResponseBuilder response;
+        
+        if (added.get().size()==1) {
+            Entity child = Iterables.getOnlyElement(added.get());
+            URI ref = serviceAbsoluteUriBuilder(uriInfo.getBaseUriBuilder(), EntityApi.class, "get")
+                    .build(child.getApplicationId(), child.getId());
+            response = created(ref);
+        } else {
+            response = Response.status(Status.CREATED);
+        }
+        return response.entity(TaskTransformer.taskSummary(added.task(), ui.getBaseUriBuilder())).build();
+    }
+
+    @Override
+    public List<TaskSummary> listTasks(String applicationId, String entityId) {
+        Entity entity = brooklyn().getEntity(applicationId, entityId);
+        Set<Task<?>> tasks = BrooklynTaskTags.getTasksInEntityContext(mgmt().getExecutionManager(), entity);
+        return new LinkedList<TaskSummary>(Collections2.transform(tasks, 
+                TaskTransformer.fromTask(ui.getBaseUriBuilder())));
+    }
+
+    @Override
+    public TaskSummary getTask(final String application, final String entityToken, String taskId) {
+        // TODO deprecate in favour of ActivityApi.get ?
+        Task<?> t = mgmt().getExecutionManager().getTask(taskId);
+        if (t == null)
+            throw WebResourceUtils.notFound("Cannot find task '%s'", taskId);
+        return TaskTransformer.fromTask(ui.getBaseUriBuilder()).apply(t);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public List<Object> listTags(String applicationId, String entityId) {
+        Entity entity = brooklyn().getEntity(applicationId, entityId);
+        return (List<Object>) resolving(MutableList.copyOf(entity.tags().getTags())).preferJson(true).resolve();
+    }
+
+    @Override
+    public Response getIcon(String applicationId, String entityId) {
+        Entity entity = brooklyn().getEntity(applicationId, entityId);
+        String url = entity.getIconUrl();
+        if (url == null)
+            return Response.status(Status.NO_CONTENT).build();
+
+        if (brooklyn().isUrlServerSideAndSafe(url)) {
+            // classpath URL's we will serve IF they end with a recognised image format;
+            // paths (ie non-protocol) and
+            // NB, for security, file URL's are NOT served
+            MediaType mime = WebResourceUtils.getImageMediaTypeFromExtension(Files.getFileExtension(url));
+            Object content = ResourceUtils.create(brooklyn().getCatalogClassLoader()).getResourceFromUrl(url);
+            return Response.ok(content, mime).build();
+        }
+
+        // for anything else we do a redirect (e.g. http / https; perhaps ftp)
+        return Response.temporaryRedirect(URI.create(url)).build();
+    }
+
+    @Override
+    public Response rename(String application, String entity, String newName) {
+        Entity entityLocal = brooklyn().getEntity(application, entity);
+        entityLocal.setDisplayName(newName);
+        return status(Response.Status.OK).build();
+    }
+
+    @Override
+    public Response expunge(String application, String entity, boolean release) {
+        Entity entityLocal = brooklyn().getEntity(application, entity);
+        Task<?> task = brooklyn().expunge(entityLocal, release);
+        TaskSummary summary = TaskTransformer.fromTask(ui.getBaseUriBuilder()).apply(task);
+        return status(ACCEPTED).entity(summary).build();
+    }
+
+    @Override
+    public List<EntitySummary> getDescendants(String application, String entity, String typeRegex) {
+        return EntityTransformer.entitySummaries(brooklyn().descendantsOfType(application, entity, typeRegex), ui.getBaseUriBuilder());
+    }
+
+    @Override
+    public Map<String, Object> getDescendantsSensor(String application, String entity, String sensor, String typeRegex) {
+        Iterable<Entity> descs = brooklyn().descendantsOfType(application, entity, typeRegex);
+        return ApplicationResource.getSensorMap(sensor, descs);
+    }
+
+    @Override
+    public List<LocationSummary> getLocations(String application, String entity) {
+        List<LocationSummary> result = Lists.newArrayList();
+        Entity e = brooklyn().getEntity(application, entity);
+        for (Location l : e.getLocations()) {
+            result.add(LocationTransformer.newInstance(mgmt(), l, LocationDetailLevel.NONE, ui.getBaseUriBuilder()));
+        }
+        return result;
+    }
+
+    @Override
+    public String getSpec(String applicationToken, String entityToken) {
+        Entity entity = brooklyn().getEntity(applicationToken, entityToken);
+        NamedStringTag spec = BrooklynTags.findFirst(BrooklynTags.YAML_SPEC_KIND, entity.tags().getTags());
+        if (spec == null)
+            return null;
+        return (String) WebResourceUtils.getValueForDisplay(spec.getContents(), true, true);
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/LocationResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/LocationResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/LocationResource.java
new file mode 100644
index 0000000..8f4251c
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/LocationResource.java
@@ -0,0 +1,186 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import java.net.URI;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.ws.rs.core.Response;
+
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.location.LocationDefinition;
+import org.apache.brooklyn.api.typereg.RegisteredType;
+import org.apache.brooklyn.core.location.LocationConfigKeys;
+import org.apache.brooklyn.rest.api.LocationApi;
+import org.apache.brooklyn.rest.domain.LocationSpec;
+import org.apache.brooklyn.rest.domain.LocationSummary;
+import org.apache.brooklyn.rest.filter.HaHotStateRequired;
+import org.apache.brooklyn.rest.transform.LocationTransformer;
+import org.apache.brooklyn.rest.transform.LocationTransformer.LocationDetailLevel;
+import org.apache.brooklyn.rest.util.EntityLocationUtils;
+import org.apache.brooklyn.rest.util.WebResourceUtils;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.text.NaturalOrderComparator;
+import org.apache.brooklyn.util.text.Strings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
+import static org.apache.brooklyn.rest.util.WebResourceUtils.serviceAbsoluteUriBuilder;
+
+@SuppressWarnings("deprecation")
+@HaHotStateRequired
+public class LocationResource extends AbstractBrooklynRestResource implements LocationApi {
+
+    private static final Logger log = LoggerFactory.getLogger(LocationResource.class);
+
+    private final Set<String> specsWarnedOnException = Sets.newConcurrentHashSet();
+
+    @Override
+    public List<LocationSummary> list() {
+        Function<LocationDefinition, LocationSummary> transformer = new Function<LocationDefinition, LocationSummary>() {
+            @Override
+            public LocationSummary apply(LocationDefinition l) {
+                try {
+                    return LocationTransformer.newInstance(mgmt(), l, LocationDetailLevel.LOCAL_EXCLUDING_SECRET, ui.getBaseUriBuilder());
+                } catch (Exception e) {
+                    Exceptions.propagateIfFatal(e);
+                    String spec = l.getSpec();
+                    if (spec == null || specsWarnedOnException.add(spec)) {
+                        log.warn("Unable to find details of location {} in REST call to list (ignoring location): {}", l, e);
+                        if (log.isDebugEnabled()) log.debug("Error details for location " + l, e);
+                    } else {
+                        if (log.isTraceEnabled())
+                            log.trace("Unable again to find details of location {} in REST call to list (ignoring location): {}", l, e);
+                    }
+                    return null;
+                }
+            }
+        };
+        return FluentIterable.from(brooklyn().getLocationRegistry().getDefinedLocations().values())
+                .transform(transformer)
+                .filter(LocationSummary.class)
+                .toSortedList(nameOrSpecComparator());
+    }
+
+    private static NaturalOrderComparator COMPARATOR = new NaturalOrderComparator();
+    private static Comparator<LocationSummary> nameOrSpecComparator() {
+        return new Comparator<LocationSummary>() {
+            @Override
+            public int compare(LocationSummary o1, LocationSummary o2) {
+                return COMPARATOR.compare(getNameOrSpec(o1).toLowerCase(), getNameOrSpec(o2).toLowerCase());
+            }
+        };
+    }
+    private static String getNameOrSpec(LocationSummary o) {
+        if (Strings.isNonBlank(o.getName())) return o.getName();
+        if (Strings.isNonBlank(o.getSpec())) return o.getSpec();
+        return o.getId();
+    }
+
+    // this is here to support the web GUI's circles
+    @Override
+    public Map<String,Map<String,Object>> getLocatedLocations() {
+      Map<String,Map<String,Object>> result = new LinkedHashMap<String,Map<String,Object>>();
+      Map<Location, Integer> counts = new EntityLocationUtils(mgmt()).countLeafEntitiesByLocatedLocations();
+      for (Map.Entry<Location,Integer> count: counts.entrySet()) {
+          Location l = count.getKey();
+          Map<String,Object> m = MutableMap.<String,Object>of(
+                  "id", l.getId(),
+                  "name", l.getDisplayName(),
+                  "leafEntityCount", count.getValue(),
+                  "latitude", l.getConfig(LocationConfigKeys.LATITUDE),
+                  "longitude", l.getConfig(LocationConfigKeys.LONGITUDE)
+              );
+          result.put(l.getId(), m);
+      }
+      return result;
+    }
+
+    /** @deprecated since 0.7.0; REST call now handled by below (optional query parameter added) */
+    public LocationSummary get(String locationId) {
+        return get(locationId, false);
+    }
+
+    @Override
+    public LocationSummary get(String locationId, String fullConfig) {
+        return get(locationId, Boolean.valueOf(fullConfig));
+    }
+
+    public LocationSummary get(String locationId, boolean fullConfig) {
+        LocationDetailLevel configLevel = fullConfig ? LocationDetailLevel.FULL_EXCLUDING_SECRET : LocationDetailLevel.LOCAL_EXCLUDING_SECRET;
+        Location l1 = mgmt().getLocationManager().getLocation(locationId);
+        if (l1!=null) {
+            return LocationTransformer.newInstance(mgmt(), l1, configLevel, ui.getBaseUriBuilder());
+        }
+
+        LocationDefinition l2 = brooklyn().getLocationRegistry().getDefinedLocationById(locationId);
+        if (l2==null) throw WebResourceUtils.notFound("No location matching %s", locationId);
+        return LocationTransformer.newInstance(mgmt(), l2, configLevel, ui.getBaseUriBuilder());
+    }
+
+    @Override
+    public Response create(LocationSpec locationSpec) {
+        String name = locationSpec.getName();
+        ImmutableList.Builder<String> yaml = ImmutableList.<String>builder().add(
+                "brooklyn.catalog:",
+                "  symbolicName: "+name,
+                "",
+                "brooklyn.locations:",
+                "- type: "+locationSpec.getSpec());
+        if (locationSpec.getConfig().size() > 0) {
+            yaml.add("  brooklyn.config:");
+            for (Map.Entry<String, ?> entry : locationSpec.getConfig().entrySet()) {
+                yaml.add("    " + entry.getKey() + ": " + entry.getValue());
+            }
+        }
+
+        String locationBlueprint = Joiner.on("\n").join(yaml.build());
+        brooklyn().getCatalog().addItems(locationBlueprint);
+        LocationDefinition l = brooklyn().getLocationRegistry().getDefinedLocationByName(name);
+        URI ref = serviceAbsoluteUriBuilder(ui.getBaseUriBuilder(), LocationApi.class, "get").build(name);
+        return Response.created(ref)
+                .entity(LocationTransformer.newInstance(mgmt(), l, LocationDetailLevel.LOCAL_EXCLUDING_SECRET, ui.getBaseUriBuilder()))
+                .build();
+    }
+
+    @Override
+    @Deprecated
+    public void delete(String locationId) {
+        // TODO make all locations be part of the catalog, then flip the JS GUI to use catalog api
+        if (deleteAllVersions(locationId)>0) return;
+        throw WebResourceUtils.notFound("No catalog item location matching %s; only catalog item locations can be deleted", locationId);
+    }
+    
+    private int deleteAllVersions(String locationId) {
+        RegisteredType item = mgmt().getTypeRegistry().get(locationId);
+        if (item==null) return 0; 
+        brooklyn().getCatalog().deleteCatalogItem(item.getSymbolicName(), item.getVersion());
+        return 1 + deleteAllVersions(locationId);
+    }
+}


[13/34] brooklyn-server git commit: [BROOKLYN-183] REST API using CXF JAX-RS 2.0 implementation

Posted by he...@apache.org.
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java
new file mode 100644
index 0000000..6091096
--- /dev/null
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java
@@ -0,0 +1,697 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import static com.google.common.collect.Iterables.find;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeoutException;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+
+import org.apache.brooklyn.api.entity.Application;
+import org.apache.brooklyn.core.entity.Attributes;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.entity.EntityFunctions;
+import org.apache.brooklyn.core.entity.EntityPredicates;
+import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
+import org.apache.brooklyn.core.location.AbstractLocation;
+import org.apache.brooklyn.core.location.LocationConfigKeys;
+import org.apache.brooklyn.core.location.geo.HostGeoInfo;
+import org.apache.brooklyn.core.location.internal.LocationInternal;
+import org.apache.brooklyn.entity.stock.BasicApplication;
+import org.apache.brooklyn.entity.stock.BasicEntity;
+import org.apache.brooklyn.rest.domain.ApiError;
+import org.apache.brooklyn.rest.domain.ApplicationSpec;
+import org.apache.brooklyn.rest.domain.ApplicationSummary;
+import org.apache.brooklyn.rest.domain.CatalogEntitySummary;
+import org.apache.brooklyn.rest.domain.CatalogItemSummary;
+import org.apache.brooklyn.rest.domain.EffectorSummary;
+import org.apache.brooklyn.rest.domain.EntityConfigSummary;
+import org.apache.brooklyn.rest.domain.EntitySpec;
+import org.apache.brooklyn.rest.domain.EntitySummary;
+import org.apache.brooklyn.rest.domain.PolicySummary;
+import org.apache.brooklyn.rest.domain.SensorSummary;
+import org.apache.brooklyn.rest.domain.Status;
+import org.apache.brooklyn.rest.domain.TaskSummary;
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+import org.apache.brooklyn.rest.testing.mocks.CapitalizePolicy;
+import org.apache.brooklyn.rest.testing.mocks.NameMatcherGroup;
+import org.apache.brooklyn.rest.testing.mocks.RestMockApp;
+import org.apache.brooklyn.rest.testing.mocks.RestMockAppBuilder;
+import org.apache.brooklyn.rest.testing.mocks.RestMockSimpleEntity;
+import org.apache.brooklyn.test.Asserts;
+import org.apache.brooklyn.util.collections.CollectionFunctionals;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.http.HttpAsserts;
+import org.apache.brooklyn.util.text.Strings;
+import org.apache.brooklyn.util.time.Duration;
+import org.apache.http.HttpHeaders;
+import org.apache.http.entity.ContentType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Maps;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.MultivaluedHashMap;
+import org.apache.cxf.jaxrs.client.WebClient;
+
+@Test(singleThreaded = true,
+        // by using a different suite name we disallow interleaving other tests between the methods of this test class, which wrecks the test fixtures
+        suiteName = "ApplicationResourceTest")
+public class ApplicationResourceTest extends BrooklynRestResourceTest {
+
+    /*
+     * In simpleSpec, not using EverythingGroup because caused problems! The group is a child of the
+     * app, and the app is a member of the group. It failed in jenkins with:
+     *   BasicApplicationImpl{id=GSPjBCe4} GSPjBCe4
+     *     service.isUp: true
+     *     service.problems: {service-lifecycle-indicators-from-children-and-members=Required entity not healthy: EverythingGroupImpl{id=KQ4mSEOJ}}
+     *     service.state: on-fire
+     *     service.state.expected: running @ 1412003485617 / Mon Sep 29 15:11:25 UTC 2014
+     *   EverythingGroupImpl{id=KQ4mSEOJ} KQ4mSEOJ
+     *     service.isUp: true
+     *     service.problems: {service-lifecycle-indicators-from-children-and-members=Required entities not healthy: BasicApplicationImpl{id=GSPjBCe4}, EverythingGroupImpl{id=KQ4mSEOJ}}
+     *     service.state: on-fire
+     * I'm guessing there's a race: the app was not yet healthy because EverythingGroup hadn't set itself to running; 
+     * but then the EverythingGroup would never transition to healthy because one of its members was not healthy.
+     */
+
+    private static final Logger log = LoggerFactory.getLogger(ApplicationResourceTest.class);
+    
+    private final ApplicationSpec simpleSpec = ApplicationSpec.builder().name("simple-app")
+          .entities(ImmutableSet.of(
+                  new EntitySpec("simple-ent", RestMockSimpleEntity.class.getName()),
+                  new EntitySpec("simple-group", NameMatcherGroup.class.getName(), ImmutableMap.of("namematchergroup.regex", "simple-ent"))
+          ))
+          .locations(ImmutableSet.of("localhost"))
+          .build();
+
+    // Convenience for finding an EntitySummary within a collection, based on its name
+    private static Predicate<EntitySummary> withName(final String name) {
+        return new Predicate<EntitySummary>() {
+            public boolean apply(EntitySummary input) {
+                return name.equals(input.getName());
+            }
+        };
+    }
+
+    // Convenience for finding a Map within a collection, based on the value of one of its keys
+    private static Predicate<? super Map<?,?>> withValueForKey(final Object key, final Object value) {
+        return new Predicate<Object>() {
+            public boolean apply(Object input) {
+                if (!(input instanceof Map)) return false;
+                return value.equals(((Map<?, ?>) input).get(key));
+            }
+        };
+    }
+
+    @Test
+    public void testGetUndefinedApplication() {
+        try {
+            client().path("/applications/dummy-not-found").get(ApplicationSummary.class);
+        } catch (WebApplicationException e) {
+            assertEquals(e.getResponse().getStatus(), 404);
+        }
+    }
+
+    private static void assertRegexMatches(String actual, String patternExpected) {
+        if (actual==null) Assert.fail("Actual value is null; expected "+patternExpected);
+        if (!actual.matches(patternExpected)) {
+            Assert.fail("Text '"+actual+"' does not match expected pattern "+patternExpected);
+        }
+    }
+
+    @Test
+    public void testDeployApplication() throws Exception {
+        Response response = clientDeploy(simpleSpec);
+
+        HttpAsserts.assertHealthyStatusCode(response.getStatus());
+        assertEquals(getManagementContext().getApplications().size(), 1);
+        assertRegexMatches(response.getLocation().getPath(), "/applications/.*");
+        // Object taskO = response.getEntity(Object.class);
+        TaskSummary task = response.readEntity(TaskSummary.class);
+        log.info("deployed, got " + task);
+        assertEquals(task.getEntityId(), getManagementContext().getApplications().iterator().next().getApplicationId());
+
+        waitForApplicationToBeRunning(response.getLocation());
+    }
+
+    @Test(dependsOnMethods = { "testDeleteApplication" })
+    // this must happen after we've deleted the main application, as testLocatedLocations assumes a single location
+    public void testDeployApplicationImpl() throws Exception {
+    ApplicationSpec spec = ApplicationSpec.builder()
+            .type(RestMockApp.class.getCanonicalName())
+            .name("simple-app-impl")
+            .locations(ImmutableSet.of("localhost"))
+            .build();
+
+        Response response = clientDeploy(spec);
+        assertTrue(response.getStatus() / 100 == 2, "response is " + response);
+
+        // Expect app to be running
+        URI appUri = response.getLocation();
+        waitForApplicationToBeRunning(response.getLocation());
+        assertEquals(client().path(appUri).get(ApplicationSummary.class).getSpec().getName(), "simple-app-impl");
+    }
+
+    @Test(dependsOnMethods = { "testDeployApplication", "testLocatedLocation" })
+    public void testDeployApplicationFromInterface() throws Exception {
+        ApplicationSpec spec = ApplicationSpec.builder()
+                .type(BasicApplication.class.getCanonicalName())
+                .name("simple-app-interface")
+                .locations(ImmutableSet.of("localhost"))
+                .build();
+
+        Response response = clientDeploy(spec);
+        assertTrue(response.getStatus() / 100 == 2, "response is " + response);
+
+        // Expect app to be running
+        URI appUri = response.getLocation();
+        waitForApplicationToBeRunning(response.getLocation());
+        assertEquals(client().path(appUri).get(ApplicationSummary.class).getSpec().getName(), "simple-app-interface");
+    }
+
+    @Test(dependsOnMethods = { "testDeployApplication", "testLocatedLocation" })
+    public void testDeployApplicationFromBuilder() throws Exception {
+        ApplicationSpec spec = ApplicationSpec.builder()
+                .type(RestMockAppBuilder.class.getCanonicalName())
+                .name("simple-app-builder")
+                .locations(ImmutableSet.of("localhost"))
+                .build();
+
+        Response response = clientDeploy(spec);
+        assertTrue(response.getStatus() / 100 == 2, "response is " + response);
+
+        // Expect app to be running
+        URI appUri = response.getLocation();
+        waitForApplicationToBeRunning(response.getLocation(), Duration.TEN_SECONDS);
+        assertEquals(client().path(appUri).get(ApplicationSummary.class).getSpec().getName(), "simple-app-builder");
+
+        // Expect app to have the child-entity
+        Set<EntitySummary> entities = client().path(appUri.toString() + "/entities")
+                .get(new GenericType<Set<EntitySummary>>() {});
+        assertEquals(entities.size(), 1);
+        assertEquals(Iterables.getOnlyElement(entities).getName(), "child1");
+        assertEquals(Iterables.getOnlyElement(entities).getType(), RestMockSimpleEntity.class.getCanonicalName());
+    }
+
+    @Test(dependsOnMethods = { "testDeployApplication", "testLocatedLocation" })
+    public void testDeployApplicationYaml() throws Exception {
+        String yaml = "{ name: simple-app-yaml, location: localhost, services: [ { serviceType: "+BasicApplication.class.getCanonicalName()+" } ] }";
+
+        Response response = client().path("/applications")
+                .post(Entity.entity(yaml, "application/x-yaml"));
+        assertTrue(response.getStatus()/100 == 2, "response is "+response);
+
+        // Expect app to be running
+        URI appUri = response.getLocation();
+        waitForApplicationToBeRunning(response.getLocation());
+        assertEquals(client().path(appUri).get(ApplicationSummary.class).getSpec().getName(), "simple-app-yaml");
+    }
+
+    @Test
+    public void testReferenceCatalogEntity() throws Exception {
+        getManagementContext().getCatalog().addItems("{ name: "+BasicEntity.class.getName()+", "
+            + "services: [ { type: "+BasicEntity.class.getName()+" } ] }");
+
+        String yaml = "{ name: simple-app-yaml, location: localhost, services: [ { type: " + BasicEntity.class.getName() + " } ] }";
+
+        Response response = client().path("/applications")
+                .post(Entity.entity(yaml, "application/x-yaml"));
+        assertTrue(response.getStatus()/100 == 2, "response is "+response);
+
+        // Expect app to be running
+        URI appUri = response.getLocation();
+        waitForApplicationToBeRunning(response.getLocation());
+        assertEquals(client().path(appUri).get(ApplicationSummary.class).getSpec().getName(), "simple-app-yaml");
+
+        Response response2 = client().path(appUri.getPath())
+                .delete();
+        assertEquals(response2.getStatus(), Response.Status.ACCEPTED.getStatusCode());
+    }
+
+    @Test
+    public void testDeployWithInvalidEntityType() {
+        try {
+            clientDeploy(ApplicationSpec.builder()
+                    .name("invalid-app")
+                    .entities(ImmutableSet.of(new EntitySpec("invalid-ent", "not.existing.entity")))
+                    .locations(ImmutableSet.of("localhost"))
+                    .build());
+        } catch (WebApplicationException e) {
+            ApiError error = e.getResponse().readEntity(ApiError.class);
+            assertEquals(error.getMessage(), "Undefined type 'not.existing.entity'");
+        }
+    }
+
+    @Test
+    public void testDeployWithInvalidLocation() {
+        try {
+            clientDeploy(ApplicationSpec.builder()
+                    .name("invalid-app")
+                    .entities(ImmutableSet.of(new EntitySpec("simple-ent", RestMockSimpleEntity.class.getName())))
+                    .locations(ImmutableSet.of("3423"))
+                    .build());
+
+        } catch (WebApplicationException e) {
+            ApiError error = e.getResponse().readEntity(ApiError.class);
+            assertEquals(error.getMessage(), "Undefined location '3423'");
+        }
+    }
+
+    @Test(dependsOnMethods = "testDeployApplication")
+    public void testListEntities() {
+        Set<EntitySummary> entities = client().path("/applications/simple-app/entities")
+                .get(new GenericType<Set<EntitySummary>>() {});
+
+        assertEquals(entities.size(), 2);
+
+        EntitySummary entity = Iterables.find(entities, withName("simple-ent"), null);
+        EntitySummary group = Iterables.find(entities, withName("simple-group"), null);
+        Assert.assertNotNull(entity);
+        Assert.assertNotNull(group);
+
+        client().path(entity.getLinks().get("self")).get();
+
+        Set<EntitySummary> children = client().path(entity.getLinks().get("children"))
+                .get(new GenericType<Set<EntitySummary>>() {});
+        assertEquals(children.size(), 0);
+    }
+
+    @Test(dependsOnMethods = "testDeployApplication")
+    public void testListApplications() {
+        Set<ApplicationSummary> applications = client().path("/applications")
+                .get(new GenericType<Set<ApplicationSummary>>() { });
+        log.info("Applications listed are: " + applications);
+        for (ApplicationSummary app : applications) {
+            if (simpleSpec.getName().equals(app.getSpec().getName())) return;
+        }
+        Assert.fail("simple-app not found in list of applications: "+applications);
+    }
+
+    @Test(dependsOnMethods = "testDeployApplication")
+    public void testGetApplicationOnFire() {
+        Application app = Iterables.find(manager.getApplications(), EntityPredicates.displayNameEqualTo(simpleSpec.getName()));
+        Lifecycle origState = app.getAttribute(Attributes.SERVICE_STATE_ACTUAL);
+        
+        ApplicationSummary summary = client().path("/applications/"+app.getId())
+                .get(ApplicationSummary.class);
+        assertEquals(summary.getStatus(), Status.RUNNING);
+
+        app.sensors().set(Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
+        try {
+            ApplicationSummary summary2 = client().path("/applications/"+app.getId())
+                    .get(ApplicationSummary.class);
+            log.info("Application: " + summary2);
+            assertEquals(summary2.getStatus(), Status.ERROR);
+            
+        } finally {
+            app.sensors().set(Attributes.SERVICE_STATE_ACTUAL, origState);
+        }
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Test(dependsOnMethods = "testDeployApplication")
+    public void testFetchApplicationsAndEntity() {
+        Collection apps = client().path("/applications/fetch").get(Collection.class);
+        log.info("Applications fetched are: " + apps);
+
+        Map app = null;
+        for (Object appI : apps) {
+            Object name = ((Map) appI).get("name");
+            if ("simple-app".equals(name)) {
+                app = (Map) appI;
+            }
+            if (ImmutableSet.of("simple-ent", "simple-group").contains(name))
+                Assert.fail(name + " should not be listed at high level: " + apps);
+        }
+
+        Assert.assertNotNull(app);
+        Assert.assertFalse(Strings.isBlank((String) app.get("applicationId")),
+                "expected value for applicationId, was: " + app.get("applicationId"));
+        Assert.assertFalse(Strings.isBlank((String) app.get("serviceState")),
+                "expected value for serviceState, was: " + app.get("serviceState"));
+        Assert.assertNotNull(app.get("serviceUp"), "expected non-null value for serviceUp");
+
+        Collection children = (Collection) app.get("children");
+        Assert.assertEquals(children.size(), 2);
+
+        Map entitySummary = (Map) Iterables.find(children, withValueForKey("name", "simple-ent"), null);
+        Map groupSummary = (Map) Iterables.find(children, withValueForKey("name", "simple-group"), null);
+        Assert.assertNotNull(entitySummary);
+        Assert.assertNotNull(groupSummary);
+
+        String itemIds = app.get("id") + "," + entitySummary.get("id") + "," + groupSummary.get("id");
+        Collection entities = client().path("/applications/fetch").query("items", itemIds)
+                .get(Collection.class);
+        log.info("Applications+Entities fetched are: " + entities);
+
+        Assert.assertEquals(entities.size(), apps.size() + 2);
+        Map entityDetails = (Map) Iterables.find(entities, withValueForKey("name", "simple-ent"), null);
+        Map groupDetails = (Map) Iterables.find(entities, withValueForKey("name", "simple-group"), null);
+        Assert.assertNotNull(entityDetails);
+        Assert.assertNotNull(groupDetails);
+
+        Assert.assertEquals(entityDetails.get("parentId"), app.get("id"));
+        Assert.assertNull(entityDetails.get("children"));
+        Assert.assertEquals(groupDetails.get("parentId"), app.get("id"));
+        Assert.assertNull(groupDetails.get("children"));
+
+        Collection entityGroupIds = (Collection) entityDetails.get("groupIds");
+        Assert.assertNotNull(entityGroupIds);
+        Assert.assertEquals(entityGroupIds.size(), 1);
+        Assert.assertEquals(entityGroupIds.iterator().next(), groupDetails.get("id"));
+
+        Collection groupMembers = (Collection) groupDetails.get("members");
+        Assert.assertNotNull(groupMembers);
+
+        for (Application appi : getManagementContext().getApplications()) {
+            Entities.dumpInfo(appi);
+        }
+        log.info("MEMBERS: " + groupMembers);
+
+        Assert.assertEquals(groupMembers.size(), 1);
+        Map entityMemberDetails = (Map) Iterables.find(groupMembers, withValueForKey("name", "simple-ent"), null);
+        Assert.assertNotNull(entityMemberDetails);
+        Assert.assertEquals(entityMemberDetails.get("id"), entityDetails.get("id"));
+    }
+
+    @Test(dependsOnMethods = "testDeployApplication")
+    public void testListSensors() {
+        Set<SensorSummary> sensors = client().path("/applications/simple-app/entities/simple-ent/sensors")
+                .get(new GenericType<Set<SensorSummary>>() { });
+        assertTrue(sensors.size() > 0);
+        SensorSummary sample = Iterables.find(sensors, new Predicate<SensorSummary>() {
+            @Override
+            public boolean apply(SensorSummary sensorSummary) {
+                return sensorSummary.getName().equals(RestMockSimpleEntity.SAMPLE_SENSOR.getName());
+            }
+        });
+        assertEquals(sample.getType(), "java.lang.String");
+    }
+
+    @Test(dependsOnMethods = "testDeployApplication")
+    public void testListConfig() {
+        Set<EntityConfigSummary> config = client().path("/applications/simple-app/entities/simple-ent/config")
+                .get(new GenericType<Set<EntityConfigSummary>>() { });
+        assertTrue(config.size() > 0);
+        System.out.println(("CONFIG: " + config));
+    }
+
+    @Test(dependsOnMethods = "testListConfig")
+    public void testListConfig2() {
+        Set<EntityConfigSummary> config = client().path("/applications/simple-app/entities/simple-ent/config")
+                .get(new GenericType<Set<EntityConfigSummary>>() {});
+        assertTrue(config.size() > 0);
+        System.out.println(("CONFIG: " + config));
+    }
+
+    @Test(dependsOnMethods = "testDeployApplication")
+    public void testListEffectors() {
+        Set<EffectorSummary> effectors = client().path("/applications/simple-app/entities/simple-ent/effectors")
+                .get(new GenericType<Set<EffectorSummary>>() {});
+
+        assertTrue(effectors.size() > 0);
+
+        EffectorSummary sampleEffector = find(effectors, new Predicate<EffectorSummary>() {
+            @Override
+            public boolean apply(EffectorSummary input) {
+                return input.getName().equals("sampleEffector");
+            }
+        });
+        assertEquals(sampleEffector.getReturnType(), "java.lang.String");
+    }
+
+    @Test(dependsOnMethods = "testListSensors")
+    public void testTriggerSampleEffector() throws InterruptedException, IOException {
+        Response response = client()
+                .path("/applications/simple-app/entities/simple-ent/effectors/"+RestMockSimpleEntity.SAMPLE_EFFECTOR.getName())
+                .type(MediaType.APPLICATION_JSON_TYPE)
+                .post(ImmutableMap.of("param1", "foo", "param2", 4));
+
+        assertEquals(response.getStatus(), Response.Status.ACCEPTED.getStatusCode());
+
+        String result = response.readEntity(String.class);
+        assertEquals(result, "foo4");
+    }
+
+    @Test(dependsOnMethods = "testListSensors")
+    public void testTriggerSampleEffectorWithFormData() throws InterruptedException, IOException {
+        MultivaluedMap<String, String> data = new MultivaluedHashMap<>();
+        data.add("param1", "foo");
+        data.add("param2", "4");
+        Response response = client()
+                .path("/applications/simple-app/entities/simple-ent/effectors/"+RestMockSimpleEntity.SAMPLE_EFFECTOR.getName())
+                .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE)
+                .post(data);
+
+        assertEquals(response.getStatus(), Response.Status.ACCEPTED.getStatusCode());
+
+        String result = response.readEntity(String.class);
+        assertEquals(result, "foo4");
+    }
+
+    @Test(dependsOnMethods = "testTriggerSampleEffector")
+    public void testBatchSensorValues() {
+        WebClient client = client().path("/applications/simple-app/entities/simple-ent/sensors/current-state");
+        Map<String, Object> sensors = client.get(new GenericType<Map<String, Object>>() {});
+        assertTrue(sensors.size() > 0);
+        assertEquals(sensors.get(RestMockSimpleEntity.SAMPLE_SENSOR.getName()), "foo4");
+    }
+
+    @Test(dependsOnMethods = "testBatchSensorValues")
+    public void testReadEachSensor() {
+    Set<SensorSummary> sensors = client().path("/applications/simple-app/entities/simple-ent/sensors")
+            .get(new GenericType<Set<SensorSummary>>() {});
+
+        Map<String, String> readings = Maps.newHashMap();
+        for (SensorSummary sensor : sensors) {
+            try {
+                readings.put(sensor.getName(), client().path(sensor.getLinks().get("self")).accept(MediaType.TEXT_PLAIN).get(String.class));
+            } catch (WebApplicationException uie) {
+                if (uie.getResponse().getStatus() == 204) { // no content
+                    readings.put(sensor.getName(), null);
+                } else {
+                    Exceptions.propagate(uie);
+                }
+            }
+        }
+
+        assertEquals(readings.get(RestMockSimpleEntity.SAMPLE_SENSOR.getName()), "foo4");
+    }
+
+    @Test(dependsOnMethods = "testTriggerSampleEffector")
+    public void testPolicyWhichCapitalizes() throws Exception {
+        String policiesEndpoint = "/applications/simple-app/entities/simple-ent/policies";
+        Set<PolicySummary> policies = client().path(policiesEndpoint).get(new GenericType<Set<PolicySummary>>(){});
+        assertEquals(policies.size(), 0);
+
+        Response response = client().path(policiesEndpoint)
+                .query("type", CapitalizePolicy.class.getCanonicalName())
+                .type(MediaType.APPLICATION_JSON_TYPE)
+                .post(toJsonEntity(ImmutableMap.of()));
+        assertEquals(response.getStatus(), 200);
+        PolicySummary policy = response.readEntity(PolicySummary.class);
+        assertNotNull(policy.getId());
+        String newPolicyId = policy.getId();
+        log.info("POLICY CREATED: " + newPolicyId);
+        policies = client().path(policiesEndpoint).get(new GenericType<Set<PolicySummary>>() {});
+        assertEquals(policies.size(), 1);
+
+        Lifecycle status = client().path(policiesEndpoint + "/" + newPolicyId).get(Lifecycle.class);
+        log.info("POLICY STATUS: " + status);
+
+        response = client().path(policiesEndpoint+"/"+newPolicyId+"/start")
+                .post(null);
+        assertEquals(response.getStatus(), 204);
+        status = client().path(policiesEndpoint + "/" + newPolicyId).get(Lifecycle.class);
+        assertEquals(status, Lifecycle.RUNNING);
+
+        response = client().path(policiesEndpoint+"/"+newPolicyId+"/stop")
+                .post(null);
+        assertEquals(response.getStatus(), 204);
+        status = client().path(policiesEndpoint + "/" + newPolicyId).get(Lifecycle.class);
+        assertEquals(status, Lifecycle.STOPPED);
+
+        response = client().path(policiesEndpoint+"/"+newPolicyId+"/destroy")
+                .post(null);
+        assertEquals(response.getStatus(), 204);
+
+        response = client().path(policiesEndpoint+"/"+newPolicyId).get();
+        log.info("POLICY STATUS RESPONSE AFTER DESTROY: " + response.getStatus());
+        assertEquals(response.getStatus(), 404);
+
+        policies = client().path(policiesEndpoint).get(new GenericType<Set<PolicySummary>>() {});
+        assertEquals(0, policies.size());
+    }
+
+    @SuppressWarnings({ "rawtypes" })
+    @Test(dependsOnMethods = "testDeployApplication")
+    public void testLocatedLocation() {
+        log.info("starting testLocatedLocations");
+        testListApplications();
+
+        LocationInternal l = (LocationInternal) getManagementContext().getApplications().iterator().next().getLocations().iterator().next();
+        if (l.config().getLocalRaw(LocationConfigKeys.LATITUDE).isAbsent()) {
+            log.info("Supplying fake locations for localhost because could not be autodetected");
+            ((AbstractLocation) l).setHostGeoInfo(new HostGeoInfo("localhost", "localhost", 50, 0));
+        }
+        Map result = client().path("/locations/usage/LocatedLocations")
+                .get(Map.class);
+        log.info("LOCATIONS: " + result);
+        Assert.assertEquals(result.size(), 1);
+        Map details = (Map) result.values().iterator().next();
+        assertEquals(details.get("leafEntityCount"), 2);
+    }
+
+    @Test(dependsOnMethods = {"testListEffectors", "testFetchApplicationsAndEntity", "testTriggerSampleEffector", "testListApplications","testReadEachSensor","testPolicyWhichCapitalizes","testLocatedLocation"})
+    public void testDeleteApplication() throws TimeoutException, InterruptedException {
+        waitForPageFoundResponse("/applications/simple-app", ApplicationSummary.class);
+        Collection<Application> apps = getManagementContext().getApplications();
+        log.info("Deleting simple-app from " + apps);
+        int size = apps.size();
+
+        Response response = client().path("/applications/simple-app")
+                .delete();
+
+        assertEquals(response.getStatus(), Response.Status.ACCEPTED.getStatusCode());
+        TaskSummary task = response.readEntity(TaskSummary.class);
+        assertTrue(task.getDescription().toLowerCase().contains("destroy"), task.getDescription());
+        assertTrue(task.getDescription().toLowerCase().contains("simple-app"), task.getDescription());
+
+        waitForPageNotFoundResponse("/applications/simple-app", ApplicationSummary.class);
+
+        log.info("App appears gone, apps are: " + getManagementContext().getApplications());
+        // more logging above, for failure in the check below
+
+        Asserts.eventually(
+                EntityFunctions.applications(getManagementContext()),
+                Predicates.compose(Predicates.equalTo(size-1), CollectionFunctionals.sizeFunction()) );
+    }
+
+    @Test
+    public void testDisabledApplicationCatalog() throws TimeoutException, InterruptedException {
+        String itemSymbolicName = "my.catalog.item.id.for.disabling";
+        String itemVersion = "1.0";
+        String serviceType = "org.apache.brooklyn.entity.stock.BasicApplication";
+        
+        // Deploy the catalog item
+        addTestCatalogItem(itemSymbolicName, "template", itemVersion, serviceType);
+        List<CatalogEntitySummary> itemSummaries = client().path("/catalog/applications")
+                .query("fragment", itemSymbolicName).query("allVersions", "true").get(new GenericType<List<CatalogEntitySummary>>() {});
+        CatalogItemSummary itemSummary = Iterables.getOnlyElement(itemSummaries);
+        String itemVersionedId = String.format("%s:%s", itemSummary.getSymbolicName(), itemSummary.getVersion());
+        assertEquals(itemSummary.getId(), itemVersionedId);
+
+        try {
+            // Create an app before disabling: this should work
+            String yaml = "{ name: my-app, location: localhost, services: [ { type: \""+itemVersionedId+"\" } ] }";
+            Response response = client().path("/applications")
+                    .post(Entity.entity(yaml, "application/x-yaml"));
+            HttpAsserts.assertHealthyStatusCode(response.getStatus());
+            waitForPageFoundResponse("/applications/my-app", ApplicationSummary.class);
+
+            // Deprecate
+            deprecateCatalogItem(itemSymbolicName, itemVersion, true);
+
+            // Create an app when deprecated: this should work
+            String yaml2 = "{ name: my-app2, location: localhost, services: [ { type: \""+itemVersionedId+"\" } ] }";
+            Response response2 = client().path("/applications")
+                    .post(Entity.entity(yaml2, "application/x-yaml"));
+            HttpAsserts.assertHealthyStatusCode(response2.getStatus());
+            waitForPageFoundResponse("/applications/my-app2", ApplicationSummary.class);
+    
+            // Disable
+            disableCatalogItem(itemSymbolicName, itemVersion, true);
+
+            // Now try creating an app; this should fail because app is disabled
+            String yaml3 = "{ name: my-app3, location: localhost, services: [ { type: \""+itemVersionedId+"\" } ] }";
+            Response response3 = client().path("/applications")
+                    .post(Entity.entity(yaml3, "application/x-yaml"));
+            HttpAsserts.assertClientErrorStatusCode(response3.getStatus());
+            assertTrue(response3.readEntity(String.class).contains("cannot be matched"));
+            waitForPageNotFoundResponse("/applications/my-app3", ApplicationSummary.class);
+            
+        } finally {
+            client().path("/applications/my-app")
+                    .delete();
+
+            client().path("/applications/my-app2")
+                    .delete();
+
+            client().path("/applications/my-app3")
+                    .delete();
+
+            client().path("/catalog/entities/"+itemVersionedId+"/"+itemVersion)
+                    .delete();
+        }
+    }
+
+    private void deprecateCatalogItem(String symbolicName, String version, boolean deprecated) {
+        String id = String.format("%s:%s", symbolicName, version);
+        Response response = client().path(String.format("/catalog/entities/%s/deprecated", id))
+                    .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+                    .post(deprecated);
+        assertEquals(response.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
+    }
+    
+    private void disableCatalogItem(String symbolicName, String version, boolean disabled) {
+        String id = String.format("%s:%s", symbolicName, version);
+        Response response = client().path(String.format("/catalog/entities/%s/disabled", id))
+                .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+                .post(disabled);
+        assertEquals(response.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
+    }
+
+    private void addTestCatalogItem(String catalogItemId, String itemType, String version, String service) {
+        String yaml =
+                "brooklyn.catalog:\n"+
+                "  id: " + catalogItemId + "\n"+
+                "  name: My Catalog App\n"+
+                (itemType!=null ? "  item_type: "+itemType+"\n" : "")+
+                "  description: My description\n"+
+                "  icon_url: classpath:///redis-logo.png\n"+
+                "  version: " + version + "\n"+
+                "\n"+
+                "services:\n"+
+                "- type: " + service + "\n";
+
+        client().path("/catalog").post(yaml);
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/CatalogResetTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/CatalogResetTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/CatalogResetTest.java
new file mode 100644
index 0000000..55e536c
--- /dev/null
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/CatalogResetTest.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import static org.testng.Assert.assertNotNull;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+import org.apache.brooklyn.api.catalog.BrooklynCatalog;
+import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry;
+import org.apache.brooklyn.test.http.TestHttpRequestHandler;
+import org.apache.brooklyn.test.http.TestHttpServer;
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+import org.apache.brooklyn.util.core.ResourceUtils;
+import org.apache.brooklyn.util.http.HttpTool;
+import static org.testng.Assert.assertEquals;
+
+@Test( // by using a different suite name we disallow interleaving other tests between the methods of this test class, which wrecks the test fixtures
+    suiteName = "CatalogResetTest")
+public class CatalogResetTest extends BrooklynRestResourceTest {
+
+    private TestHttpServer server;
+    private String serverUrl;
+
+    @BeforeClass(alwaysRun=true)
+    public void setUp() throws Exception {
+        server = new TestHttpServer()
+            .handler("/404", new TestHttpRequestHandler().code(Response.Status.NOT_FOUND.getStatusCode()).response("Not Found"))
+            .handler("/200", new TestHttpRequestHandler().response("OK"))
+            .start();
+        serverUrl = server.getUrl();
+    }
+
+    @Override
+    protected boolean useLocalScannedCatalog() {
+        return true;
+    }
+
+    @AfterClass(alwaysRun=true)
+    public void tearDown() throws Exception {
+        if (server != null)
+            server.stop();
+    }
+
+    @Test
+    public void testConnectionError() throws Exception {
+        Response response = reset("http://0.0.0.0/can-not-connect", false);
+        assertEquals(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), response.getStatus());
+    }
+
+    @Test
+    public void testConnectionErrorIgnore() throws Exception {
+        reset("http://0.0.0.0/can-not-connect", true);
+    }
+
+    @Test
+    public void testResourceMissingError() throws Exception {
+        Response response = reset(serverUrl + "/404", false);
+        assertEquals(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), response.getStatus());
+    }
+
+    @Test
+    public void testResourceMissingIgnore() throws Exception {
+        reset(serverUrl + "/404", true);
+    }
+
+    @Test
+    public void testResourceInvalidError() throws Exception {
+        Response response = reset(serverUrl + "/200", false);
+        assertEquals(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), response.getStatus());
+    }
+
+    @Test
+    public void testResourceInvalidIgnore() throws Exception {
+        reset(serverUrl + "/200", true);
+    }
+
+    private Response reset(String bundleLocation, boolean ignoreErrors) throws Exception {
+        String xml = ResourceUtils.create(this).getResourceAsString("classpath://reset-catalog.xml");
+        Response response = client().path("/catalog/reset")
+            .query("ignoreErrors", Boolean.toString(ignoreErrors))
+            .header("Content-type", MediaType.APPLICATION_XML)
+            .post(xml.replace("${bundle-location}", bundleLocation));
+
+        //if above succeeds assert catalog contents
+        if (HttpTool.isStatusCodeHealthy(response.getStatus()))
+            assertItems();
+        
+        return response;
+    }
+    
+    private void assertItems() {
+        BrooklynTypeRegistry types = getManagementContext().getTypeRegistry();
+        assertNotNull(types.get("org.apache.brooklyn.entity.stock.BasicApplication", BrooklynCatalog.DEFAULT_VERSION));
+        assertNotNull(types.get("org.apache.brooklyn.test.osgi.entities.SimpleApplication", BrooklynCatalog.DEFAULT_VERSION));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/CatalogResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/CatalogResourceTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/CatalogResourceTest.java
new file mode 100644
index 0000000..b6a77d5
--- /dev/null
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/CatalogResourceTest.java
@@ -0,0 +1,510 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.awt.*;
+import java.io.IOException;
+import java.net.URI;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.apache.brooklyn.api.typereg.OsgiBundleWithUrl;
+import org.apache.brooklyn.api.typereg.RegisteredType;
+import org.apache.brooklyn.core.catalog.internal.CatalogUtils;
+import org.apache.brooklyn.core.mgmt.osgi.OsgiStandaloneTest;
+import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.apache.brooklyn.policy.autoscaling.AutoScalerPolicy;
+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.testing.BrooklynRestResourceTest;
+import org.apache.brooklyn.test.support.TestResourceUnavailableException;
+import org.apache.brooklyn.util.javalang.Reflections;
+import org.apache.http.HttpHeaders;
+import org.apache.http.entity.ContentType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+import org.testng.reporters.Files;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.Iterables;
+import java.io.InputStream;
+import javax.ws.rs.core.GenericType;
+
+@Test( // by using a different suite name we disallow interleaving other tests between the methods of this test class, which wrecks the test fixtures
+        suiteName = "CatalogResourceTest")
+public class CatalogResourceTest extends BrooklynRestResourceTest {
+
+    private static final Logger log = LoggerFactory.getLogger(CatalogResourceTest.class);
+    
+    private static String TEST_VERSION = "0.1.2";
+
+    @Override
+    protected boolean useLocalScannedCatalog() {
+        return true;
+    }
+    
+    @Test
+    /** based on CampYamlLiteTest */
+    public void testRegisterCustomEntityTopLevelSyntaxWithBundleWhereEntityIsFromCoreAndIconFromBundle() {
+        TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH);
+
+        String symbolicName = "my.catalog.entity.id";
+        String bundleUrl = OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL;
+        String yaml =
+                "brooklyn.catalog:\n"+
+                "  id: " + symbolicName + "\n"+
+                "  name: My Catalog App\n"+
+                "  description: My description\n"+
+                "  icon_url: classpath:/org/apache/brooklyn/test/osgi/entities/icon.gif\n"+
+                "  version: " + TEST_VERSION + "\n"+
+                "  libraries:\n"+
+                "  - url: " + bundleUrl + "\n"+
+                "\n"+
+                "services:\n"+
+                "- type: org.apache.brooklyn.core.test.entity.TestEntity\n";
+
+        Response response = client().path("/catalog")
+                .post(yaml);
+
+        assertEquals(response.getStatus(), Response.Status.CREATED.getStatusCode());
+
+        CatalogEntitySummary entityItem = client().path("/catalog/entities/"+symbolicName + "/" + TEST_VERSION)
+                .get(CatalogEntitySummary.class);
+
+        Assert.assertNotNull(entityItem.getPlanYaml());
+        Assert.assertTrue(entityItem.getPlanYaml().contains("org.apache.brooklyn.core.test.entity.TestEntity"));
+
+        assertEquals(entityItem.getId(), ver(symbolicName));
+        assertEquals(entityItem.getSymbolicName(), symbolicName);
+        assertEquals(entityItem.getVersion(), TEST_VERSION);
+
+        // and internally let's check we have libraries
+        RegisteredType item = getManagementContext().getTypeRegistry().get(symbolicName, TEST_VERSION);
+        Assert.assertNotNull(item);
+        Collection<OsgiBundleWithUrl> libs = item.getLibraries();
+        assertEquals(libs.size(), 1);
+        assertEquals(Iterables.getOnlyElement(libs).getUrl(), bundleUrl);
+
+        // now let's check other things on the item
+        URI expectedIconUrl = URI.create(getEndpointAddress() + "/catalog/icon/" + symbolicName + "/" + entityItem.getVersion()).normalize();
+        assertEquals(entityItem.getName(), "My Catalog App");
+        assertEquals(entityItem.getDescription(), "My description");
+        assertEquals(entityItem.getIconUrl(), expectedIconUrl.getPath());
+        assertEquals(item.getIconUrl(), "classpath:/org/apache/brooklyn/test/osgi/entities/icon.gif");
+
+        // an InterfacesTag should be created for every catalog item
+        assertEquals(entityItem.getTags().size(), 1);
+        Object tag = entityItem.getTags().iterator().next();
+        List<String> actualInterfaces = ((Map<String, List<String>>) tag).get("traits");
+        List<Class<?>> expectedInterfaces = Reflections.getAllInterfaces(TestEntity.class);
+        assertEquals(actualInterfaces.size(), expectedInterfaces.size());
+        for (Class<?> expectedInterface : expectedInterfaces) {
+            assertTrue(actualInterfaces.contains(expectedInterface.getName()));
+        }
+
+        byte[] iconData = client().path("/catalog/icon/" + symbolicName + "/" + TEST_VERSION).get(byte[].class);
+        assertEquals(iconData.length, 43);
+    }
+
+    @Test
+    public void testRegisterOsgiPolicyTopLevelSyntax() {
+        TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH);
+
+        String symbolicName = "my.catalog.policy.id";
+        String policyType = "org.apache.brooklyn.test.osgi.entities.SimplePolicy";
+        String bundleUrl = OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL;
+
+        String yaml =
+                "brooklyn.catalog:\n"+
+                "  id: " + symbolicName + "\n"+
+                "  name: My Catalog App\n"+
+                "  description: My description\n"+
+                "  version: " + TEST_VERSION + "\n" +
+                "  libraries:\n"+
+                "  - url: " + bundleUrl + "\n"+
+                "\n"+
+                "brooklyn.policies:\n"+
+                "- type: " + policyType;
+
+        CatalogPolicySummary entityItem = Iterables.getOnlyElement( client().path("/catalog")
+                .post(yaml, new GenericType<Map<String,CatalogPolicySummary>>() {}).values() );
+
+        Assert.assertNotNull(entityItem.getPlanYaml());
+        Assert.assertTrue(entityItem.getPlanYaml().contains(policyType));
+        assertEquals(entityItem.getId(), ver(symbolicName));
+        assertEquals(entityItem.getSymbolicName(), symbolicName);
+        assertEquals(entityItem.getVersion(), TEST_VERSION);
+    }
+
+    @Test
+    public void testListAllEntities() {
+        List<CatalogEntitySummary> entities = client().path("/catalog/entities")
+                .get(new GenericType<List<CatalogEntitySummary>>() {});
+        assertTrue(entities.size() > 0);
+    }
+
+    @Test
+    public void testListAllEntitiesAsItem() {
+        // ensure things are happily downcasted and unknown properties ignored (e.g. sensors, effectors)
+        List<CatalogItemSummary> entities = client().path("/catalog/entities")
+                .get(new GenericType<List<CatalogItemSummary>>() {});
+        assertTrue(entities.size() > 0);
+    }
+
+    @Test
+    public void testFilterListOfEntitiesByName() {
+        List<CatalogEntitySummary> entities = client().path("/catalog/entities")
+                .query("fragment", "reDISclusTER").get(new GenericType<List<CatalogEntitySummary>>() {});
+        assertEquals(entities.size(), 1);
+
+        log.info("RedisCluster-like entities are: " + entities);
+
+        List<CatalogEntitySummary> entities2 = client().path("/catalog/entities")
+                .query("regex", "[Rr]ed.[sulC]+ter").get(new GenericType<List<CatalogEntitySummary>>() {});
+        assertEquals(entities2.size(), 1);
+
+        assertEquals(entities, entities2);
+    
+        List<CatalogEntitySummary> entities3 = client().path("/catalog/entities")
+                .query("fragment", "bweqQzZ").get(new GenericType<List<CatalogEntitySummary>>() {});
+        assertEquals(entities3.size(), 0);
+
+        List<CatalogEntitySummary> entities4 = client().path("/catalog/entities")
+                .query("regex", "bweq+z+").get(new GenericType<List<CatalogEntitySummary>>() {});
+        assertEquals(entities4.size(), 0);
+    }
+
+    @Test
+    @Deprecated
+    // If we move to using a yaml catalog item, the details will be of the wrapping app,
+    // not of the entity itself, so the test won't make sense any more.
+    public void testGetCatalogEntityDetails() {
+        CatalogEntitySummary details = client()
+                .path(URI.create("/catalog/entities/org.apache.brooklyn.entity.nosql.redis.RedisStore"))
+                .get(CatalogEntitySummary.class);
+        assertTrue(details.toString().contains("redis.port"), "expected more config, only got: "+details);
+        String iconUrl = "/catalog/icon/" + details.getSymbolicName();
+        assertTrue(details.getIconUrl().contains(iconUrl), "expected brooklyn URL for icon image, but got: " + details.getIconUrl());
+    }
+
+    @Test
+    @Deprecated
+    // If we move to using a yaml catalog item, the details will be of the wrapping app,
+    // not of the entity itself, so the test won't make sense any more.
+    public void testGetCatalogEntityPlusVersionDetails() {
+        CatalogEntitySummary details = client()
+                .path(URI.create("/catalog/entities/org.apache.brooklyn.entity.nosql.redis.RedisStore:0.0.0.SNAPSHOT"))
+                .get(CatalogEntitySummary.class);
+        assertTrue(details.toString().contains("redis.port"), "expected more config, only got: "+details);
+        URI expectedIconUrl = URI.create(getEndpointAddress() + "/catalog/icon/" + details.getSymbolicName() + "/" + details.getVersion()).normalize();
+        assertEquals(details.getIconUrl(), expectedIconUrl.getPath(), "expected brooklyn URL for icon image ("+expectedIconUrl+"), but got: "+details.getIconUrl());
+    }
+
+    @Test
+    public void testGetCatalogEntityIconDetails() throws IOException {
+        String catalogItemId = "testGetCatalogEntityIconDetails";
+        addTestCatalogItemRedisAsEntity(catalogItemId);
+        Response response = client().path(URI.create("/catalog/icon/" + catalogItemId + "/" + TEST_VERSION))
+                .get();
+        response.bufferEntity();
+        Assert.assertEquals(response.getStatus(), 200);
+        Assert.assertEquals(response.getMediaType(), MediaType.valueOf("image/png"));
+        Image image = Toolkit.getDefaultToolkit().createImage(Files.readFile(response.readEntity(InputStream.class)));
+        Assert.assertNotNull(image);
+    }
+
+    private void addTestCatalogItemRedisAsEntity(String catalogItemId) {
+        addTestCatalogItem(catalogItemId, null, TEST_VERSION, "org.apache.brooklyn.entity.nosql.redis.RedisStore");
+    }
+
+    private void addTestCatalogItem(String catalogItemId, String itemType, String version, String service) {
+        String yaml =
+                "brooklyn.catalog:\n"+
+                "  id: " + catalogItemId + "\n"+
+                "  name: My Catalog App\n"+
+                (itemType!=null ? "  item_type: "+itemType+"\n" : "")+
+                "  description: My description\n"+
+                "  icon_url: classpath:///redis-logo.png\n"+
+                "  version: " + version + "\n"+
+                "\n"+
+                "services:\n"+
+                "- type: " + service + "\n";
+
+        client().path("/catalog").post(yaml);
+    }
+
+    private enum DeprecateStyle {
+        NEW_STYLE,
+        LEGACY_STYLE
+    }
+    private void deprecateCatalogItem(DeprecateStyle style, String symbolicName, String version, boolean deprecated) {
+        String id = String.format("%s:%s", symbolicName, version);
+        Response response;
+        if (style == DeprecateStyle.NEW_STYLE) {
+            response = client().path(String.format("/catalog/entities/%s/deprecated", id))
+                    .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+                    .post(deprecated);
+        } else {
+            response = client().path(String.format("/catalog/entities/%s/deprecated/%s", id, deprecated))
+                    .post(null);
+        }
+        assertEquals(response.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
+    }
+
+    private void disableCatalogItem(String symbolicName, String version, boolean disabled) {
+        String id = String.format("%s:%s", symbolicName, version);
+        Response getDisableResponse = client().path(String.format("/catalog/entities/%s/disabled", id))
+                .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+                .post(disabled);
+        assertEquals(getDisableResponse.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
+    }
+
+    @Test
+    public void testListPolicies() {
+        Set<CatalogPolicySummary> policies = client().path("/catalog/policies")
+                .get(new GenericType<Set<CatalogPolicySummary>>() {});
+
+        assertTrue(policies.size() > 0);
+        CatalogItemSummary asp = null;
+        for (CatalogItemSummary p : policies) {
+            if (AutoScalerPolicy.class.getName().equals(p.getType()))
+                asp = p;
+        }
+        Assert.assertNotNull(asp, "didn't find AutoScalerPolicy");
+    }
+
+    @Test
+    public void testLocationAddGetAndRemove() {
+        String symbolicName = "my.catalog.location.id";
+        String locationType = "localhost";
+        String yaml = Joiner.on("\n").join(
+                "brooklyn.catalog:",
+                "  id: " + symbolicName,
+                "  name: My Catalog Location",
+                "  description: My description",
+                "  version: " + TEST_VERSION,
+                "",
+                "brooklyn.locations:",
+                "- type: " + locationType);
+
+        // Create location item
+        Map<String, CatalogLocationSummary> items = client().path("/catalog")
+                .post(yaml, new GenericType<Map<String,CatalogLocationSummary>>() {});
+        CatalogLocationSummary locationItem = Iterables.getOnlyElement(items.values());
+
+        Assert.assertNotNull(locationItem.getPlanYaml());
+        Assert.assertTrue(locationItem.getPlanYaml().contains(locationType));
+        assertEquals(locationItem.getId(), ver(symbolicName));
+        assertEquals(locationItem.getSymbolicName(), symbolicName);
+        assertEquals(locationItem.getVersion(), TEST_VERSION);
+
+        // Retrieve location item
+        CatalogLocationSummary location = client().path("/catalog/locations/"+symbolicName+"/"+TEST_VERSION)
+                .get(CatalogLocationSummary.class);
+        assertEquals(location.getSymbolicName(), symbolicName);
+
+        // Retrieve all locations
+        Set<CatalogLocationSummary> locations = client().path("/catalog/locations")
+                .get(new GenericType<Set<CatalogLocationSummary>>() {});
+        boolean found = false;
+        for (CatalogLocationSummary contender : locations) {
+            if (contender.getSymbolicName().equals(symbolicName)) {
+                found = true;
+                break;
+            }
+        }
+        Assert.assertTrue(found, "contenders="+locations);
+        
+        // Delete
+        Response deleteResponse = client().path("/catalog/locations/"+symbolicName+"/"+TEST_VERSION)
+                .delete();
+        assertEquals(deleteResponse.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
+
+        Response getPostDeleteResponse = client().path("/catalog/locations/"+symbolicName+"/"+TEST_VERSION)
+                .get();
+        assertEquals(getPostDeleteResponse.getStatus(), Response.Status.NOT_FOUND.getStatusCode());
+    }
+
+    @Test
+    public void testDeleteCustomEntityFromCatalog() {
+        String symbolicName = "my.catalog.app.id.to.subsequently.delete";
+        String yaml =
+                "name: "+symbolicName+"\n"+
+                // FIXME name above should be unnecessary when brooklyn.catalog below is working
+                "brooklyn.catalog:\n"+
+                "  id: " + symbolicName + "\n"+
+                "  name: My Catalog App To Be Deleted\n"+
+                "  description: My description\n"+
+                "  version: " + TEST_VERSION + "\n"+
+                "\n"+
+                "services:\n"+
+                "- type: org.apache.brooklyn.core.test.entity.TestEntity\n";
+
+        client().path("/catalog")
+                .post(yaml);
+
+        Response deleteResponse = client().path("/catalog/entities/"+symbolicName+"/"+TEST_VERSION)
+                .delete();
+
+        assertEquals(deleteResponse.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
+
+        Response getPostDeleteResponse = client().path("/catalog/entities/"+symbolicName+"/"+TEST_VERSION)
+                .get();
+        assertEquals(getPostDeleteResponse.getStatus(), Response.Status.NOT_FOUND.getStatusCode());
+    }
+
+    @Test
+    public void testSetDeprecated() {
+        runSetDeprecated(DeprecateStyle.NEW_STYLE);
+    }
+    
+    // Uses old-style "/catalog/{itemId}/deprecated/true", rather than the "true" in the request body.
+    @Test
+    @Deprecated
+    public void testSetDeprecatedLegacy() {
+        runSetDeprecated(DeprecateStyle.LEGACY_STYLE);
+    }
+
+    protected void runSetDeprecated(DeprecateStyle style) {
+        String symbolicName = "my.catalog.item.id.for.deprecation";
+        String serviceType = "org.apache.brooklyn.entity.stock.BasicApplication";
+        addTestCatalogItem(symbolicName, "template", TEST_VERSION, serviceType);
+        addTestCatalogItem(symbolicName, "template", "2.0", serviceType);
+        try {
+            List<CatalogEntitySummary> applications = client().path("/catalog/applications")
+                    .query("fragment", symbolicName).query("allVersions", "true").get(new GenericType<List<CatalogEntitySummary>>() {});
+            assertEquals(applications.size(), 2);
+            CatalogItemSummary summary0 = applications.get(0);
+            CatalogItemSummary summary1 = applications.get(1);
+    
+            // Deprecate: that app should be excluded
+            deprecateCatalogItem(style, summary0.getSymbolicName(), summary0.getVersion(), true);
+    
+            List<CatalogEntitySummary> applicationsAfterDeprecation = client().path("/catalog/applications")
+                    .query("fragment", "basicapp").query("allVersions", "true").get(new GenericType<List<CatalogEntitySummary>>() {});
+    
+            assertEquals(applicationsAfterDeprecation.size(), 1);
+            assertTrue(applicationsAfterDeprecation.contains(summary1));
+    
+            // Un-deprecate: that app should be included again
+            deprecateCatalogItem(style, summary0.getSymbolicName(), summary0.getVersion(), false);
+    
+            List<CatalogEntitySummary> applicationsAfterUnDeprecation = client().path("/catalog/applications")
+                    .query("fragment", "basicapp").query("allVersions", "true").get(new GenericType<List<CatalogEntitySummary>>() {});
+    
+            assertEquals(applications, applicationsAfterUnDeprecation);
+        } finally {
+            client().path("/catalog/entities/"+symbolicName+"/"+TEST_VERSION)
+                    .delete();
+            client().path("/catalog/entities/"+symbolicName+"/"+"2.0")
+                    .delete();
+        }
+    }
+
+    @Test
+    public void testSetDisabled() {
+        String symbolicName = "my.catalog.item.id.for.disabling";
+        String serviceType = "org.apache.brooklyn.entity.stock.BasicApplication";
+        addTestCatalogItem(symbolicName, "template", TEST_VERSION, serviceType);
+        addTestCatalogItem(symbolicName, "template", "2.0", serviceType);
+        try {
+            List<CatalogEntitySummary> applications = client().path("/catalog/applications")
+                    .query("fragment", symbolicName).query("allVersions", "true").get(new GenericType<List<CatalogEntitySummary>>() {});
+            assertEquals(applications.size(), 2);
+            CatalogItemSummary summary0 = applications.get(0);
+            CatalogItemSummary summary1 = applications.get(1);
+    
+            // Disable: that app should be excluded
+            disableCatalogItem(summary0.getSymbolicName(), summary0.getVersion(), true);
+    
+            List<CatalogEntitySummary> applicationsAfterDisabled = client().path("/catalog/applications")
+                    .query("fragment", "basicapp").query("allVersions", "true").get(new GenericType<List<CatalogEntitySummary>>() {});
+    
+            assertEquals(applicationsAfterDisabled.size(), 1);
+            assertTrue(applicationsAfterDisabled.contains(summary1));
+    
+            // Un-disable: that app should be included again
+            disableCatalogItem(summary0.getSymbolicName(), summary0.getVersion(), false);
+    
+            List<CatalogEntitySummary> applicationsAfterUnDisabled = client().path("/catalog/applications")
+                    .query("fragment", "basicapp").query("allVersions", "true").get(new GenericType<List<CatalogEntitySummary>>() {});
+    
+            assertEquals(applications, applicationsAfterUnDisabled);
+        } finally {
+            client().path("/catalog/entities/"+symbolicName+"/"+TEST_VERSION)
+                    .delete();
+            client().path("/catalog/entities/"+symbolicName+"/"+"2.0")
+                    .delete();
+        }
+    }
+
+    @Test
+    public void testAddUnreachableItem() {
+        addInvalidCatalogItem("http://0.0.0.0/can-not-connect");
+    }
+
+    @Test
+    public void testAddInvalidItem() {
+        //equivalent to HTTP response 200 text/html
+        addInvalidCatalogItem("classpath://not-a-jar-file.txt");
+    }
+
+    @Test
+    public void testAddMissingItem() {
+        //equivalent to HTTP response 404 text/html
+        addInvalidCatalogItem("classpath://missing-jar-file.txt");
+    }
+
+    private void addInvalidCatalogItem(String bundleUrl) {
+        String symbolicName = "my.catalog.entity.id";
+        String yaml =
+                "brooklyn.catalog:\n"+
+                "  id: " + symbolicName + "\n"+
+                "  name: My Catalog App\n"+
+                "  description: My description\n"+
+                "  icon_url: classpath:/org/apache/brooklyn/test/osgi/entities/icon.gif\n"+
+                "  version: " + TEST_VERSION + "\n"+
+                "  libraries:\n"+
+                "  - url: " + bundleUrl + "\n"+
+                "\n"+
+                "services:\n"+
+                "- type: org.apache.brooklyn.core.test.entity.TestEntity\n";
+
+        Response response = client().path("/catalog")
+                .post(yaml);
+
+        assertEquals(response.getStatus(), Response.Status.INTERNAL_SERVER_ERROR.getStatusCode());
+    }
+
+    private static String ver(String id) {
+        return CatalogUtils.getVersionedId(id, TEST_VERSION);
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/DelegatingPrintStream.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/DelegatingPrintStream.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/DelegatingPrintStream.java
new file mode 100644
index 0000000..713dccb
--- /dev/null
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/DelegatingPrintStream.java
@@ -0,0 +1,183 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.util.Locale;
+
+public abstract class DelegatingPrintStream extends PrintStream {
+    
+    public DelegatingPrintStream() {
+        super(new IllegalOutputStream());
+    }
+
+    public static class IllegalOutputStream extends OutputStream {
+        @Override public void write(int b) {
+            throw new IllegalStateException("should not write to this output stream");
+        }
+        @Override public void write(byte[] b, int off, int len) {
+            throw new IllegalStateException("should not write to this output stream");
+        }
+    }
+    
+    protected abstract PrintStream getDelegate();
+
+    public int hashCode() {
+        return getDelegate().hashCode();
+    }
+
+    public void write(byte[] b) throws IOException {
+        getDelegate().write(b);
+    }
+
+    public boolean equals(Object obj) {
+        return getDelegate().equals(obj);
+    }
+
+    public String toString() {
+        return getDelegate().toString();
+    }
+
+    public void flush() {
+        getDelegate().flush();
+    }
+
+    public void close() {
+        getDelegate().close();
+    }
+
+    public boolean checkError() {
+        return getDelegate().checkError();
+    }
+
+    public void write(int b) {
+        getDelegate().write(b);
+    }
+
+    public void write(byte[] buf, int off, int len) {
+        getDelegate().write(buf, off, len);
+    }
+
+    public void print(boolean b) {
+        getDelegate().print(b);
+    }
+
+    public void print(char c) {
+        getDelegate().print(c);
+    }
+
+    public void print(int i) {
+        getDelegate().print(i);
+    }
+
+    public void print(long l) {
+        getDelegate().print(l);
+    }
+
+    public void print(float f) {
+        getDelegate().print(f);
+    }
+
+    public void print(double d) {
+        getDelegate().print(d);
+    }
+
+    public void print(char[] s) {
+        getDelegate().print(s);
+    }
+
+    public void print(String s) {
+        getDelegate().print(s);
+    }
+
+    public void print(Object obj) {
+        getDelegate().print(obj);
+    }
+
+    public void println() {
+        getDelegate().println();
+    }
+
+    public void println(boolean x) {
+        getDelegate().println(x);
+    }
+
+    public void println(char x) {
+        getDelegate().println(x);
+    }
+
+    public void println(int x) {
+        getDelegate().println(x);
+    }
+
+    public void println(long x) {
+        getDelegate().println(x);
+    }
+
+    public void println(float x) {
+        getDelegate().println(x);
+    }
+
+    public void println(double x) {
+        getDelegate().println(x);
+    }
+
+    public void println(char[] x) {
+        getDelegate().println(x);
+    }
+
+    public void println(String x) {
+        getDelegate().println(x);
+    }
+
+    public void println(Object x) {
+        getDelegate().println(x);
+    }
+
+    public PrintStream printf(String format, Object... args) {
+        return getDelegate().printf(format, args);
+    }
+
+    public PrintStream printf(Locale l, String format, Object... args) {
+        return getDelegate().printf(l, format, args);
+    }
+
+    public PrintStream format(String format, Object... args) {
+        return getDelegate().format(format, args);
+    }
+
+    public PrintStream format(Locale l, String format, Object... args) {
+        return getDelegate().format(l, format, args);
+    }
+
+    public PrintStream append(CharSequence csq) {
+        return getDelegate().append(csq);
+    }
+
+    public PrintStream append(CharSequence csq, int start, int end) {
+        return getDelegate().append(csq, start, end);
+    }
+
+    public PrintStream append(char c) {
+        return getDelegate().append(c);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/DescendantsTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/DescendantsTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/DescendantsTest.java
new file mode 100644
index 0000000..65a6a3b
--- /dev/null
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/DescendantsTest.java
@@ -0,0 +1,128 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeoutException;
+
+import org.apache.brooklyn.api.entity.Application;
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.core.sensor.Sensors;
+import org.apache.brooklyn.rest.domain.ApplicationSpec;
+import org.apache.brooklyn.rest.domain.EntitySpec;
+import org.apache.brooklyn.rest.domain.EntitySummary;
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+import org.apache.brooklyn.rest.testing.mocks.RestMockSimpleEntity;
+import org.apache.brooklyn.util.collections.MutableList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.Response;
+
+@Test(singleThreaded = true,
+        // by using a different suite name we disallow interleaving other tests between the methods of this test class, which wrecks the test fixtures
+        suiteName = "DescendantsTest")
+public class DescendantsTest extends BrooklynRestResourceTest {
+
+    private static final Logger log = LoggerFactory.getLogger(DescendantsTest.class);
+
+    private final ApplicationSpec simpleSpec = ApplicationSpec.builder().name("simple-app").
+        entities(ImmutableSet.of(
+            new EntitySpec("simple-ent-1", RestMockSimpleEntity.class.getName()),
+            new EntitySpec("simple-ent-2", RestMockSimpleEntity.class.getName()))).
+        locations(ImmutableSet.of("localhost")).
+        build();
+
+    @Test
+    public void testDescendantsInSimpleDeployedApplication() throws InterruptedException, TimeoutException, IOException {
+        Response response = clientDeploy(simpleSpec);
+        assertTrue(response.getStatus()/100 == 2, "response is "+response);
+        Application application = Iterables.getOnlyElement( getManagementContext().getApplications() );
+        List<Entity> entities = MutableList.copyOf( application.getChildren() );
+        log.debug("Created app "+application+" with children entities "+entities);
+        assertEquals(entities.size(), 2);
+        
+        Set<EntitySummary> descs;
+        descs = client().path("/applications/"+application.getApplicationId()+"/descendants")
+            .get(new GenericType<Set<EntitySummary>>() {});
+        // includes itself
+        assertEquals(descs.size(), 3);
+        
+        descs = client().path("/applications/"+application.getApplicationId()+"/descendants")
+            .query("typeRegex", ".*\\.RestMockSimpleEntity")
+            .get(new GenericType<Set<EntitySummary>>() {});
+        assertEquals(descs.size(), 2);
+        
+        descs = client().path("/applications/"+application.getApplicationId()+"/descendants")
+            .query("typeRegex", ".*\\.BestBockSimpleEntity")
+            .get(new GenericType<Set<EntitySummary>>() {});
+        assertEquals(descs.size(), 0);
+
+        descs = client().path("/applications/"+application.getApplicationId()
+                + "/entities/"+entities.get(1).getId() + "/descendants")
+            .query("typeRegex", ".*\\.RestMockSimpleEntity")
+            .get(new GenericType<Set<EntitySummary>>() {});
+        assertEquals(descs.size(), 1);
+        
+        Map<String,Object> sensors = client().path("/applications/"+application.getApplicationId()+"/descendants/sensor/foo")
+            .query("typeRegex", ".*\\.RestMockSimpleEntity")
+            .get(new GenericType<Map<String,Object>>() {});
+        assertEquals(sensors.size(), 0);
+
+        long v = 0;
+        application.sensors().set(Sensors.newLongSensor("foo"), v);
+        for (Entity e: entities)
+            e.sensors().set(Sensors.newLongSensor("foo"), v+=123);
+        
+        sensors = client().path("/applications/"+application.getApplicationId()+"/descendants/sensor/foo")
+            .get(new GenericType<Map<String,Object>>() {});
+        assertEquals(sensors.size(), 3);
+        assertEquals(sensors.get(entities.get(1).getId()), 246);
+        
+        sensors = client().path("/applications/"+application.getApplicationId()+"/descendants/sensor/foo")
+            .query("typeRegex", ".*\\.RestMockSimpleEntity")
+            .get(new GenericType<Map<String,Object>>() {});
+        assertEquals(sensors.size(), 2);
+        
+        sensors = client().path("/applications/"+application.getApplicationId()+"/"
+            + "entities/"+entities.get(1).getId()+"/"
+            + "descendants/sensor/foo")
+            .query("typeRegex", ".*\\.RestMockSimpleEntity")
+            .get(new GenericType<Map<String,Object>>() {});
+        assertEquals(sensors.size(), 1);
+
+        sensors = client().path("/applications/"+application.getApplicationId()+"/"
+            + "entities/"+entities.get(1).getId()+"/"
+            + "descendants/sensor/foo")
+            .query("typeRegex", ".*\\.FestPockSimpleEntity")
+            .get(new GenericType<Map<String,Object>>() {});
+        assertEquals(sensors.size(), 0);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/EntityConfigResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/EntityConfigResourceTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/EntityConfigResourceTest.java
new file mode 100644
index 0000000..a800ed2
--- /dev/null
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/EntityConfigResourceTest.java
@@ -0,0 +1,171 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nullable;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+import org.apache.brooklyn.core.entity.EntityInternal;
+import org.apache.brooklyn.core.entity.EntityPredicates;
+import org.apache.brooklyn.rest.domain.ApplicationSpec;
+import org.apache.brooklyn.rest.domain.EntityConfigSummary;
+import org.apache.brooklyn.rest.domain.EntitySpec;
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+import org.apache.brooklyn.rest.testing.mocks.RestMockSimpleEntity;
+import org.apache.brooklyn.util.collections.MutableMap;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import javax.ws.rs.core.GenericType;
+
+@Test(singleThreaded = true,
+        // by using a different suite name we disallow interleaving other tests between the methods of this test class, which wrecks the test fixtures
+        suiteName = "EntityConfigResourceTest")
+public class EntityConfigResourceTest extends BrooklynRestResourceTest {
+    
+    private final static Logger log = LoggerFactory.getLogger(EntityConfigResourceTest.class);
+    private URI applicationUri;
+    private EntityInternal entity;
+
+    @BeforeClass(alwaysRun = true)
+    public void setUp() throws Exception {
+        // Deploy an application that we'll use to read the configuration of
+        final ApplicationSpec simpleSpec = ApplicationSpec.builder().name("simple-app").
+                  entities(ImmutableSet.of(new EntitySpec("simple-ent", RestMockSimpleEntity.class.getName(), ImmutableMap.of("install.version", "1.0.0")))).
+                  locations(ImmutableSet.of("localhost")).
+                  build();
+
+        startServer();
+        Response response = clientDeploy(simpleSpec);
+        int status = response.getStatus();
+        assertTrue(status >= 200 && status <= 299, "expected HTTP Response of 2xx but got " + status);
+        applicationUri = response.getLocation();
+        log.debug("Built app: application");
+        waitForApplicationToBeRunning(applicationUri);
+        
+        entity = (EntityInternal) Iterables.find(getManagementContext().getEntityManager().getEntities(), EntityPredicates.displayNameEqualTo("simple-ent"));
+    }
+
+    @Test
+    public void testList() throws Exception {
+        List<EntityConfigSummary> entityConfigSummaries = client().path(
+                URI.create("/applications/simple-app/entities/simple-ent/config"))
+                .get(new GenericType<List<EntityConfigSummary>>() {
+                });
+        
+        // Default entities have over a dozen config entries, but it's unnecessary to test them all; just pick one
+        // representative config key
+        Optional<EntityConfigSummary> configKeyOptional = Iterables.tryFind(entityConfigSummaries, new Predicate<EntityConfigSummary>() {
+            @Override
+            public boolean apply(@Nullable EntityConfigSummary input) {
+                return input != null && "install.version".equals(input.getName());
+            }
+        });
+        assertTrue(configKeyOptional.isPresent());
+        
+        assertEquals(configKeyOptional.get().getType(), "java.lang.String");
+        assertEquals(configKeyOptional.get().getDescription(), "Suggested version");
+        assertFalse(configKeyOptional.get().isReconfigurable());
+        assertNull(configKeyOptional.get().getDefaultValue());
+        assertNull(configKeyOptional.get().getLabel());
+        assertNull(configKeyOptional.get().getPriority());
+    }
+
+    @Test
+    public void testBatchConfigRead() throws Exception {
+        Map<String, Object> currentState = client().path(
+                URI.create("/applications/simple-app/entities/simple-ent/config/current-state"))
+                .get(new GenericType<Map<String, Object>>() {
+                });
+        assertTrue(currentState.containsKey("install.version"));
+        assertEquals(currentState.get("install.version"), "1.0.0");
+    }
+
+    @Test
+    public void testGetJson() throws Exception {
+        String configValue = client().path(
+                URI.create("/applications/simple-app/entities/simple-ent/config/install.version"))
+                .accept(MediaType.APPLICATION_JSON_TYPE)
+                .get(String.class);
+        assertEquals(configValue, "\"1.0.0\"");
+    }
+
+    @Test
+    public void testGetPlain() throws Exception {
+        String configValue = client().path(
+                URI.create("/applications/simple-app/entities/simple-ent/config/install.version"))
+                .accept(MediaType.TEXT_PLAIN_TYPE)
+                .get(String.class);
+        assertEquals(configValue, "1.0.0");
+    }
+
+    @Test
+    public void testSet() throws Exception {
+        try {
+            String uri = "/applications/simple-app/entities/simple-ent/config/"+
+                RestMockSimpleEntity.SAMPLE_CONFIG.getName();
+            Response response = client().path(uri)
+                .type(MediaType.APPLICATION_JSON_TYPE)
+                .post("\"hello world\"");
+            assertEquals(response.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
+
+            assertEquals(entity.getConfig(RestMockSimpleEntity.SAMPLE_CONFIG), "hello world");
+            
+            String value = client().path(uri).accept(MediaType.APPLICATION_JSON_TYPE).get(String.class);
+            assertEquals(value, "\"hello world\"");
+
+        } finally { entity.config().set(RestMockSimpleEntity.SAMPLE_CONFIG, RestMockSimpleEntity.SAMPLE_CONFIG.getDefaultValue()); }
+    }
+
+    @Test
+    public void testSetFromMap() throws Exception {
+        try {
+            String uri = "/applications/simple-app/entities/simple-ent/config";
+            Response response = client().path(uri)
+                .type(MediaType.APPLICATION_JSON_TYPE)
+                .post(MutableMap.of(
+                    RestMockSimpleEntity.SAMPLE_CONFIG.getName(), "hello world"));
+            assertEquals(response.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
+
+            assertEquals(entity.getConfig(RestMockSimpleEntity.SAMPLE_CONFIG), "hello world");
+            
+            String value = client().path(uri+"/"+RestMockSimpleEntity.SAMPLE_CONFIG.getName()).accept(MediaType.APPLICATION_JSON_TYPE).get(String.class);
+            assertEquals(value, "\"hello world\"");
+
+        } finally { entity.config().set(RestMockSimpleEntity.SAMPLE_CONFIG, RestMockSimpleEntity.SAMPLE_CONFIG.getDefaultValue()); }
+    }
+
+}


[08/34] brooklyn-server git commit: [BROOKLYN-183] REST API using CXF JAX-RS 2.0 implementation

Posted by he...@apache.org.
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/PolicyResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/PolicyResource.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/PolicyResource.java
deleted file mode 100644
index 7ec0b22..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/PolicyResource.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.rest.resources;
-
-import java.util.List;
-import java.util.Map;
-
-import javax.ws.rs.core.Response;
-
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.policy.Policy;
-import org.apache.brooklyn.api.policy.PolicySpec;
-import org.apache.brooklyn.core.policy.Policies;
-import org.apache.brooklyn.rest.api.PolicyApi;
-import org.apache.brooklyn.rest.domain.PolicySummary;
-import org.apache.brooklyn.rest.domain.Status;
-import org.apache.brooklyn.rest.domain.SummaryComparators;
-import org.apache.brooklyn.rest.filter.HaHotStateRequired;
-import org.apache.brooklyn.rest.transform.ApplicationTransformer;
-import org.apache.brooklyn.rest.transform.PolicyTransformer;
-import org.apache.brooklyn.rest.util.WebResourceUtils;
-import org.apache.brooklyn.util.exceptions.Exceptions;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.common.base.Function;
-import com.google.common.collect.FluentIterable;
-import com.google.common.collect.Maps;
-
-@HaHotStateRequired
-public class PolicyResource extends AbstractBrooklynRestResource implements PolicyApi {
-
-    private static final Logger log = LoggerFactory.getLogger(PolicyResource.class);
-
-    @Override
-    public List<PolicySummary> list( final String application, final String entityToken) {
-        final Entity entity = brooklyn().getEntity(application, entityToken);
-        return FluentIterable.from(entity.policies())
-            .transform(new Function<Policy, PolicySummary>() {
-                @Override
-                public PolicySummary apply(Policy policy) {
-                    return PolicyTransformer.policySummary(entity, policy);
-                }
-            })
-            .toSortedList(SummaryComparators.nameComparator());
-    }
-
-    // TODO support parameters  ?show=value,summary&name=xxx
-    // (and in sensors class)
-    @Override
-    public Map<String, Boolean> batchConfigRead( String application, String entityToken) {
-        // TODO: add test
-        Entity entity = brooklyn().getEntity(application, entityToken);
-        Map<String, Boolean> result = Maps.newLinkedHashMap();
-        for (Policy p : entity.policies()) {
-            result.put(p.getId(), !p.isSuspended());
-        }
-        return result;
-    }
-
-    // TODO would like to make 'config' arg optional but jersey complains if we do
-    @SuppressWarnings("unchecked")
-    @Override
-    public PolicySummary addPolicy( String application,String entityToken, String policyTypeName,
-            Map<String, String> config) {
-        Entity entity = brooklyn().getEntity(application, entityToken);
-        Class<? extends Policy> policyType;
-        try {
-            policyType = (Class<? extends Policy>) Class.forName(policyTypeName);
-        } catch (ClassNotFoundException e) {
-            throw WebResourceUtils.badRequest("No policy with type %s found", policyTypeName);
-        } catch (ClassCastException e) {
-            throw WebResourceUtils.badRequest("No policy with type %s found", policyTypeName);
-        } catch (Exception e) {
-            throw Exceptions.propagate(e);
-        }
-
-        Policy policy = entity.policies().add(PolicySpec.create(policyType).configure(config));
-        log.debug("REST API added policy " + policy + " to " + entity);
-
-        return PolicyTransformer.policySummary(entity, policy);
-    }
-
-    @Override
-    public Status getStatus(String application, String entityToken, String policyId) {
-        Policy policy = brooklyn().getPolicy(application, entityToken, policyId);
-        return ApplicationTransformer.statusFromLifecycle(Policies.getPolicyStatus(policy));
-    }
-
-    @Override
-    public Response start( String application, String entityToken, String policyId) {
-        Policy policy = brooklyn().getPolicy(application, entityToken, policyId);
-
-        policy.resume();
-        return Response.status(Response.Status.NO_CONTENT).build();
-    }
-
-    @Override
-    public Response stop(String application, String entityToken, String policyId) {
-        Policy policy = brooklyn().getPolicy(application, entityToken, policyId);
-
-        policy.suspend();
-        return Response.status(Response.Status.NO_CONTENT).build();
-    }
-
-    @Override
-    public Response destroy(String application, String entityToken, String policyToken) {
-        Entity entity = brooklyn().getEntity(application, entityToken);
-        Policy policy = brooklyn().getPolicy(entity, policyToken);
-
-        policy.suspend();
-        entity.policies().remove(policy);
-        return Response.status(Response.Status.NO_CONTENT).build();
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ScriptResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ScriptResource.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ScriptResource.java
deleted file mode 100644
index 77989c3..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ScriptResource.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.rest.resources;
-
-import org.apache.brooklyn.rest.api.ScriptApi;
-import org.apache.brooklyn.rest.domain.ScriptExecutionSummary;
-import org.apache.brooklyn.util.stream.ThreadLocalPrintStream;
-import org.apache.brooklyn.util.stream.ThreadLocalPrintStream.OutputCapturingContext;
-
-import groovy.lang.Binding;
-import groovy.lang.GroovyShell;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpSession;
-
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-public class ScriptResource extends AbstractBrooklynRestResource implements ScriptApi {
-
-    private static final Logger log = LoggerFactory.getLogger(ScriptResource.class);
-    
-    public static final String USER_DATA_MAP_SESSION_ATTRIBUTE = "brooklyn.script.groovy.user.data";
-    public static final String USER_LAST_VALUE_SESSION_ATTRIBUTE = "brooklyn.script.groovy.user.last";
-    
-    @SuppressWarnings("rawtypes")
-    @Override
-    public ScriptExecutionSummary groovy(HttpServletRequest request, String script) {
-        log.info("Web REST executing user-supplied script");
-        if (log.isDebugEnabled()) {
-            log.debug("Web REST user-supplied script contents:\n"+script);
-        }
-        
-        Binding binding = new Binding();
-        binding.setVariable("mgmt", mgmt());
-        
-        HttpSession session = request!=null ? request.getSession() : null;
-        if (session!=null) {
-            Map data = (Map) session.getAttribute(USER_DATA_MAP_SESSION_ATTRIBUTE);
-            if (data==null) {
-                data = new LinkedHashMap();
-                session.setAttribute(USER_DATA_MAP_SESSION_ATTRIBUTE, data);
-            }
-            binding.setVariable("data", data);
-
-            Object last = session.getAttribute(USER_LAST_VALUE_SESSION_ATTRIBUTE);
-            binding.setVariable("last", last);
-        }
-        
-        GroovyShell shell = new GroovyShell(binding);
-
-        OutputCapturingContext stdout = ThreadLocalPrintStream.stdout().captureTee();
-        OutputCapturingContext stderr = ThreadLocalPrintStream.stderr().captureTee();
-
-        Object value = null;
-        Throwable problem = null;
-        try {
-            value = shell.evaluate(script);
-            if (session!=null)
-                session.setAttribute(USER_LAST_VALUE_SESSION_ATTRIBUTE, value);
-        } catch (Throwable t) {
-            log.warn("Problem in user-supplied script: "+t, t);
-            problem = t;
-        } finally {
-            stdout.end();
-            stderr.end();
-        }
-
-        if (log.isDebugEnabled()) {
-            log.debug("Web REST user-supplied script completed:\n"+
-                    (value!=null ? "RESULT: "+value.toString()+"\n" : "")+ 
-                    (problem!=null ? "ERROR: "+problem.toString()+"\n" : "")+
-                    (!stdout.isEmpty() ? "STDOUT: "+stdout.toString()+"\n" : "")+
-                    (!stderr.isEmpty() ? "STDERR: "+stderr.toString()+"\n" : ""));
-        }
-
-        // call toString on the result, in case it is not serializable
-        return new ScriptExecutionSummary(
-                value!=null ? value.toString() : null, 
-                problem!=null ? problem.toString() : null,
-                stdout.toString(), stderr.toString());
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/SensorResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/SensorResource.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/SensorResource.java
deleted file mode 100644
index 2f03196..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/SensorResource.java
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.rest.resources;
-
-import static com.google.common.collect.Iterables.filter;
-
-import java.util.List;
-import java.util.Map;
-
-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.entity.EntityInternal;
-import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
-import org.apache.brooklyn.core.mgmt.entitlement.Entitlements.EntityAndItem;
-import org.apache.brooklyn.core.sensor.BasicAttributeSensor;
-import org.apache.brooklyn.rest.api.SensorApi;
-import org.apache.brooklyn.rest.domain.SensorSummary;
-import org.apache.brooklyn.rest.filter.HaHotStateRequired;
-import org.apache.brooklyn.rest.transform.SensorTransformer;
-import org.apache.brooklyn.rest.util.WebResourceUtils;
-import org.apache.brooklyn.util.core.task.ValueResolver;
-import org.apache.brooklyn.util.text.Strings;
-import org.apache.brooklyn.util.time.Duration;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-
-@HaHotStateRequired
-public class SensorResource extends AbstractBrooklynRestResource implements SensorApi {
-
-    private static final Logger log = LoggerFactory.getLogger(SensorResource.class);
-
-    @Override
-    public List<SensorSummary> list(final String application, final String entityToken) {
-        final Entity entity = brooklyn().getEntity(application, entityToken);
-        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY, entity)) {
-            throw WebResourceUtils.forbidden("User '%s' is not authorized to see entity '%s'",
-                    Entitlements.getEntitlementContext().user(), entity);
-        }
-
-        List<SensorSummary> result = Lists.newArrayList();
-        
-        for (AttributeSensor<?> sensor : filter(entity.getEntityType().getSensors(), AttributeSensor.class)) {
-            // Exclude config that user is not allowed to see
-            if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_SENSOR, new EntityAndItem<String>(entity, sensor.getName()))) {
-                log.trace("User {} not authorized to see sensor {} of entity {}; excluding from AttributeSensor list results", 
-                        new Object[] {Entitlements.getEntitlementContext().user(), sensor.getName(), entity});
-                continue;
-            }
-            result.add(SensorTransformer.sensorSummary(entity, sensor));
-        }
-        
-        return result;
-    }
-
-    @Override
-    public Map<String, Object> batchSensorRead(final String application, final String entityToken, final Boolean raw) {
-        final Entity entity = brooklyn().getEntity(application, entityToken);
-        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY, entity)) {
-            throw WebResourceUtils.forbidden("User '%s' is not authorized to see entity '%s'",
-                    Entitlements.getEntitlementContext().user(), entity);
-        }
-
-        Map<String, Object> sensorMap = Maps.newHashMap();
-        @SuppressWarnings("rawtypes")
-        Iterable<AttributeSensor> sensors = filter(entity.getEntityType().getSensors(), AttributeSensor.class);
-
-        for (AttributeSensor<?> sensor : sensors) {
-            // Exclude sensors that user is not allowed to see
-            if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_SENSOR, new EntityAndItem<String>(entity, sensor.getName()))) {
-                log.trace("User {} not authorized to see sensor {} of entity {}; excluding from current-state results", 
-                        new Object[] {Entitlements.getEntitlementContext().user(), sensor.getName(), entity});
-                continue;
-            }
-
-            Object value = entity.getAttribute(findSensor(entity, sensor.getName()));
-            sensorMap.put(sensor.getName(), 
-                resolving(value).preferJson(true).asJerseyOutermostReturnValue(false).raw(raw).context(entity).timeout(Duration.ZERO).renderAs(sensor).resolve());
-        }
-        return sensorMap;
-    }
-
-    protected Object get(boolean preferJson, String application, String entityToken, String sensorName, Boolean raw) {
-        final Entity entity = brooklyn().getEntity(application, entityToken);
-        AttributeSensor<?> sensor = findSensor(entity, sensorName);
-        
-        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY, entity)) {
-            throw WebResourceUtils.forbidden("User '%s' is not authorized to see entity '%s'",
-                    Entitlements.getEntitlementContext().user(), entity);
-        }
-        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_SENSOR, new EntityAndItem<String>(entity, sensor.getName()))) {
-            throw WebResourceUtils.forbidden("User '%s' is not authorized to see entity '%s' sensor '%s'",
-                    Entitlements.getEntitlementContext().user(), entity, sensor.getName());
-        }
-        
-        Object value = entity.getAttribute(sensor);
-        return resolving(value).preferJson(preferJson).asJerseyOutermostReturnValue(true).raw(raw).context(entity).timeout(ValueResolver.PRETTY_QUICK_WAIT).renderAs(sensor).resolve();
-    }
-
-    @Override
-    public String getPlain(String application, String entityToken, String sensorName, final Boolean raw) {
-        return (String) get(false, application, entityToken, sensorName, raw);
-    }
-
-    @Override
-    public Object get(final String application, final String entityToken, String sensorName, final Boolean raw) {
-        return get(true, application, entityToken, sensorName, raw);
-    }
-
-    private AttributeSensor<?> findSensor(Entity entity, String name) {
-        Sensor<?> s = entity.getEntityType().getSensor(name);
-        if (s instanceof AttributeSensor) return (AttributeSensor<?>) s;
-        return new BasicAttributeSensor<Object>(Object.class, name);
-    }
-    
-    @SuppressWarnings({ "rawtypes", "unchecked" })
-    @Override
-    public void setFromMap(String application, String entityToken, Map newValues) {
-        final Entity entity = brooklyn().getEntity(application, entityToken);
-        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_ENTITY, entity)) {
-            throw WebResourceUtils.forbidden("User '%s' is not authorized to modify entity '%s'",
-                Entitlements.getEntitlementContext().user(), entity);
-        }
-
-        if (log.isDebugEnabled())
-            log.debug("REST user "+Entitlements.getEntitlementContext()+" setting sensors "+newValues);
-        for (Object entry: newValues.entrySet()) {
-            String sensorName = Strings.toString(((Map.Entry)entry).getKey());
-            Object newValue = ((Map.Entry)entry).getValue();
-            
-            AttributeSensor sensor = findSensor(entity, sensorName);
-            entity.sensors().set(sensor, newValue);
-        }
-    }
-    
-    @SuppressWarnings({ "rawtypes", "unchecked" })
-    @Override
-    public void set(String application, String entityToken, String sensorName, Object newValue) {
-        final Entity entity = brooklyn().getEntity(application, entityToken);
-        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_ENTITY, entity)) {
-            throw WebResourceUtils.forbidden("User '%s' is not authorized to modify entity '%s'",
-                Entitlements.getEntitlementContext().user(), entity);
-        }
-        
-        AttributeSensor sensor = findSensor(entity, sensorName);
-        if (log.isDebugEnabled())
-            log.debug("REST user "+Entitlements.getEntitlementContext()+" setting sensor "+sensorName+" to "+newValue);
-        entity.sensors().set(sensor, newValue);
-    }
-    
-    @Override
-    public void delete(String application, String entityToken, String sensorName) {
-        final Entity entity = brooklyn().getEntity(application, entityToken);
-        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_ENTITY, entity)) {
-            throw WebResourceUtils.forbidden("User '%s' is not authorized to modify entity '%s'",
-                Entitlements.getEntitlementContext().user(), entity);
-        }
-        
-        AttributeSensor<?> sensor = findSensor(entity, sensorName);
-        if (log.isDebugEnabled())
-            log.debug("REST user "+Entitlements.getEntitlementContext()+" deleting sensor "+sensorName);
-        ((EntityInternal)entity).sensors().remove(sensor);
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ServerResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ServerResource.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ServerResource.java
deleted file mode 100644
index 426870d..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ServerResource.java
+++ /dev/null
@@ -1,495 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.rest.resources;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Properties;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import javax.ws.rs.core.Context;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-
-import org.apache.brooklyn.api.entity.Application;
-import org.apache.brooklyn.api.mgmt.ManagementContext;
-import org.apache.brooklyn.api.mgmt.Task;
-import org.apache.brooklyn.api.mgmt.entitlement.EntitlementContext;
-import org.apache.brooklyn.api.mgmt.ha.HighAvailabilityManager;
-import org.apache.brooklyn.api.mgmt.ha.HighAvailabilityMode;
-import org.apache.brooklyn.api.mgmt.ha.ManagementNodeState;
-import org.apache.brooklyn.api.mgmt.ha.ManagementPlaneSyncRecord;
-import org.apache.brooklyn.api.mgmt.ha.MementoCopyMode;
-import org.apache.brooklyn.config.ConfigKey;
-import org.apache.brooklyn.core.BrooklynVersion;
-import org.apache.brooklyn.core.config.ConfigKeys;
-import org.apache.brooklyn.core.entity.Attributes;
-import org.apache.brooklyn.core.entity.Entities;
-import org.apache.brooklyn.core.entity.StartableApplication;
-import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
-import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
-import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
-import org.apache.brooklyn.core.mgmt.persist.BrooklynPersistenceUtils;
-import org.apache.brooklyn.core.mgmt.persist.FileBasedObjectStore;
-import org.apache.brooklyn.core.mgmt.persist.PersistenceObjectStore;
-import org.apache.brooklyn.rest.api.ServerApi;
-import org.apache.brooklyn.rest.domain.BrooklynFeatureSummary;
-import org.apache.brooklyn.rest.domain.HighAvailabilitySummary;
-import org.apache.brooklyn.rest.domain.VersionSummary;
-import org.apache.brooklyn.rest.transform.BrooklynFeatureTransformer;
-import org.apache.brooklyn.rest.transform.HighAvailabilityTransformer;
-import org.apache.brooklyn.rest.util.ShutdownHandler;
-import org.apache.brooklyn.rest.util.WebResourceUtils;
-import org.apache.brooklyn.util.collections.MutableMap;
-import org.apache.brooklyn.util.core.ResourceUtils;
-import org.apache.brooklyn.util.core.file.ArchiveBuilder;
-import org.apache.brooklyn.util.core.flags.TypeCoercions;
-import org.apache.brooklyn.util.exceptions.Exceptions;
-import org.apache.brooklyn.util.guava.Maybe;
-import org.apache.brooklyn.util.os.Os;
-import org.apache.brooklyn.util.text.Identifiers;
-import org.apache.brooklyn.util.text.Strings;
-import org.apache.brooklyn.util.time.CountdownTimer;
-import org.apache.brooklyn.util.time.Duration;
-import org.apache.brooklyn.util.time.Time;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.common.base.Preconditions;
-import com.google.common.collect.FluentIterable;
-
-public class ServerResource extends AbstractBrooklynRestResource implements ServerApi {
-
-    private static final int SHUTDOWN_TIMEOUT_CHECK_INTERVAL = 200;
-
-    private static final Logger log = LoggerFactory.getLogger(ServerResource.class);
-
-    private static final String BUILD_SHA_1_PROPERTY = "git-sha-1";
-    private static final String BUILD_BRANCH_PROPERTY = "git-branch-name";
-    
-    @Context
-    private ShutdownHandler shutdownHandler;
-
-    @Override
-    public void reloadBrooklynProperties() {
-        brooklyn().reloadBrooklynProperties();
-    }
-
-    private boolean isMaster() {
-        return ManagementNodeState.MASTER.equals(mgmt().getHighAvailabilityManager().getNodeState());
-    }
-
-    @Override
-    public void shutdown(final boolean stopAppsFirst, final boolean forceShutdownOnError,
-            String shutdownTimeoutRaw, String requestTimeoutRaw, String delayForHttpReturnRaw,
-            Long delayMillis) {
-        
-        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ALL_SERVER_INFO, null))
-            throw WebResourceUtils.forbidden("User '%s' is not authorized for this operation", Entitlements.getEntitlementContext().user());
-        
-        log.info("REST call to shutdown server, stopAppsFirst="+stopAppsFirst+", delayForHttpReturn="+shutdownTimeoutRaw);
-
-        if (stopAppsFirst && !isMaster()) {
-            log.warn("REST call to shutdown non-master server while stopping apps is disallowed");
-            throw WebResourceUtils.forbidden("Not allowed to stop all apps when server is not master");
-        }
-        final Duration shutdownTimeout = parseDuration(shutdownTimeoutRaw, Duration.of(20, TimeUnit.SECONDS));
-        Duration requestTimeout = parseDuration(requestTimeoutRaw, Duration.of(20, TimeUnit.SECONDS));
-        final Duration delayForHttpReturn;
-        if (delayMillis == null) {
-            delayForHttpReturn = parseDuration(delayForHttpReturnRaw, Duration.FIVE_SECONDS);
-        } else {
-            log.warn("'delayMillis' is deprecated, use 'delayForHttpReturn' instead.");
-            delayForHttpReturn = Duration.of(delayMillis, TimeUnit.MILLISECONDS);
-        }
-
-        Preconditions.checkState(delayForHttpReturn.nanos() >= 0, "Only positive or 0 delay allowed for delayForHttpReturn");
-
-        boolean isSingleTimeout = shutdownTimeout.equals(requestTimeout);
-        final AtomicBoolean completed = new AtomicBoolean();
-        final AtomicBoolean hasAppErrorsOrTimeout = new AtomicBoolean();
-
-        new Thread("shutdown") {
-            @Override
-            public void run() {
-                boolean terminateTried = false;
-                ManagementContext mgmt = mgmt();
-                try {
-                    if (stopAppsFirst) {
-                        CountdownTimer shutdownTimeoutTimer = null;
-                        if (!shutdownTimeout.equals(Duration.ZERO)) {
-                            shutdownTimeoutTimer = shutdownTimeout.countdownTimer();
-                        }
-
-                        log.debug("Stopping applications");
-                        List<Task<?>> stoppers = new ArrayList<Task<?>>();
-                        int allStoppableApps = 0;
-                        for (Application app: mgmt.getApplications()) {
-                            allStoppableApps++;
-                            Lifecycle appState = app.getAttribute(Attributes.SERVICE_STATE_ACTUAL);
-                            if (app instanceof StartableApplication &&
-                                    // Don't try to stop an already stopping app. Subsequent stops will complete faster
-                                    // cancelling the first stop task.
-                                    appState != Lifecycle.STOPPING) {
-                                stoppers.add(Entities.invokeEffector(app, app, StartableApplication.STOP));
-                            } else {
-                                log.debug("App " + app + " is already stopping, will not stop second time. Will wait for original stop to complete.");
-                            }
-                        }
-
-                        log.debug("Waiting for " + allStoppableApps + " apps to stop, of which " + stoppers.size() + " stopped explicitly.");
-                        for (Task<?> t: stoppers) {
-                            if (!waitAppShutdown(shutdownTimeoutTimer, t)) {
-                                //app stop error
-                                hasAppErrorsOrTimeout.set(true);
-                            }
-                        }
-
-                        // Wait for apps which were already stopping when we tried to shut down.
-                        if (hasStoppableApps(mgmt)) {
-                            log.debug("Apps are still stopping, wait for proper unmanage.");
-                            while (hasStoppableApps(mgmt) && (shutdownTimeoutTimer == null || !shutdownTimeoutTimer.isExpired())) {
-                                Duration wait;
-                                if (shutdownTimeoutTimer != null) {
-                                    wait = Duration.min(shutdownTimeoutTimer.getDurationRemaining(), Duration.ONE_SECOND);
-                                } else {
-                                    wait = Duration.ONE_SECOND;
-                                }
-                                Time.sleep(wait);
-                            }
-                            if (hasStoppableApps(mgmt)) {
-                                hasAppErrorsOrTimeout.set(true);
-                            }
-                        }
-                    }
-
-                    terminateTried = true;
-                    ((ManagementContextInternal)mgmt).terminate(); 
-
-                } catch (Throwable e) {
-                    Throwable interesting = Exceptions.getFirstInteresting(e);
-                    if (interesting instanceof TimeoutException) {
-                        //timeout while waiting for apps to stop
-                        log.warn("Timeout shutting down: "+Exceptions.collapseText(e));
-                        log.debug("Timeout shutting down: "+e, e);
-                        hasAppErrorsOrTimeout.set(true);
-                        
-                    } else {
-                        // swallow fatal, so we notify the outer loop to continue with shutdown
-                        log.error("Unexpected error shutting down: "+Exceptions.collapseText(e), e);
-                        
-                    }
-                    hasAppErrorsOrTimeout.set(true);
-                    
-                    if (!terminateTried) {
-                        ((ManagementContextInternal)mgmt).terminate(); 
-                    }
-                } finally {
-
-                    complete();
-                
-                    if (!hasAppErrorsOrTimeout.get() || forceShutdownOnError) {
-                        //give the http request a chance to complete gracefully, the server will be stopped in a shutdown hook
-                        Time.sleep(delayForHttpReturn);
-
-                        if (shutdownHandler != null) {
-                            shutdownHandler.onShutdownRequest();
-                        } else {
-                            // should normally be set, as @Context is required by jersey injection
-                            log.warn("ShutdownHandler not set, exiting process");
-                            System.exit(0);
-                        }
-                        
-                    } else {
-                        // There are app errors, don't exit the process, allowing any exception to continue throwing
-                        log.warn("Abandoning shutdown because there were errors and shutdown was not forced.");
-                        
-                    }
-                }
-            }
-
-            private boolean hasStoppableApps(ManagementContext mgmt) {
-                for (Application app : mgmt.getApplications()) {
-                    if (app instanceof StartableApplication) {
-                        Lifecycle state = app.getAttribute(Attributes.SERVICE_STATE_ACTUAL);
-                        if (state != Lifecycle.STOPPING && state != Lifecycle.STOPPED) {
-                            log.warn("Shutting down, expecting all apps to be in stopping state, but found application " + app + " to be in state " + state + ". Just started?");
-                        }
-                        return true;
-                    }
-                }
-                return false;
-            }
-
-            private void complete() {
-                synchronized (completed) {
-                    completed.set(true);
-                    completed.notifyAll();
-                }
-            }
-
-            private boolean waitAppShutdown(CountdownTimer shutdownTimeoutTimer, Task<?> t) throws TimeoutException {
-                Duration waitInterval = null;
-                //wait indefinitely if no shutdownTimeoutTimer (shutdownTimeout == 0)
-                if (shutdownTimeoutTimer != null) {
-                    waitInterval = Duration.of(SHUTDOWN_TIMEOUT_CHECK_INTERVAL, TimeUnit.MILLISECONDS);
-                }
-                // waitInterval == null - blocks indefinitely
-                while(!t.blockUntilEnded(waitInterval)) {
-                    if (shutdownTimeoutTimer.isExpired()) {
-                        log.warn("Timeout while waiting for applications to stop at "+t+".\n"+t.getStatusDetail(true));
-                        throw new TimeoutException();
-                    }
-                }
-                if (t.isError()) {
-                    log.warn("Error stopping application "+t+" during shutdown (ignoring)\n"+t.getStatusDetail(true));
-                    return false;
-                } else {
-                    return true;
-                }
-            }
-        }.start();
-
-        synchronized (completed) {
-            if (!completed.get()) {
-                try {
-                    long waitTimeout = 0;
-                    //If the timeout for both shutdownTimeout and requestTimeout is equal
-                    //then better wait until the 'completed' flag is set, rather than timing out
-                    //at just about the same time (i.e. always wait for the shutdownTimeout in this case).
-                    //This will prevent undefined behaviour where either one of shutdownTimeout or requestTimeout
-                    //will be first to expire and the error flag won't be set predictably, it will
-                    //toggle depending on which expires first.
-                    //Note: shutdownTimeout is checked at SHUTDOWN_TIMEOUT_CHECK_INTERVAL interval, meaning it is
-                    //practically rounded up to the nearest SHUTDOWN_TIMEOUT_CHECK_INTERVAL.
-                    if (!isSingleTimeout) {
-                        waitTimeout = requestTimeout.toMilliseconds();
-                    }
-                    completed.wait(waitTimeout);
-                } catch (InterruptedException e) {
-                    throw Exceptions.propagate(e);
-                }
-            }
-        }
-
-        if (hasAppErrorsOrTimeout.get()) {
-            WebResourceUtils.badRequest("Error or timeout while stopping applications. See log for details.");
-        }
-    }
-
-    private Duration parseDuration(String str, Duration defaultValue) {
-        if (Strings.isEmpty(str)) {
-            return defaultValue;
-        } else {
-            return Duration.parse(str);
-        }
-    }
-
-    @Override
-    public VersionSummary getVersion() {
-        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SERVER_STATUS, null))
-            throw WebResourceUtils.forbidden("User '%s' is not authorized for this operation", Entitlements.getEntitlementContext().user());
-        
-        // TODO
-        // * "build-metadata.properties" is probably the wrong name
-        // * we should include brooklyn.version and a build timestamp in this file
-        // * the authority for brooklyn should probably be core rather than brooklyn-rest-server
-        InputStream input = ResourceUtils.create().getResourceFromUrl("classpath://build-metadata.properties");
-        Properties properties = new Properties();
-        String gitSha1 = null, gitBranch = null;
-        try {
-            properties.load(input);
-            gitSha1 = properties.getProperty(BUILD_SHA_1_PROPERTY);
-            gitBranch = properties.getProperty(BUILD_BRANCH_PROPERTY);
-        } catch (IOException e) {
-            log.error("Failed to load build-metadata.properties", e);
-        }
-        gitSha1 = BrooklynVersion.INSTANCE.getSha1FromOsgiManifest();
-
-        FluentIterable<BrooklynFeatureSummary> features = FluentIterable.from(BrooklynVersion.getFeatures(mgmt()))
-                .transform(BrooklynFeatureTransformer.FROM_FEATURE);
-
-        return new VersionSummary(BrooklynVersion.get(), gitSha1, gitBranch, features.toList());
-    }
-
-    @Override
-    public boolean isUp() {
-        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SERVER_STATUS, null))
-            throw WebResourceUtils.forbidden("User '%s' is not authorized for this operation", Entitlements.getEntitlementContext().user());
-
-        Maybe<ManagementContext> mm = mgmtMaybe();
-        return !mm.isAbsent() && mm.get().isStartupComplete() && mm.get().isRunning();
-    }
-    
-    @Override
-    public boolean isShuttingDown() {
-        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SERVER_STATUS, null))
-            throw WebResourceUtils.forbidden("User '%s' is not authorized for this operation", Entitlements.getEntitlementContext().user());
-        Maybe<ManagementContext> mm = mgmtMaybe();
-        return !mm.isAbsent() && mm.get().isStartupComplete() && !mm.get().isRunning();
-    }
-    
-    @Override
-    public boolean isHealthy() {
-        return isUp() && ((ManagementContextInternal) mgmt()).errors().isEmpty();
-    }
-    
-    @Override
-    public Map<String,Object> getUpExtended() {
-        return MutableMap.<String,Object>of(
-            "up", isUp(),
-            "shuttingDown", isShuttingDown(),
-            "healthy", isHealthy(),
-            "ha", getHighAvailabilityPlaneStates());
-    }
-    
-    
-    @Deprecated
-    @Override
-    public String getStatus() {
-        return getHighAvailabilityNodeState().toString();
-    }
-
-    @Override
-    public String getConfig(String configKey) {
-        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ALL_SERVER_INFO, null)) {
-            throw WebResourceUtils.forbidden("User '%s' is not authorized for this operation", Entitlements.getEntitlementContext().user());
-        }
-        ConfigKey<String> config = ConfigKeys.newStringConfigKey(configKey);
-        return mgmt().getConfig().getConfig(config);
-    }
-
-    @Deprecated
-    @Override
-    public HighAvailabilitySummary getHighAvailability() {
-        return getHighAvailabilityPlaneStates();
-    }
-
-    @Override
-    public ManagementNodeState getHighAvailabilityNodeState() {
-        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SERVER_STATUS, null))
-            throw WebResourceUtils.forbidden("User '%s' is not authorized for this operation", Entitlements.getEntitlementContext().user());
-        
-        Maybe<ManagementContext> mm = mgmtMaybe();
-        if (mm.isAbsent()) return ManagementNodeState.INITIALIZING;
-        return mm.get().getHighAvailabilityManager().getNodeState();
-    }
-
-    @Override
-    public ManagementNodeState setHighAvailabilityNodeState(HighAvailabilityMode mode) {
-        if (mode==null)
-            throw new IllegalStateException("Missing parameter: mode");
-        
-        HighAvailabilityManager haMgr = mgmt().getHighAvailabilityManager();
-        ManagementNodeState existingState = haMgr.getNodeState();
-        haMgr.changeMode(mode);
-        return existingState;
-    }
-
-    @Override
-    public Map<String, Object> getHighAvailabilityMetrics() {
-        return mgmt().getHighAvailabilityManager().getMetrics();
-    }
-    
-    @Override
-    public long getHighAvailabitlityPriority() {
-        return mgmt().getHighAvailabilityManager().getPriority();
-    }
-
-    @Override
-    public long setHighAvailabilityPriority(long priority) {
-        HighAvailabilityManager haMgr = mgmt().getHighAvailabilityManager();
-        long oldPrio = haMgr.getPriority();
-        haMgr.setPriority(priority);
-        return oldPrio;
-    }
-
-    @Override
-    public HighAvailabilitySummary getHighAvailabilityPlaneStates() {
-        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SERVER_STATUS, null))
-            throw WebResourceUtils.forbidden("User '%s' is not authorized for this operation", Entitlements.getEntitlementContext().user());
-        ManagementPlaneSyncRecord memento = mgmt().getHighAvailabilityManager().getLastManagementPlaneSyncRecord();
-        if (memento==null) memento = mgmt().getHighAvailabilityManager().loadManagementPlaneSyncRecord(true);
-        if (memento==null) return null;
-        return HighAvailabilityTransformer.highAvailabilitySummary(mgmt().getManagementNodeId(), memento);
-    }
-
-    @Override
-    public Response clearHighAvailabilityPlaneStates() {
-        mgmt().getHighAvailabilityManager().publishClearNonMaster();
-        return Response.ok().build();
-    }
-
-    @Override
-    public String getUser() {
-        EntitlementContext entitlementContext = Entitlements.getEntitlementContext();
-        if (entitlementContext!=null && entitlementContext.user()!=null){
-            return entitlementContext.user();
-        } else {
-            return null; //User can be null if no authentication was requested
-        }
-    }
-
-    @Override
-    public Response exportPersistenceData(String preferredOrigin) {
-        return exportPersistenceData(TypeCoercions.coerce(preferredOrigin, MementoCopyMode.class));
-    }
-    
-    protected Response exportPersistenceData(MementoCopyMode preferredOrigin) {
-        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ALL_SERVER_INFO, null))
-            throw WebResourceUtils.forbidden("User '%s' is not authorized for this operation", Entitlements.getEntitlementContext().user());
-
-        File dir = null;
-        try {
-            String label = mgmt().getManagementNodeId()+"-"+Time.makeDateSimpleStampString();
-            PersistenceObjectStore targetStore = BrooklynPersistenceUtils.newPersistenceObjectStore(mgmt(), null, 
-                "tmp/web-persistence-"+label+"-"+Identifiers.makeRandomId(4));
-            dir = ((FileBasedObjectStore)targetStore).getBaseDir();
-            // only register the parent dir because that will prevent leaks for the random ID
-            Os.deleteOnExitEmptyParentsUpTo(dir.getParentFile(), dir.getParentFile());
-            BrooklynPersistenceUtils.writeMemento(mgmt(), targetStore, preferredOrigin);            
-            
-            ByteArrayOutputStream baos = new ByteArrayOutputStream();
-            ArchiveBuilder.zip().addDirContentsAt( ((FileBasedObjectStore)targetStore).getBaseDir(), ((FileBasedObjectStore)targetStore).getBaseDir().getName() ).stream(baos);
-            Os.deleteRecursively(dir);
-            String filename = "brooklyn-state-"+label+".zip";
-            return Response.ok(baos.toByteArray(), MediaType.APPLICATION_OCTET_STREAM_TYPE)
-                .header("Content-Disposition","attachment; filename = "+filename)
-                .build();
-        } catch (Exception e) {
-            log.warn("Unable to serve persistence data (rethrowing): "+e, e);
-            if (dir!=null) {
-                try {
-                    Os.deleteRecursively(dir);
-                } catch (Exception e2) {
-                    log.warn("Ignoring error deleting '"+dir+"' after another error, throwing original error ("+e+"); ignored error deleting is: "+e2);
-                }
-            }
-            throw Exceptions.propagate(e);
-        }
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/UsageResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/UsageResource.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/UsageResource.java
deleted file mode 100644
index ebcd2fa..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/UsageResource.java
+++ /dev/null
@@ -1,256 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.rest.resources;
-
-import static org.apache.brooklyn.rest.util.WebResourceUtils.notFound;
-
-import java.net.URI;
-import java.util.Date;
-import java.util.List;
-import java.util.Set;
-
-import javax.annotation.Nullable;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
-import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
-import org.apache.brooklyn.core.mgmt.usage.ApplicationUsage;
-import org.apache.brooklyn.core.mgmt.usage.LocationUsage;
-import org.apache.brooklyn.core.mgmt.usage.ApplicationUsage.ApplicationEvent;
-import org.apache.brooklyn.rest.api.UsageApi;
-import org.apache.brooklyn.rest.domain.UsageStatistic;
-import org.apache.brooklyn.rest.domain.UsageStatistics;
-import org.apache.brooklyn.rest.transform.ApplicationTransformer;
-import org.apache.brooklyn.util.exceptions.UserFacingException;
-import org.apache.brooklyn.util.text.Strings;
-import org.apache.brooklyn.util.time.Time;
-
-import com.google.common.base.Objects;
-import com.google.common.base.Predicate;
-import com.google.common.base.Predicates;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Lists;
-
-
-public class UsageResource extends AbstractBrooklynRestResource implements UsageApi {
-
-    private static final Logger log = LoggerFactory.getLogger(UsageResource.class);
-
-    private static final Set<Lifecycle> WORKING_LIFECYCLES = ImmutableSet.of(Lifecycle.RUNNING, Lifecycle.CREATED, Lifecycle.STARTING);
-
-    @Override
-    public List<UsageStatistics> listApplicationsUsage(@Nullable String start, @Nullable String end) {
-        log.debug("REST call to get application usage for all applications: dates {} -> {}", new Object[] {start, end});
-        
-        List<UsageStatistics> response = Lists.newArrayList();
-        
-        Date startDate = parseDate(start, new Date(0));
-        Date endDate = parseDate(end, new Date());
-        
-        checkDates(startDate, endDate);
-
-        Set<ApplicationUsage> usages = ((ManagementContextInternal) mgmt()).getUsageManager().getApplicationUsage(Predicates.alwaysTrue());
-
-        for (ApplicationUsage usage : usages) {
-            List<UsageStatistic> statistics = retrieveApplicationUsage(usage, startDate, endDate);
-            if (statistics.size() > 0) {
-                response.add(new UsageStatistics(statistics, ImmutableMap.<String,URI>of()));
-            }
-        }
-        return response;
-    }
-    
-    @Override
-    public UsageStatistics getApplicationUsage(String application, String start, String end) {
-        log.debug("REST call to get application usage for application {}: dates {} -> {}", new Object[] {application, start, end});
-        
-        Date startDate = parseDate(start, new Date(0));
-        Date endDate = parseDate(end, new Date());
-
-        checkDates(startDate, endDate);
-
-        ApplicationUsage usage = ((ManagementContextInternal) mgmt()).getUsageManager().getApplicationUsage(application);
-        if (usage != null) {
-            List<UsageStatistic> statistics = retrieveApplicationUsage(usage, startDate, endDate);
-            return new UsageStatistics(statistics, ImmutableMap.<String,URI>of());
-        } else {
-            throw notFound("Application '%s' not found", application);
-        }
-    }
-
-    private List<UsageStatistic> retrieveApplicationUsage(ApplicationUsage usage, Date startDate, Date endDate) {
-        log.debug("Determining application usage for application {}: dates {} -> {}", new Object[] {usage.getApplicationId(), startDate, endDate});
-        log.trace("Considering application usage events of {}: {}", usage.getApplicationId(), usage.getEvents());
-
-        List<UsageStatistic> result = Lists.newArrayList();
-
-        // Getting duration of state by comparing with next event (if next event is of same type, we just generate two statistics)...
-        for (int i = 0; i < usage.getEvents().size(); i++) {
-            ApplicationEvent current = usage.getEvents().get(i);
-            Date eventStartDate = current.getDate();
-            Date eventEndDate;
-
-            if (i <  usage.getEvents().size() - 1) {
-                ApplicationEvent next =  usage.getEvents().get(i + 1);
-                eventEndDate = next.getDate();
-            } else if (current.getState() == Lifecycle.DESTROYED) {
-                eventEndDate = eventStartDate;
-            } else {
-                eventEndDate = new Date();
-            }
-
-            if (eventStartDate.compareTo(endDate) > 0 || eventEndDate.compareTo(startDate) < 0) {
-                continue;
-            }
-
-            if (eventStartDate.compareTo(startDate) < 0) {
-                eventStartDate = startDate;
-            }
-            if (eventEndDate.compareTo(endDate) > 0) {
-                eventEndDate = endDate;
-            }
-            long duration = eventEndDate.getTime() - eventStartDate.getTime();
-            UsageStatistic statistic = new UsageStatistic(ApplicationTransformer.statusFromLifecycle(current.getState()), usage.getApplicationId(), usage.getApplicationId(), format(eventStartDate), format(eventEndDate), duration, usage.getMetadata());
-            log.trace("Adding application usage statistic to response for app {}: {}", usage.getApplicationId(), statistic);
-            result.add(statistic);
-        }
-        
-        return result;
-    }
-
-    @Override
-    public List<UsageStatistics> listMachinesUsage(final String application, final String start, final String end) {
-        log.debug("REST call to get machine usage for application {}: dates {} -> {}", new Object[] {application, start, end});
-        
-        final Date startDate = parseDate(start, new Date(0));
-        final Date endDate = parseDate(end, new Date());
-
-        checkDates(startDate, endDate);
-        
-        // Note currently recording ALL metrics for a machine that contains an Event from given Application
-        Set<LocationUsage> matches = ((ManagementContextInternal) mgmt()).getUsageManager().getLocationUsage(new Predicate<LocationUsage>() {
-            @Override
-            public boolean apply(LocationUsage input) {
-                LocationUsage.LocationEvent first = input.getEvents().get(0);
-                if (endDate.compareTo(first.getDate()) < 0) {
-                    return false;
-                }
-                LocationUsage.LocationEvent last = input.getEvents().get(input.getEvents().size() - 1);
-                if (!WORKING_LIFECYCLES.contains(last.getState()) && startDate.compareTo(last.getDate()) > 0) {
-                    return false;
-                }
-                if (application != null) {
-                    for (LocationUsage.LocationEvent e : input.getEvents()) {
-                        if (Objects.equal(application, e.getApplicationId())) {
-                            return true;
-                        }
-                    }
-                    return false;
-                }
-                return true;
-            }
-        });
-        
-        List<UsageStatistics> response = Lists.newArrayList();
-        for (LocationUsage usage : matches) {
-            List<UsageStatistic> statistics = retrieveMachineUsage(usage, startDate, endDate);
-            if (statistics.size() > 0) {
-                response.add(new UsageStatistics(statistics, ImmutableMap.<String,URI>of()));
-            }
-        }
-        return response;
-    }
-
-    @Override
-    public UsageStatistics getMachineUsage(final String machine, final String start, final String end) {
-        log.debug("REST call to get machine usage for machine {}: dates {} -> {}", new Object[] {machine, start, end});
-        
-        final Date startDate = parseDate(start, new Date(0));
-        final Date endDate = parseDate(end, new Date());
-
-        checkDates(startDate, endDate);
-        
-        // Note currently recording ALL metrics for a machine that contains an Event from given Application
-        LocationUsage usage = ((ManagementContextInternal) mgmt()).getUsageManager().getLocationUsage(machine);
-        
-        if (usage == null) {
-            throw notFound("Machine '%s' not found", machine);
-        }
-        
-        List<UsageStatistic> statistics = retrieveMachineUsage(usage, startDate, endDate);
-        return new UsageStatistics(statistics, ImmutableMap.<String,URI>of());
-    }
-
-    private List<UsageStatistic> retrieveMachineUsage(LocationUsage usage, Date startDate, Date endDate) {
-        log.debug("Determining machine usage for location {}", usage.getLocationId());
-        log.trace("Considering machine usage events of {}: {}", usage.getLocationId(), usage.getEvents());
-
-        List<UsageStatistic> result = Lists.newArrayList();
-
-        // Getting duration of state by comparing with next event (if next event is of same type, we just generate two statistics)...
-        for (int i = 0; i < usage.getEvents().size(); i++) {
-            LocationUsage.LocationEvent current = usage.getEvents().get(i);
-            Date eventStartDate = current.getDate();
-            Date eventEndDate;
-
-            if (i <  usage.getEvents().size() - 1) {
-                LocationUsage.LocationEvent next =  usage.getEvents().get(i + 1);
-                eventEndDate = next.getDate();
-            } else if (current.getState() == Lifecycle.DESTROYED || current.getState() == Lifecycle.STOPPED) {
-                eventEndDate = eventStartDate;
-            } else {
-                eventEndDate = new Date();
-            }
-
-            if (eventStartDate.compareTo(endDate) > 0 || eventEndDate.compareTo(startDate) < 0) {
-                continue;
-            }
-
-            if (eventStartDate.compareTo(startDate) < 0) {
-                eventStartDate = startDate;
-            }
-            if (eventEndDate.compareTo(endDate) > 0) {
-                eventEndDate = endDate;
-            }
-            long duration = eventEndDate.getTime() - eventStartDate.getTime();
-            UsageStatistic statistic = new UsageStatistic(ApplicationTransformer.statusFromLifecycle(current.getState()), usage.getLocationId(), current.getApplicationId(), format(eventStartDate), format(eventEndDate), duration, usage.getMetadata());
-            log.trace("Adding machine usage statistic to response for app {}: {}", usage.getLocationId(), statistic);
-            result.add(statistic);
-        }
-        
-        return result;
-    }
-    
-    private void checkDates(Date startDate, Date endDate) {
-        if (startDate.compareTo(endDate) > 0) {
-            throw new UserFacingException(new IllegalArgumentException("Start must be less than or equal to end: " + startDate + " > " + endDate + 
-                    " (" + startDate.getTime() + " > " + endDate.getTime() + ")"));
-        }
-    }
-
-    private Date parseDate(String toParse, Date def) {
-        return Strings.isBlank(toParse) ? def : Time.parseDate(toParse);
-    }
-    
-    private String format(Date date) {
-        return Time.makeDateString(date, Time.DATE_FORMAT_ISO8601_NO_MILLIS, Time.TIME_ZONE_UTC);
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/VersionResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/VersionResource.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/VersionResource.java
deleted file mode 100644
index 7492af6..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/VersionResource.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.rest.resources;
-
-import org.apache.brooklyn.core.BrooklynVersion;
-import org.apache.brooklyn.rest.api.VersionApi;
-
-/** @deprecated since 0.7.0; use /v1/server/version */
-@Deprecated
-public class VersionResource extends AbstractBrooklynRestResource implements VersionApi {
-
-    @Override
-    public String getVersion() {
-        return BrooklynVersion.get();
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/security/PasswordHasher.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/security/PasswordHasher.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/security/PasswordHasher.java
deleted file mode 100644
index 928a6bd..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/security/PasswordHasher.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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;
-
-import com.google.common.base.Charsets;
-import com.google.common.hash.HashCode;
-import com.google.common.hash.Hashing;
-
-public class PasswordHasher {
-    public static String sha256(String salt, String password) {
-        if (salt == null) salt = "";
-        byte[] bytes = (salt + password).getBytes(Charsets.UTF_8);
-        HashCode hash = Hashing.sha256().hashBytes(bytes);
-        return hash.toString();
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/AbstractSecurityProvider.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/AbstractSecurityProvider.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/AbstractSecurityProvider.java
deleted file mode 100644
index 91c2523..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/AbstractSecurityProvider.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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;
-
-import org.apache.brooklyn.util.text.Strings;
-
-/**
- * Provides default implementations of {@link #isAuthenticated(HttpSession)} and
- * {@link #logout(HttpSession)}.
- */
-public abstract class AbstractSecurityProvider implements SecurityProvider {
-
-    @Override
-    public boolean isAuthenticated(HttpSession session) {
-        if (session == null) return false;
-        Object value = session.getAttribute(getAuthenticationKey());
-        return Strings.isNonBlank(Strings.toString(value));
-    }
-
-    @Override
-    public boolean logout(HttpSession session) {
-        if (session == null) return false;
-        session.removeAttribute(getAuthenticationKey());
-        return true;
-    }
-
-    /**
-     * Sets an authentication token for the user on the session. Always returns true.
-     */
-    protected boolean allow(HttpSession session, String user) {
-        session.setAttribute(getAuthenticationKey(), user);
-        return true;
-    }
-
-    protected String getAuthenticationKey() {
-        return getClass().getName() + ".AUTHENTICATED";
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/AnyoneSecurityProvider.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/AnyoneSecurityProvider.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/AnyoneSecurityProvider.java
deleted file mode 100644
index 97b4fe1..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/AnyoneSecurityProvider.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * 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;
-
-/** provider who allows everyone */
-public class AnyoneSecurityProvider implements SecurityProvider {
-
-    @Override
-    public boolean isAuthenticated(HttpSession session) {
-        return true;
-    }
-
-    @Override
-    public boolean authenticate(HttpSession session, String user, String password) {
-        return true;
-    }
-
-    @Override
-    public boolean logout(HttpSession session) { 
-        return true;
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/BlackholeSecurityProvider.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/BlackholeSecurityProvider.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/BlackholeSecurityProvider.java
deleted file mode 100644
index a976975..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/BlackholeSecurityProvider.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * 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;
-
-/** provider who disallows everyone */
-public class BlackholeSecurityProvider implements SecurityProvider {
-
-    @Override
-    public boolean isAuthenticated(HttpSession session) {
-        return false;
-    }
-
-    @Override
-    public boolean authenticate(HttpSession session, String user, String password) {
-        return false;
-    }
-
-    @Override
-    public boolean logout(HttpSession session) { 
-        return true;
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/BrooklynUserWithRandomPasswordSecurityProvider.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/BrooklynUserWithRandomPasswordSecurityProvider.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/BrooklynUserWithRandomPasswordSecurityProvider.java
deleted file mode 100644
index d5be027..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/BrooklynUserWithRandomPasswordSecurityProvider.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * 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;
-
-import org.apache.brooklyn.api.mgmt.ManagementContext;
-import org.apache.brooklyn.rest.filter.BrooklynPropertiesSecurityFilter;
-import org.apache.brooklyn.util.javalang.JavaClassNames;
-import org.apache.brooklyn.util.net.Networking;
-import org.apache.brooklyn.util.text.Identifiers;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class BrooklynUserWithRandomPasswordSecurityProvider extends AbstractSecurityProvider implements SecurityProvider {
-
-    public static final Logger LOG = LoggerFactory.getLogger(BrooklynUserWithRandomPasswordSecurityProvider.class);
-    private static final String USER = "brooklyn";
-    private final String password;
-
-    public BrooklynUserWithRandomPasswordSecurityProvider() {
-        this.password = Identifiers.makeRandomId(10);
-        LOG.info("Allowing access to web console from localhost or with {}:{}", USER, password);
-    }
-
-    public BrooklynUserWithRandomPasswordSecurityProvider(ManagementContext mgmt) {
-        this();
-    }
-
-    @Override
-    public boolean authenticate(HttpSession session, String user, String password) {
-        if ((USER.equals(user) && this.password.equals(password)) || isRemoteAddressLocalhost(session)) {
-            return allow(session, user);
-        } else {
-            return false;
-        }
-    }
-
-    private boolean isRemoteAddressLocalhost(HttpSession session) {
-        Object remoteAddress = session.getAttribute(BrooklynPropertiesSecurityFilter.REMOTE_ADDRESS_SESSION_ATTRIBUTE);
-        if (!(remoteAddress instanceof String)) return false;
-        if (Networking.isLocalhost((String)remoteAddress)) {
-            if (LOG.isTraceEnabled()) {
-                LOG.trace(this+": granting passwordless access to "+session+" originating from "+remoteAddress);
-            }
-            return true;
-        } else {
-            LOG.debug(this+": password required for "+session+" originating from "+remoteAddress);
-            return false;
-        }
-    }
-    
-    @Override
-    public String toString() {
-        return JavaClassNames.cleanSimpleClassName(this);
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/DelegatingSecurityProvider.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/DelegatingSecurityProvider.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/DelegatingSecurityProvider.java
deleted file mode 100644
index 11884c3..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/DelegatingSecurityProvider.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * 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.lang.reflect.Constructor;
-import java.util.concurrent.atomic.AtomicLong;
-
-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.core.mgmt.internal.ManagementContextInternal;
-import org.apache.brooklyn.rest.BrooklynWebConfig;
-import org.apache.brooklyn.util.text.Strings;
-
-public class DelegatingSecurityProvider implements SecurityProvider {
-
-    private static final Logger log = LoggerFactory.getLogger(DelegatingSecurityProvider.class);
-    protected final ManagementContext mgmt;
-
-    public DelegatingSecurityProvider(ManagementContext mgmt) {
-        this.mgmt = mgmt;
-        mgmt.addPropertiesReloadListener(new PropertiesListener());
-    }
-    
-    private SecurityProvider delegate;
-    private final AtomicLong modCount = new AtomicLong();
-
-    private class PropertiesListener implements ManagementContext.PropertiesReloadListener {
-        private static final long serialVersionUID = 8148722609022378917L;
-
-        @Override
-        public void reloaded() {
-            log.debug("{} reloading security provider", DelegatingSecurityProvider.this);
-            synchronized (DelegatingSecurityProvider.this) {
-                loadDelegate();
-                invalidateExistingSessions();
-            }
-        }
-    }
-
-    public synchronized SecurityProvider getDelegate() {
-        if (delegate == null) {
-            delegate = loadDelegate();
-        }
-        return delegate;
-    }
-
-    @SuppressWarnings("unchecked")
-    private synchronized SecurityProvider loadDelegate() {
-        StringConfigMap brooklynProperties = mgmt.getConfig();
-
-        SecurityProvider presetDelegate = brooklynProperties.getConfig(BrooklynWebConfig.SECURITY_PROVIDER_INSTANCE);
-        if (presetDelegate!=null) {
-            log.info("REST using pre-set security provider " + presetDelegate);
-            return presetDelegate;
-        }
-        
-        String className = brooklynProperties.getConfig(BrooklynWebConfig.SECURITY_PROVIDER_CLASSNAME);
-
-        if (delegate != null && BrooklynWebConfig.hasNoSecurityOptions(mgmt.getConfig())) {
-            log.debug("{} refusing to change from {}: No security provider set in reloaded properties.",
-                    this, delegate);
-            return delegate;
-        }
-        log.info("REST using security provider " + className);
-
-        try {
-            Class<? extends SecurityProvider> clazz;
-            try {
-                clazz = (Class<? extends SecurityProvider>) Class.forName(className);
-            } catch (Exception e) {
-                String oldPackage = "brooklyn.web.console.security.";
-                if (className.startsWith(oldPackage)) {
-                    className = Strings.removeFromStart(className, oldPackage);
-                    className = DelegatingSecurityProvider.class.getPackage().getName() + "." + className;
-                    clazz = (Class<? extends SecurityProvider>) Class.forName(className);
-                    log.warn("Deprecated package " + oldPackage + " detected; please update security provider to point to " + className);
-                } else throw e;
-            }
-
-            Constructor<? extends SecurityProvider> constructor;
-            try {
-                constructor = clazz.getConstructor(ManagementContext.class);
-                delegate = constructor.newInstance(mgmt);
-            } catch (Exception e) {
-                constructor = clazz.getConstructor();
-                Object delegateO = constructor.newInstance();
-                if (!(delegateO instanceof SecurityProvider)) {
-                    // if classloaders get mangled it will be a different CL's SecurityProvider
-                    throw new ClassCastException("Delegate is either not a security provider or has an incompatible classloader: "+delegateO);
-                }
-                delegate = (SecurityProvider) delegateO;
-            }
-        } catch (Exception e) {
-            log.warn("REST unable to instantiate security provider " + className + "; all logins are being disallowed", e);
-            delegate = new BlackholeSecurityProvider();
-        }
-        
-        ((ManagementContextInternal)mgmt).getBrooklynProperties().put(BrooklynWebConfig.SECURITY_PROVIDER_INSTANCE, delegate);
-        
-        return delegate;
-    }
-
-    /**
-     * Causes all existing sessions to be invalidated.
-     */
-    protected void invalidateExistingSessions() {
-        modCount.incrementAndGet();
-    }
-
-    @Override
-    public boolean isAuthenticated(HttpSession session) {
-        if (session == null) return false;
-        Object modCountWhenFirstAuthenticated = session.getAttribute(getModificationCountKey());
-        boolean authenticated = getDelegate().isAuthenticated(session) &&
-                Long.valueOf(modCount.get()).equals(modCountWhenFirstAuthenticated);
-        return authenticated;
-    }
-
-    @Override
-    public boolean authenticate(HttpSession session, String user, String password) {
-        boolean authenticated = getDelegate().authenticate(session, user, password);
-        if (authenticated) {
-            session.setAttribute(getModificationCountKey(), modCount.get());
-        }
-        if (log.isTraceEnabled() && authenticated) {
-            log.trace("User {} authenticated with provider {}", user, getDelegate());
-        } else if (!authenticated && log.isDebugEnabled()) {
-            log.debug("Failed authentication for user {} with provider {}", user, getDelegate());
-        }
-        return authenticated;
-    }
-
-    @Override
-    public boolean logout(HttpSession session) { 
-        boolean logout = getDelegate().logout(session);
-        if (logout) {
-            session.removeAttribute(getModificationCountKey());
-        }
-        return logout;
-    }
-
-    private String getModificationCountKey() {
-        return getClass().getName() + ".ModCount";
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/ExplicitUsersSecurityProvider.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/ExplicitUsersSecurityProvider.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/ExplicitUsersSecurityProvider.java
deleted file mode 100644
index 562c85b..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/ExplicitUsersSecurityProvider.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * 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.core.mgmt.internal.ManagementContextInternal;
-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 = ((ManagementContextInternal)mgmt).getBrooklynProperties();
-        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;
-    }
-}


[17/34] brooklyn-server git commit: [BROOKLYN-183] REST API using CXF JAX-RS 2.0 implementation

Posted by he...@apache.org.
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/PolicyConfigResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/PolicyConfigResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/PolicyConfigResource.java
new file mode 100644
index 0000000..c8a7962
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/PolicyConfigResource.java
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.core.Response;
+
+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.mgmt.entitlement.Entitlements;
+import org.apache.brooklyn.core.objs.BrooklynObjectInternal;
+import org.apache.brooklyn.rest.api.PolicyConfigApi;
+import org.apache.brooklyn.rest.domain.PolicyConfigSummary;
+import org.apache.brooklyn.rest.filter.HaHotStateRequired;
+import org.apache.brooklyn.rest.transform.PolicyTransformer;
+import org.apache.brooklyn.rest.util.BrooklynRestResourceUtils;
+import org.apache.brooklyn.rest.util.WebResourceUtils;
+import org.apache.brooklyn.util.core.flags.TypeCoercions;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+@HaHotStateRequired
+public class PolicyConfigResource extends AbstractBrooklynRestResource implements PolicyConfigApi {
+
+    @Override
+    public List<PolicyConfigSummary> list(
+            final String application, final String entityToken, final String policyToken) {
+        Entity entity = brooklyn().getEntity(application, entityToken);
+        Policy policy = brooklyn().getPolicy(entity, policyToken);
+
+        List<PolicyConfigSummary> result = Lists.newArrayList();
+        for (ConfigKey<?> key : policy.getPolicyType().getConfigKeys()) {
+            result.add(PolicyTransformer.policyConfigSummary(brooklyn(), entity, policy, key, ui.getBaseUriBuilder()));
+        }
+        return result;
+    }
+
+    // TODO support parameters  ?show=value,summary&name=xxx &format={string,json,xml}
+    // (and in sensors class)
+    @Override
+    public Map<String, Object> batchConfigRead(String application, String entityToken, String policyToken) {
+        // TODO: add test
+        Policy policy = brooklyn().getPolicy(application, entityToken, policyToken);
+        Map<String, Object> source = ((BrooklynObjectInternal)policy).config().getBag().getAllConfig();
+        Map<String, Object> result = Maps.newLinkedHashMap();
+        for (Map.Entry<String, Object> ek : source.entrySet()) {
+            result.put(ek.getKey(), getStringValueForDisplay(brooklyn(), policy, ek.getValue()));
+        }
+        return result;
+    }
+
+    @Override
+    public String get(String application, String entityToken, String policyToken, String configKeyName) {
+        Policy policy = brooklyn().getPolicy(application, entityToken, policyToken);
+        ConfigKey<?> ck = policy.getPolicyType().getConfigKey(configKeyName);
+        if (ck == null) throw WebResourceUtils.notFound("Cannot find config key '%s' in policy '%s' of entity '%s'", configKeyName, policy, entityToken);
+
+        return getStringValueForDisplay(brooklyn(), policy, policy.getConfig(ck));
+    }
+
+    @Override
+    @Deprecated
+    public Response set(String application, String entityToken, String policyToken, String configKeyName, String value) {
+        return set(application, entityToken, policyToken, configKeyName, (Object) value);
+    }
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    @Override
+    public Response set(String application, String entityToken, String policyToken, String configKeyName, Object value) {
+        Entity entity = brooklyn().getEntity(application, entityToken);
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_ENTITY, entity)) {
+            throw WebResourceUtils.forbidden("User '%s' is not authorized to modify entity '%s'",
+                    Entitlements.getEntitlementContext().user(), entity);
+        }
+
+        Policy policy = brooklyn().getPolicy(application, entityToken, policyToken);
+        ConfigKey<?> ck = policy.getPolicyType().getConfigKey(configKeyName);
+        if (ck == null) throw WebResourceUtils.notFound("Cannot find config key '%s' in policy '%s' of entity '%s'", configKeyName, policy, entityToken);
+
+        policy.config().set((ConfigKey) ck, TypeCoercions.coerce(value, ck.getTypeToken()));
+
+        return Response.status(Response.Status.OK).build();
+    }
+
+    public static String getStringValueForDisplay(BrooklynRestResourceUtils utils, Policy policy, Object value) {
+        return utils.getStringValueForDisplay(value);
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/PolicyResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/PolicyResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/PolicyResource.java
new file mode 100644
index 0000000..2696440
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/PolicyResource.java
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.core.Response;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.policy.Policy;
+import org.apache.brooklyn.api.policy.PolicySpec;
+import org.apache.brooklyn.core.policy.Policies;
+import org.apache.brooklyn.rest.api.PolicyApi;
+import org.apache.brooklyn.rest.domain.PolicySummary;
+import org.apache.brooklyn.rest.domain.Status;
+import org.apache.brooklyn.rest.domain.SummaryComparators;
+import org.apache.brooklyn.rest.filter.HaHotStateRequired;
+import org.apache.brooklyn.rest.transform.ApplicationTransformer;
+import org.apache.brooklyn.rest.transform.PolicyTransformer;
+import org.apache.brooklyn.rest.util.WebResourceUtils;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Function;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.Maps;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.UriInfo;
+
+@HaHotStateRequired
+public class PolicyResource extends AbstractBrooklynRestResource implements PolicyApi {
+
+    private static final Logger log = LoggerFactory.getLogger(PolicyResource.class);
+
+    private @Context UriInfo ui;
+
+    @Override
+    public List<PolicySummary> list( final String application, final String entityToken ) {
+        final Entity entity = brooklyn().getEntity(application, entityToken);
+        return FluentIterable.from(entity.policies())
+            .transform(new Function<Policy, PolicySummary>() {
+                @Override
+                public PolicySummary apply(Policy policy) {
+                    return PolicyTransformer.policySummary(entity, policy, ui.getBaseUriBuilder());
+                }
+            })
+            .toSortedList(SummaryComparators.nameComparator());
+    }
+
+    // TODO support parameters  ?show=value,summary&name=xxx
+    // (and in sensors class)
+    @Override
+    public Map<String, Boolean> batchConfigRead( String application, String entityToken) {
+        // TODO: add test
+        Entity entity = brooklyn().getEntity(application, entityToken);
+        Map<String, Boolean> result = Maps.newLinkedHashMap();
+        for (Policy p : entity.policies()) {
+            result.put(p.getId(), !p.isSuspended());
+        }
+        return result;
+    }
+
+    // TODO would like to make 'config' arg optional but jersey complains if we do
+    @SuppressWarnings("unchecked")
+    @Override
+    public PolicySummary addPolicy( String application,String entityToken, String policyTypeName,
+            Map<String, String> config) {
+        Entity entity = brooklyn().getEntity(application, entityToken);
+        Class<? extends Policy> policyType;
+        try {
+            policyType = (Class<? extends Policy>) Class.forName(policyTypeName);
+        } catch (ClassNotFoundException e) {
+            throw WebResourceUtils.badRequest("No policy with type %s found", policyTypeName);
+        } catch (ClassCastException e) {
+            throw WebResourceUtils.badRequest("No policy with type %s found", policyTypeName);
+        } catch (Exception e) {
+            throw Exceptions.propagate(e);
+        }
+
+        Policy policy = entity.policies().add(PolicySpec.create(policyType).configure(config));
+        log.debug("REST API added policy " + policy + " to " + entity);
+
+        return PolicyTransformer.policySummary(entity, policy, ui.getBaseUriBuilder());
+    }
+
+    @Override
+    public Status getStatus(String application, String entityToken, String policyId) {
+        Policy policy = brooklyn().getPolicy(application, entityToken, policyId);
+        return ApplicationTransformer.statusFromLifecycle(Policies.getPolicyStatus(policy));
+    }
+
+    @Override
+    public Response start( String application, String entityToken, String policyId) {
+        Policy policy = brooklyn().getPolicy(application, entityToken, policyId);
+
+        policy.resume();
+        return Response.status(Response.Status.NO_CONTENT).build();
+    }
+
+    @Override
+    public Response stop(String application, String entityToken, String policyId) {
+        Policy policy = brooklyn().getPolicy(application, entityToken, policyId);
+
+        policy.suspend();
+        return Response.status(Response.Status.NO_CONTENT).build();
+    }
+
+    @Override
+    public Response destroy(String application, String entityToken, String policyToken) {
+        Entity entity = brooklyn().getEntity(application, entityToken);
+        Policy policy = brooklyn().getPolicy(entity, policyToken);
+
+        policy.suspend();
+        entity.policies().remove(policy);
+        return Response.status(Response.Status.NO_CONTENT).build();
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ScriptResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ScriptResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ScriptResource.java
new file mode 100644
index 0000000..77989c3
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ScriptResource.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import org.apache.brooklyn.rest.api.ScriptApi;
+import org.apache.brooklyn.rest.domain.ScriptExecutionSummary;
+import org.apache.brooklyn.util.stream.ThreadLocalPrintStream;
+import org.apache.brooklyn.util.stream.ThreadLocalPrintStream.OutputCapturingContext;
+
+import groovy.lang.Binding;
+import groovy.lang.GroovyShell;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class ScriptResource extends AbstractBrooklynRestResource implements ScriptApi {
+
+    private static final Logger log = LoggerFactory.getLogger(ScriptResource.class);
+    
+    public static final String USER_DATA_MAP_SESSION_ATTRIBUTE = "brooklyn.script.groovy.user.data";
+    public static final String USER_LAST_VALUE_SESSION_ATTRIBUTE = "brooklyn.script.groovy.user.last";
+    
+    @SuppressWarnings("rawtypes")
+    @Override
+    public ScriptExecutionSummary groovy(HttpServletRequest request, String script) {
+        log.info("Web REST executing user-supplied script");
+        if (log.isDebugEnabled()) {
+            log.debug("Web REST user-supplied script contents:\n"+script);
+        }
+        
+        Binding binding = new Binding();
+        binding.setVariable("mgmt", mgmt());
+        
+        HttpSession session = request!=null ? request.getSession() : null;
+        if (session!=null) {
+            Map data = (Map) session.getAttribute(USER_DATA_MAP_SESSION_ATTRIBUTE);
+            if (data==null) {
+                data = new LinkedHashMap();
+                session.setAttribute(USER_DATA_MAP_SESSION_ATTRIBUTE, data);
+            }
+            binding.setVariable("data", data);
+
+            Object last = session.getAttribute(USER_LAST_VALUE_SESSION_ATTRIBUTE);
+            binding.setVariable("last", last);
+        }
+        
+        GroovyShell shell = new GroovyShell(binding);
+
+        OutputCapturingContext stdout = ThreadLocalPrintStream.stdout().captureTee();
+        OutputCapturingContext stderr = ThreadLocalPrintStream.stderr().captureTee();
+
+        Object value = null;
+        Throwable problem = null;
+        try {
+            value = shell.evaluate(script);
+            if (session!=null)
+                session.setAttribute(USER_LAST_VALUE_SESSION_ATTRIBUTE, value);
+        } catch (Throwable t) {
+            log.warn("Problem in user-supplied script: "+t, t);
+            problem = t;
+        } finally {
+            stdout.end();
+            stderr.end();
+        }
+
+        if (log.isDebugEnabled()) {
+            log.debug("Web REST user-supplied script completed:\n"+
+                    (value!=null ? "RESULT: "+value.toString()+"\n" : "")+ 
+                    (problem!=null ? "ERROR: "+problem.toString()+"\n" : "")+
+                    (!stdout.isEmpty() ? "STDOUT: "+stdout.toString()+"\n" : "")+
+                    (!stderr.isEmpty() ? "STDERR: "+stderr.toString()+"\n" : ""));
+        }
+
+        // call toString on the result, in case it is not serializable
+        return new ScriptExecutionSummary(
+                value!=null ? value.toString() : null, 
+                problem!=null ? problem.toString() : null,
+                stdout.toString(), stderr.toString());
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/SensorResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/SensorResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/SensorResource.java
new file mode 100644
index 0000000..750b7de
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/SensorResource.java
@@ -0,0 +1,184 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import static com.google.common.collect.Iterables.filter;
+
+import java.util.List;
+import java.util.Map;
+
+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.entity.EntityInternal;
+import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
+import org.apache.brooklyn.core.mgmt.entitlement.Entitlements.EntityAndItem;
+import org.apache.brooklyn.core.sensor.BasicAttributeSensor;
+import org.apache.brooklyn.rest.api.SensorApi;
+import org.apache.brooklyn.rest.domain.SensorSummary;
+import org.apache.brooklyn.rest.filter.HaHotStateRequired;
+import org.apache.brooklyn.rest.transform.SensorTransformer;
+import org.apache.brooklyn.rest.util.WebResourceUtils;
+import org.apache.brooklyn.util.core.task.ValueResolver;
+import org.apache.brooklyn.util.text.Strings;
+import org.apache.brooklyn.util.time.Duration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+@HaHotStateRequired
+public class SensorResource extends AbstractBrooklynRestResource implements SensorApi {
+
+    private static final Logger log = LoggerFactory.getLogger(SensorResource.class);
+
+    @Override
+    public List<SensorSummary> list(final String application, final String entityToken) {
+        final Entity entity = brooklyn().getEntity(application, entityToken);
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY, entity)) {
+            throw WebResourceUtils.forbidden("User '%s' is not authorized to see entity '%s'",
+                    Entitlements.getEntitlementContext().user(), entity);
+        }
+
+        List<SensorSummary> result = Lists.newArrayList();
+        
+        for (AttributeSensor<?> sensor : filter(entity.getEntityType().getSensors(), AttributeSensor.class)) {
+            // Exclude config that user is not allowed to see
+            if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_SENSOR, new EntityAndItem<String>(entity, sensor.getName()))) {
+                log.trace("User {} not authorized to see sensor {} of entity {}; excluding from AttributeSensor list results", 
+                        new Object[] {Entitlements.getEntitlementContext().user(), sensor.getName(), entity});
+                continue;
+            }
+            result.add(SensorTransformer.sensorSummary(entity, sensor, ui.getBaseUriBuilder()));
+        }
+        
+        return result;
+    }
+
+    @Override
+    public Map<String, Object> batchSensorRead(final String application, final String entityToken, final Boolean raw) {
+        final Entity entity = brooklyn().getEntity(application, entityToken);
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY, entity)) {
+            throw WebResourceUtils.forbidden("User '%s' is not authorized to see entity '%s'",
+                    Entitlements.getEntitlementContext().user(), entity);
+        }
+
+        Map<String, Object> sensorMap = Maps.newHashMap();
+        @SuppressWarnings("rawtypes")
+        Iterable<AttributeSensor> sensors = filter(entity.getEntityType().getSensors(), AttributeSensor.class);
+
+        for (AttributeSensor<?> sensor : sensors) {
+            // Exclude sensors that user is not allowed to see
+            if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_SENSOR, new EntityAndItem<String>(entity, sensor.getName()))) {
+                log.trace("User {} not authorized to see sensor {} of entity {}; excluding from current-state results", 
+                        new Object[] {Entitlements.getEntitlementContext().user(), sensor.getName(), entity});
+                continue;
+            }
+
+            Object value = entity.getAttribute(findSensor(entity, sensor.getName()));
+            sensorMap.put(sensor.getName(), 
+                resolving(value).preferJson(true).asJerseyOutermostReturnValue(false).raw(raw).context(entity).timeout(Duration.ZERO).renderAs(sensor).resolve());
+        }
+        return sensorMap;
+    }
+
+    protected Object get(boolean preferJson, String application, String entityToken, String sensorName, Boolean raw) {
+        final Entity entity = brooklyn().getEntity(application, entityToken);
+        AttributeSensor<?> sensor = findSensor(entity, sensorName);
+        
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY, entity)) {
+            throw WebResourceUtils.forbidden("User '%s' is not authorized to see entity '%s'",
+                    Entitlements.getEntitlementContext().user(), entity);
+        }
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_SENSOR, new EntityAndItem<String>(entity, sensor.getName()))) {
+            throw WebResourceUtils.forbidden("User '%s' is not authorized to see entity '%s' sensor '%s'",
+                    Entitlements.getEntitlementContext().user(), entity, sensor.getName());
+        }
+        
+        Object value = entity.getAttribute(sensor);
+        return resolving(value).preferJson(preferJson).asJerseyOutermostReturnValue(true).raw(raw).context(entity).timeout(ValueResolver.PRETTY_QUICK_WAIT).renderAs(sensor).resolve();
+    }
+
+    @Override
+    public String getPlain(String application, String entityToken, String sensorName, final Boolean raw) {
+        return (String) get(false, application, entityToken, sensorName, raw);
+    }
+
+    @Override
+    public Object get(final String application, final String entityToken, String sensorName, final Boolean raw) {
+        return get(true, application, entityToken, sensorName, raw);
+    }
+
+    private AttributeSensor<?> findSensor(Entity entity, String name) {
+        Sensor<?> s = entity.getEntityType().getSensor(name);
+        if (s instanceof AttributeSensor) return (AttributeSensor<?>) s;
+        return new BasicAttributeSensor<Object>(Object.class, name);
+    }
+    
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Override
+    public void setFromMap(String application, String entityToken, Map newValues) {
+        final Entity entity = brooklyn().getEntity(application, entityToken);
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_ENTITY, entity)) {
+            throw WebResourceUtils.forbidden("User '%s' is not authorized to modify entity '%s'",
+                Entitlements.getEntitlementContext().user(), entity);
+        }
+
+        if (log.isDebugEnabled())
+            log.debug("REST user "+Entitlements.getEntitlementContext()+" setting sensors "+newValues);
+        for (Object entry: newValues.entrySet()) {
+            String sensorName = Strings.toString(((Map.Entry)entry).getKey());
+            Object newValue = ((Map.Entry)entry).getValue();
+            
+            AttributeSensor sensor = findSensor(entity, sensorName);
+            entity.sensors().set(sensor, newValue);
+        }
+    }
+    
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Override
+    public void set(String application, String entityToken, String sensorName, Object newValue) {
+        final Entity entity = brooklyn().getEntity(application, entityToken);
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_ENTITY, entity)) {
+            throw WebResourceUtils.forbidden("User '%s' is not authorized to modify entity '%s'",
+                Entitlements.getEntitlementContext().user(), entity);
+        }
+        
+        AttributeSensor sensor = findSensor(entity, sensorName);
+        if (log.isDebugEnabled())
+            log.debug("REST user "+Entitlements.getEntitlementContext()+" setting sensor "+sensorName+" to "+newValue);
+        entity.sensors().set(sensor, newValue);
+    }
+    
+    @Override
+    public void delete(String application, String entityToken, String sensorName) {
+        final Entity entity = brooklyn().getEntity(application, entityToken);
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_ENTITY, entity)) {
+            throw WebResourceUtils.forbidden("User '%s' is not authorized to modify entity '%s'",
+                Entitlements.getEntitlementContext().user(), entity);
+        }
+        
+        AttributeSensor<?> sensor = findSensor(entity, sensorName);
+        if (log.isDebugEnabled())
+            log.debug("REST user "+Entitlements.getEntitlementContext()+" deleting sensor "+sensorName);
+        ((EntityInternal)entity).sensors().remove(sensor);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ServerResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ServerResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ServerResource.java
new file mode 100644
index 0000000..fe7893b
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ServerResource.java
@@ -0,0 +1,497 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ContextResolver;
+
+import org.apache.brooklyn.api.entity.Application;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.api.mgmt.Task;
+import org.apache.brooklyn.api.mgmt.entitlement.EntitlementContext;
+import org.apache.brooklyn.api.mgmt.ha.HighAvailabilityManager;
+import org.apache.brooklyn.api.mgmt.ha.HighAvailabilityMode;
+import org.apache.brooklyn.api.mgmt.ha.ManagementNodeState;
+import org.apache.brooklyn.api.mgmt.ha.ManagementPlaneSyncRecord;
+import org.apache.brooklyn.api.mgmt.ha.MementoCopyMode;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.BrooklynVersion;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.entity.Attributes;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.entity.StartableApplication;
+import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
+import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
+import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
+import org.apache.brooklyn.core.mgmt.persist.BrooklynPersistenceUtils;
+import org.apache.brooklyn.core.mgmt.persist.FileBasedObjectStore;
+import org.apache.brooklyn.core.mgmt.persist.PersistenceObjectStore;
+import org.apache.brooklyn.rest.api.ServerApi;
+import org.apache.brooklyn.rest.domain.BrooklynFeatureSummary;
+import org.apache.brooklyn.rest.domain.HighAvailabilitySummary;
+import org.apache.brooklyn.rest.domain.VersionSummary;
+import org.apache.brooklyn.rest.transform.BrooklynFeatureTransformer;
+import org.apache.brooklyn.rest.transform.HighAvailabilityTransformer;
+import org.apache.brooklyn.rest.util.ShutdownHandler;
+import org.apache.brooklyn.rest.util.WebResourceUtils;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.core.ResourceUtils;
+import org.apache.brooklyn.util.core.file.ArchiveBuilder;
+import org.apache.brooklyn.util.core.flags.TypeCoercions;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.guava.Maybe;
+import org.apache.brooklyn.util.os.Os;
+import org.apache.brooklyn.util.text.Identifiers;
+import org.apache.brooklyn.util.text.Strings;
+import org.apache.brooklyn.util.time.CountdownTimer;
+import org.apache.brooklyn.util.time.Duration;
+import org.apache.brooklyn.util.time.Time;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.FluentIterable;
+
+public class ServerResource extends AbstractBrooklynRestResource implements ServerApi {
+
+    private static final int SHUTDOWN_TIMEOUT_CHECK_INTERVAL = 200;
+
+    private static final Logger log = LoggerFactory.getLogger(ServerResource.class);
+
+    private static final String BUILD_SHA_1_PROPERTY = "git-sha-1";
+    private static final String BUILD_BRANCH_PROPERTY = "git-branch-name";
+    
+    @Context
+    private ContextResolver<ShutdownHandler> shutdownHandler;
+
+    @Override
+    public void reloadBrooklynProperties() {
+        brooklyn().reloadBrooklynProperties();
+    }
+
+    private boolean isMaster() {
+        return ManagementNodeState.MASTER.equals(mgmt().getHighAvailabilityManager().getNodeState());
+    }
+
+    @Override
+    public void shutdown(final boolean stopAppsFirst, final boolean forceShutdownOnError,
+            String shutdownTimeoutRaw, String requestTimeoutRaw, String delayForHttpReturnRaw,
+            Long delayMillis) {
+        
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ALL_SERVER_INFO, null))
+            throw WebResourceUtils.forbidden("User '%s' is not authorized for this operation", Entitlements.getEntitlementContext().user());
+        
+        log.info("REST call to shutdown server, stopAppsFirst="+stopAppsFirst+", delayForHttpReturn="+shutdownTimeoutRaw);
+
+        if (stopAppsFirst && !isMaster()) {
+            log.warn("REST call to shutdown non-master server while stopping apps is disallowed");
+            throw WebResourceUtils.forbidden("Not allowed to stop all apps when server is not master");
+        }
+        final Duration shutdownTimeout = parseDuration(shutdownTimeoutRaw, Duration.of(20, TimeUnit.SECONDS));
+        Duration requestTimeout = parseDuration(requestTimeoutRaw, Duration.of(20, TimeUnit.SECONDS));
+        final Duration delayForHttpReturn;
+        if (delayMillis == null) {
+            delayForHttpReturn = parseDuration(delayForHttpReturnRaw, Duration.FIVE_SECONDS);
+        } else {
+            log.warn("'delayMillis' is deprecated, use 'delayForHttpReturn' instead.");
+            delayForHttpReturn = Duration.of(delayMillis, TimeUnit.MILLISECONDS);
+        }
+
+        Preconditions.checkState(delayForHttpReturn.nanos() >= 0, "Only positive or 0 delay allowed for delayForHttpReturn");
+
+        boolean isSingleTimeout = shutdownTimeout.equals(requestTimeout);
+        final AtomicBoolean completed = new AtomicBoolean();
+        final AtomicBoolean hasAppErrorsOrTimeout = new AtomicBoolean();
+
+        //shutdownHandler is thread local
+        final ShutdownHandler handler = shutdownHandler.getContext(ShutdownHandler.class);
+        new Thread("shutdown") {
+            @Override
+            public void run() {
+                boolean terminateTried = false;
+                ManagementContext mgmt = mgmt();
+                try {
+                    if (stopAppsFirst) {
+                        CountdownTimer shutdownTimeoutTimer = null;
+                        if (!shutdownTimeout.equals(Duration.ZERO)) {
+                            shutdownTimeoutTimer = shutdownTimeout.countdownTimer();
+                        }
+
+                        log.debug("Stopping applications");
+                        List<Task<?>> stoppers = new ArrayList<Task<?>>();
+                        int allStoppableApps = 0;
+                        for (Application app: mgmt.getApplications()) {
+                            allStoppableApps++;
+                            Lifecycle appState = app.getAttribute(Attributes.SERVICE_STATE_ACTUAL);
+                            if (app instanceof StartableApplication &&
+                                    // Don't try to stop an already stopping app. Subsequent stops will complete faster
+                                    // cancelling the first stop task.
+                                    appState != Lifecycle.STOPPING) {
+                                stoppers.add(Entities.invokeEffector(app, app, StartableApplication.STOP));
+                            } else {
+                                log.debug("App " + app + " is already stopping, will not stop second time. Will wait for original stop to complete.");
+                            }
+                        }
+
+                        log.debug("Waiting for " + allStoppableApps + " apps to stop, of which " + stoppers.size() + " stopped explicitly.");
+                        for (Task<?> t: stoppers) {
+                            if (!waitAppShutdown(shutdownTimeoutTimer, t)) {
+                                //app stop error
+                                hasAppErrorsOrTimeout.set(true);
+                            }
+                        }
+
+                        // Wait for apps which were already stopping when we tried to shut down.
+                        if (hasStoppableApps(mgmt)) {
+                            log.debug("Apps are still stopping, wait for proper unmanage.");
+                            while (hasStoppableApps(mgmt) && (shutdownTimeoutTimer == null || !shutdownTimeoutTimer.isExpired())) {
+                                Duration wait;
+                                if (shutdownTimeoutTimer != null) {
+                                    wait = Duration.min(shutdownTimeoutTimer.getDurationRemaining(), Duration.ONE_SECOND);
+                                } else {
+                                    wait = Duration.ONE_SECOND;
+                                }
+                                Time.sleep(wait);
+                            }
+                            if (hasStoppableApps(mgmt)) {
+                                hasAppErrorsOrTimeout.set(true);
+                            }
+                        }
+                    }
+
+                    terminateTried = true;
+                    ((ManagementContextInternal)mgmt).terminate(); 
+
+                } catch (Throwable e) {
+                    Throwable interesting = Exceptions.getFirstInteresting(e);
+                    if (interesting instanceof TimeoutException) {
+                        //timeout while waiting for apps to stop
+                        log.warn("Timeout shutting down: "+Exceptions.collapseText(e));
+                        log.debug("Timeout shutting down: "+e, e);
+                        hasAppErrorsOrTimeout.set(true);
+                        
+                    } else {
+                        // swallow fatal, so we notify the outer loop to continue with shutdown
+                        log.error("Unexpected error shutting down: "+Exceptions.collapseText(e), e);
+                        
+                    }
+                    hasAppErrorsOrTimeout.set(true);
+                    
+                    if (!terminateTried) {
+                        ((ManagementContextInternal)mgmt).terminate(); 
+                    }
+                } finally {
+
+                    complete();
+                
+                    if (!hasAppErrorsOrTimeout.get() || forceShutdownOnError) {
+                        //give the http request a chance to complete gracefully, the server will be stopped in a shutdown hook
+                        Time.sleep(delayForHttpReturn);
+
+                        if (handler != null) {
+                            handler.onShutdownRequest();
+                        } else {
+                            log.warn("ShutdownHandler not set, exiting process");
+                            System.exit(0);
+                        }
+                        
+                    } else {
+                        // There are app errors, don't exit the process, allowing any exception to continue throwing
+                        log.warn("Abandoning shutdown because there were errors and shutdown was not forced.");
+                        
+                    }
+                }
+            }
+
+            private boolean hasStoppableApps(ManagementContext mgmt) {
+                for (Application app : mgmt.getApplications()) {
+                    if (app instanceof StartableApplication) {
+                        Lifecycle state = app.getAttribute(Attributes.SERVICE_STATE_ACTUAL);
+                        if (state != Lifecycle.STOPPING && state != Lifecycle.STOPPED) {
+                            log.warn("Shutting down, expecting all apps to be in stopping state, but found application " + app + " to be in state " + state + ". Just started?");
+                        }
+                        return true;
+                    }
+                }
+                return false;
+            }
+
+            private void complete() {
+                synchronized (completed) {
+                    completed.set(true);
+                    completed.notifyAll();
+                }
+            }
+
+            private boolean waitAppShutdown(CountdownTimer shutdownTimeoutTimer, Task<?> t) throws TimeoutException {
+                Duration waitInterval = null;
+                //wait indefinitely if no shutdownTimeoutTimer (shutdownTimeout == 0)
+                if (shutdownTimeoutTimer != null) {
+                    waitInterval = Duration.of(SHUTDOWN_TIMEOUT_CHECK_INTERVAL, TimeUnit.MILLISECONDS);
+                }
+                // waitInterval == null - blocks indefinitely
+                while(!t.blockUntilEnded(waitInterval)) {
+                    if (shutdownTimeoutTimer.isExpired()) {
+                        log.warn("Timeout while waiting for applications to stop at "+t+".\n"+t.getStatusDetail(true));
+                        throw new TimeoutException();
+                    }
+                }
+                if (t.isError()) {
+                    log.warn("Error stopping application "+t+" during shutdown (ignoring)\n"+t.getStatusDetail(true));
+                    return false;
+                } else {
+                    return true;
+                }
+            }
+        }.start();
+
+        synchronized (completed) {
+            if (!completed.get()) {
+                try {
+                    long waitTimeout = 0;
+                    //If the timeout for both shutdownTimeout and requestTimeout is equal
+                    //then better wait until the 'completed' flag is set, rather than timing out
+                    //at just about the same time (i.e. always wait for the shutdownTimeout in this case).
+                    //This will prevent undefined behaviour where either one of shutdownTimeout or requestTimeout
+                    //will be first to expire and the error flag won't be set predictably, it will
+                    //toggle depending on which expires first.
+                    //Note: shutdownTimeout is checked at SHUTDOWN_TIMEOUT_CHECK_INTERVAL interval, meaning it is
+                    //practically rounded up to the nearest SHUTDOWN_TIMEOUT_CHECK_INTERVAL.
+                    if (!isSingleTimeout) {
+                        waitTimeout = requestTimeout.toMilliseconds();
+                    }
+                    completed.wait(waitTimeout);
+                } catch (InterruptedException e) {
+                    throw Exceptions.propagate(e);
+                }
+            }
+        }
+
+        if (hasAppErrorsOrTimeout.get()) {
+            WebResourceUtils.badRequest("Error or timeout while stopping applications. See log for details.");
+        }
+    }
+
+    private Duration parseDuration(String str, Duration defaultValue) {
+        if (Strings.isEmpty(str)) {
+            return defaultValue;
+        } else {
+            return Duration.parse(str);
+        }
+    }
+
+    @Override
+    public VersionSummary getVersion() {
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SERVER_STATUS, null))
+            throw WebResourceUtils.forbidden("User '%s' is not authorized for this operation", Entitlements.getEntitlementContext().user());
+        
+        // TODO
+        // * "build-metadata.properties" is probably the wrong name
+        // * we should include brooklyn.version and a build timestamp in this file
+        // * the authority for brooklyn should probably be core rather than brooklyn-rest-server
+        InputStream input = ResourceUtils.create().getResourceFromUrl("classpath://build-metadata.properties");
+        Properties properties = new Properties();
+        String gitSha1 = null, gitBranch = null;
+        try {
+            properties.load(input);
+            gitSha1 = properties.getProperty(BUILD_SHA_1_PROPERTY);
+            gitBranch = properties.getProperty(BUILD_BRANCH_PROPERTY);
+        } catch (IOException e) {
+            log.error("Failed to load build-metadata.properties", e);
+        }
+        gitSha1 = BrooklynVersion.INSTANCE.getSha1FromOsgiManifest();
+
+        FluentIterable<BrooklynFeatureSummary> features = FluentIterable.from(BrooklynVersion.getFeatures(mgmt()))
+                .transform(BrooklynFeatureTransformer.FROM_FEATURE);
+
+        return new VersionSummary(BrooklynVersion.get(), gitSha1, gitBranch, features.toList());
+    }
+
+    @Override
+    public boolean isUp() {
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SERVER_STATUS, null))
+            throw WebResourceUtils.forbidden("User '%s' is not authorized for this operation", Entitlements.getEntitlementContext().user());
+
+        Maybe<ManagementContext> mm = mgmtMaybe();
+        return !mm.isAbsent() && mm.get().isStartupComplete() && mm.get().isRunning();
+    }
+    
+    @Override
+    public boolean isShuttingDown() {
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SERVER_STATUS, null))
+            throw WebResourceUtils.forbidden("User '%s' is not authorized for this operation", Entitlements.getEntitlementContext().user());
+        Maybe<ManagementContext> mm = mgmtMaybe();
+        return !mm.isAbsent() && mm.get().isStartupComplete() && !mm.get().isRunning();
+    }
+    
+    @Override
+    public boolean isHealthy() {
+        return isUp() && ((ManagementContextInternal) mgmt()).errors().isEmpty();
+    }
+    
+    @Override
+    public Map<String,Object> getUpExtended() {
+        return MutableMap.<String,Object>of(
+            "up", isUp(),
+            "shuttingDown", isShuttingDown(),
+            "healthy", isHealthy(),
+            "ha", getHighAvailabilityPlaneStates());
+    }
+    
+    
+    @Deprecated
+    @Override
+    public String getStatus() {
+        return getHighAvailabilityNodeState().toString();
+    }
+
+    @Override
+    public String getConfig(String configKey) {
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ALL_SERVER_INFO, null)) {
+            throw WebResourceUtils.forbidden("User '%s' is not authorized for this operation", Entitlements.getEntitlementContext().user());
+        }
+        ConfigKey<String> config = ConfigKeys.newStringConfigKey(configKey);
+        return mgmt().getConfig().getConfig(config);
+    }
+
+    @Deprecated
+    @Override
+    public HighAvailabilitySummary getHighAvailability() {
+        return getHighAvailabilityPlaneStates();
+    }
+
+    @Override
+    public ManagementNodeState getHighAvailabilityNodeState() {
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SERVER_STATUS, null))
+            throw WebResourceUtils.forbidden("User '%s' is not authorized for this operation", Entitlements.getEntitlementContext().user());
+        
+        Maybe<ManagementContext> mm = mgmtMaybe();
+        if (mm.isAbsent()) return ManagementNodeState.INITIALIZING;
+        return mm.get().getHighAvailabilityManager().getNodeState();
+    }
+
+    @Override
+    public ManagementNodeState setHighAvailabilityNodeState(HighAvailabilityMode mode) {
+        if (mode==null)
+            throw new IllegalStateException("Missing parameter: mode");
+        
+        HighAvailabilityManager haMgr = mgmt().getHighAvailabilityManager();
+        ManagementNodeState existingState = haMgr.getNodeState();
+        haMgr.changeMode(mode);
+        return existingState;
+    }
+
+    @Override
+    public Map<String, Object> getHighAvailabilityMetrics() {
+        return mgmt().getHighAvailabilityManager().getMetrics();
+    }
+    
+    @Override
+    public long getHighAvailabitlityPriority() {
+        return mgmt().getHighAvailabilityManager().getPriority();
+    }
+
+    @Override
+    public long setHighAvailabilityPriority(long priority) {
+        HighAvailabilityManager haMgr = mgmt().getHighAvailabilityManager();
+        long oldPrio = haMgr.getPriority();
+        haMgr.setPriority(priority);
+        return oldPrio;
+    }
+
+    @Override
+    public HighAvailabilitySummary getHighAvailabilityPlaneStates() {
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SERVER_STATUS, null))
+            throw WebResourceUtils.forbidden("User '%s' is not authorized for this operation", Entitlements.getEntitlementContext().user());
+        ManagementPlaneSyncRecord memento = mgmt().getHighAvailabilityManager().getLastManagementPlaneSyncRecord();
+        if (memento==null) memento = mgmt().getHighAvailabilityManager().loadManagementPlaneSyncRecord(true);
+        if (memento==null) return null;
+        return HighAvailabilityTransformer.highAvailabilitySummary(mgmt().getManagementNodeId(), memento);
+    }
+
+    @Override
+    public Response clearHighAvailabilityPlaneStates() {
+        mgmt().getHighAvailabilityManager().publishClearNonMaster();
+        return Response.ok().build();
+    }
+
+    @Override
+    public String getUser() {
+        EntitlementContext entitlementContext = Entitlements.getEntitlementContext();
+        if (entitlementContext!=null && entitlementContext.user()!=null){
+            return entitlementContext.user();
+        } else {
+            return null; //User can be null if no authentication was requested
+        }
+    }
+
+    @Override
+    public Response exportPersistenceData(String preferredOrigin) {
+        return exportPersistenceData(TypeCoercions.coerce(preferredOrigin, MementoCopyMode.class));
+    }
+    
+    protected Response exportPersistenceData(MementoCopyMode preferredOrigin) {
+        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ALL_SERVER_INFO, null))
+            throw WebResourceUtils.forbidden("User '%s' is not authorized for this operation", Entitlements.getEntitlementContext().user());
+
+        File dir = null;
+        try {
+            String label = mgmt().getManagementNodeId()+"-"+Time.makeDateSimpleStampString();
+            PersistenceObjectStore targetStore = BrooklynPersistenceUtils.newPersistenceObjectStore(mgmt(), null, 
+                "tmp/web-persistence-"+label+"-"+Identifiers.makeRandomId(4));
+            dir = ((FileBasedObjectStore)targetStore).getBaseDir();
+            // only register the parent dir because that will prevent leaks for the random ID
+            Os.deleteOnExitEmptyParentsUpTo(dir.getParentFile(), dir.getParentFile());
+            BrooklynPersistenceUtils.writeMemento(mgmt(), targetStore, preferredOrigin);            
+            
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            ArchiveBuilder.zip().addDirContentsAt( ((FileBasedObjectStore)targetStore).getBaseDir(), ((FileBasedObjectStore)targetStore).getBaseDir().getName() ).stream(baos);
+            Os.deleteRecursively(dir);
+            String filename = "brooklyn-state-"+label+".zip";
+            return Response.ok(baos.toByteArray(), MediaType.APPLICATION_OCTET_STREAM_TYPE)
+                .header("Content-Disposition","attachment; filename = "+filename)
+                .build();
+        } catch (Exception e) {
+            log.warn("Unable to serve persistence data (rethrowing): "+e, e);
+            if (dir!=null) {
+                try {
+                    Os.deleteRecursively(dir);
+                } catch (Exception e2) {
+                    log.warn("Ignoring error deleting '"+dir+"' after another error, throwing original error ("+e+"); ignored error deleting is: "+e2);
+                }
+            }
+            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/resources/UsageResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/UsageResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/UsageResource.java
new file mode 100644
index 0000000..ebcd2fa
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/UsageResource.java
@@ -0,0 +1,256 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import static org.apache.brooklyn.rest.util.WebResourceUtils.notFound;
+
+import java.net.URI;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
+import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
+import org.apache.brooklyn.core.mgmt.usage.ApplicationUsage;
+import org.apache.brooklyn.core.mgmt.usage.LocationUsage;
+import org.apache.brooklyn.core.mgmt.usage.ApplicationUsage.ApplicationEvent;
+import org.apache.brooklyn.rest.api.UsageApi;
+import org.apache.brooklyn.rest.domain.UsageStatistic;
+import org.apache.brooklyn.rest.domain.UsageStatistics;
+import org.apache.brooklyn.rest.transform.ApplicationTransformer;
+import org.apache.brooklyn.util.exceptions.UserFacingException;
+import org.apache.brooklyn.util.text.Strings;
+import org.apache.brooklyn.util.time.Time;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+
+
+public class UsageResource extends AbstractBrooklynRestResource implements UsageApi {
+
+    private static final Logger log = LoggerFactory.getLogger(UsageResource.class);
+
+    private static final Set<Lifecycle> WORKING_LIFECYCLES = ImmutableSet.of(Lifecycle.RUNNING, Lifecycle.CREATED, Lifecycle.STARTING);
+
+    @Override
+    public List<UsageStatistics> listApplicationsUsage(@Nullable String start, @Nullable String end) {
+        log.debug("REST call to get application usage for all applications: dates {} -> {}", new Object[] {start, end});
+        
+        List<UsageStatistics> response = Lists.newArrayList();
+        
+        Date startDate = parseDate(start, new Date(0));
+        Date endDate = parseDate(end, new Date());
+        
+        checkDates(startDate, endDate);
+
+        Set<ApplicationUsage> usages = ((ManagementContextInternal) mgmt()).getUsageManager().getApplicationUsage(Predicates.alwaysTrue());
+
+        for (ApplicationUsage usage : usages) {
+            List<UsageStatistic> statistics = retrieveApplicationUsage(usage, startDate, endDate);
+            if (statistics.size() > 0) {
+                response.add(new UsageStatistics(statistics, ImmutableMap.<String,URI>of()));
+            }
+        }
+        return response;
+    }
+    
+    @Override
+    public UsageStatistics getApplicationUsage(String application, String start, String end) {
+        log.debug("REST call to get application usage for application {}: dates {} -> {}", new Object[] {application, start, end});
+        
+        Date startDate = parseDate(start, new Date(0));
+        Date endDate = parseDate(end, new Date());
+
+        checkDates(startDate, endDate);
+
+        ApplicationUsage usage = ((ManagementContextInternal) mgmt()).getUsageManager().getApplicationUsage(application);
+        if (usage != null) {
+            List<UsageStatistic> statistics = retrieveApplicationUsage(usage, startDate, endDate);
+            return new UsageStatistics(statistics, ImmutableMap.<String,URI>of());
+        } else {
+            throw notFound("Application '%s' not found", application);
+        }
+    }
+
+    private List<UsageStatistic> retrieveApplicationUsage(ApplicationUsage usage, Date startDate, Date endDate) {
+        log.debug("Determining application usage for application {}: dates {} -> {}", new Object[] {usage.getApplicationId(), startDate, endDate});
+        log.trace("Considering application usage events of {}: {}", usage.getApplicationId(), usage.getEvents());
+
+        List<UsageStatistic> result = Lists.newArrayList();
+
+        // Getting duration of state by comparing with next event (if next event is of same type, we just generate two statistics)...
+        for (int i = 0; i < usage.getEvents().size(); i++) {
+            ApplicationEvent current = usage.getEvents().get(i);
+            Date eventStartDate = current.getDate();
+            Date eventEndDate;
+
+            if (i <  usage.getEvents().size() - 1) {
+                ApplicationEvent next =  usage.getEvents().get(i + 1);
+                eventEndDate = next.getDate();
+            } else if (current.getState() == Lifecycle.DESTROYED) {
+                eventEndDate = eventStartDate;
+            } else {
+                eventEndDate = new Date();
+            }
+
+            if (eventStartDate.compareTo(endDate) > 0 || eventEndDate.compareTo(startDate) < 0) {
+                continue;
+            }
+
+            if (eventStartDate.compareTo(startDate) < 0) {
+                eventStartDate = startDate;
+            }
+            if (eventEndDate.compareTo(endDate) > 0) {
+                eventEndDate = endDate;
+            }
+            long duration = eventEndDate.getTime() - eventStartDate.getTime();
+            UsageStatistic statistic = new UsageStatistic(ApplicationTransformer.statusFromLifecycle(current.getState()), usage.getApplicationId(), usage.getApplicationId(), format(eventStartDate), format(eventEndDate), duration, usage.getMetadata());
+            log.trace("Adding application usage statistic to response for app {}: {}", usage.getApplicationId(), statistic);
+            result.add(statistic);
+        }
+        
+        return result;
+    }
+
+    @Override
+    public List<UsageStatistics> listMachinesUsage(final String application, final String start, final String end) {
+        log.debug("REST call to get machine usage for application {}: dates {} -> {}", new Object[] {application, start, end});
+        
+        final Date startDate = parseDate(start, new Date(0));
+        final Date endDate = parseDate(end, new Date());
+
+        checkDates(startDate, endDate);
+        
+        // Note currently recording ALL metrics for a machine that contains an Event from given Application
+        Set<LocationUsage> matches = ((ManagementContextInternal) mgmt()).getUsageManager().getLocationUsage(new Predicate<LocationUsage>() {
+            @Override
+            public boolean apply(LocationUsage input) {
+                LocationUsage.LocationEvent first = input.getEvents().get(0);
+                if (endDate.compareTo(first.getDate()) < 0) {
+                    return false;
+                }
+                LocationUsage.LocationEvent last = input.getEvents().get(input.getEvents().size() - 1);
+                if (!WORKING_LIFECYCLES.contains(last.getState()) && startDate.compareTo(last.getDate()) > 0) {
+                    return false;
+                }
+                if (application != null) {
+                    for (LocationUsage.LocationEvent e : input.getEvents()) {
+                        if (Objects.equal(application, e.getApplicationId())) {
+                            return true;
+                        }
+                    }
+                    return false;
+                }
+                return true;
+            }
+        });
+        
+        List<UsageStatistics> response = Lists.newArrayList();
+        for (LocationUsage usage : matches) {
+            List<UsageStatistic> statistics = retrieveMachineUsage(usage, startDate, endDate);
+            if (statistics.size() > 0) {
+                response.add(new UsageStatistics(statistics, ImmutableMap.<String,URI>of()));
+            }
+        }
+        return response;
+    }
+
+    @Override
+    public UsageStatistics getMachineUsage(final String machine, final String start, final String end) {
+        log.debug("REST call to get machine usage for machine {}: dates {} -> {}", new Object[] {machine, start, end});
+        
+        final Date startDate = parseDate(start, new Date(0));
+        final Date endDate = parseDate(end, new Date());
+
+        checkDates(startDate, endDate);
+        
+        // Note currently recording ALL metrics for a machine that contains an Event from given Application
+        LocationUsage usage = ((ManagementContextInternal) mgmt()).getUsageManager().getLocationUsage(machine);
+        
+        if (usage == null) {
+            throw notFound("Machine '%s' not found", machine);
+        }
+        
+        List<UsageStatistic> statistics = retrieveMachineUsage(usage, startDate, endDate);
+        return new UsageStatistics(statistics, ImmutableMap.<String,URI>of());
+    }
+
+    private List<UsageStatistic> retrieveMachineUsage(LocationUsage usage, Date startDate, Date endDate) {
+        log.debug("Determining machine usage for location {}", usage.getLocationId());
+        log.trace("Considering machine usage events of {}: {}", usage.getLocationId(), usage.getEvents());
+
+        List<UsageStatistic> result = Lists.newArrayList();
+
+        // Getting duration of state by comparing with next event (if next event is of same type, we just generate two statistics)...
+        for (int i = 0; i < usage.getEvents().size(); i++) {
+            LocationUsage.LocationEvent current = usage.getEvents().get(i);
+            Date eventStartDate = current.getDate();
+            Date eventEndDate;
+
+            if (i <  usage.getEvents().size() - 1) {
+                LocationUsage.LocationEvent next =  usage.getEvents().get(i + 1);
+                eventEndDate = next.getDate();
+            } else if (current.getState() == Lifecycle.DESTROYED || current.getState() == Lifecycle.STOPPED) {
+                eventEndDate = eventStartDate;
+            } else {
+                eventEndDate = new Date();
+            }
+
+            if (eventStartDate.compareTo(endDate) > 0 || eventEndDate.compareTo(startDate) < 0) {
+                continue;
+            }
+
+            if (eventStartDate.compareTo(startDate) < 0) {
+                eventStartDate = startDate;
+            }
+            if (eventEndDate.compareTo(endDate) > 0) {
+                eventEndDate = endDate;
+            }
+            long duration = eventEndDate.getTime() - eventStartDate.getTime();
+            UsageStatistic statistic = new UsageStatistic(ApplicationTransformer.statusFromLifecycle(current.getState()), usage.getLocationId(), current.getApplicationId(), format(eventStartDate), format(eventEndDate), duration, usage.getMetadata());
+            log.trace("Adding machine usage statistic to response for app {}: {}", usage.getLocationId(), statistic);
+            result.add(statistic);
+        }
+        
+        return result;
+    }
+    
+    private void checkDates(Date startDate, Date endDate) {
+        if (startDate.compareTo(endDate) > 0) {
+            throw new UserFacingException(new IllegalArgumentException("Start must be less than or equal to end: " + startDate + " > " + endDate + 
+                    " (" + startDate.getTime() + " > " + endDate.getTime() + ")"));
+        }
+    }
+
+    private Date parseDate(String toParse, Date def) {
+        return Strings.isBlank(toParse) ? def : Time.parseDate(toParse);
+    }
+    
+    private String format(Date date) {
+        return Time.makeDateString(date, Time.DATE_FORMAT_ISO8601_NO_MILLIS, Time.TIME_ZONE_UTC);
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/VersionResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/VersionResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/VersionResource.java
new file mode 100644
index 0000000..7492af6
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/VersionResource.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import org.apache.brooklyn.core.BrooklynVersion;
+import org.apache.brooklyn.rest.api.VersionApi;
+
+/** @deprecated since 0.7.0; use /v1/server/version */
+@Deprecated
+public class VersionResource extends AbstractBrooklynRestResource implements VersionApi {
+
+    @Override
+    public String getVersion() {
+        return BrooklynVersion.get();
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/PasswordHasher.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/PasswordHasher.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/PasswordHasher.java
new file mode 100644
index 0000000..928a6bd
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/PasswordHasher.java
@@ -0,0 +1,32 @@
+/*
+ * 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;
+
+import com.google.common.base.Charsets;
+import com.google.common.hash.HashCode;
+import com.google.common.hash.Hashing;
+
+public class PasswordHasher {
+    public static String sha256(String salt, String password) {
+        if (salt == null) salt = "";
+        byte[] bytes = (salt + password).getBytes(Charsets.UTF_8);
+        HashCode hash = Hashing.sha256().hashBytes(bytes);
+        return hash.toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/AbstractSecurityProvider.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/AbstractSecurityProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/AbstractSecurityProvider.java
new file mode 100644
index 0000000..91c2523
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/AbstractSecurityProvider.java
@@ -0,0 +1,56 @@
+/*
+ * 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;
+
+import org.apache.brooklyn.util.text.Strings;
+
+/**
+ * Provides default implementations of {@link #isAuthenticated(HttpSession)} and
+ * {@link #logout(HttpSession)}.
+ */
+public abstract class AbstractSecurityProvider implements SecurityProvider {
+
+    @Override
+    public boolean isAuthenticated(HttpSession session) {
+        if (session == null) return false;
+        Object value = session.getAttribute(getAuthenticationKey());
+        return Strings.isNonBlank(Strings.toString(value));
+    }
+
+    @Override
+    public boolean logout(HttpSession session) {
+        if (session == null) return false;
+        session.removeAttribute(getAuthenticationKey());
+        return true;
+    }
+
+    /**
+     * Sets an authentication token for the user on the session. Always returns true.
+     */
+    protected boolean allow(HttpSession session, String user) {
+        session.setAttribute(getAuthenticationKey(), user);
+        return true;
+    }
+
+    protected String getAuthenticationKey() {
+        return getClass().getName() + ".AUTHENTICATED";
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/AnyoneSecurityProvider.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/AnyoneSecurityProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/AnyoneSecurityProvider.java
new file mode 100644
index 0000000..97b4fe1
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/AnyoneSecurityProvider.java
@@ -0,0 +1,40 @@
+/*
+ * 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;
+
+/** provider who allows everyone */
+public class AnyoneSecurityProvider implements SecurityProvider {
+
+    @Override
+    public boolean isAuthenticated(HttpSession session) {
+        return true;
+    }
+
+    @Override
+    public boolean authenticate(HttpSession session, String user, String password) {
+        return true;
+    }
+
+    @Override
+    public boolean logout(HttpSession session) { 
+        return true;
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/BlackholeSecurityProvider.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/BlackholeSecurityProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/BlackholeSecurityProvider.java
new file mode 100644
index 0000000..a976975
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/BlackholeSecurityProvider.java
@@ -0,0 +1,40 @@
+/*
+ * 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;
+
+/** provider who disallows everyone */
+public class BlackholeSecurityProvider implements SecurityProvider {
+
+    @Override
+    public boolean isAuthenticated(HttpSession session) {
+        return false;
+    }
+
+    @Override
+    public boolean authenticate(HttpSession session, String user, String password) {
+        return false;
+    }
+
+    @Override
+    public boolean logout(HttpSession session) { 
+        return true;
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/BrooklynUserWithRandomPasswordSecurityProvider.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/BrooklynUserWithRandomPasswordSecurityProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/BrooklynUserWithRandomPasswordSecurityProvider.java
new file mode 100644
index 0000000..7b8e4a5
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/BrooklynUserWithRandomPasswordSecurityProvider.java
@@ -0,0 +1,73 @@
+/*
+ * 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;
+
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.rest.BrooklynWebConfig;
+import org.apache.brooklyn.util.javalang.JavaClassNames;
+import org.apache.brooklyn.util.net.Networking;
+import org.apache.brooklyn.util.text.Identifiers;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class BrooklynUserWithRandomPasswordSecurityProvider extends AbstractSecurityProvider implements SecurityProvider {
+
+    public static final Logger LOG = LoggerFactory.getLogger(BrooklynUserWithRandomPasswordSecurityProvider.class);
+    private static final String USER = "brooklyn";
+    private final String password;
+
+    public BrooklynUserWithRandomPasswordSecurityProvider() {
+        this.password = Identifiers.makeRandomId(10);
+        LOG.info("Allowing access to web console from localhost or with {}:{}", USER, password);
+    }
+
+    public BrooklynUserWithRandomPasswordSecurityProvider(ManagementContext mgmt) {
+        this();
+    }
+
+    @Override
+    public boolean authenticate(HttpSession session, String user, String password) {
+        if ((USER.equals(user) && this.password.equals(password)) || isRemoteAddressLocalhost(session)) {
+            return allow(session, user);
+        } else {
+            return false;
+        }
+    }
+
+    private boolean isRemoteAddressLocalhost(HttpSession session) {
+        Object remoteAddress = session.getAttribute(BrooklynWebConfig.REMOTE_ADDRESS_SESSION_ATTRIBUTE);
+        if (!(remoteAddress instanceof String)) return false;
+        if (Networking.isLocalhost((String)remoteAddress)) {
+            if (LOG.isTraceEnabled()) {
+                LOG.trace(this+": granting passwordless access to "+session+" originating from "+remoteAddress);
+            }
+            return true;
+        } else {
+            LOG.debug(this+": password required for "+session+" originating from "+remoteAddress);
+            return false;
+        }
+    }
+    
+    @Override
+    public String toString() {
+        return JavaClassNames.cleanSimpleClassName(this);
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/DelegatingSecurityProvider.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/DelegatingSecurityProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/DelegatingSecurityProvider.java
new file mode 100644
index 0000000..8b2b9da
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/DelegatingSecurityProvider.java
@@ -0,0 +1,165 @@
+/*
+ * 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.lang.reflect.Constructor;
+import java.util.concurrent.atomic.AtomicLong;
+
+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.util.text.Strings;
+
+public class DelegatingSecurityProvider implements SecurityProvider {
+
+    private static final Logger log = LoggerFactory.getLogger(DelegatingSecurityProvider.class);
+    protected final ManagementContext mgmt;
+
+    public DelegatingSecurityProvider(ManagementContext mgmt) {
+        this.mgmt = mgmt;
+        mgmt.addPropertiesReloadListener(new PropertiesListener());
+    }
+    
+    private SecurityProvider delegate;
+    private final AtomicLong modCount = new AtomicLong();
+
+    private class PropertiesListener implements ManagementContext.PropertiesReloadListener {
+        private static final long serialVersionUID = 8148722609022378917L;
+
+        @Override
+        public void reloaded() {
+            log.debug("{} reloading security provider", DelegatingSecurityProvider.this);
+            synchronized (DelegatingSecurityProvider.this) {
+                loadDelegate();
+                invalidateExistingSessions();
+            }
+        }
+    }
+
+    public synchronized SecurityProvider getDelegate() {
+        if (delegate == null) {
+            delegate = loadDelegate();
+        }
+        return delegate;
+    }
+
+    @SuppressWarnings("unchecked")
+    private synchronized SecurityProvider loadDelegate() {
+        StringConfigMap brooklynProperties = mgmt.getConfig();
+
+        SecurityProvider presetDelegate = brooklynProperties.getConfig(BrooklynWebConfig.SECURITY_PROVIDER_INSTANCE);
+        if (presetDelegate!=null) {
+            log.info("REST using pre-set security provider " + presetDelegate);
+            return presetDelegate;
+        }
+        
+        String className = brooklynProperties.getConfig(BrooklynWebConfig.SECURITY_PROVIDER_CLASSNAME);
+
+        if (delegate != null && BrooklynWebConfig.hasNoSecurityOptions(mgmt.getConfig())) {
+            log.debug("{} refusing to change from {}: No security provider set in reloaded properties.",
+                    this, delegate);
+            return delegate;
+        }
+        log.info("REST using security provider " + className);
+
+        try {
+            Class<? extends SecurityProvider> clazz;
+            try {
+                clazz = (Class<? extends SecurityProvider>) Class.forName(className);
+            } catch (Exception e) {
+                String oldPackage = "brooklyn.web.console.security.";
+                if (className.startsWith(oldPackage)) {
+                    className = Strings.removeFromStart(className, oldPackage);
+                    className = DelegatingSecurityProvider.class.getPackage().getName() + "." + className;
+                    clazz = (Class<? extends SecurityProvider>) Class.forName(className);
+                    log.warn("Deprecated package " + oldPackage + " detected; please update security provider to point to " + className);
+                } else throw e;
+            }
+
+            Constructor<? extends SecurityProvider> constructor;
+            try {
+                constructor = clazz.getConstructor(ManagementContext.class);
+                delegate = constructor.newInstance(mgmt);
+            } catch (Exception e) {
+                constructor = clazz.getConstructor();
+                Object delegateO = constructor.newInstance();
+                if (!(delegateO instanceof SecurityProvider)) {
+                    // if classloaders get mangled it will be a different CL's SecurityProvider
+                    throw new ClassCastException("Delegate is either not a security provider or has an incompatible classloader: "+delegateO);
+                }
+                delegate = (SecurityProvider) delegateO;
+            }
+        } catch (Exception e) {
+            log.warn("REST unable to instantiate security provider " + className + "; all logins are being disallowed", e);
+            delegate = new BlackholeSecurityProvider();
+        }
+        
+        ((BrooklynProperties)mgmt.getConfig()).put(BrooklynWebConfig.SECURITY_PROVIDER_INSTANCE, delegate);
+        
+        return delegate;
+    }
+
+    /**
+     * Causes all existing sessions to be invalidated.
+     */
+    protected void invalidateExistingSessions() {
+        modCount.incrementAndGet();
+    }
+
+    @Override
+    public boolean isAuthenticated(HttpSession session) {
+        if (session == null) return false;
+        Object modCountWhenFirstAuthenticated = session.getAttribute(getModificationCountKey());
+        boolean authenticated = getDelegate().isAuthenticated(session) &&
+                Long.valueOf(modCount.get()).equals(modCountWhenFirstAuthenticated);
+        return authenticated;
+    }
+
+    @Override
+    public boolean authenticate(HttpSession session, String user, String password) {
+        boolean authenticated = getDelegate().authenticate(session, user, password);
+        if (authenticated) {
+            session.setAttribute(getModificationCountKey(), modCount.get());
+        }
+        if (log.isTraceEnabled() && authenticated) {
+            log.trace("User {} authenticated with provider {}", user, getDelegate());
+        } else if (!authenticated && log.isDebugEnabled()) {
+            log.debug("Failed authentication for user {} with provider {}", user, getDelegate());
+        }
+        return authenticated;
+    }
+
+    @Override
+    public boolean logout(HttpSession session) { 
+        boolean logout = getDelegate().logout(session);
+        if (logout) {
+            session.removeAttribute(getModificationCountKey());
+        }
+        return logout;
+    }
+
+    private String getModificationCountKey() {
+        return getClass().getName() + ".ModCount";
+    }
+}


[27/34] brooklyn-server git commit: REST API optional Jersey compatibility

Posted by he...@apache.org.
REST API optional Jersey compatibility


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

Branch: refs/heads/master
Commit: abd2d5f3b2a5584c639ca233acd474d2bc972035
Parents: 6f624c7
Author: Svetoslav Neykov <sv...@cloudsoftcorp.com>
Authored: Tue Feb 9 19:30:17 2016 +0200
Committer: Svetoslav Neykov <sv...@cloudsoftcorp.com>
Committed: Tue Feb 16 11:43:48 2016 +0200

----------------------------------------------------------------------
 camp/camp-server-jersey/pom.xml                 | 225 ++++++
 .../brooklyn/camp/server/RestApiSetup.java      |  53 ++
 .../rest/resource/ApiListingResource.java       | 260 +++++++
 .../rest/resource/ApidocRestResource.java       |  32 +
 .../src/main/webapp/WEB-INF/web.xml             | 142 ++++
 .../brooklyn/camp/server/RestApiSetup.java      |   4 +-
 .../brooklyn/camp/server/rest/CampServer.java   |   2 +-
 .../rest/resource/ApidocRestResource.java       |   2 +-
 camp/pom.xml                                    |   1 +
 launcher/pom.xml                                |  36 +-
 .../brooklyn/launcher/BrooklynWebServer.java    |   8 +-
 .../BrooklynJavascriptGuiLauncherTest.java      |   2 +-
 parent/pom.xml                                  |  28 +-
 pom.xml                                         |   3 +-
 .../rest/client/BrooklynApiUtilTest.java        |   2 +-
 .../brooklyn/rest/resources/ApidocResource.java |   3 +-
 .../rest/util/ManagementContextProvider.java    |   6 +-
 .../rest/util/ShutdownHandlerProvider.java      |   6 +-
 .../util/json/BrooklynJacksonJsonProvider.java  |  42 +-
 .../src/main/resources/not-a-jar-file.txt       |  18 -
 .../src/main/resources/reset-catalog.xml        |  37 -
 .../src/test/resources/not-a-jar-file.txt       |  18 +
 .../src/test/resources/reset-catalog.xml        |  37 +
 rest/rest-server-jersey/pom.xml                 | 317 +++++++++
 .../org/apache/brooklyn/rest/RestApiSetup.java  |  78 +++
 .../rest/filter/HaHotCheckResourceFilter.java   | 163 +++++
 .../brooklyn/rest/filter/NoCacheFilter.java     |  40 ++
 .../brooklyn/rest/filter/SwaggerFilter.java     |  79 +++
 .../rest/resources/ApiListingResource.java      | 260 +++++++
 .../brooklyn/rest/resources/ApidocResource.java |  33 +
 .../brooklyn/rest/util/FormMapProvider.java     |  81 +++
 .../main/resources/build-metadata.properties    |  18 +
 .../src/main/webapp/WEB-INF/web.xml             | 144 ++++
 .../BrooklynPropertiesSecurityFilterTest.java   | 151 ++++
 .../brooklyn/rest/BrooklynRestApiLauncher.java  | 499 +++++++++++++
 .../rest/BrooklynRestApiLauncherTest.java       |  77 ++
 .../BrooklynRestApiLauncherTestFixture.java     | 109 +++
 .../apache/brooklyn/rest/HaHotCheckTest.java    | 130 ++++
 .../brooklyn/rest/HaMasterCheckFilterTest.java  | 218 ++++++
 .../rest/domain/AbstractDomainTest.java         |  44 ++
 .../brooklyn/rest/domain/ApiErrorTest.java      |  71 ++
 .../rest/domain/ApplicationSpecTest.java        |  40 ++
 .../brooklyn/rest/domain/ApplicationTest.java   |  87 +++
 .../rest/domain/EffectorSummaryTest.java        |  44 ++
 .../brooklyn/rest/domain/EntitySpecTest.java    |  48 ++
 .../brooklyn/rest/domain/EntitySummaryTest.java |  48 ++
 .../brooklyn/rest/domain/LocationSpecTest.java  |  58 ++
 .../rest/domain/LocationSummaryTest.java        |  41 ++
 .../brooklyn/rest/domain/SensorSummaryTest.java | 103 +++
 .../rest/domain/VersionSummaryTest.java         |  49 ++
 .../AbstractRestApiEntitlementsTest.java        | 111 +++
 .../ActivityApiEntitlementsTest.java            | 123 ++++
 .../AuthenticateAnyoneSecurityProvider.java     |  41 ++
 .../EntityConfigApiEntitlementsTest.java        | 103 +++
 .../entitlement/SensorApiEntitlementsTest.java  | 108 +++
 .../entitlement/ServerApiEntitlementsTest.java  |  34 +
 .../StaticDelegatingEntitlementManager.java     |  37 +
 .../rest/resources/AccessResourceTest.java      |  68 ++
 .../rest/resources/ApidocResourceTest.java      | 177 +++++
 .../ApplicationResourceIntegrationTest.java     | 133 ++++
 .../rest/resources/ApplicationResourceTest.java | 701 +++++++++++++++++++
 .../rest/resources/CatalogResetTest.java        | 113 +++
 .../rest/resources/CatalogResourceTest.java     | 513 ++++++++++++++
 .../rest/resources/DelegatingPrintStream.java   | 183 +++++
 .../rest/resources/DescendantsTest.java         | 130 ++++
 .../resources/EntityConfigResourceTest.java     | 172 +++++
 .../rest/resources/EntityResourceTest.java      | 189 +++++
 .../rest/resources/ErrorResponseTest.java       |  98 +++
 .../rest/resources/LocationResourceTest.java    | 188 +++++
 .../rest/resources/PolicyResourceTest.java      | 145 ++++
 .../rest/resources/ScriptResourceTest.java      |  54 ++
 .../SensorResourceIntegrationTest.java          |  82 +++
 .../rest/resources/SensorResourceTest.java      | 271 +++++++
 .../ServerResourceIntegrationTest.java          | 125 ++++
 .../rest/resources/ServerResourceTest.java      | 168 +++++
 .../rest/resources/ServerShutdownTest.java      | 185 +++++
 .../rest/resources/UsageResourceTest.java       | 443 ++++++++++++
 .../rest/resources/VersionResourceTest.java     |  52 ++
 .../rest/security/PasswordHasherTest.java       |  37 +
 .../security/provider/TestSecurityProvider.java |  46 ++
 .../test/config/render/TestRendererHints.java   |  36 +
 .../brooklynnode/DeployBlueprintTest.java       |  89 +++
 .../rest/testing/BrooklynRestApiTest.java       | 223 ++++++
 .../rest/testing/BrooklynRestResourceTest.java  | 154 ++++
 .../rest/testing/mocks/CapitalizePolicy.java    |  33 +
 .../rest/testing/mocks/EverythingGroup.java     |  27 +
 .../rest/testing/mocks/EverythingGroupImpl.java |  32 +
 .../rest/testing/mocks/NameMatcherGroup.java    |  30 +
 .../testing/mocks/NameMatcherGroupImpl.java     |  33 +
 .../rest/testing/mocks/RestMockApp.java         |  24 +
 .../rest/testing/mocks/RestMockAppBuilder.java  |  39 ++
 .../testing/mocks/RestMockSimpleEntity.java     | 103 +++
 .../testing/mocks/RestMockSimplePolicy.java     |  64 ++
 .../util/BrooklynRestResourceUtilsTest.java     | 213 ++++++
 .../rest/util/EntityLocationUtilsTest.java      |  72 ++
 .../rest/util/HaHotStateCheckClassResource.java |  38 +
 .../rest/util/HaHotStateCheckResource.java      |  44 ++
 .../rest/util/NoOpRecordingShutdownHandler.java |  39 ++
 .../util/NullHttpServletRequestProvider.java    |  46 ++
 .../rest/util/NullServletConfigProvider.java    |  51 ++
 .../brooklyn/rest/util/RestApiTestUtils.java    |  58 ++
 .../util/ServerStoppingShutdownHandler.java     |  75 ++
 .../json/BrooklynJacksonSerializerTest.java     | 399 +++++++++++
 .../resources/brooklyn/scanning.catalog.bom     |  19 +
 .../resources/fixtures/api-error-basic.json     |   4 +
 .../fixtures/api-error-no-details.json          |   3 +
 .../resources/fixtures/application-list.json    |  44 ++
 .../resources/fixtures/application-spec.json    |  16 +
 .../resources/fixtures/application-tree.json    |  43 ++
 .../test/resources/fixtures/application.json    |  22 +
 .../fixtures/catalog-application-list.json      |  29 +
 .../resources/fixtures/catalog-application.json |   9 +
 .../fixtures/effector-summary-list.json         |  47 ++
 .../resources/fixtures/effector-summary.json    |   9 +
 .../resources/fixtures/entity-only-type.json    |   3 +
 .../resources/fixtures/entity-summary-list.json |  14 +
 .../test/resources/fixtures/entity-summary.json |  13 +
 .../src/test/resources/fixtures/entity.json     |   7 +
 .../src/test/resources/fixtures/ha-summary.json |  19 +
 .../test/resources/fixtures/location-list.json  |  10 +
 .../resources/fixtures/location-summary.json    |   8 +
 .../fixtures/location-without-credential.json   |   5 +
 .../src/test/resources/fixtures/location.json   |   4 +
 .../fixtures/sensor-current-state.json          |   6 +
 .../resources/fixtures/sensor-summary-list.json |  42 ++
 .../test/resources/fixtures/sensor-summary.json |   8 +
 .../test/resources/fixtures/server-version.json |  14 +
 .../test/resources/fixtures/service-state.json  |   1 +
 .../resources/fixtures/task-summary-list.json   |  15 +
 .../org/apache/brooklyn/rest/RestApiSetup.java  |  12 +-
 .../src/main/resources/not-a-jar-file.txt       |  18 -
 .../src/main/resources/reset-catalog.xml        |  37 -
 .../brooklyn/rest/BrooklynRestApiLauncher.java  |  61 +-
 .../rest/apidoc/ApiListingResource.java         |  20 -
 134 files changed, 11051 insertions(+), 260 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/camp/camp-server-jersey/pom.xml
----------------------------------------------------------------------
diff --git a/camp/camp-server-jersey/pom.xml b/camp/camp-server-jersey/pom.xml
new file mode 100644
index 0000000..5bb2517
--- /dev/null
+++ b/camp/camp-server-jersey/pom.xml
@@ -0,0 +1,225 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>camp-server-jersey</artifactId>
+    <packaging>jar</packaging>
+    <name>CAMP Server Jersey</name>
+    <description>
+        REST Server classes for CAMP server implementation, using Jersey dependencies
+    </description>
+
+    <parent>
+        <groupId>org.apache.brooklyn.camp</groupId>
+        <artifactId>camp-parent</artifactId>
+        <version>0.9.0-SNAPSHOT</version>  <!-- BROOKLYN_VERSION -->
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.brooklyn.camp</groupId>
+            <artifactId>camp-base</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.brooklyn</groupId>
+            <artifactId>brooklyn-utils-test-support</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.brooklyn.camp</groupId>
+            <artifactId>camp-base</artifactId>
+            <version>${project.version}</version>
+            <classifier>tests</classifier>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.testng</groupId>
+            <artifactId>testng</artifactId>
+            <scope>test</scope>
+        </dependency>
+        
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+            <!-- jackson-core and jackson-annotations are pulled in from this, with the right version -->
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.jaxrs</groupId>
+            <artifactId>jackson-jaxrs-json-provider</artifactId>
+        </dependency>
+        
+        <dependency>
+            <groupId>com.sun.jersey</groupId>
+            <artifactId>jersey-server</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.sun.jersey</groupId>
+            <artifactId>jersey-servlet</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.sun.jersey</groupId>
+            <artifactId>jersey-json</artifactId>
+              <exclusions>
+                <exclusion>
+                    <groupId>com.sun.xml.bind</groupId>
+                    <artifactId>jaxb-impl</artifactId>
+                </exclusion>
+            </exclusions>               
+        </dependency>
+        <dependency>
+            <groupId>com.sun.jersey</groupId>
+            <artifactId>jersey-core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>javax.validation</groupId>
+            <artifactId>validation-api</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-webapp</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-server</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-servlet</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-util</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>javax.servlet-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.brooklyn</groupId>
+            <artifactId>brooklyn-utils-rest-swagger</artifactId>
+            <version>${project.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>javax.ws.rs</groupId>
+                    <artifactId>javax.ws.rs-api</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.brooklyn</groupId>
+            <artifactId>brooklyn-utils-common</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        
+        <!-- TODO have a camp.log / logging module -->
+        <dependency>
+            <groupId>org.apache.brooklyn</groupId>
+            <artifactId>brooklyn-logback-xml</artifactId>
+            <version>${project.version}</version>
+            <!-- optional so that this project has logging; dependencies may redeclare or supply their own -->
+            <optional>true</optional>
+        </dependency>
+        
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <artifactId>maven-resources-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>copy-rest-sources</id>
+                        <phase>generate-sources</phase>
+                        <goals><goal>copy-resources</goal></goals>
+                        <configuration>
+                          <outputDirectory>target/generated-sources/rest-deps</outputDirectory>
+                          <resources>
+                            <resource>
+                              <directory>../camp-server/src/main/java</directory>
+                              <excludes>
+                                <exclude>**/RestApiSetup.java</exclude>
+                                <exclude>**/ApidocRestResource.java</exclude>
+                              </excludes>
+                            </resource>
+                          </resources>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <id>copy-test-rest-sources</id>
+                        <phase>generate-sources</phase>
+                        <goals><goal>copy-resources</goal></goals>
+                        <configuration>
+                          <outputDirectory>target/generated-sources/test-rest-deps</outputDirectory>
+                          <resources>
+                            <resource>
+                              <directory>../camp-server/src/test/java</directory>
+                            </resource>
+                          </resources>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>build-helper-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>rest-sources</id>
+                        <phase>generate-sources</phase>
+                        <goals><goal>add-source</goal></goals>
+                        <configuration>
+                          <sources>
+                            <source>target/generated-sources/rest-deps</source>
+                          </sources>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <id>test-rest-sources</id>
+                        <phase>generate-sources</phase>
+                        <goals><goal>add-test-source</goal></goals>
+                        <configuration>
+                          <sources>
+                            <source>target/generated-sources/test-rest-deps</source>
+                          </sources>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+         </plugins>
+    </build>
+</project>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/camp/camp-server-jersey/src/main/java/org/apache/brooklyn/camp/server/RestApiSetup.java
----------------------------------------------------------------------
diff --git a/camp/camp-server-jersey/src/main/java/org/apache/brooklyn/camp/server/RestApiSetup.java b/camp/camp-server-jersey/src/main/java/org/apache/brooklyn/camp/server/RestApiSetup.java
new file mode 100644
index 0000000..60a44e0
--- /dev/null
+++ b/camp/camp-server-jersey/src/main/java/org/apache/brooklyn/camp/server/RestApiSetup.java
@@ -0,0 +1,53 @@
+/*
+ * 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.camp.server;
+
+import java.util.EnumSet;
+
+import javax.servlet.DispatcherType;
+
+import org.apache.brooklyn.camp.server.rest.CampRestResources;
+import org.eclipse.jetty.servlet.FilterHolder;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+
+import com.sun.jersey.api.core.DefaultResourceConfig;
+import com.sun.jersey.api.core.ResourceConfig;
+import com.sun.jersey.spi.container.servlet.ServletContainer;
+
+public class RestApiSetup {
+
+    public static void install(ServletContextHandler context) {
+        ResourceConfig config = new DefaultResourceConfig();
+        // load all our REST API modules, JSON, and Swagger
+        for (Object r: CampRestResources.getAllResources())
+            config.getSingletons().add(r);
+
+        // configure to match empty path, or any thing which looks like a file path with /assets/ and extension html, css, js, or png
+        // and treat that as static content
+        config.getProperties().put(ServletContainer.PROPERTY_WEB_PAGE_CONTENT_REGEX, "(/?|[^?]*/assets/[^?]+\\.[A-Za-z0-9_]+)");
+
+        // and anything which is not matched as a servlet also falls through (but more expensive than a regex check?)
+        config.getFeatures().put(ServletContainer.FEATURE_FILTER_FORWARD_ON_404, true);
+
+        // finally create this as a _filter_ which falls through to a web app or something (optionally)
+        FilterHolder filterHolder = new FilterHolder(new ServletContainer(config));
+        context.addFilter(filterHolder, "/*", EnumSet.allOf(DispatcherType.class));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/camp/camp-server-jersey/src/main/java/org/apache/brooklyn/camp/server/rest/resource/ApiListingResource.java
----------------------------------------------------------------------
diff --git a/camp/camp-server-jersey/src/main/java/org/apache/brooklyn/camp/server/rest/resource/ApiListingResource.java b/camp/camp-server-jersey/src/main/java/org/apache/brooklyn/camp/server/rest/resource/ApiListingResource.java
new file mode 100644
index 0000000..af656cc
--- /dev/null
+++ b/camp/camp-server-jersey/src/main/java/org/apache/brooklyn/camp/server/rest/resource/ApiListingResource.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright 2015 The Apache Software Foundation.
+ *
+ * Licensed 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.camp.server.rest.resource;
+
+import com.sun.jersey.spi.container.servlet.WebConfig;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.config.FilterFactory;
+import io.swagger.config.Scanner;
+import io.swagger.config.ScannerFactory;
+import io.swagger.config.SwaggerConfig;
+import io.swagger.core.filter.SpecFilter;
+import io.swagger.core.filter.SwaggerSpecFilter;
+import io.swagger.jaxrs.Reader;
+import io.swagger.jaxrs.config.JaxrsScanner;
+import io.swagger.jaxrs.config.ReaderConfigUtils;
+import io.swagger.jaxrs.listing.SwaggerSerializers;
+import io.swagger.models.Swagger;
+import io.swagger.util.Yaml;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Cookie;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.brooklyn.util.text.Strings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * ApiListingResource usable within a jersey servlet filter.
+ *
+ * Taken from io.swagger:swagger-jaxrs, class
+ * io.swagger.jaxrs.listing.ApiListingResource, which can only be used within a
+ * servlet context. We are here using a filter, but jersey has a WebConfig class
+ * that can substitute ServletConfig and FilterConfig.
+ *
+ * @todo Remove when the rest-server is no longer running within a filter (e.g.
+ * as a standalone OSGi http service)
+ *
+ * @author Ciprian Ciubotariu <ch...@gmx.net>
+ */
+public class ApiListingResource {
+
+    static Logger LOGGER = LoggerFactory.getLogger(ApiListingResource.class);
+
+    @Context
+    ServletContext context;
+
+    boolean initialized = false;
+
+    private static class ServletConfigAdapter implements ServletConfig {
+
+        private final WebConfig webConfig;
+
+        private ServletConfigAdapter(WebConfig webConfig) {
+            this.webConfig = webConfig;
+        }
+
+        @Override
+        public String getServletName() {
+            return webConfig.getName();
+        }
+
+        @Override
+        public ServletContext getServletContext() {
+            return webConfig.getServletContext();
+        }
+
+        @Override
+        public String getInitParameter(String name) {
+            return webConfig.getInitParameter(name);
+        }
+
+        @Override
+        public Enumeration<String> getInitParameterNames() {
+            return webConfig.getInitParameterNames();
+        }
+
+    }
+
+    protected synchronized Swagger scan(Application app, WebConfig sc) {
+        Swagger swagger = null;
+        Scanner scanner = ScannerFactory.getScanner();
+        LOGGER.debug("using scanner " + scanner);
+
+        if (scanner != null) {
+            SwaggerSerializers.setPrettyPrint(scanner.getPrettyPrint());
+            swagger = (Swagger) context.getAttribute("swagger");
+
+            Set<Class<?>> classes;
+            if (scanner instanceof JaxrsScanner) {
+                JaxrsScanner jaxrsScanner = (JaxrsScanner) scanner;
+                classes = jaxrsScanner.classesFromContext(app, new ServletConfigAdapter(sc));
+            } else {
+                classes = scanner.classes();
+            }
+            if (classes != null) {
+                Reader reader = new Reader(swagger, ReaderConfigUtils.getReaderConfig(context));
+                swagger = reader.read(classes);
+                if (scanner instanceof SwaggerConfig) {
+                    swagger = ((SwaggerConfig) scanner).configure(swagger);
+                } else {
+                    SwaggerConfig configurator = (SwaggerConfig) context.getAttribute("reader");
+                    if (configurator != null) {
+                        LOGGER.debug("configuring swagger with " + configurator);
+                        configurator.configure(swagger);
+                    } else {
+                        LOGGER.debug("no configurator");
+                    }
+                }
+                context.setAttribute("swagger", swagger);
+            }
+        }
+        initialized = true;
+        return swagger;
+    }
+
+    private Swagger process(
+            Application app,
+            WebConfig sc,
+            HttpHeaders headers,
+            UriInfo uriInfo) {
+        Swagger swagger = (Swagger) context.getAttribute("swagger");
+        if (!initialized) {
+            swagger = scan(app, sc);
+        }
+        if (swagger != null) {
+            SwaggerSpecFilter filterImpl = FilterFactory.getFilter();
+            if (filterImpl != null) {
+                SpecFilter f = new SpecFilter();
+                swagger = f.filter(swagger, filterImpl, getQueryParams(uriInfo.getQueryParameters()), getCookies(headers),
+                        getHeaders(headers));
+            }
+        }
+        return swagger;
+    }
+
+    @GET
+    @Produces({MediaType.APPLICATION_JSON, "application/yaml"})
+    @ApiOperation(value = "The swagger definition in either JSON or YAML", hidden = true)
+    @Path("/swagger.{type:json|yaml}")
+    public Response getListing(
+            @Context Application app,
+            @Context WebConfig sc,
+            @Context HttpHeaders headers,
+            @Context UriInfo uriInfo,
+            @PathParam("type") String type) {
+        if (Strings.isNonBlank(type) && type.trim().equalsIgnoreCase("yaml")) {
+            return getListingYaml(app, sc, headers, uriInfo);
+        } else {
+            return getListingJson(app, sc, headers, uriInfo);
+        }
+    }
+
+    @GET
+    @Produces({MediaType.APPLICATION_JSON})
+    @Path("/swagger")
+    @ApiOperation(value = "The swagger definition in JSON", hidden = true)
+    public Response getListingJson(
+            @Context Application app,
+            @Context WebConfig sc,
+            @Context HttpHeaders headers,
+            @Context UriInfo uriInfo) {
+        Swagger swagger = process(app, sc, headers, uriInfo);
+
+        if (swagger != null) {
+            return Response.ok().entity(swagger).build();
+        } else {
+            return Response.status(404).build();
+        }
+    }
+
+    @GET
+    @Produces("application/yaml")
+    @Path("/swagger")
+    @ApiOperation(value = "The swagger definition in YAML", hidden = true)
+    public Response getListingYaml(
+            @Context Application app,
+            @Context WebConfig sc,
+            @Context HttpHeaders headers,
+            @Context UriInfo uriInfo) {
+        Swagger swagger = process(app, sc, headers, uriInfo);
+        try {
+            if (swagger != null) {
+                String yaml = Yaml.mapper().writeValueAsString(swagger);
+                StringBuilder b = new StringBuilder();
+                String[] parts = yaml.split("\n");
+                for (String part : parts) {
+                    b.append(part);
+                    b.append("\n");
+                }
+                return Response.ok().entity(b.toString()).type("application/yaml").build();
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return Response.status(404).build();
+    }
+
+    protected Map<String, List<String>> getQueryParams(MultivaluedMap<String, String> params) {
+        Map<String, List<String>> output = new HashMap<>();
+        if (params != null) {
+            for (String key : params.keySet()) {
+                List<String> values = params.get(key);
+                output.put(key, values);
+            }
+        }
+        return output;
+    }
+
+    protected Map<String, String> getCookies(HttpHeaders headers) {
+        Map<String, String> output = new HashMap<>();
+        if (headers != null) {
+            for (String key : headers.getCookies().keySet()) {
+                Cookie cookie = headers.getCookies().get(key);
+                output.put(key, cookie.getValue());
+            }
+        }
+        return output;
+    }
+
+    protected Map<String, List<String>> getHeaders(HttpHeaders headers) {
+        Map<String, List<String>> output = new HashMap<>();
+        if (headers != null) {
+            for (String key : headers.getRequestHeaders().keySet()) {
+                List<String> values = headers.getRequestHeaders().get(key);
+                output.put(key, values);
+            }
+        }
+        return output;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/camp/camp-server-jersey/src/main/java/org/apache/brooklyn/camp/server/rest/resource/ApidocRestResource.java
----------------------------------------------------------------------
diff --git a/camp/camp-server-jersey/src/main/java/org/apache/brooklyn/camp/server/rest/resource/ApidocRestResource.java b/camp/camp-server-jersey/src/main/java/org/apache/brooklyn/camp/server/rest/resource/ApidocRestResource.java
new file mode 100644
index 0000000..1696855
--- /dev/null
+++ b/camp/camp-server-jersey/src/main/java/org/apache/brooklyn/camp/server/rest/resource/ApidocRestResource.java
@@ -0,0 +1,32 @@
+/*
+ * 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.camp.server.rest.resource;
+
+import javax.ws.rs.Path;
+
+import io.swagger.annotations.Api;
+
+
+@Path(ApidocRestResource.API_URI_PATH)
+@Api("Web API Documentation")
+public class ApidocRestResource extends ApiListingResource {
+
+    public static final String API_URI_PATH = PlatformRestResource.CAMP_URI_PATH + "/apidoc";
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/camp/camp-server-jersey/src/main/webapp/WEB-INF/web.xml
----------------------------------------------------------------------
diff --git a/camp/camp-server-jersey/src/main/webapp/WEB-INF/web.xml b/camp/camp-server-jersey/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..0ea4373
--- /dev/null
+++ b/camp/camp-server-jersey/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,142 @@
+<!DOCTYPE web-app PUBLIC
+        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
+        "http://java.sun.com/dtd/web-app_2_3.dtd" >
+<!--
+    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.
+-->
+<web-app>
+    <display-name>Brooklyn REST API v1</display-name>
+
+    <filter>
+        <filter-name>Brooklyn Request Tagging Filter</filter-name>
+        <filter-class>org.apache.brooklyn.rest.filter.RequestTaggingFilter</filter-class>
+    </filter>
+    <filter-mapping>
+        <filter-name>Brooklyn Request Tagging Filter</filter-name>
+        <url-pattern>/*</url-pattern>
+    </filter-mapping>
+
+    <filter>
+        <filter-name>Brooklyn Properties Authentication Filter</filter-name>
+        <filter-class>org.apache.brooklyn.rest.filter.BrooklynPropertiesSecurityFilter</filter-class>
+    </filter>
+    <filter-mapping>
+        <filter-name>Brooklyn Properties Authentication Filter</filter-name>
+        <url-pattern>/*</url-pattern>
+    </filter-mapping>
+
+    <filter>
+        <filter-name>Brooklyn Logging Filter</filter-name>
+        <filter-class>org.apache.brooklyn.rest.filter.LoggingFilter</filter-class>
+    </filter>
+    <filter-mapping>
+        <filter-name>Brooklyn Logging Filter</filter-name>
+        <url-pattern>/*</url-pattern>
+    </filter-mapping>
+
+    <filter>
+        <filter-name>Brooklyn HA Master Filter</filter-name>
+        <filter-class>org.apache.brooklyn.rest.filter.HaMasterCheckFilter</filter-class>
+    </filter>
+    <filter-mapping>
+        <filter-name>Brooklyn HA Master Filter</filter-name>
+        <url-pattern>/*</url-pattern>
+    </filter-mapping>
+
+    <filter>
+        <filter-name>Brooklyn Swagger Bootstrap</filter-name>
+        <filter-class>org.apache.brooklyn.rest.filter.SwaggerFilter</filter-class>
+    </filter>
+    <filter-mapping>
+        <filter-name>Brooklyn Swagger Bootstrap</filter-name>
+        <url-pattern>/*</url-pattern>
+    </filter-mapping>
+
+    <!-- Brooklyn REST is usually run as a filter so static content can be placed in a webapp
+         to which this is added; to run as a servlet directly, replace the filter tags 
+         below (after the comment) with the servlet tags (commented out immediately below),
+         (and do the same for the matching tags at the bottom)
+        <servlet>
+            <servlet-name>Brooklyn REST API v1 Servlet</servlet-name>
+            <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
+     -->
+    <filter>
+        <filter-name>Brooklyn REST API v1 Filter</filter-name>
+        <filter-class>com.sun.jersey.spi.container.servlet.ServletContainer</filter-class>
+
+        <!-- load our REST API jersey resources explicitly 
+            (the package scanner will only pick up classes with @Path annotations - doesn't look at implemented interfaces) 
+        -->
+        <init-param>
+            <param-name>com.sun.jersey.config.property.resourceConfigClass</param-name>
+            <param-value>com.sun.jersey.api.core.ClassNamesResourceConfig</param-value>
+        </init-param>
+        <init-param>
+            <param-name>com.sun.jersey.config.property.classnames</param-name>
+            <param-value>
+                io.swagger.jaxrs.listing.SwaggerSerializers;
+                org.apache.brooklyn.rest.util.FormMapProvider;
+                org.codehaus.jackson.jaxrs.JacksonJsonProvider;
+                org.apache.brooklyn.rest.resources.ActivityResource;
+                org.apache.brooklyn.rest.resources.ApidocResource;
+                org.apache.brooklyn.rest.resources.ApplicationResource;
+                org.apache.brooklyn.rest.resources.CatalogResource;
+                org.apache.brooklyn.rest.resources.EffectorResource;
+                org.apache.brooklyn.rest.resources.EntityConfigResource;
+                org.apache.brooklyn.rest.resources.EntityResource;
+                org.apache.brooklyn.rest.resources.LocationResource;
+                org.apache.brooklyn.rest.resources.PolicyConfigResource;
+                org.apache.brooklyn.rest.resources.PolicyResource;
+                org.apache.brooklyn.rest.resources.ScriptResource;
+                org.apache.brooklyn.rest.resources.SensorResource;
+                org.apache.brooklyn.rest.resources.UsageResource;
+                org.apache.brooklyn.rest.resources.VersionResource;
+            </param-value>
+        </init-param>
+
+        <init-param>
+            <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
+            <param-value>true</param-value>
+        </init-param>
+
+        <!-- no need for WADL. of course you can turn it back on it you want. -->
+        <init-param>
+            <param-name>com.sun.jersey.config.feature.DisableWADL</param-name>
+            <param-value>true</param-value>
+        </init-param>
+
+        <init-param>
+            <param-name>com.sun.jersey.config.feature.FilterContextPath</param-name>
+            <param-value>/v1</param-value>
+        </init-param>
+
+    </filter>
+    <filter-mapping>
+        <filter-name>Brooklyn REST API v1 Filter</filter-name>
+        <url-pattern>/v1/*</url-pattern>
+    </filter-mapping>
+    <!-- Brooklyn REST as a filter above; replace above 5 lines with those commented out below,
+         to run it as a servlet (see note above) 
+            <load-on-startup>1</load-on-startup>
+        </servlet>
+        <servlet-mapping>
+            <servlet-name>Brooklyn REST API v1 Servlet</servlet-name>
+            <url-pattern>/*</url-pattern>
+        </servlet-mapping>
+    -->
+</web-app>

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/camp/camp-server/src/main/java/org/apache/brooklyn/camp/server/RestApiSetup.java
----------------------------------------------------------------------
diff --git a/camp/camp-server/src/main/java/org/apache/brooklyn/camp/server/RestApiSetup.java b/camp/camp-server/src/main/java/org/apache/brooklyn/camp/server/RestApiSetup.java
index 127b5bd..302dff8 100644
--- a/camp/camp-server/src/main/java/org/apache/brooklyn/camp/server/RestApiSetup.java
+++ b/camp/camp-server/src/main/java/org/apache/brooklyn/camp/server/RestApiSetup.java
@@ -20,7 +20,6 @@ package org.apache.brooklyn.camp.server;
 
 import org.apache.brooklyn.rest.apidoc.RestApiResourceScanner;
 import org.apache.cxf.jaxrs.servlet.CXFNonSpringJaxrsServlet;
-import org.eclipse.jetty.server.handler.ContextHandler;
 import org.eclipse.jetty.servlet.ServletContextHandler;
 import org.eclipse.jetty.servlet.ServletHolder;
 
@@ -28,7 +27,7 @@ import io.swagger.config.ScannerFactory;
 
 public class RestApiSetup {
 
-    public static ContextHandler installRestServlet(ServletContextHandler context) {
+    public static void install(ServletContextHandler context) {
         ScannerFactory.setScanner(new RestApiResourceScanner());
 
         CampRestApp app = new CampRestApp();
@@ -37,7 +36,6 @@ public class RestApiSetup {
         final ServletHolder servletHolder = new ServletHolder(servlet);
 
         context.addServlet(servletHolder, "/*");
-        return context;
     }
 
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/camp/camp-server/src/main/java/org/apache/brooklyn/camp/server/rest/CampServer.java
----------------------------------------------------------------------
diff --git a/camp/camp-server/src/main/java/org/apache/brooklyn/camp/server/rest/CampServer.java b/camp/camp-server/src/main/java/org/apache/brooklyn/camp/server/rest/CampServer.java
index 1274494..669342f 100644
--- a/camp/camp-server/src/main/java/org/apache/brooklyn/camp/server/rest/CampServer.java
+++ b/camp/camp-server/src/main/java/org/apache/brooklyn/camp/server/rest/CampServer.java
@@ -121,7 +121,7 @@ public class CampServer {
     public static class CampServerUtils {
 
         public static void installAsServletFilter(ServletContextHandler context) {
-            RestApiSetup.installRestServlet(context);
+            RestApiSetup.install(context);
         }
 
         public static Server startServer(ContextHandler context, String summary) {

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/camp/camp-server/src/main/java/org/apache/brooklyn/camp/server/rest/resource/ApidocRestResource.java
----------------------------------------------------------------------
diff --git a/camp/camp-server/src/main/java/org/apache/brooklyn/camp/server/rest/resource/ApidocRestResource.java b/camp/camp-server/src/main/java/org/apache/brooklyn/camp/server/rest/resource/ApidocRestResource.java
index 3c5f96d..40061ea 100644
--- a/camp/camp-server/src/main/java/org/apache/brooklyn/camp/server/rest/resource/ApidocRestResource.java
+++ b/camp/camp-server/src/main/java/org/apache/brooklyn/camp/server/rest/resource/ApidocRestResource.java
@@ -24,7 +24,7 @@ import javax.ws.rs.Path;
 
 @Path(ApidocRestResource.API_URI_PATH)
 @Api("Web API Documentation")
-public class ApidocRestResource extends org.apache.brooklyn.rest.apidoc.ApiListingResource {
+public class ApidocRestResource extends io.swagger.jaxrs.listing.ApiListingResource {
 
     public static final String API_URI_PATH = PlatformRestResource.CAMP_URI_PATH + "/apidoc";
 

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/camp/pom.xml
----------------------------------------------------------------------
diff --git a/camp/pom.xml b/camp/pom.xml
index 579c473..3bbce4b 100644
--- a/camp/pom.xml
+++ b/camp/pom.xml
@@ -39,6 +39,7 @@
     <modules>
         <module>camp-base</module>
         <module>camp-server</module>
+        <module>camp-server-jersey</module>
         <module>camp-brooklyn</module>
     </modules>
 

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/launcher/pom.xml
----------------------------------------------------------------------
diff --git a/launcher/pom.xml b/launcher/pom.xml
index a10af41..9247a51 100644
--- a/launcher/pom.xml
+++ b/launcher/pom.xml
@@ -48,7 +48,7 @@
         </dependency>
         <dependency>
             <groupId>org.apache.brooklyn</groupId>
-            <artifactId>brooklyn-rest-server</artifactId>
+            <artifactId>brooklyn-rest-server${rest-server-classifier}</artifactId>
             <version>${project.version}</version>
         </dependency>
         <dependency>
@@ -63,7 +63,7 @@
         </dependency>
         <dependency>
             <groupId>org.apache.brooklyn.camp</groupId>
-            <artifactId>camp-server</artifactId>
+            <artifactId>camp-server${rest-server-classifier}</artifactId>
             <version>${project.version}</version>
         </dependency>
         <dependency>
@@ -132,10 +132,6 @@
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
         </dependency>
-        <dependency>
-            <groupId>org.apache.cxf</groupId>
-            <artifactId>cxf-rt-frontend-jaxrs</artifactId>
-        </dependency>
 
         <dependency>
             <groupId>org.apache.brooklyn</groupId>
@@ -186,14 +182,7 @@
         </dependency>
         <dependency>
             <groupId>org.apache.brooklyn</groupId>
-            <artifactId>brooklyn-rest-resources</artifactId>
-            <version>${project.version}</version>
-            <classifier>tests</classifier>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.brooklyn</groupId>
-            <artifactId>brooklyn-rest-server</artifactId>
+            <artifactId>brooklyn-rest-server${rest-server-classifier}</artifactId>
             <version>${project.version}</version>
             <classifier>tests</classifier>
             <scope>test</scope>
@@ -283,4 +272,23 @@
             </plugin>
         </plugins>
     </build>
+    <profiles>
+        <profile>
+            <id>jax-rs-jersey</id>
+            <activation>
+                <property>
+                    <name>!jar-rs</name>
+                </property>
+            </activation>
+            <dependencies>
+                <dependency>
+                    <groupId>org.apache.brooklyn</groupId>
+                    <artifactId>brooklyn-rest-resources</artifactId>
+                    <version>${project.version}</version>
+                    <classifier>tests</classifier>
+                    <scope>test</scope>
+                </dependency>
+            </dependencies>
+        </profile>
+    </profiles>
 </project>

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynWebServer.java
----------------------------------------------------------------------
diff --git a/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynWebServer.java b/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynWebServer.java
index f2760f2..75e9f3e 100644
--- a/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynWebServer.java
+++ b/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynWebServer.java
@@ -74,8 +74,6 @@ import org.apache.brooklyn.util.stream.Streams;
 import org.apache.brooklyn.util.text.Identifiers;
 import org.apache.brooklyn.util.text.Strings;
 import org.apache.brooklyn.util.web.ContextHandlerCollectionHotSwappable;
-import org.apache.cxf.transport.common.gzip.GZIPInInterceptor;
-import org.apache.cxf.transport.common.gzip.GZIPOutInterceptor;
 import org.eclipse.jetty.http.HttpVersion;
 import org.eclipse.jetty.server.Connector;
 import org.eclipse.jetty.server.HttpConfiguration;
@@ -421,13 +419,11 @@ public class BrooklynWebServer {
     }
 
     private WebAppContext deployRestApi(WebAppContext context) {
-        RestApiSetup.installRestServlet(context,
+        RestApiSetup.installRest(context,
                 new ManagementContextProvider(managementContext),
                 new ShutdownHandlerProvider(shutdownHandler),
                 new NoCacheFilter(),
-                new HaHotCheckResourceFilter(),
-                new GZIPInInterceptor(),
-                new GZIPOutInterceptor());
+                new HaHotCheckResourceFilter());
         RestApiSetup.installServletFilters(context,
                 RequestTaggingFilter.class,
                 LoggingFilter.class,

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/launcher/src/test/java/org/apache/brooklyn/rest/jsgui/BrooklynJavascriptGuiLauncherTest.java
----------------------------------------------------------------------
diff --git a/launcher/src/test/java/org/apache/brooklyn/rest/jsgui/BrooklynJavascriptGuiLauncherTest.java b/launcher/src/test/java/org/apache/brooklyn/rest/jsgui/BrooklynJavascriptGuiLauncherTest.java
index 2896239..77adb2e 100644
--- a/launcher/src/test/java/org/apache/brooklyn/rest/jsgui/BrooklynJavascriptGuiLauncherTest.java
+++ b/launcher/src/test/java/org/apache/brooklyn/rest/jsgui/BrooklynJavascriptGuiLauncherTest.java
@@ -84,7 +84,7 @@ public class BrooklynJavascriptGuiLauncherTest {
      *
      * @todo Remove after transition to karaf launcher.
      */
-    private static ManagementContext getManagementContext(ContextHandler jettyServerHandler) {
+    public static ManagementContext getManagementContext(ContextHandler jettyServerHandler) {
         ManagementContext managementContext = Compat.getInstance().getManagementContext();
         if (managementContext == null && jettyServerHandler != null) {
             managementContext = (ManagementContext) jettyServerHandler.getAttribute(BrooklynServiceAttributes.BROOKLYN_MANAGEMENT_CONTEXT);

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/parent/pom.xml
----------------------------------------------------------------------
diff --git a/parent/pom.xml b/parent/pom.xml
index d87baae..308bb32 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -52,6 +52,10 @@
       http://stackoverflow.com/questions/5309379/how-to-keep-maven-profiles-which-are-activebydefault-active-even-if-another-prof )
     -->
 
+    <properties>
+        <rest-server-classifier />
+    </properties>
+
     <dependencyManagement>
         <dependencies>
             <dependency>
@@ -127,16 +131,6 @@
                 <version>${slf4j.version}</version>
             </dependency>
             <dependency>
-                <groupId>org.eclipse.jetty</groupId>
-                <artifactId>jetty-http</artifactId>
-                <version>${jetty.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>org.eclipse.jetty</groupId>
-                <artifactId>jetty-io</artifactId>
-                <version>${jetty.version}</version>
-            </dependency>
-            <dependency>
                 <groupId>org.apache.commons</groupId>
                 <artifactId>commons-lang3</artifactId>
                 <version>${commons-lang3.version}</version>
@@ -1841,6 +1835,18 @@
               </pluginManagement>
             </build>
         </profile>
-    </profiles>
 
+        <profile>
+            <id>jersey-deps</id>
+            <activation>
+              <property>
+                  <name>jax-rs</name>
+                  <value>jersey</value>
+              </property>
+            </activation>
+            <properties>
+              <rest-server-classifier>-jersey</rest-server-classifier>
+            </properties>
+        </profile>
+    </profiles>
 </project>

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 588198a..f710b91 100644
--- a/pom.xml
+++ b/pom.xml
@@ -208,8 +208,9 @@
         <module>logging/logback-xml</module>
         <module>rest/rest-api</module>
         <module>rest/rest-client</module>
-	<module>rest/rest-resources</module>
+        <module>rest/rest-resources</module>
         <module>rest/rest-server</module>
+        <module>rest/rest-server-jersey</module>
         <module>test-framework</module>
         <module>test-support</module>
 

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-client/src/test/java/org/apache/brooklyn/rest/client/BrooklynApiUtilTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-client/src/test/java/org/apache/brooklyn/rest/client/BrooklynApiUtilTest.java b/rest/rest-client/src/test/java/org/apache/brooklyn/rest/client/BrooklynApiUtilTest.java
index eae9b6d..2543238 100644
--- a/rest/rest-client/src/test/java/org/apache/brooklyn/rest/client/BrooklynApiUtilTest.java
+++ b/rest/rest-client/src/test/java/org/apache/brooklyn/rest/client/BrooklynApiUtilTest.java
@@ -65,7 +65,7 @@ public class BrooklynApiUtilTest {
         BrooklynApiUtil.deployBlueprint(api, YAML);
 
         RecordedRequest request = server.takeRequest();
-        assertEquals("/v1/applications", request.getPath());
+        assertEquals("/applications", request.getPath());
         assertEquals("POST", request.getMethod());
         assertEquals(YAML, new String(request.getBody()));
     }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ApidocResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ApidocResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ApidocResource.java
index 220e4e3..7372288 100644
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ApidocResource.java
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ApidocResource.java
@@ -21,9 +21,8 @@ package org.apache.brooklyn.rest.resources;
 
 import javax.ws.rs.Path;
 
-import org.apache.brooklyn.rest.apidoc.ApiListingResource;
-
 import io.swagger.annotations.Api;
+import io.swagger.jaxrs.listing.ApiListingResource;
 
 /**
  * @author Ciprian Ciubotariu <ch...@gmx.net>

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/ManagementContextProvider.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/ManagementContextProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/ManagementContextProvider.java
index ae90d0e..cbe5d91 100644
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/ManagementContextProvider.java
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/ManagementContextProvider.java
@@ -35,7 +35,11 @@ public class ManagementContextProvider implements ContextResolver<ManagementCont
 
     @Override
     public ManagementContext getContext(Class<?> type) {
-        return mgmt;
+        if (type == ManagementContext.class) {
+            return mgmt;
+        } else {
+            return null;
+        }
     }
 
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/ShutdownHandlerProvider.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/ShutdownHandlerProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/ShutdownHandlerProvider.java
index bae2922..dd0ad21 100644
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/ShutdownHandlerProvider.java
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/ShutdownHandlerProvider.java
@@ -34,7 +34,11 @@ public class ShutdownHandlerProvider implements ContextResolver<ShutdownHandler>
 
     @Override
     public ShutdownHandler getContext(Class<?> type) {
-        return shutdownHandler;
+        if (type == ShutdownHandler.class) {
+            return shutdownHandler;
+        } else {
+            return null;
+        }
     }
 
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonJsonProvider.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonJsonProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonJsonProvider.java
index 5568208..9c6704d 100644
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonJsonProvider.java
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonJsonProvider.java
@@ -23,6 +23,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
 import javax.servlet.ServletContext;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.MediaType;
+import javax.ws.rs.ext.ContextResolver;
 import javax.ws.rs.ext.MessageBodyReader;
 import javax.ws.rs.ext.MessageBodyWriter;
 
@@ -31,7 +32,6 @@ import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.core.config.ConfigKeys;
 import org.apache.brooklyn.core.internal.BrooklynProperties;
 import org.apache.brooklyn.core.server.BrooklynServiceAttributes;
-import org.apache.brooklyn.rest.util.OsgiCompat;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -48,12 +48,14 @@ public class BrooklynJacksonJsonProvider extends JacksonJsonProvider implements
 
     public static final String BROOKLYN_REST_OBJECT_MAPPER = BrooklynServiceAttributes.BROOKLYN_REST_OBJECT_MAPPER;
 
-    @Context protected ServletContext servletContext;
+    @Context
+    private ServletContext servletContext;
 
     protected ObjectMapper ourMapper;
     protected boolean notFound = false;
 
-    private ManagementContext mgmt;
+    @Context
+    private ContextResolver<ManagementContext> mgmt;
 
     @Override
     public ObjectMapper locateMapper(Class<?> type, MediaType mediaType) {
@@ -77,7 +79,7 @@ public class BrooklynJacksonJsonProvider extends JacksonJsonProvider implements
         if (ourMapper != null || notFound)
             return ourMapper;
 
-        ourMapper = findSharedObjectMapper(servletContext, mgmt);
+        ourMapper = findSharedObjectMapper(servletContext, mgmt());
         if (ourMapper == null) return null;
 
         if (notFound) {
@@ -88,6 +90,10 @@ public class BrooklynJacksonJsonProvider extends JacksonJsonProvider implements
         return ourMapper;
     }
 
+    private ManagementContext mgmt() {
+        return mgmt.getContext(ManagementContext.class);
+    }
+
     /**
      * Finds a shared {@link ObjectMapper} or makes a new one, stored against the servlet context;
      * returns null if a shared instance cannot be created.
@@ -113,19 +119,16 @@ public class BrooklynJacksonJsonProvider extends JacksonJsonProvider implements
                 }
             }
         }
-        if (mgmt != null) {
-            synchronized (mgmt) {
-                ConfigKey<ObjectMapper> key = ConfigKeys.newConfigKey(ObjectMapper.class, BROOKLYN_REST_OBJECT_MAPPER);
-                ObjectMapper mapper = mgmt.getConfig().getConfig(key);
-                if (mapper != null) return mapper;
-
-                mapper = newPrivateObjectMapper(mgmt);
-                log.debug("Storing new ObjectMapper against "+mgmt+" because no ServletContext available: "+mapper);
-                ((BrooklynProperties)mgmt.getConfig()).put(key, mapper);
-                return mapper;
-            }
+        synchronized (mgmt) {
+            ConfigKey<ObjectMapper> key = ConfigKeys.newConfigKey(ObjectMapper.class, BROOKLYN_REST_OBJECT_MAPPER);
+            ObjectMapper mapper = mgmt.getConfig().getConfig(key);
+            if (mapper != null) return mapper;
+
+            mapper = newPrivateObjectMapper(mgmt);
+            log.debug("Storing new ObjectMapper against "+mgmt+" because no ServletContext available: "+mapper);
+            ((BrooklynProperties)mgmt.getConfig()).put(key, mapper);
+            return mapper;
         }
-        return null;
     }
 
     /**
@@ -136,9 +139,6 @@ public class BrooklynJacksonJsonProvider extends JacksonJsonProvider implements
         ObjectMapper mapper = findSharedObjectMapper(servletContext, mgmt);
         if (mapper != null) return mapper;
 
-        if (mgmt == null && servletContext != null) {
-            mgmt = getManagementContext(servletContext);
-        }
         return newPrivateObjectMapper(mgmt);
     }
 
@@ -170,8 +170,4 @@ public class BrooklynJacksonJsonProvider extends JacksonJsonProvider implements
         return mapper;
     }
 
-    public static ManagementContext getManagementContext(ServletContext servletContext) {
-        return OsgiCompat.getManagementContext(servletContext);
-    }
-
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-resources/src/main/resources/not-a-jar-file.txt
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/resources/not-a-jar-file.txt b/rest/rest-resources/src/main/resources/not-a-jar-file.txt
deleted file mode 100644
index fbc22fe..0000000
--- a/rest/rest-resources/src/main/resources/not-a-jar-file.txt
+++ /dev/null
@@ -1,18 +0,0 @@
-# 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.
-
-Test loading of malformed jar file
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-resources/src/main/resources/reset-catalog.xml
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/resources/reset-catalog.xml b/rest/rest-resources/src/main/resources/reset-catalog.xml
deleted file mode 100644
index adef40a..0000000
--- a/rest/rest-resources/src/main/resources/reset-catalog.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-    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.
--->
-<catalog>
-    <name>Brooklyn Demos</name>
-
-    <template type="org.apache.brooklyn.entity.stock.BasicApplication" name="Basic application" />
-    <template type="org.apache.brooklyn.test.osgi.entities.SimpleApplication" name="Simple OSGi application">
-        <libraries>
-            <bundle>${bundle-location}</bundle>
-        </libraries>
-    </template>
-    <catalog>
-        <name>Nested catalog</name>
-        <template type="org.apache.brooklyn.test.osgi.entities.SimpleApplication" name="Simple OSGi application">
-            <libraries>
-                <bundle>${bundle-location}</bundle>
-            </libraries>
-        </template>
-    </catalog>
-</catalog>

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-resources/src/test/resources/not-a-jar-file.txt
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/resources/not-a-jar-file.txt b/rest/rest-resources/src/test/resources/not-a-jar-file.txt
new file mode 100644
index 0000000..fbc22fe
--- /dev/null
+++ b/rest/rest-resources/src/test/resources/not-a-jar-file.txt
@@ -0,0 +1,18 @@
+# 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.
+
+Test loading of malformed jar file
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-resources/src/test/resources/reset-catalog.xml
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/resources/reset-catalog.xml b/rest/rest-resources/src/test/resources/reset-catalog.xml
new file mode 100644
index 0000000..adef40a
--- /dev/null
+++ b/rest/rest-resources/src/test/resources/reset-catalog.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<catalog>
+    <name>Brooklyn Demos</name>
+
+    <template type="org.apache.brooklyn.entity.stock.BasicApplication" name="Basic application" />
+    <template type="org.apache.brooklyn.test.osgi.entities.SimpleApplication" name="Simple OSGi application">
+        <libraries>
+            <bundle>${bundle-location}</bundle>
+        </libraries>
+    </template>
+    <catalog>
+        <name>Nested catalog</name>
+        <template type="org.apache.brooklyn.test.osgi.entities.SimpleApplication" name="Simple OSGi application">
+            <libraries>
+                <bundle>${bundle-location}</bundle>
+            </libraries>
+        </template>
+    </catalog>
+</catalog>

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/pom.xml
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/pom.xml b/rest/rest-server-jersey/pom.xml
new file mode 100644
index 0000000..bf5e2c2
--- /dev/null
+++ b/rest/rest-server-jersey/pom.xml
@@ -0,0 +1,317 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>brooklyn-rest-server-jersey</artifactId>
+    <packaging>jar</packaging>
+    <name>Brooklyn REST Server with Jersey dependencies</name>
+    <description>
+        Brooklyn REST Endpoint, using Jersey as the JAR-RS implementation
+    </description>
+
+    <parent>
+        <groupId>org.apache.brooklyn</groupId>
+        <artifactId>brooklyn-parent</artifactId>
+        <version>0.9.0-SNAPSHOT</version><!-- BROOKLYN_VERSION -->
+        <relativePath>../../parent/pom.xml</relativePath>
+    </parent>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.brooklyn</groupId>
+            <artifactId>brooklyn-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.brooklyn</groupId>
+            <artifactId>brooklyn-core</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-annotations</artifactId>
+            <version>${fasterxml.jackson.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+            <version>${fasterxml.jackson.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>javax.validation</groupId>
+            <artifactId>validation-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.code.findbugs</groupId>
+            <artifactId>jsr305</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>commons-lang</groupId>
+            <artifactId>commons-lang</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>javax.servlet-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.sun.jersey</groupId>
+            <artifactId>jersey-core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.brooklyn</groupId>
+            <artifactId>brooklyn-utils-common</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.swagger</groupId>
+            <artifactId>swagger-annotations</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.testng</groupId>
+            <artifactId>testng</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.brooklyn</groupId>
+            <artifactId>brooklyn-test-support</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+
+
+        <dependency>
+            <groupId>org.apache.brooklyn</groupId>
+            <artifactId>brooklyn-camp</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.brooklyn.camp</groupId>
+            <artifactId>camp-base</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.brooklyn</groupId>
+            <artifactId>brooklyn-software-base</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.brooklyn</groupId>
+            <artifactId>brooklyn-utils-rest-swagger</artifactId>
+            <version>${project.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>javax.ws.rs</groupId>
+                    <artifactId>javax.ws.rs-api</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>org.yaml</groupId>
+            <artifactId>snakeyaml</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.reflections</groupId>
+            <artifactId>reflections</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>javax.ws.rs</groupId>
+            <artifactId>jsr311-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.jaxrs</groupId>
+            <artifactId>jackson-jaxrs-json-provider</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.sun.jersey</groupId>
+            <artifactId>jersey-server</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.sun.jersey</groupId>
+            <artifactId>jersey-servlet</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-webapp</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-server</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-servlet</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.brooklyn</groupId>
+            <artifactId>brooklyn-policy</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.brooklyn</groupId>
+            <artifactId>brooklyn-core</artifactId>
+            <version>${project.version}</version>
+            <classifier>tests</classifier>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.brooklyn</groupId>
+            <artifactId>brooklyn-software-base</artifactId>
+            <version>${project.version}</version>
+            <classifier>tests</classifier>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.brooklyn</groupId>
+            <artifactId>brooklyn-locations-jclouds</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>hamcrest-all</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.sun.jersey</groupId>
+            <artifactId>jersey-client</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.sun.jersey.jersey-test-framework</groupId>
+            <artifactId>jersey-test-framework-inmemory</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.sun.jersey.jersey-test-framework</groupId>
+            <artifactId>jersey-test-framework-grizzly2</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.brooklyn</groupId>
+            <artifactId>brooklyn-rt-osgi</artifactId>
+            <version>${project.version}</version>
+            <classifier>tests</classifier>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <resources>
+            <resource>
+                <directory>../rest-server/src/main/resources</directory>
+                <!-- Required to set values in build-metadata.properties -->
+                <filtering>true</filtering>
+            </resource>
+            <resource>
+                <directory>src/main/webapp</directory>
+            </resource>
+        </resources>
+        <plugins>
+            <plugin>
+                <artifactId>maven-resources-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>copy-rest-sources</id>
+                        <phase>generate-sources</phase>
+                        <goals><goal>copy-resources</goal></goals>
+                        <configuration>
+                          <outputDirectory>target/generated-sources/rest-deps</outputDirectory>
+                          <resources>
+                            <resource>
+                              <directory>../rest-api/src/main/java</directory>
+                            </resource>
+                            <resource>
+                              <directory>../rest-resources/src/main/java</directory>
+                              <excludes>
+                                <exclude>**/NoCacheFilter.java</exclude>
+                                <exclude>**/HaHotCheckResourceFilter.java</exclude>
+                                <exclude>**/FormMapProvider.java</exclude>
+                                <exclude>**/ApidocResource.java</exclude>
+                              </excludes>
+                            </resource>
+                            <resource>
+                              <directory>../rest-server/src/main/java</directory>
+                              <excludes>
+                                <exclude>**/RestApiSetup.java</exclude>
+                              </excludes>
+                            </resource>
+                          </resources>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <id>copy-rest-test-resources</id>
+                        <phase>generate-test-resources</phase>
+                        <goals><goal>copy-resources</goal></goals>
+                        <configuration>
+                          <outputDirectory>target/generated-test-resources/rest-deps</outputDirectory>
+                          <resources>
+                            <resource>
+                              <directory>../rest-resources/src/test/resources</directory>
+                            </resource>
+                            <resource>
+                              <directory>../rest-server/src/test/resources</directory>
+                            </resource>
+                          </resources>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>build-helper-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>rest-sources</id>
+                        <phase>generate-sources</phase>
+                        <goals><goal>add-source</goal></goals>
+                        <configuration>
+                          <sources>
+                            <source>target/generated-sources/rest-deps</source>
+                          </sources>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <id>rest-resources</id>
+                        <phase>generate-test-resources</phase>
+                        <goals><goal>add-test-resource</goal></goals>
+                        <configuration>
+                          <resources>
+                            <resource>
+                                <directory>target/generated-test-resources/rest-deps</directory>
+                            </resource>
+                          </resources>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+         </plugins>
+    </build>
+</project>

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/RestApiSetup.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/RestApiSetup.java b/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/RestApiSetup.java
new file mode 100644
index 0000000..9084756
--- /dev/null
+++ b/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/RestApiSetup.java
@@ -0,0 +1,78 @@
+/*
+ * 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;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.EnumSet;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.Filter;
+
+import org.apache.brooklyn.rest.filter.HaHotCheckResourceFilter;
+import org.apache.brooklyn.rest.filter.SwaggerFilter;
+import org.eclipse.jetty.servlet.FilterHolder;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+
+import com.google.common.collect.ImmutableList;
+import com.sun.jersey.api.container.filter.GZIPContentEncodingFilter;
+import com.sun.jersey.api.core.DefaultResourceConfig;
+import com.sun.jersey.api.core.ResourceConfig;
+import com.sun.jersey.spi.container.servlet.ServletContainer;
+
+public class RestApiSetup {
+
+    public static void installRest(ServletContextHandler context, Object... providers) {
+        ResourceConfig config = new DefaultResourceConfig();
+        // load all our REST API modules, JSON, and Swagger
+        for (Object r: BrooklynRestApi.getAllResources())
+            config.getSingletons().add(r);
+        for (Object o: providers)
+            config.getSingletons().add(o);
+
+        // Accept gzipped requests and responses, disable caching for dynamic content
+        config.getProperties().put(ResourceConfig.PROPERTY_CONTAINER_REQUEST_FILTERS, GZIPContentEncodingFilter.class.getName());
+        config.getProperties().put(ResourceConfig.PROPERTY_CONTAINER_RESPONSE_FILTERS, ImmutableList.of(GZIPContentEncodingFilter.class/*, NoCacheFilter.class*/));
+        // Checks if appropriate request given HA status
+        config.getProperties().put(ResourceConfig.PROPERTY_RESOURCE_FILTER_FACTORIES, HaHotCheckResourceFilter.class.getName());
+        // configure to match empty path, or any thing which looks like a file path with /assets/ and extension html, css, js, or png
+        // and treat that as static content
+        config.getProperties().put(ServletContainer.PROPERTY_WEB_PAGE_CONTENT_REGEX, "(/?|[^?]*/assets/[^?]+\\.[A-Za-z0-9_]+)");
+        // and anything which is not matched as a servlet also falls through (but more expensive than a regex check?)
+        config.getFeatures().put(ServletContainer.FEATURE_FILTER_FORWARD_ON_404, true);
+        // finally create this as a _filter_ which falls through to a web app or something (optionally)
+        FilterHolder filterHolder = new FilterHolder(new ServletContainer(config));
+
+        filterHolder.setInitParameter(ServletContainer.PROPERTY_FILTER_CONTEXT_PATH, "/v1");
+        context.addFilter(filterHolder, "/v1/*", EnumSet.allOf(DispatcherType.class));
+
+        installServletFilters(context, SwaggerFilter.class);
+    }
+
+    @SafeVarargs
+    public static void installServletFilters(ServletContextHandler context, Class<? extends Filter>... filters) {
+        installServletFilters(context, Arrays.asList(filters));
+    }
+
+    public static void installServletFilters(ServletContextHandler context, Collection<Class<? extends Filter>> filters) {
+        for (Class<? extends Filter> filter : filters) {
+            context.addFilter(filter, "/*", EnumSet.allOf(DispatcherType.class));
+        }
+    }
+}


[26/34] brooklyn-server git commit: REST API optional Jersey compatibility

Posted by he...@apache.org.
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/filter/HaHotCheckResourceFilter.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/filter/HaHotCheckResourceFilter.java b/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/filter/HaHotCheckResourceFilter.java
new file mode 100644
index 0000000..c65c78f
--- /dev/null
+++ b/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/filter/HaHotCheckResourceFilter.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.filter;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ContextResolver;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.api.mgmt.ha.ManagementNodeState;
+import org.apache.brooklyn.rest.domain.ApiError;
+import org.apache.brooklyn.util.text.Strings;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableSet;
+import com.sun.jersey.api.model.AbstractMethod;
+import com.sun.jersey.spi.container.ContainerRequest;
+import com.sun.jersey.spi.container.ContainerRequestFilter;
+import com.sun.jersey.spi.container.ContainerResponseFilter;
+import com.sun.jersey.spi.container.ResourceFilter;
+import com.sun.jersey.spi.container.ResourceFilterFactory;
+
+/** 
+ * Checks that if the method or resource class corresponding to a request
+ * has a {@link HaHotStateRequired} annotation,
+ * that the server is in that state (and up). 
+ * Requests with {@link #SKIP_CHECK_HEADER} set as a header skip this check.
+ * <p>
+ * This follows a different pattern to {@link HaMasterCheckFilter} 
+ * as this needs to know the method being invoked. 
+ */
+public class HaHotCheckResourceFilter implements ResourceFilterFactory {
+    public static final String SKIP_CHECK_HEADER = "Brooklyn-Allow-Non-Master-Access";
+    
+    private static final Logger log = LoggerFactory.getLogger(HaHotCheckResourceFilter.class);
+    
+    private static final Set<ManagementNodeState> HOT_STATES = ImmutableSet.of(
+            ManagementNodeState.MASTER, ManagementNodeState.HOT_STANDBY, ManagementNodeState.HOT_BACKUP);
+
+    @Context
+    private ContextResolver<ManagementContext> mgmt;
+
+    public HaHotCheckResourceFilter() {}
+    
+    @VisibleForTesting
+    public HaHotCheckResourceFilter(ContextResolver<ManagementContext> mgmt) {
+        this.mgmt = mgmt;
+    }
+
+    private ManagementContext mgmt() {
+        return mgmt.getContext(ManagementContext.class);
+    }
+
+    private static class MethodFilter implements ResourceFilter, ContainerRequestFilter {
+
+        private AbstractMethod am;
+        private ManagementContext mgmt;
+
+        public MethodFilter(AbstractMethod am, ManagementContext mgmt) {
+            this.am = am;
+            this.mgmt = mgmt;
+        }
+
+        @Override
+        public ContainerRequestFilter getRequestFilter() {
+            return this;
+        }
+
+        @Override
+        public ContainerResponseFilter getResponseFilter() {
+            return null;
+        }
+
+        private String lookForProblem(ContainerRequest request) {
+            if (isSkipCheckHeaderSet(request)) 
+                return null;
+            
+            if (!isHaHotStateRequired(request))
+                return null;
+            
+            String problem = lookForProblemIfServerNotRunning(mgmt);
+            if (Strings.isNonBlank(problem)) 
+                return problem;
+            
+            if (!isHaHotStatus())
+                return "server not in required HA hot state";
+            if (isStateNotYetValid())
+                return "server not yet completed loading data for required HA hot state";
+            
+            return null;
+        }
+        
+        @Override
+        public ContainerRequest filter(ContainerRequest request) {
+            String problem = lookForProblem(request);
+            if (Strings.isNonBlank(problem)) {
+                log.warn("Disallowing web request as "+problem+": "+request+"/"+am+" (caller should set '"+SKIP_CHECK_HEADER+"' to force)");
+                throw new WebApplicationException(ApiError.builder()
+                    .message("This request is only permitted against an active hot Brooklyn server")
+                    .errorCode(Response.Status.FORBIDDEN).build().asJsonResponse());
+            }
+            return request;
+        }
+
+        // Maybe there should be a separate state to indicate that we have switched state
+        // but still haven't finished rebinding. (Previously there was a time delay and an
+        // isRebinding check, but introducing RebindManager#isAwaitingInitialRebind() seems cleaner.)
+        private boolean isStateNotYetValid() {
+            return mgmt.getRebindManager().isAwaitingInitialRebind();
+        }
+
+        private boolean isHaHotStateRequired(ContainerRequest request) {
+            return (am.getAnnotation(HaHotStateRequired.class) != null ||
+                    am.getResource().getAnnotation(HaHotStateRequired.class) != null);
+        }
+
+        private boolean isSkipCheckHeaderSet(ContainerRequest request) {
+            return "true".equalsIgnoreCase(request.getHeaderValue(SKIP_CHECK_HEADER));
+        }
+
+        private boolean isHaHotStatus() {
+            ManagementNodeState state = mgmt.getHighAvailabilityManager().getNodeState();
+            return HOT_STATES.contains(state);
+        }
+
+    }
+
+    public static String lookForProblemIfServerNotRunning(ManagementContext mgmt) {
+        if (mgmt==null) return "no management context available";
+        if (!mgmt.isRunning()) return "server no longer running";
+        if (!mgmt.isStartupComplete()) return "server not in required startup-completed state";
+        return null;
+    }
+    
+    @Override
+    public List<ResourceFilter> create(AbstractMethod am) {
+        return Collections.<ResourceFilter>singletonList(new MethodFilter(am, mgmt()));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/filter/NoCacheFilter.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/filter/NoCacheFilter.java b/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/filter/NoCacheFilter.java
new file mode 100644
index 0000000..8a3c1c6
--- /dev/null
+++ b/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/filter/NoCacheFilter.java
@@ -0,0 +1,40 @@
+/*
+ * 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.filter;
+
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MultivaluedMap;
+
+import com.sun.jersey.spi.container.ContainerRequest;
+import com.sun.jersey.spi.container.ContainerResponse;
+import com.sun.jersey.spi.container.ContainerResponseFilter;
+
+public class NoCacheFilter implements ContainerResponseFilter {
+
+    @Override
+    public ContainerResponse filter(ContainerRequest request, ContainerResponse response) {
+        //https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching_FAQ
+        MultivaluedMap<String, Object> headers = response.getHttpHeaders();
+        headers.putSingle(HttpHeaders.CACHE_CONTROL, "no-cache, no-store");
+        headers.putSingle("Pragma", "no-cache");
+        headers.putSingle(HttpHeaders.EXPIRES, "0");
+        return response;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/filter/SwaggerFilter.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/filter/SwaggerFilter.java b/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/filter/SwaggerFilter.java
new file mode 100644
index 0000000..d9013f0
--- /dev/null
+++ b/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/filter/SwaggerFilter.java
@@ -0,0 +1,79 @@
+/*
+ * 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.filter;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URISyntaxException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.ws.rs.core.UriBuilder;
+
+import org.apache.brooklyn.rest.apidoc.RestApiResourceScanner;
+
+import io.swagger.config.ScannerFactory;
+import io.swagger.models.Info;
+import io.swagger.models.License;
+import io.swagger.models.Swagger;
+
+/**
+ * Bootstraps swagger.
+ * <p>
+ * Swagger was intended to run as a servlet.
+ *
+ * @author Ciprian Ciubotariu <ch...@gmx.net>
+ */
+public class SwaggerFilter implements Filter {
+
+    static Info info = new Info()
+            .title("Brooklyn API Documentation")
+            .version("v1") // API version, not BROOKLYN_VERSION
+            .license(new License()
+                    .name("Apache 2.0")
+                    .url("http://www.apache.org/licenses/LICENSE-2.0.html"));
+
+    @Override
+    public void init(FilterConfig filterConfig) throws ServletException {
+//        ReflectiveJaxrsScanner scanner = new ReflectiveJaxrsScanner();
+//        scanner.setResourcePackage("org.apache.brooklyn.rest.api,org.apache.brooklyn.rest.apidoc,org.apache.brooklyn.rest.resources");
+//        ScannerFactory.setScanner(scanner);
+        ScannerFactory.setScanner(new RestApiResourceScanner());
+
+        ServletContext context = filterConfig.getServletContext();
+        Swagger swagger = new Swagger()
+                .info(info);
+        context.setAttribute("swagger", swagger);
+    }
+
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
+        chain.doFilter(request, response);
+    }
+
+    @Override
+    public void destroy() {
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/resources/ApiListingResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/resources/ApiListingResource.java b/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/resources/ApiListingResource.java
new file mode 100644
index 0000000..74f8426
--- /dev/null
+++ b/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/resources/ApiListingResource.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright 2015 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import com.sun.jersey.spi.container.servlet.WebConfig;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.config.FilterFactory;
+import io.swagger.config.Scanner;
+import io.swagger.config.ScannerFactory;
+import io.swagger.config.SwaggerConfig;
+import io.swagger.core.filter.SpecFilter;
+import io.swagger.core.filter.SwaggerSpecFilter;
+import io.swagger.jaxrs.Reader;
+import io.swagger.jaxrs.config.JaxrsScanner;
+import io.swagger.jaxrs.config.ReaderConfigUtils;
+import io.swagger.jaxrs.listing.SwaggerSerializers;
+import io.swagger.models.Swagger;
+import io.swagger.util.Yaml;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Cookie;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.brooklyn.util.text.Strings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * ApiListingResource usable within a jersey servlet filter.
+ *
+ * Taken from io.swagger:swagger-jaxrs, class
+ * io.swagger.jaxrs.listing.ApiListingResource, which can only be used within a
+ * servlet context. We are here using a filter, but jersey has a WebConfig class
+ * that can substitute ServletConfig and FilterConfig.
+ *
+ * @todo Remove when the rest-server is no longer running within a filter (e.g.
+ * as a standalone OSGi http service)
+ *
+ * @author Ciprian Ciubotariu <ch...@gmx.net>
+ */
+public class ApiListingResource {
+
+    static Logger LOGGER = LoggerFactory.getLogger(ApiListingResource.class);
+
+    @Context
+    ServletContext context;
+
+    boolean initialized = false;
+
+    private static class ServletConfigAdapter implements ServletConfig {
+
+        private final WebConfig webConfig;
+
+        private ServletConfigAdapter(WebConfig webConfig) {
+            this.webConfig = webConfig;
+        }
+
+        @Override
+        public String getServletName() {
+            return webConfig.getName();
+        }
+
+        @Override
+        public ServletContext getServletContext() {
+            return webConfig.getServletContext();
+        }
+
+        @Override
+        public String getInitParameter(String name) {
+            return webConfig.getInitParameter(name);
+        }
+
+        @Override
+        public Enumeration<String> getInitParameterNames() {
+            return webConfig.getInitParameterNames();
+        }
+
+    }
+
+    protected synchronized Swagger scan(Application app, WebConfig sc) {
+        Swagger swagger = null;
+        Scanner scanner = ScannerFactory.getScanner();
+        LOGGER.debug("using scanner " + scanner);
+
+        if (scanner != null) {
+            SwaggerSerializers.setPrettyPrint(scanner.getPrettyPrint());
+            swagger = (Swagger) context.getAttribute("swagger");
+
+            Set<Class<?>> classes;
+            if (scanner instanceof JaxrsScanner) {
+                JaxrsScanner jaxrsScanner = (JaxrsScanner) scanner;
+                classes = jaxrsScanner.classesFromContext(app, new ServletConfigAdapter(sc));
+            } else {
+                classes = scanner.classes();
+            }
+            if (classes != null) {
+                Reader reader = new Reader(swagger, ReaderConfigUtils.getReaderConfig(context));
+                swagger = reader.read(classes);
+                if (scanner instanceof SwaggerConfig) {
+                    swagger = ((SwaggerConfig) scanner).configure(swagger);
+                } else {
+                    SwaggerConfig configurator = (SwaggerConfig) context.getAttribute("reader");
+                    if (configurator != null) {
+                        LOGGER.debug("configuring swagger with " + configurator);
+                        configurator.configure(swagger);
+                    } else {
+                        LOGGER.debug("no configurator");
+                    }
+                }
+                context.setAttribute("swagger", swagger);
+            }
+        }
+        initialized = true;
+        return swagger;
+    }
+
+    private Swagger process(
+            Application app,
+            WebConfig sc,
+            HttpHeaders headers,
+            UriInfo uriInfo) {
+        Swagger swagger = (Swagger) context.getAttribute("swagger");
+        if (!initialized) {
+            swagger = scan(app, sc);
+        }
+        if (swagger != null) {
+            SwaggerSpecFilter filterImpl = FilterFactory.getFilter();
+            if (filterImpl != null) {
+                SpecFilter f = new SpecFilter();
+                swagger = f.filter(swagger, filterImpl, getQueryParams(uriInfo.getQueryParameters()), getCookies(headers),
+                        getHeaders(headers));
+            }
+        }
+        return swagger;
+    }
+
+    @GET
+    @Produces({MediaType.APPLICATION_JSON, "application/yaml"})
+    @ApiOperation(value = "The swagger definition in either JSON or YAML", hidden = true)
+    @Path("/swagger.{type:json|yaml}")
+    public Response getListing(
+            @Context Application app,
+            @Context WebConfig sc,
+            @Context HttpHeaders headers,
+            @Context UriInfo uriInfo,
+            @PathParam("type") String type) {
+        if (Strings.isNonBlank(type) && type.trim().equalsIgnoreCase("yaml")) {
+            return getListingYaml(app, sc, headers, uriInfo);
+        } else {
+            return getListingJson(app, sc, headers, uriInfo);
+        }
+    }
+
+    @GET
+    @Produces({MediaType.APPLICATION_JSON})
+    @Path("/swagger")
+    @ApiOperation(value = "The swagger definition in JSON", hidden = true)
+    public Response getListingJson(
+            @Context Application app,
+            @Context WebConfig sc,
+            @Context HttpHeaders headers,
+            @Context UriInfo uriInfo) {
+        Swagger swagger = process(app, sc, headers, uriInfo);
+
+        if (swagger != null) {
+            return Response.ok().entity(swagger).build();
+        } else {
+            return Response.status(404).build();
+        }
+    }
+
+    @GET
+    @Produces("application/yaml")
+    @Path("/swagger")
+    @ApiOperation(value = "The swagger definition in YAML", hidden = true)
+    public Response getListingYaml(
+            @Context Application app,
+            @Context WebConfig sc,
+            @Context HttpHeaders headers,
+            @Context UriInfo uriInfo) {
+        Swagger swagger = process(app, sc, headers, uriInfo);
+        try {
+            if (swagger != null) {
+                String yaml = Yaml.mapper().writeValueAsString(swagger);
+                StringBuilder b = new StringBuilder();
+                String[] parts = yaml.split("\n");
+                for (String part : parts) {
+                    b.append(part);
+                    b.append("\n");
+                }
+                return Response.ok().entity(b.toString()).type("application/yaml").build();
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return Response.status(404).build();
+    }
+
+    protected Map<String, List<String>> getQueryParams(MultivaluedMap<String, String> params) {
+        Map<String, List<String>> output = new HashMap<>();
+        if (params != null) {
+            for (String key : params.keySet()) {
+                List<String> values = params.get(key);
+                output.put(key, values);
+            }
+        }
+        return output;
+    }
+
+    protected Map<String, String> getCookies(HttpHeaders headers) {
+        Map<String, String> output = new HashMap<>();
+        if (headers != null) {
+            for (String key : headers.getCookies().keySet()) {
+                Cookie cookie = headers.getCookies().get(key);
+                output.put(key, cookie.getValue());
+            }
+        }
+        return output;
+    }
+
+    protected Map<String, List<String>> getHeaders(HttpHeaders headers) {
+        Map<String, List<String>> output = new HashMap<>();
+        if (headers != null) {
+            for (String key : headers.getRequestHeaders().keySet()) {
+                List<String> values = headers.getRequestHeaders().get(key);
+                output.put(key, values);
+            }
+        }
+        return output;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/resources/ApidocResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/resources/ApidocResource.java b/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/resources/ApidocResource.java
new file mode 100644
index 0000000..1cf6523
--- /dev/null
+++ b/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/resources/ApidocResource.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+
+import javax.ws.rs.Path;
+
+import io.swagger.annotations.Api;
+
+/**
+ * @author Ciprian Ciubotariu <ch...@gmx.net>
+ */
+@Api("API Documentation")
+@Path("/apidoc")
+public class ApidocResource extends ApiListingResource {
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/util/FormMapProvider.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/util/FormMapProvider.java b/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/util/FormMapProvider.java
new file mode 100644
index 0000000..2b5c19b
--- /dev/null
+++ b/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/util/FormMapProvider.java
@@ -0,0 +1,81 @@
+/*
+ * 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 java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.List;
+import java.util.Map;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyReader;
+import javax.ws.rs.ext.Provider;
+
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.sun.jersey.core.impl.provider.entity.FormMultivaluedMapProvider;
+import com.sun.jersey.core.util.MultivaluedMapImpl;
+
+/**
+ * A MessageBodyReader producing a <code>Map&lt;String, Object&gt;</code>, where Object
+ * is either a <code>String</code>, a <code>List&lt;String&gt;</code> or null.
+ */
+@Provider
+@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+public class FormMapProvider implements MessageBodyReader<Map<String, Object>> {
+
+    @Override
+    public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
+        if (!Map.class.equals(type) || !(genericType instanceof ParameterizedType)) {
+            return false;
+        }
+        ParameterizedType parameterized = (ParameterizedType) genericType;
+        return parameterized.getActualTypeArguments().length == 2 &&
+                parameterized.getActualTypeArguments()[0] == String.class &&
+                parameterized.getActualTypeArguments()[1] == Object.class;
+    }
+
+    @Override
+    public Map<String, Object> readFrom(Class<Map<String, Object>> type, Type genericType, Annotation[] annotations,
+            MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream)
+            throws IOException, WebApplicationException {
+        FormMultivaluedMapProvider delegate = new FormMultivaluedMapProvider();
+        MultivaluedMap<String, String> multi = new MultivaluedMapImpl();
+        multi = delegate.readFrom(multi, mediaType, entityStream);
+
+        Map<String, Object> map = Maps.newHashMapWithExpectedSize(multi.keySet().size());
+        for (String key : multi.keySet()) {
+            List<String> value = multi.get(key);
+            if (value.size() > 1) {
+                map.put(key, Lists.newArrayList(value));
+            } else if (value.size() == 1) {
+                map.put(key, Iterables.getOnlyElement(value));
+            } else {
+                map.put(key, null);
+            }
+        }
+        return map;
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/main/resources/build-metadata.properties
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/main/resources/build-metadata.properties b/rest/rest-server-jersey/src/main/resources/build-metadata.properties
new file mode 100644
index 0000000..eab85ef
--- /dev/null
+++ b/rest/rest-server-jersey/src/main/resources/build-metadata.properties
@@ -0,0 +1,18 @@
+# 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.
+git-sha-1 = ${buildNumber}
+git-branch-name = ${scmBranch}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/main/webapp/WEB-INF/web.xml
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/main/webapp/WEB-INF/web.xml b/rest/rest-server-jersey/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..a7f99f4
--- /dev/null
+++ b/rest/rest-server-jersey/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,144 @@
+<!DOCTYPE web-app PUBLIC
+        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
+        "http://java.sun.com/dtd/web-app_2_3.dtd" >
+<!--
+    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.
+-->
+<web-app>
+    <display-name>Brooklyn REST API v1</display-name>
+
+    <filter>
+        <filter-name>Brooklyn Request Tagging Filter</filter-name>
+        <filter-class>org.apache.brooklyn.rest.filter.RequestTaggingFilter</filter-class>
+    </filter>
+    <filter-mapping>
+        <filter-name>Brooklyn Request Tagging Filter</filter-name>
+        <url-pattern>/*</url-pattern>
+    </filter-mapping>
+
+    <filter>
+        <filter-name>Brooklyn Properties Authentication Filter</filter-name>
+        <filter-class>org.apache.brooklyn.rest.filter.BrooklynPropertiesSecurityFilter</filter-class>
+    </filter>
+    <filter-mapping>
+        <filter-name>Brooklyn Properties Authentication Filter</filter-name>
+        <url-pattern>/*</url-pattern>
+    </filter-mapping>
+
+    <filter>
+        <filter-name>Brooklyn Logging Filter</filter-name>
+        <filter-class>org.apache.brooklyn.rest.filter.LoggingFilter</filter-class>
+    </filter>
+    <filter-mapping>
+        <filter-name>Brooklyn Logging Filter</filter-name>
+        <url-pattern>/*</url-pattern>
+    </filter-mapping>
+
+    <filter>
+        <filter-name>Brooklyn HA Master Filter</filter-name>
+        <filter-class>org.apache.brooklyn.rest.filter.HaMasterCheckFilter</filter-class>
+    </filter>
+    <filter-mapping>
+        <filter-name>Brooklyn HA Master Filter</filter-name>
+        <url-pattern>/*</url-pattern>
+    </filter-mapping>
+
+    <filter>
+        <filter-name>Brooklyn Swagger Bootstrap</filter-name>
+        <filter-class>org.apache.brooklyn.rest.filter.SwaggerFilter</filter-class>
+    </filter>
+    <filter-mapping>
+        <filter-name>Brooklyn Swagger Bootstrap</filter-name>
+        <url-pattern>/*</url-pattern>
+    </filter-mapping>
+
+    <!-- Brooklyn REST is usually run as a filter so static content can be placed in a webapp
+         to which this is added; to run as a servlet directly, replace the filter tags 
+         below (after the comment) with the servlet tags (commented out immediately below),
+         (and do the same for the matching tags at the bottom)
+        <servlet>
+            <servlet-name>Brooklyn REST API v1 Servlet</servlet-name>
+            <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
+     -->
+    <filter>
+        <filter-name>Brooklyn REST API v1 Filter</filter-name>
+        <filter-class>com.sun.jersey.spi.container.servlet.ServletContainer</filter-class>
+
+        <!-- load our REST API jersey resources explicitly 
+            (the package scanner will only pick up classes with @Path annotations - doesn't look at implemented interfaces) 
+        -->
+        <init-param>
+            <param-name>com.sun.jersey.config.property.resourceConfigClass</param-name>
+            <param-value>com.sun.jersey.api.core.ClassNamesResourceConfig</param-value>
+        </init-param>
+        <init-param>
+            <param-name>com.sun.jersey.config.property.classnames</param-name>
+            <param-value>
+                io.swagger.jaxrs.listing.SwaggerSerializers;
+                org.apache.brooklyn.rest.util.FormMapProvider;
+                com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
+                org.apache.brooklyn.rest.resources.AccessResource;
+                org.apache.brooklyn.rest.resources.ActivityResource;
+                org.apache.brooklyn.rest.resources.ApidocResource;
+                org.apache.brooklyn.rest.resources.ApplicationResource;
+                org.apache.brooklyn.rest.resources.CatalogResource;
+                org.apache.brooklyn.rest.resources.EffectorResource;
+                org.apache.brooklyn.rest.resources.EntityConfigResource;
+                org.apache.brooklyn.rest.resources.EntityResource;
+                org.apache.brooklyn.rest.resources.LocationResource;
+                org.apache.brooklyn.rest.resources.PolicyConfigResource;
+                org.apache.brooklyn.rest.resources.PolicyResource;
+                org.apache.brooklyn.rest.resources.ScriptResource;
+                org.apache.brooklyn.rest.resources.SensorResource;
+                org.apache.brooklyn.rest.resources.ServerResource;
+                org.apache.brooklyn.rest.resources.UsageResource;
+                org.apache.brooklyn.rest.resources.VersionResource;
+            </param-value>
+        </init-param>
+
+        <init-param>
+            <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
+            <param-value>true</param-value>
+        </init-param>
+
+        <!-- no need for WADL. of course you can turn it back on it you want. -->
+        <init-param>
+            <param-name>com.sun.jersey.config.feature.DisableWADL</param-name>
+            <param-value>true</param-value>
+        </init-param>
+
+        <init-param>
+            <param-name>com.sun.jersey.config.feature.FilterContextPath</param-name>
+            <param-value>/v1</param-value>
+        </init-param>
+
+    </filter>
+    <filter-mapping>
+        <filter-name>Brooklyn REST API v1 Filter</filter-name>
+        <url-pattern>/v1/*</url-pattern>
+    </filter-mapping>
+    <!-- Brooklyn REST as a filter above; replace above 5 lines with those commented out below,
+         to run it as a servlet (see note above) 
+            <load-on-startup>1</load-on-startup>
+        </servlet>
+        <servlet-mapping>
+            <servlet-name>Brooklyn REST API v1 Servlet</servlet-name>
+            <url-pattern>/*</url-pattern>
+        </servlet-mapping>
+    -->
+</web-app>

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/BrooklynPropertiesSecurityFilterTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/BrooklynPropertiesSecurityFilterTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/BrooklynPropertiesSecurityFilterTest.java
new file mode 100644
index 0000000..e855841
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/BrooklynPropertiesSecurityFilterTest.java
@@ -0,0 +1,151 @@
+/*
+ * 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;
+
+import static org.testng.Assert.assertTrue;
+
+import java.net.URI;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.http.HttpHeaders;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.utils.URLEncodedUtils;
+import org.apache.http.entity.ContentType;
+import org.apache.http.message.BasicNameValuePair;
+import org.eclipse.jetty.server.Server;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.Test;
+import org.apache.brooklyn.rest.security.provider.AnyoneSecurityProvider;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.http.HttpTool;
+import org.apache.brooklyn.util.http.HttpToolResponse;
+import org.apache.brooklyn.util.time.Time;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.base.Charsets;
+import com.google.common.base.Stopwatch;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+
+public class BrooklynPropertiesSecurityFilterTest extends BrooklynRestApiLauncherTestFixture {
+
+    private static final Logger LOG = LoggerFactory.getLogger(BrooklynPropertiesSecurityFilterTest.class);
+
+    /*
+        Exception java.lang.AssertionError
+        
+        Message: error creating app. response code=400 expected [true] but found [false]
+        Stacktrace:
+        
+        
+        at org.testng.Assert.fail(Assert.java:94)
+        at org.testng.Assert.failNotEquals(Assert.java:494)
+        at org.testng.Assert.assertTrue(Assert.java:42)
+        at org.apache.brooklyn.rest.BrooklynPropertiesSecurityFilterTest.startAppAtNode(BrooklynPropertiesSecurityFilterTest.java:94)
+        at org.apache.brooklyn.rest.BrooklynPropertiesSecurityFilterTest.testInteractionOfSecurityFilterAndFormMapProvider(BrooklynPropertiesSecurityFilterTest.java:64)
+        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
+        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
+        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
+        at java.lang.reflect.Method.invoke(Method.java:606)
+        at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:84)
+        at org.testng.internal.Invoker.invokeMethod(Invoker.java:714)
+        at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:901)
+        at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1231)
+        at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:127)
+        at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:111)
+        at org.testng.TestRunner.privateRun(TestRunner.java:767)
+        at org.testng.TestRunner.run(TestRunner.java:617)
+        at org.testng.SuiteRunner.runTest(SuiteRunner.java:348)
+        at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:343)
+        at org.testng.SuiteRunner.privateRun(SuiteRunner.java:305)
+        at org.testng.SuiteRunner.run(SuiteRunner.java:254)
+        at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
+        at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:86)
+        at org.testng.TestNG.runSuitesSequentially(TestNG.java:1224)
+        at org.testng.TestNG.runSuitesLocally(TestNG.java:1149)
+        at org.testng.TestNG.run(TestNG.java:1057)
+        at org.apache.maven.surefire.testng.TestNGExecutor.run(TestNGExecutor.java:115)
+        at org.apache.maven.surefire.testng.TestNGDirectoryTestSuite.executeMulti(TestNGDirectoryTestSuite.java:205)
+        at org.apache.maven.surefire.testng.TestNGDirectoryTestSuite.execute(TestNGDirectoryTestSuite.java:108)
+        at org.apache.maven.surefire.testng.TestNGProvider.invoke(TestNGProvider.java:111)
+        at org.apache.maven.surefire.booter.ForkedBooter.invokeProviderInSameClassLoader(ForkedBooter.java:203)
+        at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:155)
+        at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:103)
+     */
+    // Would be great for this to be a unit test but it takes almost ten seconds.
+    @Test(groups = {"Integration","Broken"})
+    public void testInteractionOfSecurityFilterAndFormMapProvider() throws Exception {
+        Stopwatch stopwatch = Stopwatch.createStarted();
+        try {
+            Server server = useServerForTest(BrooklynRestApiLauncher.launcher()
+                    .securityProvider(AnyoneSecurityProvider.class)
+                    .forceUseOfDefaultCatalogWithJavaClassPath(true)
+                    .withoutJsgui()
+                    .start());
+            String appId = startAppAtNode(server);
+            String entityId = getTestEntityInApp(server, appId);
+            HttpClient client = HttpTool.httpClientBuilder()
+                    .uri(getBaseUri(server))
+                    .build();
+            List<? extends NameValuePair> nvps = Lists.newArrayList(
+                    new BasicNameValuePair("arg", "bar"));
+            String effector = String.format("/v1/applications/%s/entities/%s/effectors/identityEffector", appId, entityId);
+            HttpToolResponse response = HttpTool.httpPost(client, URI.create(getBaseUri() + effector),
+                    ImmutableMap.of(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED.getMimeType()),
+                    URLEncodedUtils.format(nvps, Charsets.UTF_8).getBytes());
+
+            LOG.info("Effector response: {}", response.getContentAsString());
+            assertTrue(HttpTool.isStatusCodeHealthy(response.getResponseCode()), "response code=" + response.getResponseCode());
+        } finally {
+            LOG.info("testInteractionOfSecurityFilterAndFormMapProvider complete in " + Time.makeTimeStringRounded(stopwatch));
+        }
+    }
+
+    private String startAppAtNode(Server server) throws Exception {
+        String blueprint = "name: TestApp\n" +
+                "location: localhost\n" +
+                "services:\n" +
+                "- type: org.apache.brooklyn.test.entity.TestEntity";
+        HttpClient client = HttpTool.httpClientBuilder()
+                .uri(getBaseUri(server))
+                .build();
+        HttpToolResponse response = HttpTool.httpPost(client, URI.create(getBaseUri() + "/v1/applications"),
+                ImmutableMap.of(HttpHeaders.CONTENT_TYPE, "application/x-yaml"),
+                blueprint.getBytes());
+        assertTrue(HttpTool.isStatusCodeHealthy(response.getResponseCode()), "error creating app. response code=" + response.getResponseCode());
+        @SuppressWarnings("unchecked")
+        Map<String, Object> body = new ObjectMapper().readValue(response.getContent(), HashMap.class);
+        return (String) body.get("entityId");
+    }
+
+    @SuppressWarnings("rawtypes")
+    private String getTestEntityInApp(Server server, String appId) throws Exception {
+        HttpClient client = HttpTool.httpClientBuilder()
+                .uri(getBaseUri(server))
+                .build();
+        List entities = new ObjectMapper().readValue(
+                HttpTool.httpGet(client, URI.create(getBaseUri() + "/v1/applications/" + appId + "/entities"), MutableMap.<String, String>of()).getContent(), List.class);
+        LOG.info((String) ((Map) entities.get(0)).get("id"));
+        return (String) ((Map) entities.get(0)).get("id");
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncher.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncher.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncher.java
new file mode 100644
index 0000000..b47a591
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncher.java
@@ -0,0 +1,499 @@
+/*
+ * 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;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.EnumSet;
+import java.util.List;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.Filter;
+
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.camp.brooklyn.BrooklynCampPlatformLauncherAbstract;
+import org.apache.brooklyn.camp.brooklyn.BrooklynCampPlatformLauncherNoServer;
+import org.apache.brooklyn.core.internal.BrooklynProperties;
+import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
+import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
+import org.apache.brooklyn.core.server.BrooklynServerConfig;
+import org.apache.brooklyn.core.server.BrooklynServiceAttributes;
+import org.apache.brooklyn.rest.filter.BrooklynPropertiesSecurityFilter;
+import org.apache.brooklyn.rest.filter.HaMasterCheckFilter;
+import org.apache.brooklyn.rest.filter.LoggingFilter;
+import org.apache.brooklyn.rest.filter.NoCacheFilter;
+import org.apache.brooklyn.rest.filter.RequestTaggingFilter;
+import org.apache.brooklyn.rest.filter.SwaggerFilter;
+import org.apache.brooklyn.rest.security.provider.AnyoneSecurityProvider;
+import org.apache.brooklyn.rest.security.provider.SecurityProvider;
+import org.apache.brooklyn.rest.util.ManagementContextProvider;
+import org.apache.brooklyn.rest.util.NullServletConfigProvider;
+import org.apache.brooklyn.rest.util.ServerStoppingShutdownHandler;
+import org.apache.brooklyn.rest.util.ShutdownHandlerProvider;
+import org.apache.brooklyn.util.core.osgi.Compat;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.guava.Maybe;
+import org.apache.brooklyn.util.net.Networking;
+import org.apache.brooklyn.util.text.WildcardGlobs;
+import org.eclipse.jetty.server.NetworkConnector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.servlet.FilterHolder;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.reflections.util.ClasspathHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Charsets;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.io.Files;
+import com.sun.jersey.api.core.DefaultResourceConfig;
+import com.sun.jersey.api.core.ResourceConfig;
+import com.sun.jersey.spi.container.servlet.ServletContainer;
+
+/** Convenience and demo for launching programmatically. Also used for automated tests.
+ * <p>
+ * BrooklynLauncher has a more full-featured CLI way to start, 
+ * but if you want more control you can:
+ * <li> take the WAR this project builds (REST API) -- NB probably want the unshaded one (containing all deps)
+ * <li> take the WAR from the brooklyn-jsgui project (brooklyn-ui repo) _and_ this WAR and combine them
+ *      (this one should run as a filter on the others, _not_ as a ResourceCollection where they fight over who's got root)
+ * <li> programmatically install things, following the examples herein; 
+ *      in particular {@link #installAsServletFilter(ServletContextHandler)} is quite handy! 
+ * <p>
+ * You can also just run this class. In most installs it just works, assuming your IDE or maven-fu gives you the classpath.
+ * Add more apps and entities on the classpath and they'll show up in the catalog.
+ **/
+public class BrooklynRestApiLauncher {
+
+    private static final Logger log = LoggerFactory.getLogger(BrooklynRestApiLauncher.class);
+    final static int FAVOURITE_PORT = 8081;
+    public static final String SCANNING_CATALOG_BOM_URL = "classpath://brooklyn/scanning.catalog.bom";
+
+    enum StartMode {
+        FILTER, SERVLET, /** web-xml is not fully supported */ @Beta WEB_XML
+    }
+
+    public static final List<Class<? extends Filter>> DEFAULT_FILTERS = ImmutableList.of(
+            RequestTaggingFilter.class,
+            BrooklynPropertiesSecurityFilter.class,
+            LoggingFilter.class,
+            HaMasterCheckFilter.class,
+            SwaggerFilter.class);
+
+    private boolean forceUseOfDefaultCatalogWithJavaClassPath = false;
+    private Class<? extends SecurityProvider> securityProvider;
+    private List<Class<? extends Filter>> filters = DEFAULT_FILTERS;
+    private StartMode mode = StartMode.FILTER;
+    private ManagementContext mgmt;
+    private ContextHandler customContext;
+    private boolean deployJsgui = true;
+    private boolean disableHighAvailability = true;
+    private ServerStoppingShutdownHandler shutdownListener;
+
+    protected BrooklynRestApiLauncher() {}
+
+    public BrooklynRestApiLauncher managementContext(ManagementContext mgmt) {
+        this.mgmt = mgmt;
+        return this;
+    }
+
+    public BrooklynRestApiLauncher forceUseOfDefaultCatalogWithJavaClassPath(boolean forceUseOfDefaultCatalogWithJavaClassPath) {
+        this.forceUseOfDefaultCatalogWithJavaClassPath = forceUseOfDefaultCatalogWithJavaClassPath;
+        return this;
+    }
+
+    public BrooklynRestApiLauncher securityProvider(Class<? extends SecurityProvider> securityProvider) {
+        this.securityProvider = securityProvider;
+        return this;
+    }
+
+    /**
+     * Runs the server with the given set of filters. 
+     * Overrides any previously supplied set (or {@link #DEFAULT_FILTERS} which is used by default).
+     */
+    public BrooklynRestApiLauncher filters(@SuppressWarnings("unchecked") Class<? extends Filter>... filters) {
+        this.filters = Lists.newArrayList(filters);
+        return this;
+    }
+
+    public BrooklynRestApiLauncher mode(StartMode mode) {
+        this.mode = checkNotNull(mode, "mode");
+        return this;
+    }
+
+    /** Overrides start mode to use an explicit context */
+    public BrooklynRestApiLauncher customContext(ContextHandler customContext) {
+        this.customContext = checkNotNull(customContext, "customContext");
+        return this;
+    }
+
+    public BrooklynRestApiLauncher withJsgui() {
+        this.deployJsgui = true;
+        return this;
+    }
+
+    public BrooklynRestApiLauncher withoutJsgui() {
+        this.deployJsgui = false;
+        return this;
+    }
+
+    public BrooklynRestApiLauncher disableHighAvailability(boolean value) {
+        this.disableHighAvailability = value;
+        return this;
+    }
+
+    public Server start() {
+        if (this.mgmt == null) {
+            mgmt = new LocalManagementContext();
+        }
+        BrooklynCampPlatformLauncherAbstract platform = new BrooklynCampPlatformLauncherNoServer()
+                .useManagementContext(mgmt)
+                .launch();
+        ((LocalManagementContext)mgmt).noteStartupComplete();
+        log.debug("started "+platform);
+
+        ContextHandler context;
+        String summary;
+        if (customContext == null) {
+            switch (mode) {
+            case SERVLET:
+                context = servletContextHandler(mgmt);
+                summary = "programmatic Jersey ServletContainer servlet";
+                break;
+            case WEB_XML:
+                context = webXmlContextHandler(mgmt);
+                summary = "from WAR at " + ((WebAppContext) context).getWar();
+                break;
+            case FILTER:
+            default:
+                context = filterContextHandler(mgmt);
+                summary = "programmatic Jersey ServletContainer filter on webapp at " + ((WebAppContext) context).getWar();
+                break;
+            }
+        } else {
+            context = customContext;
+            summary = (context instanceof WebAppContext)
+                    ? "from WAR at " + ((WebAppContext) context).getWar()
+                    : "from custom context";
+        }
+
+        if (securityProvider != null) {
+            ((BrooklynProperties) mgmt.getConfig()).put(
+                    BrooklynWebConfig.SECURITY_PROVIDER_CLASSNAME, securityProvider.getName());
+        }
+
+        if (forceUseOfDefaultCatalogWithJavaClassPath) {
+            // sets URLs for a surefire
+            ((BrooklynProperties) mgmt.getConfig()).put(BrooklynServerConfig.BROOKLYN_CATALOG_URL, SCANNING_CATALOG_BOM_URL);
+            ((LocalManagementContext) mgmt).setBaseClassPathForScanning(ClasspathHelper.forJavaClassPath());
+        } else {
+            // don't use any catalog.xml which is set
+            ((BrooklynProperties) mgmt.getConfig()).put(BrooklynServerConfig.BROOKLYN_CATALOG_URL, ManagementContextInternal.EMPTY_CATALOG_URL);
+        }
+
+        Server server = startServer(mgmt, context, summary, disableHighAvailability);
+        if (shutdownListener!=null) {
+            // not available in some modes, eg webapp
+            shutdownListener.setServer(server);
+        }
+        return server;
+    }
+
+    private ContextHandler filterContextHandler(ManagementContext mgmt) {
+        WebAppContext context = new WebAppContext();
+        context.setAttribute(BrooklynServiceAttributes.BROOKLYN_MANAGEMENT_CONTEXT, mgmt);
+        context.setContextPath("/");
+        installWar(context);
+        installAsServletFilter(context, this.filters);
+        return context;
+    }
+
+    private void installWar(WebAppContext context) {
+        // here we run with the JS GUI, for convenience, if we can find it, else set up an empty dir
+        // TODO pretty sure there is an option to monitor this dir and load changes to static content
+        // NOTE: When running Brooklyn from an IDE (i.e. by launching BrooklynJavascriptGuiLauncher.main())
+        // you will need to ensure that the working directory is set to the brooklyn-ui repo folder. For IntelliJ,
+        // set the 'Working directory' of the Run/Debug Configuration to $MODULE_DIR$/brooklyn-server/launcher.
+        // For Eclipse, use the default option of ${workspace_loc:brooklyn-launcher}.
+        // If the working directory is not set correctly, Brooklyn will be unable to find the jsgui .war
+        // file and the 'gui not available' message will be shown.
+        context.setWar(this.deployJsgui && findJsguiWebappInSource().isPresent()
+                       ? findJsguiWebappInSource().get()
+                       : createTempWebDirWithIndexHtml("Brooklyn REST API <p> (gui not available)"));
+    }
+
+    private ContextHandler servletContextHandler(ManagementContext managementContext) {
+        ResourceConfig config = new DefaultResourceConfig();
+        for (Object r: BrooklynRestApi.getAllResources())
+            config.getSingletons().add(r);
+        config.getSingletons().add(new ManagementContextProvider(mgmt));
+        addShutdownListener(config, mgmt);
+
+
+        WebAppContext context = new WebAppContext();
+        context.setAttribute(BrooklynServiceAttributes.BROOKLYN_MANAGEMENT_CONTEXT, managementContext);
+        ServletHolder servletHolder = new ServletHolder(new ServletContainer(config));
+        context.addServlet(servletHolder, "/v1/*");
+        context.setContextPath("/");
+
+        installWar(context);
+        installBrooklynFilters(context, this.filters);
+        return context;
+    }
+
+    /** NB: not fully supported; use one of the other {@link StartMode}s */
+    private ContextHandler webXmlContextHandler(ManagementContext mgmt) {
+        // TODO add security to web.xml
+        WebAppContext context;
+        if (findMatchingFile("src/main/webapp")!=null) {
+            // running in source mode; need to use special classpath
+            context = new WebAppContext("src/main/webapp", "/");
+            context.setExtraClasspath("./target/classes");
+        } else if (findRestApiWar()!=null) {
+            context = new WebAppContext(findRestApiWar(), "/");
+        } else {
+            throw new IllegalStateException("Cannot find WAR for REST API. Expected in target/*.war, Maven repo, or in source directories.");
+        }
+        context.setAttribute(BrooklynServiceAttributes.BROOKLYN_MANAGEMENT_CONTEXT, mgmt);
+        // TODO shutdown hook
+        
+        return context;
+    }
+
+    /** starts a server, on all NICs if security is configured,
+     * otherwise (no security) only on loopback interface 
+     * @deprecated since 0.9.0 becoming private */
+    @Deprecated
+    public static Server startServer(ManagementContext mgmt, ContextHandler context, String summary, boolean disableHighAvailability) {
+        // TODO this repeats code in BrooklynLauncher / WebServer. should merge the two paths.
+        boolean secure = mgmt != null && !BrooklynWebConfig.hasNoSecurityOptions(mgmt.getConfig());
+        if (secure) {
+            log.debug("Detected security configured, launching server on all network interfaces");
+        } else {
+            log.debug("Detected no security configured, launching server on loopback (localhost) network interface only");
+            if (mgmt!=null) {
+                log.debug("Detected no security configured, running on loopback; disabling authentication");
+                ((BrooklynProperties)mgmt.getConfig()).put(BrooklynWebConfig.SECURITY_PROVIDER_CLASSNAME, AnyoneSecurityProvider.class.getName());
+            }
+        }
+        if (mgmt != null && disableHighAvailability)
+            mgmt.getHighAvailabilityManager().disabled();
+        InetSocketAddress bindLocation = new InetSocketAddress(
+                secure ? Networking.ANY_NIC : Networking.LOOPBACK,
+                        Networking.nextAvailablePort(FAVOURITE_PORT));
+        return startServer(context, summary, bindLocation);
+    }
+
+    /** @deprecated since 0.9.0 becoming private */
+    @Deprecated
+    public static Server startServer(ContextHandler context, String summary, InetSocketAddress bindLocation) {
+        Server server = new Server(bindLocation);
+        
+        server.setHandler(context);
+        try {
+            server.start();
+        } catch (Exception e) {
+            throw Exceptions.propagate(e);
+        }
+        log.info("Brooklyn REST server started ("+summary+") on");
+        log.info("  http://localhost:"+((NetworkConnector)server.getConnectors()[0]).getLocalPort()+"/");
+
+        return server;
+    }
+
+    public static BrooklynRestApiLauncher launcher() {
+        return new BrooklynRestApiLauncher();
+    }
+
+    public static void main(String[] args) throws Exception {
+        startRestResourcesViaFilter();
+        log.info("Press Ctrl-C to quit.");
+    }
+
+    public static Server startRestResourcesViaFilter() {
+        return new BrooklynRestApiLauncher()
+                .mode(StartMode.FILTER)
+                .start();
+    }
+
+    public static Server startRestResourcesViaServlet() throws Exception {
+        return new BrooklynRestApiLauncher()
+                .mode(StartMode.SERVLET)
+                .start();
+    }
+
+    public static Server startRestResourcesViaWebXml() throws Exception {
+        return new BrooklynRestApiLauncher()
+                .mode(StartMode.WEB_XML)
+                .start();
+    }
+
+    public void installAsServletFilter(ServletContextHandler context) {
+        installAsServletFilter(context, DEFAULT_FILTERS);
+    }
+
+    private void installAsServletFilter(ServletContextHandler context, List<Class<? extends Filter>> filters) {
+        installBrooklynFilters(context, filters);
+
+        // now set up the REST servlet resources
+        ResourceConfig config = new DefaultResourceConfig();
+        // load all our REST API modules, JSON, and Swagger
+        for (Object r: BrooklynRestApi.getAllResources())
+            config.getSingletons().add(r);
+
+        // disable caching for dynamic content
+        config.getProperties().put(ResourceConfig.PROPERTY_CONTAINER_RESPONSE_FILTERS, NoCacheFilter.class.getName());
+        // Checks if appropriate request given HA status
+        config.getProperties().put(ResourceConfig.PROPERTY_RESOURCE_FILTER_FACTORIES, org.apache.brooklyn.rest.filter.HaHotCheckResourceFilter.class.getName());
+        // configure to match empty path, or any thing which looks like a file path with /assets/ and extension html, css, js, or png
+        // and treat that as static content
+        config.getProperties().put(ServletContainer.PROPERTY_WEB_PAGE_CONTENT_REGEX, "(/?|[^?]*/assets/[^?]+\\.[A-Za-z0-9_]+)");
+        // and anything which is not matched as a servlet also falls through (but more expensive than a regex check?)
+        config.getFeatures().put(ServletContainer.FEATURE_FILTER_FORWARD_ON_404, true);
+        // finally create this as a _filter_ which falls through to a web app or something (optionally)
+        FilterHolder filterHolder = new FilterHolder(new ServletContainer(config));
+        // Let the filter know the context path where it lives
+        filterHolder.setInitParameter(ServletContainer.PROPERTY_FILTER_CONTEXT_PATH, "/v1");
+        context.addFilter(filterHolder, "/v1/*", EnumSet.allOf(DispatcherType.class));
+
+        ManagementContext mgmt = getManagementContext(context);
+        config.getSingletons().add(new ManagementContextProvider(mgmt));
+        addShutdownListener(config, mgmt);
+    }
+
+    protected synchronized void addShutdownListener(ResourceConfig config, ManagementContext mgmt) {
+        if (shutdownListener!=null) throw new IllegalStateException("Can only retrieve one shutdown listener");
+        shutdownListener = new ServerStoppingShutdownHandler(mgmt);
+        config.getSingletons().add(new ShutdownHandlerProvider(shutdownListener));
+    }
+
+    private static void installBrooklynFilters(ServletContextHandler context, List<Class<? extends Filter>> filters) {
+        for (Class<? extends Filter> filter : filters) {
+            context.addFilter(filter, "/*", EnumSet.allOf(DispatcherType.class));
+        }
+    }
+
+    /**
+     * Starts the server on all nics (even if security not enabled).
+     * @deprecated since 0.6.0; use {@link #launcher()} and set a custom context
+     */
+    @Deprecated
+    public static Server startServer(ContextHandler context, String summary) {
+        return BrooklynRestApiLauncher.startServer(context, summary,
+                new InetSocketAddress(Networking.ANY_NIC, Networking.nextAvailablePort(FAVOURITE_PORT)));
+    }
+
+    /** look for the JS GUI webapp in common source places, returning path to it if found, or null.
+     * assumes `brooklyn-ui` is checked out as a sibling to `brooklyn-server`, and both are 2, 3, 1, or 0
+     * levels above the CWD. */
+    @Beta
+    public static Maybe<String> findJsguiWebappInSource() {
+    	// normally up 2 levels to where brooklyn-* folders are, then into ui
+    	// (but in rest projects it might be 3 up, and in some IDEs we might run from parent dirs.)
+        // TODO could also look in maven repo ?
+    	return findFirstMatchingFile(
+    			"../../brooklyn-ui/src/main/webapp",
+    			"../../../brooklyn-ui/src/main/webapp",
+    			"../brooklyn-ui/src/main/webapp",
+    			"./brooklyn-ui/src/main/webapp",
+    			"../../brooklyn-ui/target/*.war",
+    			"../../..brooklyn-ui/target/*.war",
+    			"../brooklyn-ui/target/*.war",
+    			"./brooklyn-ui/target/*.war");
+    }
+
+    /** look for the REST WAR file in common places, returning path to it if found, or null */
+    private static String findRestApiWar() {
+        // don't look at src/main/webapp here -- because classes won't be there!
+        // could also look in maven repo ?
+    	// TODO looks like this stopped working at runtime a long time ago;
+    	// only needed for WEB_XML mode, and not used, but should remove or check?
+    	// (probably will be superseded by CXF/OSGi work however)
+        return findMatchingFile("../rest/target/*.war").orNull();
+    }
+
+    /** as {@link #findMatchingFile(String)} but finding the first */
+    public static Maybe<String> findFirstMatchingFile(String ...filenames) {
+    	for (String f: filenames) {
+    		Maybe<String> result = findMatchingFile(f);
+    		if (result.isPresent()) return result;
+    	}
+    	return Maybe.absent();
+    }
+    
+    /** returns the supplied filename if it exists (absolute or relative to the current directory);
+     * supports globs in the filename portion only, in which case it returns the _newest_ matching file.
+     * <p>
+     * otherwise returns null */
+    @Beta // public because used in dependent test projects
+    public static Maybe<String> findMatchingFile(String filename) {
+        final File f = new File(filename);
+        if (f.exists()) return Maybe.of(filename);
+        File dir = f.getParentFile();
+        File result = null;
+        if (dir.exists()) {
+            File[] matchingFiles = dir.listFiles(new FilenameFilter() {
+                @Override
+                public boolean accept(File dir, String name) {
+                    return WildcardGlobs.isGlobMatched(f.getName(), name);
+                }
+            });
+            for (File mf: matchingFiles) {
+                if (result==null || mf.lastModified() > result.lastModified()) result = mf;
+            }
+        }
+        if (result==null) return Maybe.absent();
+        return Maybe.of(result.getAbsolutePath());
+    }
+
+    /** create a directory with a simple index.html so we have some content being served up */
+    private static String createTempWebDirWithIndexHtml(String indexHtmlContent) {
+        File dir = Files.createTempDir();
+        dir.deleteOnExit();
+        try {
+            Files.write(indexHtmlContent, new File(dir, "index.html"), Charsets.UTF_8);
+        } catch (IOException e) {
+            Exceptions.propagate(e);
+        }
+        return dir.getAbsolutePath();
+    }
+    
+    /**
+     * Compatibility methods between karaf launcher and monolithic launcher.
+     *
+     * @todo Remove after transition to karaf launcher.
+     */
+    static ManagementContext getManagementContext(ContextHandler jettyServerHandler) {
+        ManagementContext managementContext = Compat.getInstance().getManagementContext();
+        if (managementContext == null && jettyServerHandler != null) {
+            managementContext = (ManagementContext) jettyServerHandler.getAttribute(BrooklynServiceAttributes.BROOKLYN_MANAGEMENT_CONTEXT);
+        }
+        return managementContext;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTest.java
new file mode 100644
index 0000000..1bf756d
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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;
+
+import static org.apache.brooklyn.rest.BrooklynRestApiLauncher.StartMode.FILTER;
+import static org.apache.brooklyn.rest.BrooklynRestApiLauncher.StartMode.SERVLET;
+import static org.apache.brooklyn.rest.BrooklynRestApiLauncher.StartMode.WEB_XML;
+
+import java.util.concurrent.Callable;
+
+import org.apache.brooklyn.entity.brooklynnode.BrooklynNode;
+import org.apache.brooklyn.rest.security.provider.AnyoneSecurityProvider;
+import org.apache.brooklyn.test.Asserts;
+import org.apache.brooklyn.util.http.HttpAsserts;
+import org.apache.brooklyn.util.http.HttpTool;
+import org.apache.http.HttpStatus;
+import org.eclipse.jetty.server.NetworkConnector;
+import org.eclipse.jetty.server.Server;
+import org.testng.annotations.Test;
+
+public class BrooklynRestApiLauncherTest extends BrooklynRestApiLauncherTestFixture {
+
+    @Test
+    public void testFilterStart() throws Exception {
+        checkRestCatalogEntities(useServerForTest(baseLauncher().mode(FILTER).start()));
+    }
+
+    @Test
+    public void testServletStart() throws Exception {
+        checkRestCatalogEntities(useServerForTest(baseLauncher().mode(SERVLET).start()));
+    }
+
+    @Test
+    public void testWebAppStart() throws Exception {
+        checkRestCatalogEntities(useServerForTest(baseLauncher().mode(WEB_XML).start()));
+    }
+
+    private BrooklynRestApiLauncher baseLauncher() {
+        return BrooklynRestApiLauncher.launcher()
+                .securityProvider(AnyoneSecurityProvider.class)
+                .forceUseOfDefaultCatalogWithJavaClassPath(true);
+    }
+    
+    private static void checkRestCatalogEntities(Server server) throws Exception {
+        final String rootUrl = "http://localhost:"+((NetworkConnector)server.getConnectors()[0]).getLocalPort();
+        int code = Asserts.succeedsEventually(new Callable<Integer>() {
+            @Override
+            public Integer call() throws Exception {
+                int code = HttpTool.getHttpStatusCode(rootUrl+"/v1/catalog/entities");
+                if (code == HttpStatus.SC_FORBIDDEN) {
+                    throw new RuntimeException("Retry request");
+                } else {
+                    return code;
+                }
+            }
+        });
+        HttpAsserts.assertHealthyStatusCode(code);
+        HttpAsserts.assertContentContainsText(rootUrl+"/v1/catalog/entities", BrooklynNode.class.getSimpleName());
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTestFixture.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTestFixture.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTestFixture.java
new file mode 100644
index 0000000..c894f3e
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTestFixture.java
@@ -0,0 +1,109 @@
+/*
+ * 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;
+
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.internal.BrooklynProperties;
+import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
+import org.apache.brooklyn.core.server.BrooklynServerConfig;
+import org.apache.brooklyn.rest.security.provider.AnyoneSecurityProvider;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.eclipse.jetty.server.NetworkConnector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.reflections.util.ClasspathHelper;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+
+public abstract class BrooklynRestApiLauncherTestFixture {
+
+    Server server = null;
+    
+    @AfterMethod(alwaysRun=true)
+    public void stopServer() throws Exception {
+        if (server!=null) {
+            ManagementContext mgmt = getManagementContextFromJettyServerAttributes(server);
+            server.stop();
+            if (mgmt!=null) Entities.destroyAll(mgmt);
+            server = null;
+        }
+    }
+    
+    protected Server newServer() {
+        try {
+            Server server = BrooklynRestApiLauncher.launcher()
+                    .forceUseOfDefaultCatalogWithJavaClassPath(true)
+                    .securityProvider(AnyoneSecurityProvider.class)
+                    .start();
+            return server;
+        } catch (Exception e) {
+            throw Exceptions.propagate(e);
+        }
+    }
+    
+    protected Server useServerForTest(Server server) {
+        if (this.server!=null) {
+            Assert.fail("Test only meant for single server; already have "+this.server+" when checking "+server);
+        } else {
+            this.server = server;
+        }
+        return server;
+    }
+    
+    protected String getBaseUri() {
+        return getBaseUri(server);
+    }
+    public static String getBaseUri(Server server) {
+        return "http://localhost:"+((NetworkConnector)server.getConnectors()[0]).getLocalPort();
+    }
+    
+    public static void forceUseOfDefaultCatalogWithJavaClassPath(Server server) {
+        ManagementContext mgmt = getManagementContextFromJettyServerAttributes(server);
+        forceUseOfDefaultCatalogWithJavaClassPath(mgmt);
+    }
+
+    public static void forceUseOfDefaultCatalogWithJavaClassPath(ManagementContext manager) {
+        // TODO duplication with BrooklynRestApiLauncher ?
+        
+        // don't use any catalog.xml which is set
+        ((BrooklynProperties)manager.getConfig()).put(BrooklynServerConfig.BROOKLYN_CATALOG_URL, BrooklynRestApiLauncher.SCANNING_CATALOG_BOM_URL);
+        // sets URLs for a surefire
+        ((LocalManagementContext)manager).setBaseClassPathForScanning(ClasspathHelper.forJavaClassPath());
+        // this also works
+//        ((LocalManagementContext)manager).setBaseClassPathForScanning(ClasspathHelper.forPackage("brooklyn"));
+        // but this (near-default behaviour) does not
+//        ((LocalManagementContext)manager).setBaseClassLoader(getClass().getClassLoader());
+    }
+
+    public static void enableAnyoneLogin(Server server) {
+        ManagementContext mgmt = getManagementContextFromJettyServerAttributes(server);
+        enableAnyoneLogin(mgmt);
+    }
+
+    public static void enableAnyoneLogin(ManagementContext mgmt) {
+        ((BrooklynProperties)mgmt.getConfig()).put(BrooklynWebConfig.SECURITY_PROVIDER_CLASSNAME, 
+                AnyoneSecurityProvider.class.getName());
+    }
+
+    public static ManagementContext getManagementContextFromJettyServerAttributes(Server server) {
+        return BrooklynRestApiLauncher.getManagementContext((ContextHandler) server.getHandler());
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/HaHotCheckTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/HaHotCheckTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/HaHotCheckTest.java
new file mode 100644
index 0000000..73dfe5f
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/HaHotCheckTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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;
+
+import static org.testng.Assert.assertEquals;
+
+import javax.ws.rs.core.MediaType;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import org.apache.brooklyn.api.mgmt.ha.HighAvailabilityManager;
+import org.apache.brooklyn.api.mgmt.ha.HighAvailabilityMode;
+import org.apache.brooklyn.api.mgmt.ha.ManagementNodeState;
+import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
+import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
+import org.apache.brooklyn.rest.filter.HaHotCheckResourceFilter;
+import org.apache.brooklyn.rest.filter.HaMasterCheckFilter;
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+import org.apache.brooklyn.rest.util.HaHotStateCheckClassResource;
+import org.apache.brooklyn.rest.util.HaHotStateCheckResource;
+import org.apache.brooklyn.rest.util.ManagementContextProvider;
+
+import com.sun.jersey.api.client.ClientResponse;
+import com.sun.jersey.api.client.WebResource.Builder;
+import com.sun.jersey.api.core.ResourceConfig;
+
+public class HaHotCheckTest extends BrooklynRestResourceTest {
+
+    // setup and teardown before/after each method
+    
+    @BeforeMethod(alwaysRun = true)
+    public void setUp() throws Exception { super.setUp(); }
+
+    @AfterMethod(alwaysRun = true)
+    public void tearDown() throws Exception { super.tearDown(); }
+    
+    @Override
+    protected void addBrooklynResources() {
+        config.getProperties().put(ResourceConfig.PROPERTY_RESOURCE_FILTER_FACTORIES, 
+            new HaHotCheckResourceFilter(new ManagementContextProvider(getManagementContext())));
+        addResource(new HaHotStateCheckResource());
+        addResource(new HaHotStateCheckClassResource());
+        
+        ((LocalManagementContext)getManagementContext()).noteStartupComplete();
+    }
+
+    @Test
+    public void testHaCheck() {
+        HighAvailabilityManager ha = getManagementContext().getHighAvailabilityManager();
+        assertEquals(ha.getNodeState(), ManagementNodeState.MASTER);
+        testResourceFetch("/v1/ha/method/ok", 200);
+        testResourceFetch("/v1/ha/method/fail", 200);
+        testResourceFetch("/v1/ha/class/fail", 200);
+
+        getManagementContext().getHighAvailabilityManager().changeMode(HighAvailabilityMode.STANDBY);
+        assertEquals(ha.getNodeState(), ManagementNodeState.STANDBY);
+
+        testResourceFetch("/v1/ha/method/ok", 200);
+        testResourceFetch("/v1/ha/method/fail", 403);
+        testResourceFetch("/v1/ha/class/fail", 403);
+
+        ((ManagementContextInternal)getManagementContext()).terminate();
+        assertEquals(ha.getNodeState(), ManagementNodeState.TERMINATED);
+
+        testResourceFetch("/v1/ha/method/ok", 200);
+        testResourceFetch("/v1/ha/method/fail", 403);
+        testResourceFetch("/v1/ha/class/fail", 403);
+    }
+
+    @Test
+    public void testHaCheckForce() {
+        HighAvailabilityManager ha = getManagementContext().getHighAvailabilityManager();
+        assertEquals(ha.getNodeState(), ManagementNodeState.MASTER);
+        testResourceForcedFetch("/v1/ha/method/ok", 200);
+        testResourceForcedFetch("/v1/ha/method/fail", 200);
+        testResourceForcedFetch("/v1/ha/class/fail", 200);
+
+        getManagementContext().getHighAvailabilityManager().changeMode(HighAvailabilityMode.STANDBY);
+        assertEquals(ha.getNodeState(), ManagementNodeState.STANDBY);
+
+        testResourceForcedFetch("/v1/ha/method/ok", 200);
+        testResourceForcedFetch("/v1/ha/method/fail", 200);
+        testResourceForcedFetch("/v1/ha/class/fail", 200);
+
+        ((ManagementContextInternal)getManagementContext()).terminate();
+        assertEquals(ha.getNodeState(), ManagementNodeState.TERMINATED);
+
+        testResourceForcedFetch("/v1/ha/method/ok", 200);
+        testResourceForcedFetch("/v1/ha/method/fail", 200);
+        testResourceForcedFetch("/v1/ha/class/fail", 200);
+    }
+
+
+    private void testResourceFetch(String resourcePath, int code) {
+        testResourceFetch(resourcePath, false, code);
+    }
+
+    private void testResourceForcedFetch(String resourcePath, int code) {
+        testResourceFetch(resourcePath, true, code);
+    }
+
+    private void testResourceFetch(String resourcePath, boolean force, int code) {
+        Builder resource = client().resource(resourcePath)
+                .accept(MediaType.APPLICATION_JSON_TYPE);
+        if (force) {
+            resource.header(HaHotCheckResourceFilter.SKIP_CHECK_HEADER, "true");
+        }
+        ClientResponse response = resource
+                .get(ClientResponse.class);
+        assertEquals(response.getStatus(), code);
+    }
+
+}


[02/34] brooklyn-server git commit: [BROOKLYN-183] REST API using CXF JAX-RS 2.0 implementation

Posted by he...@apache.org.
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/UsageResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/UsageResourceTest.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/UsageResourceTest.java
deleted file mode 100644
index 72392fe..0000000
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/UsageResourceTest.java
+++ /dev/null
@@ -1,443 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.rest.resources;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertTrue;
-
-import java.util.Arrays;
-import java.util.Calendar;
-import java.util.GregorianCalendar;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.Callable;
-import java.util.concurrent.TimeUnit;
-
-import javax.ws.rs.core.Response;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-import org.apache.brooklyn.api.entity.EntitySpec;
-import org.apache.brooklyn.api.location.Location;
-import org.apache.brooklyn.api.location.LocationSpec;
-import org.apache.brooklyn.api.location.NoMachinesAvailableException;
-import org.apache.brooklyn.core.mgmt.internal.LocalUsageManager;
-import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
-import org.apache.brooklyn.core.test.entity.TestApplication;
-import org.apache.brooklyn.entity.software.base.SoftwareProcessEntityTest;
-import org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation;
-import org.apache.brooklyn.location.ssh.SshMachineLocation;
-import org.apache.brooklyn.rest.domain.ApplicationSpec;
-import org.apache.brooklyn.rest.domain.Status;
-import org.apache.brooklyn.rest.domain.TaskSummary;
-import org.apache.brooklyn.rest.domain.UsageStatistic;
-import org.apache.brooklyn.rest.domain.UsageStatistics;
-import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
-import org.apache.brooklyn.rest.testing.mocks.RestMockSimpleEntity;
-import org.apache.brooklyn.util.repeat.Repeater;
-import org.apache.brooklyn.util.time.Time;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
-import com.sun.jersey.api.client.ClientResponse;
-import com.sun.jersey.api.client.GenericType;
-
-public class UsageResourceTest extends BrooklynRestResourceTest {
-
-    @SuppressWarnings("unused")
-    private static final Logger LOG = LoggerFactory.getLogger(UsageResourceTest.class);
-
-    private static final long TIMEOUT_MS = 10*1000;
-    
-    private Calendar testStartTime;
-    
-    private final ApplicationSpec simpleSpec = ApplicationSpec.builder().name("simple-app").
-            entities(ImmutableSet.of(new org.apache.brooklyn.rest.domain.EntitySpec("simple-ent", RestMockSimpleEntity.class.getName()))).
-            locations(ImmutableSet.of("localhost")).
-            build();
-
-    @BeforeMethod(alwaysRun=true)
-    public void setUpMethod() {
-        ((ManagementContextInternal)getManagementContext()).getStorage().remove(LocalUsageManager.APPLICATION_USAGE_KEY);
-        ((ManagementContextInternal)getManagementContext()).getStorage().remove(LocalUsageManager.LOCATION_USAGE_KEY);
-        testStartTime = new GregorianCalendar();
-    }
-
-    @Test
-    public void testListApplicationUsages() throws Exception {
-        // Create an app
-        Calendar preStart = new GregorianCalendar();
-        String appId = createApp(simpleSpec);
-        Calendar postStart = new GregorianCalendar();
-        
-        // We will retrieve usage from one millisecond after start; this guarantees to not be  
-        // told about both STARTING+RUNNING, which could otherwise happen if they are in the 
-        // same milliscond.
-        Calendar afterPostStart = Time.newCalendarFromMillisSinceEpochUtc(postStart.getTime().getTime()+1);
-        
-        // Check that app's usage is returned
-        ClientResponse response = client().resource("/v1/usage/applications").get(ClientResponse.class);
-        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
-        Iterable<UsageStatistics> usages = response.getEntity(new GenericType<List<UsageStatistics>>() {});
-        UsageStatistics usage = Iterables.getOnlyElement(usages);
-        assertAppUsage(usage, appId, ImmutableList.of(Status.STARTING, Status.RUNNING), roundDown(preStart), postStart);
-
-        // check app ignored if endCalendar before app started
-        response = client().resource("/v1/usage/applications?start="+0+"&end="+(preStart.getTime().getTime()-1)).get(ClientResponse.class);
-        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
-        usages = response.getEntity(new GenericType<List<UsageStatistics>>() {});
-        assertTrue(Iterables.isEmpty(usages), "usages="+usages);
-        
-        // Wait, so that definitely asking about things that have happened (not things in the future, 
-        // or events that are happening this exact same millisecond)
-        waitForFuture(afterPostStart.getTime().getTime());
-
-        // Check app start + end date truncated, even if running for longer (i.e. only tell us about this time window).
-        // Note that start==end means we get a snapshot of the apps in use at that exact time.
-        //
-        // The start/end times in UsageStatistic are in String format, and are rounded down to the nearest second.
-        // The comparison does use the milliseconds passed in the REST call though.
-        // The rounding down result should be the same as roundDown(afterPostStart), because that is the time-window
-        // we asked for.
-        response = client().resource("/v1/usage/applications?start="+afterPostStart.getTime().getTime()+"&end="+afterPostStart.getTime().getTime()).get(ClientResponse.class);
-        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
-        usages = response.getEntity(new GenericType<List<UsageStatistics>>() {});
-        usage = Iterables.getOnlyElement(usages);
-        assertAppUsage(usage, appId, ImmutableList.of(Status.RUNNING), roundDown(preStart), postStart);
-        assertAppUsageTimesTruncated(usage, roundDown(afterPostStart), roundDown(afterPostStart));
-
-        // Delete the app
-        Calendar preDelete = new GregorianCalendar();
-        deleteApp(appId);
-        Calendar postDelete = new GregorianCalendar();
-
-        // Deleted app still returned, if in time range
-        response = client().resource("/v1/usage/applications").get(ClientResponse.class);
-        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
-        usages = response.getEntity(new GenericType<List<UsageStatistics>>() {});
-        usage = Iterables.getOnlyElement(usages);
-        assertAppUsage(usage, appId, ImmutableList.of(Status.STARTING, Status.RUNNING, Status.DESTROYED), roundDown(preStart), postDelete);
-        assertAppUsage(ImmutableList.copyOf(usage.getStatistics()).subList(2, 3), appId, ImmutableList.of(Status.DESTROYED), roundDown(preDelete), postDelete);
-
-        long afterPostDelete = postDelete.getTime().getTime()+1;
-        waitForFuture(afterPostDelete);
-        
-        response = client().resource("/v1/usage/applications?start=" + afterPostDelete).get(ClientResponse.class);
-        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
-        usages = response.getEntity(new GenericType<List<UsageStatistics>>() {});
-        assertTrue(Iterables.isEmpty(usages), "usages="+usages);
-    }
-
-    @Test
-    public void testGetApplicationUsagesForNonExistantApp() throws Exception {
-        ClientResponse response = client().resource("/v1/usage/applications/wrongid").get(ClientResponse.class);
-        assertEquals(response.getStatus(), Response.Status.NOT_FOUND.getStatusCode());
-    }
-    
-    @Test
-    public void testGetApplicationUsage() throws Exception {
-        // Create an app
-        Calendar preStart = new GregorianCalendar();
-        String appId = createApp(simpleSpec);
-        Calendar postStart = new GregorianCalendar();
-        
-        // Normal request returns all
-        ClientResponse response = client().resource("/v1/usage/applications/" + appId).get(ClientResponse.class);
-        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
-        UsageStatistics usage = response.getEntity(new GenericType<UsageStatistics>() {});
-        assertAppUsage(usage, appId, ImmutableList.of(Status.STARTING, Status.RUNNING), roundDown(preStart), postStart);
-
-        // Time-constrained requests
-        response = client().resource("/v1/usage/applications/" + appId + "?start=1970-01-01T00:00:00-0100").get(ClientResponse.class);
-        usage = response.getEntity(new GenericType<UsageStatistics>() {});
-        assertAppUsage(usage, appId, ImmutableList.of(Status.STARTING, Status.RUNNING), roundDown(preStart), postStart);
-        
-        response = client().resource("/v1/usage/applications/" + appId + "?start=9999-01-01T00:00:00+0100").get(ClientResponse.class);
-        assertTrue(response.getStatus() >= 400, "end defaults to NOW, so future start should fail, instead got code "+response.getStatus());
-        
-        response = client().resource("/v1/usage/applications/" + appId + "?start=9999-01-01T00:00:00%2B0100&end=9999-01-02T00:00:00%2B0100").get(ClientResponse.class);
-        usage = response.getEntity(new GenericType<UsageStatistics>() {});
-        assertTrue(usage.getStatistics().isEmpty());
-
-        response = client().resource("/v1/usage/applications/" + appId + "?end=9999-01-01T00:00:00+0100").get(ClientResponse.class);
-        usage = response.getEntity(new GenericType<UsageStatistics>() {});
-        assertAppUsage(usage, appId, ImmutableList.of(Status.STARTING, Status.RUNNING), roundDown(preStart), postStart);
-
-        response = client().resource("/v1/usage/applications/" + appId + "?start=9999-01-01T00:00:00+0100&end=9999-02-01T00:00:00+0100").get(ClientResponse.class);
-        usage = response.getEntity(new GenericType<UsageStatistics>() {});
-        assertTrue(usage.getStatistics().isEmpty());
-
-        response = client().resource("/v1/usage/applications/" + appId + "?start=1970-01-01T00:00:00-0100&end=9999-01-01T00:00:00+0100").get(ClientResponse.class);
-        usage = response.getEntity(new GenericType<UsageStatistics>() {});
-        assertAppUsage(usage, appId, ImmutableList.of(Status.STARTING, Status.RUNNING), roundDown(preStart), postStart);
-        
-        response = client().resource("/v1/usage/applications/" + appId + "?end=1970-01-01T00:00:00-0100").get(ClientResponse.class);
-        usage = response.getEntity(new GenericType<UsageStatistics>() {});
-        assertTrue(usage.getStatistics().isEmpty());
-        
-        // Delete the app
-        Calendar preDelete = new GregorianCalendar();
-        deleteApp(appId);
-        Calendar postDelete = new GregorianCalendar();
-
-        // Deleted app still returned, if in time range
-        response = client().resource("/v1/usage/applications/" + appId).get(ClientResponse.class);
-        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
-        usage = response.getEntity(new GenericType<UsageStatistics>() {});
-        assertAppUsage(usage, appId, ImmutableList.of(Status.STARTING, Status.RUNNING, Status.DESTROYED), roundDown(preStart), postDelete);
-        assertAppUsage(ImmutableList.copyOf(usage.getStatistics()).subList(2, 3), appId, ImmutableList.of(Status.DESTROYED), roundDown(preDelete), postDelete);
-
-        // Deleted app not returned if terminated before time range begins
-        long afterPostDelete = postDelete.getTime().getTime()+1;
-        waitForFuture(afterPostDelete);
-        response = client().resource("/v1/usage/applications/" + appId +"?start=" + afterPostDelete).get(ClientResponse.class);
-        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
-        usage = response.getEntity(new GenericType<UsageStatistics>() {});
-        assertTrue(usage.getStatistics().isEmpty(), "usages="+usage);
-    }
-
-    @Test
-    public void testGetMachineUsagesForNonExistantMachine() throws Exception {
-        ClientResponse response = client().resource("/v1/usage/machines/wrongid").get(ClientResponse.class);
-        assertEquals(response.getStatus(), Response.Status.NOT_FOUND.getStatusCode());
-    }
-
-    @Test
-    public void testGetMachineUsagesInitiallyEmpty() throws Exception {
-        // All machines: empty
-        ClientResponse response = client().resource("/v1/usage/machines").get(ClientResponse.class);
-        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
-        Iterable<UsageStatistics> usages = response.getEntity(new GenericType<List<UsageStatistics>>() {});
-        assertTrue(Iterables.isEmpty(usages));
-        
-        // Specific machine that does not exist: get 404
-        response = client().resource("/v1/usage/machines/machineIdThatDoesNotExist").get(ClientResponse.class);
-        assertEquals(response.getStatus(), Response.Status.NOT_FOUND.getStatusCode());
-    }
-
-    @Test
-    public void testListAndGetMachineUsage() throws Exception {
-        Location location = getManagementContext().getLocationManager().createLocation(LocationSpec.create(DynamicLocalhostMachineProvisioningLocation.class));
-        TestApplication app = getManagementContext().getEntityManager().createEntity(EntitySpec.create(TestApplication.class));
-        SoftwareProcessEntityTest.MyService entity = app.createAndManageChild(org.apache.brooklyn.api.entity.EntitySpec.create(SoftwareProcessEntityTest.MyService.class));
-        
-        Calendar preStart = new GregorianCalendar();
-        app.start(ImmutableList.of(location));
-        Calendar postStart = new GregorianCalendar();
-        Location machine = Iterables.getOnlyElement(entity.getLocations());
-
-        // All machines
-        ClientResponse response = client().resource("/v1/usage/machines").get(ClientResponse.class);
-        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
-        Iterable<UsageStatistics> usages = response.getEntity(new GenericType<List<UsageStatistics>>() {});
-        UsageStatistics usage = Iterables.getOnlyElement(usages);
-        assertMachineUsage(usage, app.getId(), machine.getId(), ImmutableList.of(Status.ACCEPTED), roundDown(preStart), postStart);
-
-        // Specific machine
-        response = client().resource("/v1/usage/machines/"+machine.getId()).get(ClientResponse.class);
-        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
-        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
-        usage = response.getEntity(new GenericType<UsageStatistics>() {});
-        assertMachineUsage(usage, app.getId(), machine.getId(), ImmutableList.of(Status.ACCEPTED), roundDown(preStart), postStart);
-    }
-
-    @Test
-    public void testListMachinesUsageForApp() throws Exception {
-        Location location = getManagementContext().getLocationManager().createLocation(LocationSpec.create(DynamicLocalhostMachineProvisioningLocation.class));
-        TestApplication app = getManagementContext().getEntityManager().createEntity(EntitySpec.create(TestApplication.class));
-        SoftwareProcessEntityTest.MyService entity = app.createAndManageChild(org.apache.brooklyn.api.entity.EntitySpec.create(SoftwareProcessEntityTest.MyService.class));
-        String appId = app.getId();
-        
-        Calendar preStart = new GregorianCalendar();
-        app.start(ImmutableList.of(location));
-        Calendar postStart = new GregorianCalendar();
-        Location machine = Iterables.getOnlyElement(entity.getLocations());
-
-        // For running machine
-        ClientResponse response = client().resource("/v1/usage/machines?application="+appId).get(ClientResponse.class);
-        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
-        Iterable<UsageStatistics> usages = response.getEntity(new GenericType<List<UsageStatistics>>() {});
-        UsageStatistics usage = Iterables.getOnlyElement(usages);
-        assertMachineUsage(usage, app.getId(), machine.getId(), ImmutableList.of(Status.ACCEPTED), roundDown(preStart), postStart);
-        
-        // Stop the machine
-        Calendar preStop = new GregorianCalendar();
-        app.stop();
-        Calendar postStop = new GregorianCalendar();
-        
-        // Deleted machine still returned, if in time range
-        response = client().resource("/v1/usage/machines?application=" + appId).get(ClientResponse.class);
-        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
-        usages = response.getEntity(new GenericType<List<UsageStatistics>>() {});
-        usage = Iterables.getOnlyElement(usages);
-        assertMachineUsage(usage, app.getId(), machine.getId(), ImmutableList.of(Status.ACCEPTED, Status.DESTROYED), roundDown(preStart), postStop);
-        assertMachineUsage(ImmutableList.copyOf(usage.getStatistics()).subList(1,2), appId, machine.getId(), ImmutableList.of(Status.DESTROYED), roundDown(preStop), postStop);
-
-        // Terminated machines ignored if terminated since start-time
-        long futureTime = postStop.getTime().getTime()+1;
-        waitForFuture(futureTime);
-        response = client().resource("/v1/usage/applications?start=" + futureTime).get(ClientResponse.class);
-        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
-        usages = response.getEntity(new GenericType<List<UsageStatistics>>() {});
-        assertTrue(Iterables.isEmpty(usages), "usages="+usages);
-    }
-
-    private String createApp(ApplicationSpec spec) {
-        ClientResponse response = clientDeploy(spec);
-        assertEquals(response.getStatus(), Response.Status.CREATED.getStatusCode());
-        TaskSummary createTask = response.getEntity(TaskSummary.class);
-        waitForTask(createTask.getId());
-        return createTask.getEntityId();
-    }
-    
-    private void deleteApp(String appId) {
-        ClientResponse response = client().resource("/v1/applications/"+appId)
-                .delete(ClientResponse.class);
-        assertEquals(response.getStatus(), Response.Status.ACCEPTED.getStatusCode());
-        TaskSummary deletionTask = response.getEntity(TaskSummary.class);
-        waitForTask(deletionTask.getId());
-    }
-    
-    private void assertCalendarOrders(Object context, Calendar... Calendars) {
-        if (Calendars.length <= 1) return;
-        
-        long[] times = new long[Calendars.length];
-        for (int i = 0; i < times.length; i++) {
-            times[i] = millisSinceStart(Calendars[i]);
-        }
-        String err = "context="+context+"; Calendars="+Arrays.toString(Calendars) + "; CalendarsSanitized="+Arrays.toString(times);
-        
-        Calendar Calendar = Calendars[0];
-        for (int i = 1; i < Calendars.length; i++) {
-            assertTrue(Calendar.getTime().getTime() <= Calendars[i].getTime().getTime(), err);
-        }
-    }
-    
-    private void waitForTask(final String taskId) {
-        boolean success = Repeater.create()
-                .repeat(new Runnable() { public void run() {}})
-                .until(new Callable<Boolean>() {
-                    @Override public Boolean call() {
-                        ClientResponse response = client().resource("/v1/activities/"+taskId).get(ClientResponse.class);
-                        if (response.getStatus() == Response.Status.NOT_FOUND.getStatusCode()) {
-                            return true;
-                        }
-                        TaskSummary summary = response.getEntity(TaskSummary.class);
-                        return summary != null && summary.getEndTimeUtc() != null;
-                    }})
-                .every(10L, TimeUnit.MILLISECONDS)
-                .limitTimeTo(TIMEOUT_MS, TimeUnit.MILLISECONDS)
-                .run();
-        assertTrue(success, "task "+taskId+" not finished");
-    }
-
-    private long millisSinceStart(Calendar time) {
-        return time.getTime().getTime() - testStartTime.getTime().getTime();
-    }
-    
-    private Calendar roundDown(Calendar calendar) {
-        long time = calendar.getTime().getTime();
-        long timeDown = ((long)(time / 1000)) * 1000;
-        return Time.newCalendarFromMillisSinceEpochUtc(timeDown);
-    }
-    
-    @SuppressWarnings("unused")
-    private Calendar roundUp(Calendar calendar) {
-        long time = calendar.getTime().getTime();
-        long timeDown = ((long)(time / 1000)) * 1000;
-        long timeUp = (time == timeDown) ? time : timeDown + 1000;
-        return Time.newCalendarFromMillisSinceEpochUtc(timeUp);
-    }
-
-    private void assertMachineUsage(UsageStatistics usage, String appId, String machineId, List<Status> states, Calendar pre, Calendar post) throws Exception {
-        assertUsage(usage.getStatistics(), appId, machineId, states, pre, post, false);
-    }
-    
-    private void assertMachineUsage(Iterable<UsageStatistic> usages, String appId, String machineId, List<Status> states, Calendar pre, Calendar post) throws Exception {
-        assertUsage(usages, appId, machineId, states, pre, post, false);
-    }
-    
-    private void assertAppUsage(UsageStatistics usage, String appId, List<Status> states, Calendar pre, Calendar post) throws Exception {
-        assertUsage(usage.getStatistics(), appId, appId, states, pre, post, false);
-    }
-    
-    private void assertAppUsage(Iterable<UsageStatistic> usages, String appId, List<Status> states, Calendar pre, Calendar post) throws Exception {
-        assertUsage(usages, appId, appId, states, pre, post, false);
-    }
-
-    private void assertUsage(Iterable<UsageStatistic> usages, String appId, String id, List<Status> states, Calendar pre, Calendar post, boolean allowGaps) throws Exception {
-        String errMsg = "usages="+usages;
-        Calendar now = new GregorianCalendar();
-        Calendar lowerBound = pre;
-        Calendar strictStart = null;
-        
-        assertEquals(Iterables.size(usages), states.size(), errMsg);
-        for (int i = 0; i < Iterables.size(usages); i++) {
-            UsageStatistic usage = Iterables.get(usages, i);
-            Calendar usageStart = Time.parseCalendar(usage.getStart());
-            Calendar usageEnd = Time.parseCalendar(usage.getEnd());
-            assertEquals(usage.getId(), id, errMsg);
-            assertEquals(usage.getApplicationId(), appId, errMsg);
-            assertEquals(usage.getStatus(), states.get(i), errMsg);
-            assertCalendarOrders(usages, lowerBound, usageStart, post);
-            assertCalendarOrders(usages, usageEnd, now);
-            if (strictStart != null) {
-                assertEquals(usageStart, strictStart, errMsg);
-            }
-            if (!allowGaps) {
-                strictStart = usageEnd;
-            }
-            lowerBound = usageEnd;
-        }
-    }
-
-    private void assertAppUsageTimesTruncated(UsageStatistics usages, Calendar strictStart, Calendar strictEnd) throws Exception {
-        String errMsg = "strictStart="+Time.makeDateString(strictStart)+"; strictEnd="+Time.makeDateString(strictEnd)+";usages="+usages;
-        Calendar usageStart = Time.parseCalendar(Iterables.getFirst(usages.getStatistics(), null).getStart());
-        Calendar usageEnd = Time.parseCalendar(Iterables.getLast(usages.getStatistics()).getStart());
-        // time zones might be different - so must convert to date
-        assertEquals(usageStart.getTime(), strictStart.getTime(), "usageStart="+Time.makeDateString(usageStart)+";"+errMsg);
-        assertEquals(usageEnd.getTime(), strictEnd.getTime(), errMsg);
-    }
-    
-    public static class DynamicLocalhostMachineProvisioningLocation extends LocalhostMachineProvisioningLocation {
-        @Override
-        public SshMachineLocation obtain(Map<?, ?> flags) throws NoMachinesAvailableException {
-            return super.obtain(flags);
-        }
-        
-        @Override
-        public void release(SshMachineLocation machine) {
-            super.release(machine);
-            super.machines.remove(machine);
-            getManagementContext().getLocationManager().unmanage(machine);
-        }
-    }
-
-    private void waitForFuture(long futureTime) throws InterruptedException {
-        long now;
-        while ((now = System.currentTimeMillis()) < futureTime) {
-            Thread.sleep(futureTime - now);
-        }
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/VersionResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/VersionResourceTest.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/VersionResourceTest.java
deleted file mode 100644
index 384feb0..0000000
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/VersionResourceTest.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.rest.resources;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertTrue;
-
-import javax.ws.rs.core.Response;
-
-import org.testng.annotations.Test;
-
-import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
-
-import com.sun.jersey.api.client.ClientResponse;
-
-public class VersionResourceTest extends BrooklynRestResourceTest {
-
-    @Test
-    public void testGetVersion() {
-        ClientResponse response = client().resource("/v1/version")
-                .get(ClientResponse.class);
-
-        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
-        String version = response.getEntity(String.class);
-// TODO johnmccabe - 19/12/2015 :: temporarily disabled while the repo split work is ongoing,
-// must be restored when switching back to a valid brooklyn version
-//        assertTrue(version.matches("^\\d+\\.\\d+\\.\\d+.*"));
-        assertTrue(true);
-    }
-
-    @SuppressWarnings("deprecation")
-    @Override
-    protected void addBrooklynResources() {
-        addResource(new VersionResource());
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/security/PasswordHasherTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/security/PasswordHasherTest.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/security/PasswordHasherTest.java
deleted file mode 100644
index 575d6a4..0000000
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/security/PasswordHasherTest.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * 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;
-
-import static org.testng.Assert.assertEquals;
-
-import org.testng.annotations.Test;
-
-public class PasswordHasherTest {
-
-    @Test
-    public void testHashSha256() throws Exception {
-        // Note: expected hash values generated externally:
-        // echo -n mysaltmypassword | openssl dgst -sha256
-
-        assertEquals(PasswordHasher.sha256("mysalt", "mypassword"), "d02878b06efa88579cd84d9e50b211c0a7caa92cf243bad1622c66081f7e2692");
-        assertEquals(PasswordHasher.sha256("", "mypassword"), "89e01536ac207279409d4de1e5253e01f4a1769e696db0d6062ca9b8f56767c8");
-        assertEquals(PasswordHasher.sha256(null, "mypassword"), "89e01536ac207279409d4de1e5253e01f4a1769e696db0d6062ca9b8f56767c8");
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/test/entity/brooklynnode/DeployBlueprintTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/test/entity/brooklynnode/DeployBlueprintTest.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/test/entity/brooklynnode/DeployBlueprintTest.java
deleted file mode 100644
index 2ab62a9..0000000
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/test/entity/brooklynnode/DeployBlueprintTest.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * 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.test.entity.brooklynnode;
-
-import static org.testng.Assert.assertEquals;
-
-import java.net.URI;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.brooklyn.api.entity.EntitySpec;
-import org.apache.brooklyn.api.mgmt.EntityManager;
-import org.apache.brooklyn.entity.brooklynnode.BrooklynNode;
-import org.apache.brooklyn.entity.brooklynnode.BrooklynNode.DeployBlueprintEffector;
-import org.apache.brooklyn.entity.stock.BasicApplication;
-import org.apache.brooklyn.feed.http.JsonFunctions;
-import org.apache.brooklyn.rest.BrooklynRestApiLauncherTestFixture;
-import org.apache.brooklyn.test.HttpTestUtils;
-import org.apache.brooklyn.util.guava.Functionals;
-import org.eclipse.jetty.server.Server;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import com.google.common.base.Function;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-
-public class DeployBlueprintTest extends BrooklynRestApiLauncherTestFixture {
-
-    private static final Logger log = LoggerFactory.getLogger(DeployBlueprintTest.class);
-
-    Server server;
-
-    @BeforeMethod(alwaysRun=true)
-    public void setUp() throws Exception {
-        server = newServer();
-        useServerForTest(server);
-    }
-
-    @Test
-    public void testStartsAppViaEffector() throws Exception {
-        URI webConsoleUri = URI.create(getBaseUri());
-
-        EntitySpec<BrooklynNode> spec = EntitySpec.create(BrooklynNode.class);
-        EntityManager mgr = getManagementContextFromJettyServerAttributes(server).getEntityManager();
-        BrooklynNode node = mgr.createEntity(spec);
-        node.sensors().set(BrooklynNode.WEB_CONSOLE_URI, webConsoleUri);
-        mgr.manage(node);
-        Map<String, String> params = ImmutableMap.of(DeployBlueprintEffector.BLUEPRINT_CAMP_PLAN.getName(), "{ services: [ serviceType: \"java:"+BasicApplication.class.getName()+"\" ] }");
-        String id = node.invoke(BrooklynNode.DEPLOY_BLUEPRINT, params).getUnchecked();
-
-        log.info("got: "+id);
-
-        String apps = HttpTestUtils.getContent(webConsoleUri.toString()+"/v1/applications");
-        List<String> appType = parseJsonList(apps, ImmutableList.of("spec", "type"), String.class);
-        assertEquals(appType, ImmutableList.of(BasicApplication.class.getName()));
-        
-        String status = HttpTestUtils.getContent(webConsoleUri.toString()+"/v1/applications/"+id+"/entities/"+id+"/sensors/service.status");
-        log.info("STATUS: "+status);
-    }
-    
-    private <T> List<T> parseJsonList(String json, List<String> elements, Class<T> clazz) {
-        Function<String, List<T>> func = Functionals.chain(
-                JsonFunctions.asJson(),
-                JsonFunctions.forEach(Functionals.chain(
-                        JsonFunctions.walk(elements),
-                        JsonFunctions.cast(clazz))));
-        return func.apply(json);
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestApiTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestApiTest.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestApiTest.java
deleted file mode 100644
index 45aec4a..0000000
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestApiTest.java
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
- * 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.testing;
-
-import org.apache.brooklyn.api.location.LocationRegistry;
-import org.apache.brooklyn.api.mgmt.ManagementContext;
-import org.apache.brooklyn.camp.brooklyn.BrooklynCampPlatformLauncherNoServer;
-import org.apache.brooklyn.core.entity.Entities;
-import org.apache.brooklyn.core.location.BasicLocationRegistry;
-import org.apache.brooklyn.core.mgmt.ManagementContextInjectable;
-import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
-import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
-import org.testng.annotations.AfterClass;
-import org.testng.annotations.BeforeMethod;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.google.common.base.Preconditions;
-import com.sun.jersey.api.client.Client;
-import com.sun.jersey.api.client.WebResource;
-import com.sun.jersey.api.core.DefaultResourceConfig;
-import com.sun.jersey.test.framework.AppDescriptor;
-import com.sun.jersey.test.framework.JerseyTest;
-import com.sun.jersey.test.framework.LowLevelAppDescriptor;
-import com.sun.jersey.test.framework.spi.container.TestContainerException;
-import com.sun.jersey.test.framework.spi.container.TestContainerFactory;
-import com.sun.jersey.test.framework.spi.container.inmemory.InMemoryTestContainerFactory;
-
-import org.apache.brooklyn.rest.BrooklynRestApi;
-import org.apache.brooklyn.rest.BrooklynRestApiLauncherTest;
-import org.apache.brooklyn.rest.util.BrooklynRestResourceUtils;
-import org.apache.brooklyn.rest.util.NullHttpServletRequestProvider;
-import org.apache.brooklyn.rest.util.NullServletConfigProvider;
-import org.apache.brooklyn.rest.util.ShutdownHandlerProvider;
-import org.apache.brooklyn.rest.util.NoOpRecordingShutdownHandler;
-import org.apache.brooklyn.rest.util.json.BrooklynJacksonJsonProvider;
-import org.apache.brooklyn.util.exceptions.Exceptions;
-
-public abstract class BrooklynRestApiTest {
-
-    protected ManagementContext manager;
-    
-    protected boolean useLocalScannedCatalog = false;
-    protected NoOpRecordingShutdownHandler shutdownListener = createShutdownHandler();
-
-    @BeforeMethod(alwaysRun = true)
-    public void setUpMethod() {
-        shutdownListener.reset();
-    }
-    
-    protected synchronized void useLocalScannedCatalog() {
-        if (manager!=null && !useLocalScannedCatalog)
-            throw new IllegalStateException("useLocalScannedCatalog must be specified before manager is accessed/created");
-        useLocalScannedCatalog = true;
-    }
-    
-    private NoOpRecordingShutdownHandler createShutdownHandler() {
-        return new NoOpRecordingShutdownHandler();
-    }
-
-    protected synchronized ManagementContext getManagementContext() {
-        if (manager==null) {
-            if (useLocalScannedCatalog) {
-                manager = new LocalManagementContext();
-                BrooklynRestApiLauncherTest.forceUseOfDefaultCatalogWithJavaClassPath(manager);
-            } else {
-                manager = new LocalManagementContextForTests();
-            }
-            manager.getHighAvailabilityManager().disabled();
-            BasicLocationRegistry.setupLocationRegistryForTesting(manager);
-            
-            new BrooklynCampPlatformLauncherNoServer()
-                .useManagementContext(manager)
-                .launch();
-        }
-        return manager;
-    }
-    
-    protected ObjectMapper mapper() {
-        return BrooklynJacksonJsonProvider.findSharedObjectMapper(null, getManagementContext());
-    }
-    
-    @AfterClass
-    public void tearDown() throws Exception {
-        destroyManagementContext();
-    }
-
-    protected void destroyManagementContext() {
-        if (manager!=null) {
-            Entities.destroyAll(manager);
-            manager = null;
-        }
-    }
-    
-    public LocationRegistry getLocationRegistry() {
-        return new BrooklynRestResourceUtils(getManagementContext()).getLocationRegistry();
-    }
-
-    private JerseyTest jerseyTest;
-    protected DefaultResourceConfig config = new DefaultResourceConfig();
-    
-    protected final void addResource(Object resource) {
-        Preconditions.checkNotNull(config, "Must run before setUpJersey");
-        
-        if (resource instanceof Class)
-            config.getClasses().add((Class<?>)resource);
-        else
-            config.getSingletons().add(resource);
-        
-        if (resource instanceof ManagementContextInjectable) {
-            ((ManagementContextInjectable)resource).setManagementContext(getManagementContext());
-        }
-    }
-    
-    protected final void addProvider(Class<?> provider) {
-        Preconditions.checkNotNull(config, "Must run before setUpJersey");
-        
-        config.getClasses().add(provider);
-    }
-    
-    protected void addDefaultResources() {
-        // seems we have to provide our own injector because the jersey test framework 
-        // doesn't inject ServletConfig and it all blows up
-        // and the servlet config provider must be an instance; addClasses doesn't work for some reason
-        addResource(new NullServletConfigProvider());
-        addProvider(NullHttpServletRequestProvider.class);
-        addResource(new ShutdownHandlerProvider(shutdownListener));
-    }
-
-    protected final void setUpResources() {
-        addDefaultResources();
-        addBrooklynResources();
-        for (Object r: BrooklynRestApi.getMiscResources())
-            addResource(r);
-    }
-
-    /** intended for overriding if you only want certain resources added, or additional ones added */
-    protected void addBrooklynResources() {
-        for (Object r: BrooklynRestApi.getBrooklynRestResources())
-            addResource(r);
-    }
-
-    protected void setUpJersey() {
-        setUpResources();
-        
-        jerseyTest = createJerseyTest();
-        config = null;
-        try {
-            jerseyTest.setUp();
-        } catch (Exception e) {
-            throw Exceptions.propagate(e);
-        }
-    }
-
-    protected JerseyTest createJerseyTest() {
-        return new JerseyTest() {
-            @Override
-            protected AppDescriptor configure() {
-                return new LowLevelAppDescriptor.Builder(config).build();
-            }
-
-            @Override
-            protected TestContainerFactory getTestContainerFactory() throws TestContainerException {
-                return new InMemoryTestContainerFactory();
-            }
-        };
-    }
-    
-    protected void tearDownJersey() {
-        if (jerseyTest != null) {
-            try {
-                jerseyTest.tearDown();
-            } catch (Exception e) {
-                throw Exceptions.propagate(e);
-            }
-        }
-        config = new DefaultResourceConfig();
-    }
-
-    public Client client() {
-        Preconditions.checkNotNull(jerseyTest, "Must run setUpJersey first");
-        return jerseyTest.client();
-    }
-
-    public WebResource resource(String uri) {
-        Preconditions.checkNotNull(jerseyTest, "Must run setUpJersey first");
-        return jerseyTest.resource().path(uri);
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestResourceTest.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestResourceTest.java
deleted file mode 100644
index b94e73c..0000000
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestResourceTest.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * 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.testing;
-
-import static org.testng.Assert.assertTrue;
-
-import java.net.URI;
-import java.util.Collection;
-import java.util.concurrent.Callable;
-import java.util.concurrent.TimeUnit;
-import java.util.logging.Level;
-
-import javax.ws.rs.core.MediaType;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.Assert;
-import org.testng.annotations.AfterClass;
-import org.testng.annotations.BeforeClass;
-import org.apache.brooklyn.api.entity.Application;
-import org.apache.brooklyn.core.entity.Entities;
-import org.apache.brooklyn.rest.domain.ApplicationSpec;
-import org.apache.brooklyn.rest.domain.ApplicationSummary;
-import org.apache.brooklyn.rest.domain.Status;
-import org.apache.brooklyn.util.exceptions.Exceptions;
-import org.apache.brooklyn.util.repeat.Repeater;
-import org.apache.brooklyn.util.time.Duration;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.sun.jersey.api.client.ClientResponse;
-import com.sun.jersey.api.client.UniformInterfaceException;
-import com.sun.jersey.spi.inject.Errors;
-
-public abstract class BrooklynRestResourceTest extends BrooklynRestApiTest {
-
-    private static final Logger log = LoggerFactory.getLogger(BrooklynRestResourceTest.class);
-    
-    @BeforeClass(alwaysRun = true)
-    public void setUp() throws Exception {
-        // need this to debug jersey inject errors
-        java.util.logging.Logger.getLogger(Errors.class.getName()).setLevel(Level.INFO);
-
-        setUpJersey();
-    }
-
-    @Override
-    @AfterClass(alwaysRun = true)
-    public void tearDown() throws Exception {
-        tearDownJersey();
-        super.tearDown();
-    }
-
-
-    protected ClientResponse clientDeploy(ApplicationSpec spec) {
-        try {
-            // dropwizard TestClient won't skip deserialization of trivial things like string and byte[] and inputstream
-            // if we pass in an object it serializes, so we have to serialize things ourselves
-            return client().resource("/v1/applications")
-                .entity(new ObjectMapper().writer().writeValueAsBytes(spec), MediaType.APPLICATION_OCTET_STREAM)
-                .post(ClientResponse.class);
-        } catch (Exception e) {
-            throw Exceptions.propagate(e);
-        }
-    }
-    
-    protected void waitForApplicationToBeRunning(final URI applicationRef) {
-        waitForApplicationToBeRunning(applicationRef, Duration.minutes(3));
-    }
-    protected void waitForApplicationToBeRunning(final URI applicationRef, Duration timeout) {
-        if (applicationRef==null)
-            throw new NullPointerException("No application URI available (consider using BrooklynRestResourceTest.clientDeploy)");
-        
-        boolean started = Repeater.create("Wait for application startup")
-                .until(new Callable<Boolean>() {
-                    @Override
-                    public Boolean call() throws Exception {
-                        Status status = getApplicationStatus(applicationRef);
-                        if (status == Status.ERROR) {
-                            Assert.fail("Application failed with ERROR");
-                        }
-                        return status == Status.RUNNING;
-                    }
-                })
-                .backoffTo(Duration.ONE_SECOND)
-                .limitTimeTo(timeout)
-                .run();
-        
-        if (!started) {
-            log.warn("Did not start application "+applicationRef+":");
-            Collection<Application> apps = getManagementContext().getApplications();
-            for (Application app: apps)
-                Entities.dumpInfo(app);
-        }
-        assertTrue(started);
-    }
-
-    protected Status getApplicationStatus(URI uri) {
-        return client().resource(uri).get(ApplicationSummary.class).getStatus();
-    }
-
-    protected void waitForPageFoundResponse(final String resource, final Class<?> clazz) {
-        boolean found = Repeater.create("Wait for page found")
-                .until(new Callable<Boolean>() {
-                    @Override
-                    public Boolean call() throws Exception {
-                        try {
-                            client().resource(resource).get(clazz);
-                            return true;
-                        } catch (UniformInterfaceException e) {
-                            return false;
-                        }
-                    }
-                })
-                .every(1, TimeUnit.SECONDS)
-                .limitTimeTo(30, TimeUnit.SECONDS)
-                .run();
-        assertTrue(found);
-    }
-    
-    protected void waitForPageNotFoundResponse(final String resource, final Class<?> clazz) {
-        boolean success = Repeater.create("Wait for page not found")
-                .until(new Callable<Boolean>() {
-                    @Override
-                    public Boolean call() throws Exception {
-                        try {
-                            client().resource(resource).get(clazz);
-                            return false;
-                        } catch (UniformInterfaceException e) {
-                            return e.getResponse().getStatus() == 404;
-                        }
-                    }
-                })
-                .every(1, TimeUnit.SECONDS)
-                .limitTimeTo(30, TimeUnit.SECONDS)
-                .run();
-        assertTrue(success);
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/CapitalizePolicy.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/CapitalizePolicy.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/CapitalizePolicy.java
deleted file mode 100644
index 7d80a6f..0000000
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/CapitalizePolicy.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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.testing.mocks;
-
-import org.apache.brooklyn.api.entity.EntityLocal;
-import org.apache.brooklyn.core.policy.AbstractPolicy;
-
-@SuppressWarnings("deprecation")
-public class CapitalizePolicy extends AbstractPolicy {
-
-    @Override
-    public void setEntity(EntityLocal entity) {
-        super.setEntity(entity);
-        // TODO subscribe to foo and emit an enriched sensor on different channel which is capitalized
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/EverythingGroup.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/EverythingGroup.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/EverythingGroup.java
deleted file mode 100644
index 707c4a3..0000000
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/EverythingGroup.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * 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.testing.mocks;
-
-import org.apache.brooklyn.api.entity.Group;
-import org.apache.brooklyn.api.entity.ImplementedBy;
-
-@ImplementedBy(EverythingGroupImpl.class)
-public interface EverythingGroup extends Group {
-
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/EverythingGroupImpl.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/EverythingGroupImpl.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/EverythingGroupImpl.java
deleted file mode 100644
index 8b2c98b..0000000
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/EverythingGroupImpl.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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.testing.mocks;
-
-import org.apache.brooklyn.entity.group.DynamicGroupImpl;
-
-import com.google.common.base.Predicates;
-
-public class EverythingGroupImpl extends DynamicGroupImpl implements EverythingGroup {
-
-    public EverythingGroupImpl() {
-        super();
-        config().set(ENTITY_FILTER, Predicates.alwaysTrue());
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/NameMatcherGroup.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/NameMatcherGroup.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/NameMatcherGroup.java
deleted file mode 100644
index f9a2e21..0000000
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/NameMatcherGroup.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * 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.testing.mocks;
-
-import org.apache.brooklyn.api.entity.Group;
-import org.apache.brooklyn.api.entity.ImplementedBy;
-import org.apache.brooklyn.config.ConfigKey;
-import org.apache.brooklyn.core.config.ConfigKeys;
-
-@ImplementedBy(NameMatcherGroupImpl.class)
-public interface NameMatcherGroup extends Group {
-
-    public static final ConfigKey<String> NAME_REGEX = ConfigKeys.newStringConfigKey("namematchergroup.regex");
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/NameMatcherGroupImpl.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/NameMatcherGroupImpl.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/NameMatcherGroupImpl.java
deleted file mode 100644
index bec416f..0000000
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/NameMatcherGroupImpl.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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.testing.mocks;
-
-import org.apache.brooklyn.core.entity.EntityPredicates;
-import org.apache.brooklyn.entity.group.DynamicGroupImpl;
-import org.apache.brooklyn.util.text.StringPredicates;
-
-public class NameMatcherGroupImpl extends DynamicGroupImpl implements NameMatcherGroup {
-
-    @Override
-    public void init() {
-        super.init();
-        config().set(ENTITY_FILTER, EntityPredicates.displayNameSatisfies(StringPredicates.matchesRegex(getConfig(NAME_REGEX))));
-        rescanEntities();
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockApp.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockApp.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockApp.java
deleted file mode 100644
index 6d92e65..0000000
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockApp.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * 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.testing.mocks;
-
-import org.apache.brooklyn.core.entity.AbstractApplication;
-
-public class RestMockApp extends AbstractApplication {
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockAppBuilder.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockAppBuilder.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockAppBuilder.java
deleted file mode 100644
index 1ca10bd..0000000
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockAppBuilder.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * 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.testing.mocks;
-
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.entity.EntitySpec;
-import org.apache.brooklyn.core.entity.StartableApplication;
-import org.apache.brooklyn.core.entity.factory.ApplicationBuilder;
-import org.apache.brooklyn.util.javalang.Reflections;
-
-public class RestMockAppBuilder extends ApplicationBuilder {
-
-    public RestMockAppBuilder() {
-        super(EntitySpec.create(StartableApplication.class).impl(RestMockApp.class));
-    }
-    
-    @Override
-    protected void doBuild() {
-        addChild(EntitySpec.create(Entity.class).impl(RestMockSimpleEntity.class)
-            .additionalInterfaces(Reflections.getAllInterfaces(RestMockSimpleEntity.class))
-            .displayName("child1"));
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockSimpleEntity.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockSimpleEntity.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockSimpleEntity.java
deleted file mode 100644
index 58d24aa..0000000
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockSimpleEntity.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * 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.testing.mocks;
-
-import java.util.Map;
-
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.entity.EntityLocal;
-import org.apache.brooklyn.api.sensor.AttributeSensor;
-import org.apache.brooklyn.config.ConfigKey;
-import org.apache.brooklyn.core.annotation.Effector;
-import org.apache.brooklyn.core.annotation.EffectorParam;
-import org.apache.brooklyn.core.config.BasicConfigKey;
-import org.apache.brooklyn.core.effector.MethodEffector;
-import org.apache.brooklyn.core.sensor.BasicAttributeSensor;
-import org.apache.brooklyn.entity.software.base.AbstractSoftwareProcessSshDriver;
-import org.apache.brooklyn.entity.software.base.SoftwareProcessImpl;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.apache.brooklyn.location.ssh.SshMachineLocation;
-import org.apache.brooklyn.util.core.flags.SetFromFlag;
-
-public class RestMockSimpleEntity extends SoftwareProcessImpl {
-
-    private static final Logger log = LoggerFactory.getLogger(RestMockSimpleEntity.class);
-    
-    public RestMockSimpleEntity() {
-        super();
-    }
-
-    public RestMockSimpleEntity(Entity parent) {
-        super(parent);
-    }
-
-    public RestMockSimpleEntity(@SuppressWarnings("rawtypes") Map flags, Entity parent) {
-        super(flags, parent);
-    }
-
-    public RestMockSimpleEntity(@SuppressWarnings("rawtypes") Map flags) {
-        super(flags);
-    }
-    
-    @Override
-    protected void connectSensors() {
-        super.connectSensors();
-        connectServiceUpIsRunning();
-    }
-
-    @SetFromFlag("sampleConfig")
-    public static final ConfigKey<String> SAMPLE_CONFIG = new BasicConfigKey<String>(
-            String.class, "brooklyn.rest.mock.sample.config", "Mock sample config", "DEFAULT_VALUE");
-
-    public static final AttributeSensor<String> SAMPLE_SENSOR = new BasicAttributeSensor<String>(
-            String.class, "brooklyn.rest.mock.sample.sensor", "Mock sample sensor");
-
-    public static final MethodEffector<String> SAMPLE_EFFECTOR = new MethodEffector<String>(RestMockSimpleEntity.class, "sampleEffector");
-    
-    @Effector
-    public String sampleEffector(@EffectorParam(name="param1", description="param one") String param1, 
-            @EffectorParam(name="param2") Integer param2) {
-        log.info("Invoked sampleEffector("+param1+","+param2+")");
-        String result = ""+param1+param2;
-        sensors().set(SAMPLE_SENSOR, result);
-        return result;
-    }
-
-    @SuppressWarnings("rawtypes")
-    @Override
-    public Class getDriverInterface() {
-        return MockSshDriver.class;
-    }
-    
-    public static class MockSshDriver extends AbstractSoftwareProcessSshDriver {
-        public MockSshDriver(EntityLocal entity, SshMachineLocation machine) {
-            super(entity, machine);
-        }
-        public boolean isRunning() { return true; }
-        public void stop() {}
-        public void kill() {}
-        public void install() {}
-        public void customize() {}
-        public void launch() {}
-        public void setup() { }
-        public void copyInstallResources() { }
-        public void copyRuntimeResources() { }
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockSimplePolicy.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockSimplePolicy.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockSimplePolicy.java
deleted file mode 100644
index e15cdd1..0000000
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockSimplePolicy.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * 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.testing.mocks;
-
-import java.util.Map;
-
-import org.apache.brooklyn.config.ConfigKey;
-import org.apache.brooklyn.core.config.BasicConfigKey;
-import org.apache.brooklyn.core.policy.AbstractPolicy;
-import org.apache.brooklyn.util.core.flags.SetFromFlag;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class RestMockSimplePolicy extends AbstractPolicy {
-
-    @SuppressWarnings("unused")
-    private static final Logger log = LoggerFactory.getLogger(RestMockSimplePolicy.class);
-
-    public RestMockSimplePolicy() {
-        super();
-    }
-
-    @SuppressWarnings("rawtypes")
-    public RestMockSimplePolicy(Map flags) {
-        super(flags);
-    }
-
-    @SetFromFlag("sampleConfig")
-    public static final ConfigKey<String> SAMPLE_CONFIG = BasicConfigKey.builder(String.class)
-            .name("brooklyn.rest.mock.sample.config")
-            .description("Mock sample config")
-            .defaultValue("DEFAULT_VALUE")
-            .reconfigurable(true)
-            .build();
-
-    @SetFromFlag
-    public static final ConfigKey<Integer> INTEGER_CONFIG = BasicConfigKey.builder(Integer.class)
-            .name("brooklyn.rest.mock.sample.integer")
-            .description("Mock integer config")
-            .defaultValue(1)
-            .reconfigurable(true)
-            .build();
-
-    @Override
-    protected <T> void doReconfigureConfig(ConfigKey<T> key, T val) {
-        // no-op
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/util/BrooklynRestResourceUtilsTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/util/BrooklynRestResourceUtilsTest.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/util/BrooklynRestResourceUtilsTest.java
deleted file mode 100644
index 48908e3..0000000
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/util/BrooklynRestResourceUtilsTest.java
+++ /dev/null
@@ -1,213 +0,0 @@
-/*
- * 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.testng.Assert.assertEquals;
-import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.assertTrue;
-
-import java.util.Map;
-
-import org.apache.brooklyn.api.catalog.Catalog;
-import org.apache.brooklyn.api.entity.Application;
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.policy.Policy;
-import org.apache.brooklyn.core.catalog.internal.CatalogItemBuilder;
-import org.apache.brooklyn.core.catalog.internal.CatalogTemplateItemDto;
-import org.apache.brooklyn.core.catalog.internal.CatalogUtils;
-import org.apache.brooklyn.core.entity.AbstractApplication;
-import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
-import org.apache.brooklyn.core.objs.proxy.EntityProxy;
-import org.apache.brooklyn.core.policy.AbstractPolicy;
-import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
-import org.apache.brooklyn.core.test.entity.TestApplication;
-import org.apache.brooklyn.core.test.entity.TestEntity;
-import org.apache.brooklyn.entity.stock.BasicEntity;
-import org.apache.brooklyn.rest.domain.ApplicationSpec;
-import org.apache.brooklyn.rest.domain.EntitySpec;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
-
-public class BrooklynRestResourceUtilsTest {
-
-    private LocalManagementContext managementContext;
-    private BrooklynRestResourceUtils util;
-
-    @BeforeMethod(alwaysRun=true)
-    public void setUp() throws Exception {
-        managementContext = LocalManagementContextForTests.newInstance();
-        util = new BrooklynRestResourceUtils(managementContext);
-    }
-    
-    @AfterMethod(alwaysRun=true)
-    public void tearDown() throws Exception {
-        if (managementContext != null) managementContext.terminate();
-    }
-
-    @Test
-    public void testCreateAppFromImplClass() {
-        ApplicationSpec spec = ApplicationSpec.builder()
-                .name("myname")
-                .type(SampleNoOpApplication.class.getName())
-                .locations(ImmutableSet.of("localhost"))
-                .build();
-        Application app = util.create(spec);
-        
-        assertEquals(ImmutableList.copyOf(managementContext.getApplications()), ImmutableList.of(app));
-        assertEquals(app.getDisplayName(), "myname");
-        assertTrue(app instanceof EntityProxy);
-        assertTrue(app instanceof MyInterface);
-        assertFalse(app instanceof SampleNoOpApplication);
-    }
-
-    @Test
-    public void testCreateAppFromCatalogByType() {
-        createAppFromCatalog(SampleNoOpApplication.class.getName());
-    }
-
-    @Test
-    public void testCreateAppFromCatalogByName() {
-        createAppFromCatalog("app.noop");
-    }
-
-    @Test
-    public void testCreateAppFromCatalogById() {
-        createAppFromCatalog("app.noop:0.0.1");
-    }
-
-    @Test
-    @SuppressWarnings("deprecation")
-    public void testCreateAppFromCatalogByTypeMultipleItems() {
-        CatalogTemplateItemDto item = CatalogItemBuilder.newTemplate("app.noop", "0.0.2-SNAPSHOT")
-                .javaType(SampleNoOpApplication.class.getName())
-                .build();
-        managementContext.getCatalog().addItem(item);
-        createAppFromCatalog(SampleNoOpApplication.class.getName());
-    }
-
-    @SuppressWarnings("deprecation")
-    private void createAppFromCatalog(String type) {
-        CatalogTemplateItemDto item = CatalogItemBuilder.newTemplate("app.noop", "0.0.1")
-            .javaType(SampleNoOpApplication.class.getName())
-            .build();
-        managementContext.getCatalog().addItem(item);
-        
-        ApplicationSpec spec = ApplicationSpec.builder()
-                .name("myname")
-                .type(type)
-                .locations(ImmutableSet.of("localhost"))
-                .build();
-        Application app = util.create(spec);
-
-        assertEquals(app.getCatalogItemId(), "app.noop:0.0.1");
-    }
-
-    @Test
-    public void testEntityAppFromCatalogByType() {
-        createEntityFromCatalog(BasicEntity.class.getName());
-    }
-
-    @Test
-    public void testEntityAppFromCatalogByName() {
-        createEntityFromCatalog("app.basic");
-    }
-
-    @Test
-    public void testEntityAppFromCatalogById() {
-        createEntityFromCatalog("app.basic:0.0.1");
-    }
-
-    @Test
-    @SuppressWarnings("deprecation")
-    public void testEntityAppFromCatalogByTypeMultipleItems() {
-        CatalogTemplateItemDto item = CatalogItemBuilder.newTemplate("app.basic", "0.0.2-SNAPSHOT")
-                .javaType(SampleNoOpApplication.class.getName())
-                .build();
-        managementContext.getCatalog().addItem(item);
-        createEntityFromCatalog(BasicEntity.class.getName());
-    }
-
-    @SuppressWarnings("deprecation")
-    private void createEntityFromCatalog(String type) {
-        String symbolicName = "app.basic";
-        String version = "0.0.1";
-        CatalogTemplateItemDto item = CatalogItemBuilder.newTemplate(symbolicName, version)
-            .javaType(BasicEntity.class.getName())
-            .build();
-        managementContext.getCatalog().addItem(item);
-
-        ApplicationSpec spec = ApplicationSpec.builder()
-                .name("myname")
-                .entities(ImmutableSet.of(new EntitySpec(type)))
-                .locations(ImmutableSet.of("localhost"))
-                .build();
-        Application app = util.create(spec);
-
-        Entity entity = Iterables.getOnlyElement(app.getChildren());
-        assertEquals(entity.getCatalogItemId(), CatalogUtils.getVersionedId(symbolicName, version));
-    }
-
-    @Test
-    public void testNestedApplications() {
-        // hierarchy is: app -> subapp -> subentity (where subentity has a policy)
-        
-        Application app = managementContext.getEntityManager().createEntity(org.apache.brooklyn.api.entity.EntitySpec.create(TestApplication.class)
-                .displayName("app")
-                .child(org.apache.brooklyn.api.entity.EntitySpec.create(TestApplication.class)
-                        .displayName("subapp")
-                        .child(org.apache.brooklyn.api.entity.EntitySpec.create(TestEntity.class)
-                                .displayName("subentity")
-                                .policy(org.apache.brooklyn.api.policy.PolicySpec.create(MyPolicy.class)
-                                        .displayName("mypolicy")))));
-
-        Application subapp = (Application) Iterables.getOnlyElement(app.getChildren());
-        TestEntity subentity = (TestEntity) Iterables.getOnlyElement(subapp.getChildren());
-        
-        Entity subappRetrieved = util.getEntity(app.getId(), subapp.getId());
-        assertEquals(subappRetrieved.getDisplayName(), "subapp");
-        
-        Entity subentityRetrieved = util.getEntity(app.getId(), subentity.getId());
-        assertEquals(subentityRetrieved.getDisplayName(), "subentity");
-        
-        Policy subappPolicy = util.getPolicy(app.getId(), subentity.getId(), "mypolicy");
-        assertEquals(subappPolicy.getDisplayName(), "mypolicy");
-    }
-
-    public interface MyInterface {
-    }
-
-    @Catalog(name="Sample No-Op Application",
-            description="Application which does nothing, included only as part of the test cases.",
-            iconUrl="")
-    public static class SampleNoOpApplication extends AbstractApplication implements MyInterface {
-    }
-    
-    public static class MyPolicy extends AbstractPolicy {
-        public MyPolicy() {
-        }
-        public MyPolicy(Map<String, ?> flags) {
-            super(flags);
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/util/EntityLocationUtilsTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/util/EntityLocationUtilsTest.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/util/EntityLocationUtilsTest.java
deleted file mode 100644
index f0c65e4..0000000
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/util/EntityLocationUtilsTest.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * 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.testng.Assert.assertEquals;
-
-import java.util.Arrays;
-import java.util.Map;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-import org.apache.brooklyn.api.entity.EntitySpec;
-import org.apache.brooklyn.api.location.Location;
-import org.apache.brooklyn.core.entity.Entities;
-import org.apache.brooklyn.core.location.AbstractLocation;
-import org.apache.brooklyn.core.location.geo.HostGeoInfo;
-import org.apache.brooklyn.core.location.internal.LocationInternal;
-import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
-import org.apache.brooklyn.entity.software.base.SoftwareProcess;
-import org.apache.brooklyn.rest.testing.mocks.RestMockSimpleEntity;
-
-import com.google.common.collect.ImmutableList;
-
-public class EntityLocationUtilsTest extends BrooklynAppUnitTestSupport {
-
-    private static final Logger log = LoggerFactory.getLogger(EntityLocationUtilsTest.class);
-    
-    private Location loc;
-    
-    @BeforeMethod(alwaysRun=true)
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        loc = mgmt.getLocationRegistry().resolve("localhost");
-        ((AbstractLocation)loc).setHostGeoInfo(new HostGeoInfo("localhost", "localhost", 50, 0));
-    }
-    
-    @Test
-    public void testCount() {
-        @SuppressWarnings("unused")
-        SoftwareProcess r1 = app.createAndManageChild(EntitySpec.create(SoftwareProcess.class, RestMockSimpleEntity.class));
-        SoftwareProcess r2 = app.createAndManageChild(EntitySpec.create(SoftwareProcess.class, RestMockSimpleEntity.class));
-        Entities.start(app, Arrays.<Location>asList(loc));
-
-        Entities.dumpInfo(app);
-
-        log.info("r2loc: "+r2.getLocations());
-        log.info("props: "+((LocationInternal)r2.getLocations().iterator().next()).config().getBag().getAllConfig());
-
-        Map<Location, Integer> counts = new EntityLocationUtils(mgmt).countLeafEntitiesByLocatedLocations();
-        log.info("count: "+counts);
-        assertEquals(ImmutableList.copyOf(counts.values()), ImmutableList.of(2), "counts="+counts);
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/util/HaHotStateCheckClassResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/util/HaHotStateCheckClassResource.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/util/HaHotStateCheckClassResource.java
deleted file mode 100644
index 80e9c46..0000000
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/util/HaHotStateCheckClassResource.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * 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 javax.ws.rs.GET;
-import javax.ws.rs.Path;
-import javax.ws.rs.Produces;
-import javax.ws.rs.core.MediaType;
-
-import org.apache.brooklyn.rest.filter.HaHotStateRequired;
-
-@Path("/ha/class")
-@Produces(MediaType.APPLICATION_JSON)
-@HaHotStateRequired
-public class HaHotStateCheckClassResource {
-
-    @GET
-    @Path("fail")
-    public String fail() {
-        return "FAIL";
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/util/HaHotStateCheckResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/util/HaHotStateCheckResource.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/util/HaHotStateCheckResource.java
deleted file mode 100644
index 5c9d4d1..0000000
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/util/HaHotStateCheckResource.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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 javax.ws.rs.GET;
-import javax.ws.rs.Path;
-import javax.ws.rs.Produces;
-import javax.ws.rs.core.MediaType;
-
-import org.apache.brooklyn.rest.filter.HaHotStateRequired;
-
-@Path("/ha/method")
-@Produces(MediaType.APPLICATION_JSON)
-public class HaHotStateCheckResource {
-
-    @GET
-    @Path("ok")
-    public String ok() {
-        return "OK";
-    }
-
-    @GET
-    @Path("fail")
-    @HaHotStateRequired
-    public String fail() {
-        return "FAIL";
-    }
-}


[20/34] brooklyn-server git commit: [BROOKLYN-183] REST API using CXF JAX-RS 2.0 implementation

Posted by he...@apache.org.
[BROOKLYN-183] REST API using CXF JAX-RS 2.0 implementation

Changes:

* Replace Jersey dependency (JAX-RS 1.1) implementation with CXF (JAX-RS 2.0)
* Use relative paths for REST resource paths

Move the context root at /v1, and use paths relative to that in resource
annotations.

* Add CXF REST resources to karaf features
* Remove /v1 prefix from brooklyn-rest-api resources
* Move CXF servlet context to /v1

In order to preserve compatibility with the existing brooklyn REST API
we need to expose resources at path /v1 instead of /cxf/v1.

For now move the whole CXF servlet at /v1, and with resources mounted at
/ we get the historical path /v1/ for the REST API. We'll need to make
the JSGUI configurable with the base REST API path in the future though.

Also add appropriate providers to the OSGi CXF service blueprint.

* Use relative paths when referring REST resources

Don't use absolute paths based on the assumption that the REST API is
deployed at /v1.

Instead use an UriBuilder provided by JAX-RS as a context-injected
UriInfo object, which should work with any root context path.

Also drop the /v1 prefix from REST API tests and test fixtures.

* Fix usage of resources in CXF unit tests

Use HTTP transport instead of local transport so we can have a ServletContext to get/setAttribute.
Allow CXF to use BrooklynJacksonJsonProvider for message formatting and parsing.
Inject the ManagementContext in resources and providers.
Add request/response logging during tests.

* Pull up common implementation of configureCXF

All tests ended up using the same configuration of the REST API, so it
makes sense to move that into the base class

This had the side effect of worsening the interleaving of test methods
by testng, so each resource test was separated into its own suite.

* Fix DeployBlueprintTest

Also change getEndpointAddress() to use template method instead of
setter/getter.

* Wait for CXF resources to start in karaf itest

* Remove CatalogApi's multipart/form-data create method

There is no standard JAX-RS way to process multipart/form-data contents,
so this service was removed from the API for the purpose of unifying the
CXF and Jersey API.

* Adjust OSGi package import directives to allow a version range of 1.1 to
2.0 of JAX-RS directives.


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

Branch: refs/heads/master
Commit: 6f624c78b1e7fe72c6df1ecd297b922721b2c023
Parents: 495b1d3
Author: Ciprian Ciubotariu <ch...@gmx.net>
Authored: Tue Feb 9 19:23:20 2016 +0200
Committer: Svetoslav Neykov <sv...@cloudsoftcorp.com>
Committed: Mon Feb 15 16:43:57 2016 +0200

----------------------------------------------------------------------
 camp/camp-server/pom.xml                        |  21 +-
 .../brooklyn/camp/server/CampRestApp.java       |  36 +
 .../brooklyn/camp/server/RestApiSetup.java      |  43 ++
 .../brooklyn/camp/server/rest/CampServer.java   |  32 +-
 karaf/apache-brooklyn/pom.xml                   |   2 +-
 .../main/resources/etc/org.apache.cxf.osgi.cfg  |  20 +
 karaf/features/src/main/feature/feature.xml     |  30 +-
 karaf/itest/pom.xml                             |  13 +
 .../java/org/apache/brooklyn/AssemblyTest.java  |   4 +-
 launcher/pom.xml                                |  19 +-
 .../org/apache/brooklyn/launcher/Activator.java |   2 +
 .../brooklyn/launcher/BrooklynWebServer.java    |  99 +--
 .../camp/BrooklynCampPlatformLauncher.java      |   1 +
 .../jsgui/BrooklynJavascriptGuiLauncher.java    |   2 +-
 .../BrooklynJavascriptGuiLauncherTest.java      |  18 +-
 locations/jclouds/pom.xml                       |  12 +
 parent/pom.xml                                  | 182 +++--
 pom.xml                                         |   7 +-
 rest/rest-api/pom.xml                           |  21 +-
 .../org/apache/brooklyn/rest/api/AccessApi.java |   2 +-
 .../apache/brooklyn/rest/api/ActivityApi.java   |   2 +-
 .../brooklyn/rest/api/ApplicationApi.java       |   2 +-
 .../apache/brooklyn/rest/api/CatalogApi.java    |  28 +-
 .../apache/brooklyn/rest/api/EffectorApi.java   |   2 +-
 .../org/apache/brooklyn/rest/api/EntityApi.java |   4 +-
 .../brooklyn/rest/api/EntityConfigApi.java      |   4 +-
 .../apache/brooklyn/rest/api/LocationApi.java   |   2 +-
 .../org/apache/brooklyn/rest/api/PolicyApi.java |   2 +-
 .../brooklyn/rest/api/PolicyConfigApi.java      |   2 +-
 .../org/apache/brooklyn/rest/api/ScriptApi.java |   2 +-
 .../org/apache/brooklyn/rest/api/SensorApi.java |   5 +-
 .../org/apache/brooklyn/rest/api/ServerApi.java |   2 +-
 .../org/apache/brooklyn/rest/api/UsageApi.java  |   2 +-
 .../apache/brooklyn/rest/api/VersionApi.java    |   4 +-
 rest/rest-api/src/main/webapp/WEB-INF/web.xml   | 121 ----
 .../rest/domain/ApplicationSpecTest.java        |   2 +-
 .../rest/domain/EffectorSummaryTest.java        |   2 +-
 .../brooklyn/rest/domain/EntitySummaryTest.java |   8 +-
 .../rest/domain/LocationSummaryTest.java        |   2 +-
 .../resources/fixtures/application-list.json    |   8 +-
 .../resources/fixtures/application-spec.json    |   2 +-
 .../test/resources/fixtures/application.json    |   4 +-
 .../fixtures/effector-summary-list.json         |  18 +-
 .../resources/fixtures/effector-summary.json    |   2 +-
 .../resources/fixtures/entity-summary-list.json |   8 +-
 .../test/resources/fixtures/entity-summary.json |   8 +-
 .../test/resources/fixtures/location-list.json  |   2 +-
 .../resources/fixtures/location-summary.json    |   2 +-
 .../resources/fixtures/sensor-summary-list.json |  16 +-
 .../test/resources/fixtures/sensor-summary.json |   2 +-
 rest/rest-client/pom.xml                        |   7 +
 .../rest/client/BrooklynApiRestClientTest.java  |   3 +-
 rest/rest-resources/pom.xml                     | 220 ++++++
 .../apache/brooklyn/rest/BrooklynRestApi.java   |  91 +++
 .../apache/brooklyn/rest/BrooklynRestApp.java   |  56 ++
 .../apache/brooklyn/rest/BrooklynWebConfig.java | 164 +++++
 .../rest/filter/HaHotCheckResourceFilter.java   | 159 +++++
 .../rest/filter/HaHotStateRequired.java         |  38 +
 .../brooklyn/rest/filter/NoCacheFilter.java     |  40 ++
 .../resources/AbstractBrooklynRestResource.java | 156 +++++
 .../brooklyn/rest/resources/AccessResource.java |  46 ++
 .../rest/resources/ActivityResource.java        |  99 +++
 .../brooklyn/rest/resources/ApidocResource.java |  35 +
 .../rest/resources/ApplicationResource.java     | 451 ++++++++++++
 .../rest/resources/CatalogResource.java         | 509 ++++++++++++++
 .../rest/resources/EffectorResource.java        | 114 +++
 .../rest/resources/EntityConfigResource.java    | 206 ++++++
 .../brooklyn/rest/resources/EntityResource.java | 223 ++++++
 .../rest/resources/LocationResource.java        | 186 +++++
 .../rest/resources/PolicyConfigResource.java    | 108 +++
 .../brooklyn/rest/resources/PolicyResource.java | 135 ++++
 .../brooklyn/rest/resources/ScriptResource.java | 102 +++
 .../brooklyn/rest/resources/SensorResource.java | 184 +++++
 .../brooklyn/rest/resources/ServerResource.java | 497 +++++++++++++
 .../brooklyn/rest/resources/UsageResource.java  | 256 +++++++
 .../rest/resources/VersionResource.java         |  32 +
 .../brooklyn/rest/security/PasswordHasher.java  |  32 +
 .../provider/AbstractSecurityProvider.java      |  56 ++
 .../provider/AnyoneSecurityProvider.java        |  40 ++
 .../provider/BlackholeSecurityProvider.java     |  40 ++
 ...nUserWithRandomPasswordSecurityProvider.java |  73 ++
 .../provider/DelegatingSecurityProvider.java    | 165 +++++
 .../provider/ExplicitUsersSecurityProvider.java | 117 ++++
 .../security/provider/LdapSecurityProvider.java | 132 ++++
 .../security/provider/SecurityProvider.java     |  35 +
 .../rest/transform/AccessTransformer.java       |  42 ++
 .../rest/transform/ApplicationTransformer.java  | 125 ++++
 .../transform/BrooklynFeatureTransformer.java   |  45 ++
 .../rest/transform/CatalogTransformer.java      | 188 +++++
 .../rest/transform/EffectorTransformer.java     |  91 +++
 .../rest/transform/EntityTransformer.java       | 182 +++++
 .../transform/HighAvailabilityTransformer.java  |  50 ++
 .../rest/transform/LocationTransformer.java     | 202 ++++++
 .../rest/transform/PolicyTransformer.java       |  97 +++
 .../rest/transform/SensorTransformer.java       |  88 +++
 .../rest/transform/TaskTransformer.java         | 153 ++++
 .../rest/util/BrooklynRestResourceUtils.java    | 609 ++++++++++++++++
 .../rest/util/DefaultExceptionMapper.java       | 111 +++
 .../brooklyn/rest/util/EntityLocationUtils.java |  85 +++
 .../brooklyn/rest/util/FormMapProvider.java     |  86 +++
 .../rest/util/ManagementContextProvider.java    |  41 ++
 .../apache/brooklyn/rest/util/OsgiCompat.java   |  40 ++
 .../brooklyn/rest/util/ShutdownHandler.java     |  23 +
 .../rest/util/ShutdownHandlerProvider.java      |  41 ++
 .../brooklyn/rest/util/URLParamEncoder.java     |  27 +
 .../brooklyn/rest/util/WebResourceUtils.java    | 197 ++++++
 .../rest/util/json/BidiSerialization.java       | 173 +++++
 .../util/json/BrooklynJacksonJsonProvider.java  | 177 +++++
 .../json/ConfigurableSerializerProvider.java    |  90 +++
 .../ErrorAndToStringUnknownTypeSerializer.java  | 123 ++++
 .../rest/util/json/MultimapSerializer.java      |  64 ++
 ...StrictPreferringFieldsVisibilityChecker.java | 108 +++
 .../resources/OSGI-INF/blueprint/service.xml    | 112 +++
 .../main/resources/build-metadata.properties    |  18 +
 .../src/main/resources/not-a-jar-file.txt       |  18 +
 .../src/main/resources/reset-catalog.xml        |  37 +
 .../brooklyn/rest/domain/ApplicationTest.java   |  86 +++
 .../brooklyn/rest/domain/SensorSummaryTest.java | 102 +++
 .../brooklyn/rest/filter/HaHotCheckTest.java    | 120 ++++
 .../rest/resources/AccessResourceTest.java      |  67 ++
 .../rest/resources/ApidocResourceTest.java      | 135 ++++
 .../ApplicationResourceIntegrationTest.java     | 132 ++++
 .../rest/resources/ApplicationResourceTest.java | 697 ++++++++++++++++++
 .../rest/resources/CatalogResetTest.java        | 118 ++++
 .../rest/resources/CatalogResourceTest.java     | 510 ++++++++++++++
 .../rest/resources/DelegatingPrintStream.java   | 183 +++++
 .../rest/resources/DescendantsTest.java         | 128 ++++
 .../resources/EntityConfigResourceTest.java     | 171 +++++
 .../rest/resources/EntityResourceTest.java      | 188 +++++
 .../rest/resources/ErrorResponseTest.java       |  98 +++
 .../rest/resources/LocationResourceTest.java    | 190 +++++
 .../rest/resources/PolicyResourceTest.java      | 143 ++++
 .../rest/resources/ScriptResourceTest.java      |  56 ++
 .../SensorResourceIntegrationTest.java          |  74 ++
 .../rest/resources/SensorResourceTest.java      | 267 +++++++
 .../rest/resources/ServerResourceTest.java      | 168 +++++
 .../rest/resources/ServerShutdownTest.java      | 164 +++++
 .../rest/resources/UsageResourceTest.java       | 444 ++++++++++++
 .../rest/resources/VersionResourceTest.java     |  46 ++
 .../rest/security/PasswordHasherTest.java       |  37 +
 .../test/config/render/TestRendererHints.java   |  36 +
 .../brooklynnode/DeployBlueprintTest.java       |  97 +++
 .../rest/testing/BrooklynRestApiTest.java       | 223 ++++++
 .../rest/testing/BrooklynRestResourceTest.java  | 212 ++++++
 .../rest/testing/mocks/CapitalizePolicy.java    |  33 +
 .../rest/testing/mocks/EverythingGroup.java     |  27 +
 .../rest/testing/mocks/EverythingGroupImpl.java |  32 +
 .../rest/testing/mocks/NameMatcherGroup.java    |  30 +
 .../testing/mocks/NameMatcherGroupImpl.java     |  33 +
 .../rest/testing/mocks/RestMockApp.java         |  24 +
 .../rest/testing/mocks/RestMockAppBuilder.java  |  39 ++
 .../testing/mocks/RestMockSimpleEntity.java     | 103 +++
 .../testing/mocks/RestMockSimplePolicy.java     |  64 ++
 .../util/BrooklynRestResourceUtilsTest.java     | 213 ++++++
 .../rest/util/EntityLocationUtilsTest.java      |  72 ++
 .../rest/util/HaHotStateCheckClassResource.java |  38 +
 .../rest/util/HaHotStateCheckResource.java      |  44 ++
 .../brooklyn/rest/util/TestShutdownHandler.java |  37 +
 .../json/BrooklynJacksonSerializerTest.java     | 264 +++++++
 .../META-INF/cxf/org.apache.cxf.Logger          |  18 +
 .../resources/brooklyn/scanning.catalog.bom     |  19 +
 rest/rest-server/pom.xml                        |  76 +-
 .../apache/brooklyn/rest/BrooklynRestApi.java   |  89 ---
 .../apache/brooklyn/rest/BrooklynWebConfig.java | 158 -----
 .../org/apache/brooklyn/rest/RestApiSetup.java  |  68 ++
 .../BrooklynPropertiesSecurityFilter.java       |  19 +-
 .../rest/filter/HaHotCheckResourceFilter.java   | 150 ----
 .../rest/filter/HaHotStateRequired.java         |  36 -
 .../rest/filter/HaMasterCheckFilter.java        |  18 +-
 .../brooklyn/rest/filter/LoggingFilter.java     |   2 +
 .../brooklyn/rest/filter/NoCacheFilter.java     |  40 --
 .../rest/filter/RequestTaggingFilter.java       |   1 +
 .../brooklyn/rest/filter/SwaggerFilter.java     |  76 --
 .../resources/AbstractBrooklynRestResource.java | 152 ----
 .../brooklyn/rest/resources/AccessResource.java |  46 --
 .../rest/resources/ActivityResource.java        |  99 ---
 .../brooklyn/rest/resources/ApidocResource.java |  31 -
 .../rest/resources/ApplicationResource.java     | 449 ------------
 .../rest/resources/CatalogResource.java         | 521 --------------
 .../rest/resources/EffectorResource.java        | 114 ---
 .../rest/resources/EntityConfigResource.java    | 206 ------
 .../brooklyn/rest/resources/EntityResource.java | 223 ------
 .../rest/resources/LocationResource.java        | 184 -----
 .../rest/resources/PolicyConfigResource.java    | 108 ---
 .../brooklyn/rest/resources/PolicyResource.java | 131 ----
 .../brooklyn/rest/resources/ScriptResource.java | 102 ---
 .../brooklyn/rest/resources/SensorResource.java | 184 -----
 .../brooklyn/rest/resources/ServerResource.java | 495 -------------
 .../brooklyn/rest/resources/UsageResource.java  | 256 -------
 .../rest/resources/VersionResource.java         |  32 -
 .../brooklyn/rest/security/PasswordHasher.java  |  32 -
 .../provider/AbstractSecurityProvider.java      |  56 --
 .../provider/AnyoneSecurityProvider.java        |  40 --
 .../provider/BlackholeSecurityProvider.java     |  40 --
 ...nUserWithRandomPasswordSecurityProvider.java |  73 --
 .../provider/DelegatingSecurityProvider.java    | 166 -----
 .../provider/ExplicitUsersSecurityProvider.java | 118 ----
 .../security/provider/LdapSecurityProvider.java | 132 ----
 .../security/provider/SecurityProvider.java     |  35 -
 .../rest/transform/AccessTransformer.java       |  39 --
 .../rest/transform/ApplicationTransformer.java  | 116 ---
 .../transform/BrooklynFeatureTransformer.java   |  45 --
 .../rest/transform/CatalogTransformer.java      | 192 -----
 .../rest/transform/EffectorTransformer.java     |  85 ---
 .../rest/transform/EntityTransformer.java       | 165 -----
 .../transform/HighAvailabilityTransformer.java  |  50 --
 .../rest/transform/LocationTransformer.java     | 193 -----
 .../rest/transform/PolicyTransformer.java       |  83 ---
 .../rest/transform/SensorTransformer.java       |  84 ---
 .../rest/transform/TaskTransformer.java         | 146 ----
 .../rest/util/BrooklynRestResourceUtils.java    | 608 ----------------
 .../rest/util/DefaultExceptionMapper.java       | 104 ---
 .../brooklyn/rest/util/EntityLocationUtils.java |  85 ---
 .../brooklyn/rest/util/FormMapProvider.java     |  81 ---
 .../rest/util/ManagementContextProvider.java    |  33 -
 .../apache/brooklyn/rest/util/OsgiCompat.java   |  46 --
 .../brooklyn/rest/util/ShutdownHandler.java     |  23 -
 .../rest/util/ShutdownHandlerProvider.java      |  30 -
 .../brooklyn/rest/util/URLParamEncoder.java     |  27 -
 .../brooklyn/rest/util/WebResourceUtils.java    | 161 -----
 .../rest/util/json/BidiSerialization.java       | 173 -----
 .../util/json/BrooklynJacksonJsonProvider.java  | 165 -----
 .../json/ConfigurableSerializerProvider.java    |  89 ---
 .../ErrorAndToStringUnknownTypeSerializer.java  | 123 ----
 .../rest/util/json/MultimapSerializer.java      |  62 --
 ...StrictPreferringFieldsVisibilityChecker.java | 108 ---
 .../rest-server/src/main/webapp/WEB-INF/web.xml |  96 +--
 .../BrooklynPropertiesSecurityFilterTest.java   |   6 +-
 .../brooklyn/rest/BrooklynRestApiLauncher.java  |  56 +-
 .../rest/BrooklynRestApiLauncherTest.java       |   6 -
 .../BrooklynRestApiLauncherTestFixture.java     |  22 +-
 .../apache/brooklyn/rest/HaHotCheckTest.java    | 129 ----
 .../brooklyn/rest/HaMasterCheckFilterTest.java  |   2 +-
 .../brooklyn/rest/domain/ApplicationTest.java   |  87 ---
 .../brooklyn/rest/domain/SensorSummaryTest.java | 101 ---
 .../rest/resources/AccessResourceTest.java      |  68 --
 .../rest/resources/ApidocResourceTest.java      | 177 -----
 .../ApplicationResourceIntegrationTest.java     | 133 ----
 .../rest/resources/ApplicationResourceTest.java | 701 -------------------
 .../rest/resources/CatalogResetTest.java        | 113 ---
 .../rest/resources/CatalogResourceTest.java     | 512 --------------
 .../rest/resources/DelegatingPrintStream.java   | 183 -----
 .../rest/resources/DescendantsTest.java         | 130 ----
 .../resources/EntityConfigResourceTest.java     | 172 -----
 .../rest/resources/EntityResourceTest.java      | 189 -----
 .../rest/resources/ErrorResponseTest.java       |  98 ---
 .../rest/resources/LocationResourceTest.java    | 189 -----
 .../rest/resources/PolicyResourceTest.java      | 145 ----
 .../rest/resources/ScriptResourceTest.java      |  54 --
 .../SensorResourceIntegrationTest.java          |  82 ---
 .../rest/resources/SensorResourceTest.java      | 271 -------
 .../ServerResourceIntegrationTest.java          |  12 +-
 .../rest/resources/ServerResourceTest.java      | 168 -----
 .../rest/resources/ServerShutdownTest.java      | 185 -----
 .../rest/resources/UsageResourceTest.java       | 443 ------------
 .../rest/resources/VersionResourceTest.java     |  52 --
 .../rest/security/PasswordHasherTest.java       |  37 -
 .../brooklynnode/DeployBlueprintTest.java       |  89 ---
 .../rest/testing/BrooklynRestApiTest.java       | 204 ------
 .../rest/testing/BrooklynRestResourceTest.java  | 154 ----
 .../rest/testing/mocks/CapitalizePolicy.java    |  33 -
 .../rest/testing/mocks/EverythingGroup.java     |  27 -
 .../rest/testing/mocks/EverythingGroupImpl.java |  32 -
 .../rest/testing/mocks/NameMatcherGroup.java    |  30 -
 .../testing/mocks/NameMatcherGroupImpl.java     |  33 -
 .../rest/testing/mocks/RestMockApp.java         |  24 -
 .../rest/testing/mocks/RestMockAppBuilder.java  |  39 --
 .../testing/mocks/RestMockSimpleEntity.java     | 103 ---
 .../testing/mocks/RestMockSimplePolicy.java     |  64 --
 .../util/BrooklynRestResourceUtilsTest.java     | 213 ------
 .../rest/util/EntityLocationUtilsTest.java      |  72 --
 .../rest/util/HaHotStateCheckClassResource.java |  38 -
 .../rest/util/HaHotStateCheckResource.java      |  44 --
 .../util/NullHttpServletRequestProvider.java    |  46 --
 .../rest/util/NullServletConfigProvider.java    |  51 --
 ...rooklynJacksonSerializerIntegrationTest.java | 173 +++++
 .../json/BrooklynJacksonSerializerTest.java     | 399 -----------
 .../resources/brooklyn/scanning.catalog.bom     |  19 -
 utils/rest-swagger/pom.xml                      |  28 +-
 .../rest/apidoc/ApiListingResource.java         | 242 +------
 .../rest/apidoc/RestApiResourceScanner.java     |  23 +-
 281 files changed, 15076 insertions(+), 14665 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/camp/camp-server/pom.xml
----------------------------------------------------------------------
diff --git a/camp/camp-server/pom.xml b/camp/camp-server/pom.xml
index 534a6cb..642474b 100644
--- a/camp/camp-server/pom.xml
+++ b/camp/camp-server/pom.xml
@@ -73,23 +73,6 @@
         </dependency>
         
         <dependency>
-            <groupId>com.sun.jersey</groupId>
-            <artifactId>jersey-server</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>com.sun.jersey</groupId>
-            <artifactId>jersey-servlet</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>com.sun.jersey.contribs</groupId>
-            <artifactId>jersey-multipart</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>com.sun.jersey</groupId>
-            <artifactId>jersey-core</artifactId>
-        </dependency>
-
-        <dependency>
             <groupId>javax.validation</groupId>
             <artifactId>validation-api</artifactId>
         </dependency>
@@ -136,6 +119,10 @@
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.apache.cxf</groupId>
+            <artifactId>cxf-rt-frontend-jaxrs</artifactId>
+        </dependency>
         
         <!-- TODO have a camp.log / logging module -->
         <dependency>

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/camp/camp-server/src/main/java/org/apache/brooklyn/camp/server/CampRestApp.java
----------------------------------------------------------------------
diff --git a/camp/camp-server/src/main/java/org/apache/brooklyn/camp/server/CampRestApp.java b/camp/camp-server/src/main/java/org/apache/brooklyn/camp/server/CampRestApp.java
new file mode 100644
index 0000000..7a12936
--- /dev/null
+++ b/camp/camp-server/src/main/java/org/apache/brooklyn/camp/server/CampRestApp.java
@@ -0,0 +1,36 @@
+/*
+ * 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.camp.server;
+
+import java.util.Set;
+
+import javax.ws.rs.core.Application;
+
+import org.apache.brooklyn.camp.server.rest.CampRestResources;
+
+import com.google.common.collect.Sets;
+
+public class CampRestApp extends Application {
+
+    @Override
+    public Set<Object> getSingletons() {
+        return Sets.newHashSet(CampRestResources.getAllResources());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/camp/camp-server/src/main/java/org/apache/brooklyn/camp/server/RestApiSetup.java
----------------------------------------------------------------------
diff --git a/camp/camp-server/src/main/java/org/apache/brooklyn/camp/server/RestApiSetup.java b/camp/camp-server/src/main/java/org/apache/brooklyn/camp/server/RestApiSetup.java
new file mode 100644
index 0000000..127b5bd
--- /dev/null
+++ b/camp/camp-server/src/main/java/org/apache/brooklyn/camp/server/RestApiSetup.java
@@ -0,0 +1,43 @@
+/*
+ * 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.camp.server;
+
+import org.apache.brooklyn.rest.apidoc.RestApiResourceScanner;
+import org.apache.cxf.jaxrs.servlet.CXFNonSpringJaxrsServlet;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+
+import io.swagger.config.ScannerFactory;
+
+public class RestApiSetup {
+
+    public static ContextHandler installRestServlet(ServletContextHandler context) {
+        ScannerFactory.setScanner(new RestApiResourceScanner());
+
+        CampRestApp app = new CampRestApp();
+
+        CXFNonSpringJaxrsServlet servlet = new CXFNonSpringJaxrsServlet(app);
+        final ServletHolder servletHolder = new ServletHolder(servlet);
+
+        context.addServlet(servletHolder, "/*");
+        return context;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/camp/camp-server/src/main/java/org/apache/brooklyn/camp/server/rest/CampServer.java
----------------------------------------------------------------------
diff --git a/camp/camp-server/src/main/java/org/apache/brooklyn/camp/server/rest/CampServer.java b/camp/camp-server/src/main/java/org/apache/brooklyn/camp/server/rest/CampServer.java
index 0ad6bc2..1274494 100644
--- a/camp/camp-server/src/main/java/org/apache/brooklyn/camp/server/rest/CampServer.java
+++ b/camp/camp-server/src/main/java/org/apache/brooklyn/camp/server/rest/CampServer.java
@@ -20,18 +20,17 @@ package org.apache.brooklyn.camp.server.rest;
 
 import java.io.File;
 import java.io.IOException;
-import java.util.EnumSet;
-
-import javax.servlet.DispatcherType;
 
 import org.apache.brooklyn.camp.CampPlatform;
+import org.apache.brooklyn.camp.server.RestApiSetup;
 import org.apache.brooklyn.camp.server.rest.resource.PlatformRestResource;
 import org.apache.brooklyn.camp.server.rest.util.DtoFactory;
 import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.net.Networking;
+import org.eclipse.jetty.server.NetworkConnector;
 import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
 import org.eclipse.jetty.server.handler.ContextHandler;
-import org.eclipse.jetty.servlet.FilterHolder;
 import org.eclipse.jetty.servlet.ServletContextHandler;
 import org.eclipse.jetty.util.thread.QueuedThreadPool;
 import org.eclipse.jetty.webapp.WebAppContext;
@@ -40,11 +39,6 @@ import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Charsets;
 import com.google.common.io.Files;
-import com.sun.jersey.api.core.DefaultResourceConfig;
-import com.sun.jersey.api.core.ResourceConfig;
-import com.sun.jersey.spi.container.servlet.ServletContainer;
-import org.eclipse.jetty.server.NetworkConnector;
-import org.eclipse.jetty.server.ServerConnector;
 
 public class CampServer {
 
@@ -127,25 +121,7 @@ public class CampServer {
     public static class CampServerUtils {
 
         public static void installAsServletFilter(ServletContextHandler context) {
-            // TODO security
-            //        installBrooklynPropertiesSecurityFilter(context);
-
-            // now set up the REST servlet resources
-            ResourceConfig config = new DefaultResourceConfig();
-            // load all our REST API modules, JSON, and Swagger
-            for (Object r: CampRestResources.getAllResources())
-                config.getSingletons().add(r);
-
-            // configure to match empty path, or any thing which looks like a file path with /assets/ and extension html, css, js, or png
-            // and treat that as static content
-            config.getProperties().put(ServletContainer.PROPERTY_WEB_PAGE_CONTENT_REGEX, "(/?|[^?]*/assets/[^?]+\\.[A-Za-z0-9_]+)");
-
-            // and anything which is not matched as a servlet also falls through (but more expensive than a regex check?)
-            config.getFeatures().put(ServletContainer.FEATURE_FILTER_FORWARD_ON_404, true);
-
-            // finally create this as a _filter_ which falls through to a web app or something (optionally)
-            FilterHolder filterHolder = new FilterHolder(new ServletContainer(config));
-            context.addFilter(filterHolder, "/*", EnumSet.allOf(DispatcherType.class));
+            RestApiSetup.installRestServlet(context);
         }
 
         public static Server startServer(ContextHandler context, String summary) {

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/karaf/apache-brooklyn/pom.xml
----------------------------------------------------------------------
diff --git a/karaf/apache-brooklyn/pom.xml b/karaf/apache-brooklyn/pom.xml
index 89ab390..6ccd8f9 100755
--- a/karaf/apache-brooklyn/pom.xml
+++ b/karaf/apache-brooklyn/pom.xml
@@ -115,8 +115,8 @@
             <bootFeature>system</bootFeature>
             <bootFeature>wrap</bootFeature>
             <!-- brooklyn features -->
-            <bootFeature>brooklyn-rest-server</bootFeature>
             <bootFeature>brooklyn-jsgui</bootFeature>
+            <bootFeature>brooklyn-rest-resources</bootFeature>
           </bootFeatures>
         </configuration>
       </plugin>

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/karaf/apache-brooklyn/src/main/resources/etc/org.apache.cxf.osgi.cfg
----------------------------------------------------------------------
diff --git a/karaf/apache-brooklyn/src/main/resources/etc/org.apache.cxf.osgi.cfg b/karaf/apache-brooklyn/src/main/resources/etc/org.apache.cxf.osgi.cfg
new file mode 100644
index 0000000..e82b1d0
--- /dev/null
+++ b/karaf/apache-brooklyn/src/main/resources/etc/org.apache.cxf.osgi.cfg
@@ -0,0 +1,20 @@
+################################################################################
+#
+#    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.
+#
+################################################################################
+
+org.apache.cxf.servlet.context = /v1

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/karaf/features/src/main/feature/feature.xml
----------------------------------------------------------------------
diff --git a/karaf/features/src/main/feature/feature.xml b/karaf/features/src/main/feature/feature.xml
index 0b22046..c590469 100644
--- a/karaf/features/src/main/feature/feature.xml
+++ b/karaf/features/src/main/feature/feature.xml
@@ -22,10 +22,7 @@
     <repository>mvn:org.apache.karaf.features/enterprise/${karaf.version}/xml/features</repository>
     <repository>mvn:org.apache.karaf.features/spring/${karaf.version}/xml/features</repository>
 
-    <!-- all these are about jax-rs, and will need some love later on -->
-    <!--<bundle dependency="true">mvn:org.apache.servicemix.specs/org.apache.servicemix.specs.jsr339-api-2.0/2.4.0</bundle>-->
-    <!--<bundle dependency="true">mvn:javax.ws.rs/jsr311-api/${jsr311-api.version}</bundle>-->
-    <!--<bundle dependency="true">mvn:javax.ws.rs/javax.ws.rs-api/${javax.ws.rs-api}</bundle>-->
+    <repository>mvn:org.apache.cxf.karaf/apache-cxf/${cxf.version}/xml/features</repository>
 
     <!-- temporary feature until we migrate to swagger-1.5.4, which is properly bundled -->
     <feature name="swagger-crippled" version="${swagger.version}" description="Swagger Annotations+Core+JAXRS+Models">
@@ -79,7 +76,11 @@
         <bundle dependency="true">mvn:org.apache.commons/commons-lang3/${commons-lang3.version}</bundle>
 
         <bundle dependency="true">mvn:javax.servlet/javax.servlet-api/${javax-servlet.version}</bundle>
-        <bundle dependency="true">mvn:javax.ws.rs/jsr311-api/${jsr311-api.version}</bundle>
+        <bundle dependency="true">mvn:javax.ws.rs/javax.ws.rs-api/${jax-rs-api.version}</bundle>
+
+        <!-- TODO: version 1.1.0.Final has OSGi manifest; check if upgrade doesn't get rid of wrap -->
+        <bundle dependency="true">wrap:mvn:javax.validation/validation-api/${validation-api.version}</bundle>
+        <bundle dependency="true">mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.reflections/${reflections.bundle.version}</bundle>
     </feature>
 
     <feature name="brooklyn-rest-api" version="${project.version}" description="Brooklyn REST API">
@@ -102,7 +103,6 @@
         <bundle dependency="true">mvn:com.fasterxml.jackson.core/jackson-annotations/${fasterxml.jackson.version}</bundle>
         <bundle dependency="true">mvn:com.fasterxml.jackson.core/jackson-databind/${fasterxml.jackson.version}</bundle>
 
-        <!--<bundle dependency="true">mvn:javax.ws.rs/jsr311-api/${jsr311-api.version}</bundle>-->
         <bundle dependency="true">mvn:javax.servlet/javax.servlet-api/${javax-servlet.version}</bundle>
     </feature>
 
@@ -139,7 +139,7 @@
         <bundle dependency="true">wrap:mvn:com.maxmind.geoip2/geoip2/${maxmind.version}</bundle> <!-- from geoip2 -->
         <bundle dependency="true">wrap:mvn:xpp3/xpp3_min/1.1.4c</bundle> <!-- from com.thoughtworks.xstream/xstream -->
 
-        <bundle>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.reflections/${reflections.bundle.version}</bundle>
+        <bundle dependency="true">mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.reflections/${reflections.bundle.version}</bundle>
     </feature>
 
     <feature name="brooklyn-camp-brooklyn" version="${project.version}" description="Brooklyn CAMP REST API">
@@ -168,20 +168,20 @@
         <feature>jetty</feature> <!-- TODO: pax-jetty??? -->
     </feature>
 
-    <feature name="brooklyn-rest-server" version="${project.version}" description="Brooklyn REST Server">
-        <bundle>mvn:org.apache.brooklyn/brooklyn-rest-server/${project.version}</bundle>
+    <feature name="brooklyn-rest-resources" version="${project.version}" description="Brooklyn REST Resources">
+        <bundle>mvn:org.apache.brooklyn/brooklyn-rest-resources/${project.version}</bundle>
+        <feature>brooklyn-core</feature>
         <feature>brooklyn-rest-api</feature>
         <feature>brooklyn-camp-brooklyn</feature>
         <feature>brooklyn-camp-base</feature>
 
-        <feature>war</feature>
-
-        <!--<feature>jersey</feature>-->
-        <bundle dependency="true">mvn:com.sun.jersey/jersey-server/${jersey.version}</bundle>
-        <bundle dependency="true">mvn:com.sun.jersey/jersey-core/${jersey.version}</bundle>
-        <bundle dependency="true">mvn:com.sun.jersey/jersey-servlet/${jersey.version}</bundle>
+        <feature>cxf-jaxrs</feature>
 
         <bundle dependency="true">mvn:com.fasterxml.jackson.jaxrs/jackson-jaxrs-json-provider/${fasterxml.jackson.version}</bundle>
+
+        <!-- should be deps of jax-rs-2.0 rest-api project -->
+        <bundle dependency="true">mvn:javax.servlet/javax.servlet-api/${javax-servlet.version}</bundle>
+        <bundle dependency="true">mvn:javax.ws.rs/javax.ws.rs-api/${jax-rs-api.version}</bundle>
     </feature>
 
     <feature name="brooklyn-jsgui" version="${project.version}" description="Brooklyn REST JavaScript Web GUI">

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/karaf/itest/pom.xml
----------------------------------------------------------------------
diff --git a/karaf/itest/pom.xml b/karaf/itest/pom.xml
index 0b016a3..a09503b 100644
--- a/karaf/itest/pom.xml
+++ b/karaf/itest/pom.xml
@@ -167,6 +167,19 @@
             <version>${geronimo-jta_1.1_spec.version}</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>brooklyn-rest-resources</artifactId>
+            <version>${project.version}</version>
+            <classifier>tests</classifier>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>brooklyn-utils-common</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/karaf/itest/src/test/java/org/apache/brooklyn/AssemblyTest.java
----------------------------------------------------------------------
diff --git a/karaf/itest/src/test/java/org/apache/brooklyn/AssemblyTest.java b/karaf/itest/src/test/java/org/apache/brooklyn/AssemblyTest.java
index 49f9ea6..dc7a58b 100644
--- a/karaf/itest/src/test/java/org/apache/brooklyn/AssemblyTest.java
+++ b/karaf/itest/src/test/java/org/apache/brooklyn/AssemblyTest.java
@@ -82,7 +82,7 @@ public class AssemblyTest {
         };
     }
 
-    private static MavenArtifactUrlReference brooklynKarafDist() {
+    public static MavenArtifactUrlReference brooklynKarafDist() {
         return maven()
                 .groupId("org.apache.brooklyn")
                 .artifactId("apache-brooklyn")
@@ -90,7 +90,7 @@ public class AssemblyTest {
                 .version(asInProject());
     }
 
-    private static MavenUrlReference karafStandardFeaturesRepository() {
+    public static MavenUrlReference karafStandardFeaturesRepository() {
         return maven()
                 .groupId("org.apache.karaf.features")
                 .artifactId("standard")

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/launcher/pom.xml
----------------------------------------------------------------------
diff --git a/launcher/pom.xml b/launcher/pom.xml
index 8d847cd..a10af41 100644
--- a/launcher/pom.xml
+++ b/launcher/pom.xml
@@ -93,14 +93,6 @@
             <artifactId>concurrentlinkedhashmap-lru</artifactId>
         </dependency>
         <dependency>
-            <groupId>com.sun.jersey</groupId>
-            <artifactId>jersey-server</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>com.sun.jersey</groupId>
-            <artifactId>jersey-servlet</artifactId>
-        </dependency>
-        <dependency>
             <groupId>org.eclipse.jetty</groupId>
             <artifactId>jetty-server</artifactId>
         </dependency>
@@ -140,6 +132,10 @@
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.apache.cxf</groupId>
+            <artifactId>cxf-rt-frontend-jaxrs</artifactId>
+        </dependency>
 
         <dependency>
             <groupId>org.apache.brooklyn</groupId>
@@ -190,6 +186,13 @@
         </dependency>
         <dependency>
             <groupId>org.apache.brooklyn</groupId>
+            <artifactId>brooklyn-rest-resources</artifactId>
+            <version>${project.version}</version>
+            <classifier>tests</classifier>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.brooklyn</groupId>
             <artifactId>brooklyn-rest-server</artifactId>
             <version>${project.version}</version>
             <classifier>tests</classifier>

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/launcher/src/main/java/org/apache/brooklyn/launcher/Activator.java
----------------------------------------------------------------------
diff --git a/launcher/src/main/java/org/apache/brooklyn/launcher/Activator.java b/launcher/src/main/java/org/apache/brooklyn/launcher/Activator.java
index 6215cc7..935b6bf 100644
--- a/launcher/src/main/java/org/apache/brooklyn/launcher/Activator.java
+++ b/launcher/src/main/java/org/apache/brooklyn/launcher/Activator.java
@@ -27,12 +27,14 @@ public class Activator implements BundleActivator {
 
     public static final Logger log = LoggerFactory.getLogger(Activator.class);
 
+    @Override
     public void start(BundleContext context) throws Exception {
         //does nothing on startup, just makes resources available
         //TODO maybe it wants to register a service that others could use?
         log.info("Starting brooklyn-launcher OSGi bundle");
     }
 
+    @Override
     public void stop(BundleContext context) throws Exception {
         log.info("Stopping brooklyn-launcher OSGi bundle");
     }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynWebServer.java
----------------------------------------------------------------------
diff --git a/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynWebServer.java b/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynWebServer.java
index 0365c14..f2760f2 100644
--- a/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynWebServer.java
+++ b/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynWebServer.java
@@ -29,34 +29,11 @@ import java.security.cert.Certificate;
 import java.security.cert.X509Certificate;
 import java.text.SimpleDateFormat;
 import java.util.Date;
-import java.util.EnumSet;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 
 import javax.annotation.Nullable;
-import javax.servlet.DispatcherType;
-
-import org.apache.brooklyn.rest.filter.SwaggerFilter;
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.servlet.FilterHolder;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
-import org.eclipse.jetty.util.thread.QueuedThreadPool;
-import org.eclipse.jetty.webapp.WebAppContext;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Splitter;
-import com.google.common.base.Throwables;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Maps;
-import com.sun.jersey.api.container.filter.GZIPContentEncodingFilter;
-import com.sun.jersey.api.core.DefaultResourceConfig;
-import com.sun.jersey.api.core.ResourceConfig;
-import com.sun.jersey.spi.container.servlet.ServletContainer;
 
 import org.apache.brooklyn.api.location.PortRange;
 import org.apache.brooklyn.api.mgmt.ManagementContext;
@@ -69,8 +46,8 @@ import org.apache.brooklyn.core.server.BrooklynServerPaths;
 import org.apache.brooklyn.core.server.BrooklynServiceAttributes;
 import org.apache.brooklyn.launcher.config.CustomResourceLocator;
 import org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation;
-import org.apache.brooklyn.rest.BrooklynRestApi;
 import org.apache.brooklyn.rest.BrooklynWebConfig;
+import org.apache.brooklyn.rest.RestApiSetup;
 import org.apache.brooklyn.rest.filter.BrooklynPropertiesSecurityFilter;
 import org.apache.brooklyn.rest.filter.HaHotCheckResourceFilter;
 import org.apache.brooklyn.rest.filter.HaMasterCheckFilter;
@@ -97,11 +74,25 @@ import org.apache.brooklyn.util.stream.Streams;
 import org.apache.brooklyn.util.text.Identifiers;
 import org.apache.brooklyn.util.text.Strings;
 import org.apache.brooklyn.util.web.ContextHandlerCollectionHotSwappable;
+import org.apache.cxf.transport.common.gzip.GZIPInInterceptor;
+import org.apache.cxf.transport.common.gzip.GZIPOutInterceptor;
 import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.server.Connector;
 import org.eclipse.jetty.server.HttpConfiguration;
 import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.ServerConnector;
 import org.eclipse.jetty.server.SslConnectionFactory;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Splitter;
+import com.google.common.base.Throwables;
+import com.google.common.collect.Maps;
 
 /**
  * Starts the web-app running, connected to the given management context
@@ -113,8 +104,8 @@ public class BrooklynWebServer {
     static {
         // support loading the WAR in dev mode from an alternate location 
         CustomResourceLocator.registerAlternateLocator(new CustomResourceLocator.SearchingClassPathInDevMode(
-                BROOKLYN_WAR_URL, "/usage/launcher/target", 
-                "/usage/jsgui/target/brooklyn-jsgui-"+BrooklynVersion.get()+".war"));
+                BROOKLYN_WAR_URL, "/brooklyn-server/launcher/target", 
+                "/brooklyn-ui/target/brooklyn-jsgui-"+BrooklynVersion.get()+".war"));
     }
     
     static {
@@ -346,33 +337,6 @@ public class BrooklynWebServer {
         return this;
     }
 
-    public void installAsServletFilter(ServletContextHandler context) {
-        ResourceConfig config = new DefaultResourceConfig();
-        // load all our REST API modules, JSON, and Swagger
-        for (Object r: BrooklynRestApi.getAllResources())
-            config.getSingletons().add(r);
-
-        // Accept gzipped requests and responses, disable caching for dynamic content
-        config.getProperties().put(ResourceConfig.PROPERTY_CONTAINER_REQUEST_FILTERS, GZIPContentEncodingFilter.class.getName());
-        config.getProperties().put(ResourceConfig.PROPERTY_CONTAINER_RESPONSE_FILTERS, ImmutableList.of(GZIPContentEncodingFilter.class, NoCacheFilter.class));
-        // Checks if appropriate request given HA status
-        config.getProperties().put(ResourceConfig.PROPERTY_RESOURCE_FILTER_FACTORIES, HaHotCheckResourceFilter.class.getName());
-        // configure to match empty path, or any thing which looks like a file path with /assets/ and extension html, css, js, or png
-        // and treat that as static content
-        config.getProperties().put(ServletContainer.PROPERTY_WEB_PAGE_CONTENT_REGEX, "(/?|[^?]*/assets/[^?]+\\.[A-Za-z0-9_]+)");
-        // and anything which is not matched as a servlet also falls through (but more expensive than a regex check?)
-        config.getFeatures().put(ServletContainer.FEATURE_FILTER_FORWARD_ON_404, true);
-        // finally create this as a _filter_ which falls through to a web app or something (optionally)
-        FilterHolder filterHolder = new FilterHolder(new ServletContainer(config));
-
-        context.addFilter(filterHolder, "/*", EnumSet.allOf(DispatcherType.class));
-
-        ManagementContext mgmt = (ManagementContext) context.getAttribute(BrooklynServiceAttributes.BROOKLYN_MANAGEMENT_CONTEXT);
-        config.getSingletons().add(new ManagementContextProvider(mgmt));
-
-        config.getSingletons().add(new ShutdownHandlerProvider(shutdownHandler));
-    }
-
     ContextHandlerCollectionHotSwappable handlers = new ContextHandlerCollectionHotSwappable();
     
     /**
@@ -439,18 +403,11 @@ public class BrooklynWebServer {
             WebAppContext webapp = deploy(contextProvider);
             webapp.setTempDirectory(Os.mkdirs(new File(webappTempDir, newTimestampedDirName("war", 8))));
         }
+
         rootContext = deploy(rootWar);
+        deployRestApi(rootContext);
         rootContext.setTempDirectory(Os.mkdirs(new File(webappTempDir, "war-root")));
 
-        rootContext.addFilter(RequestTaggingFilter.class, "/*", EnumSet.allOf(DispatcherType.class));
-        if (securityFilterClazz != null) {
-            rootContext.addFilter(securityFilterClazz, "/*", EnumSet.allOf(DispatcherType.class));
-        }
-        rootContext.addFilter(LoggingFilter.class, "/*", EnumSet.allOf(DispatcherType.class));
-        rootContext.addFilter(HaMasterCheckFilter.class, "/*", EnumSet.allOf(DispatcherType.class));
-        rootContext.addFilter(SwaggerFilter.class, "/*", EnumSet.allOf(DispatcherType.class));
-        installAsServletFilter(rootContext);
-
         server.setHandler(handlers);
         server.start();
         //reinit required because some webapps (eg grails) might wipe our language extension bindings
@@ -463,6 +420,24 @@ public class BrooklynWebServer {
         log.info("Started Brooklyn console at "+getRootUrl()+", running " + rootWar + (allWars!=null && !allWars.isEmpty() ? " and " + wars.values() : ""));
     }
 
+    private WebAppContext deployRestApi(WebAppContext context) {
+        RestApiSetup.installRestServlet(context,
+                new ManagementContextProvider(managementContext),
+                new ShutdownHandlerProvider(shutdownHandler),
+                new NoCacheFilter(),
+                new HaHotCheckResourceFilter(),
+                new GZIPInInterceptor(),
+                new GZIPOutInterceptor());
+        RestApiSetup.installServletFilters(context,
+                RequestTaggingFilter.class,
+                LoggingFilter.class,
+                HaMasterCheckFilter.class);
+        if (securityFilterClazz != null) {
+            RestApiSetup.installServletFilters(context, securityFilterClazz);
+        }
+        return context;
+    }
+
     private SslContextFactory createContextFactory() throws KeyStoreException {
         SslContextFactory sslContextFactory = new SslContextFactory();
 

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/launcher/src/main/java/org/apache/brooklyn/launcher/camp/BrooklynCampPlatformLauncher.java
----------------------------------------------------------------------
diff --git a/launcher/src/main/java/org/apache/brooklyn/launcher/camp/BrooklynCampPlatformLauncher.java b/launcher/src/main/java/org/apache/brooklyn/launcher/camp/BrooklynCampPlatformLauncher.java
index ff9ab16..9c50562 100644
--- a/launcher/src/main/java/org/apache/brooklyn/launcher/camp/BrooklynCampPlatformLauncher.java
+++ b/launcher/src/main/java/org/apache/brooklyn/launcher/camp/BrooklynCampPlatformLauncher.java
@@ -63,6 +63,7 @@ public class BrooklynCampPlatformLauncher extends BrooklynCampPlatformLauncherAb
         new BrooklynCampPlatformLauncher().launch();
     }
 
+    @Override
     public void stopServers() throws Exception {
         brooklynLauncher.getServerDetails().getWebServer().stop();
         campServer.stop();

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/launcher/src/test/java/org/apache/brooklyn/rest/jsgui/BrooklynJavascriptGuiLauncher.java
----------------------------------------------------------------------
diff --git a/launcher/src/test/java/org/apache/brooklyn/rest/jsgui/BrooklynJavascriptGuiLauncher.java b/launcher/src/test/java/org/apache/brooklyn/rest/jsgui/BrooklynJavascriptGuiLauncher.java
index 02387c8..ced98db 100644
--- a/launcher/src/test/java/org/apache/brooklyn/rest/jsgui/BrooklynJavascriptGuiLauncher.java
+++ b/launcher/src/test/java/org/apache/brooklyn/rest/jsgui/BrooklynJavascriptGuiLauncher.java
@@ -64,7 +64,7 @@ public class BrooklynJavascriptGuiLauncher {
     
     /** due to the relative path search in {@link BrooklynRestApiLauncher} we can just call that method */ 
     public static Server startJavascriptAndRest() throws Exception {
-        return BrooklynRestApiLauncher.startRestResourcesViaFilter();
+        return BrooklynRestApiLauncher.startRestResourcesViaServlet();
     }
 
     /** not much fun without a REST server. 

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/launcher/src/test/java/org/apache/brooklyn/rest/jsgui/BrooklynJavascriptGuiLauncherTest.java
----------------------------------------------------------------------
diff --git a/launcher/src/test/java/org/apache/brooklyn/rest/jsgui/BrooklynJavascriptGuiLauncherTest.java b/launcher/src/test/java/org/apache/brooklyn/rest/jsgui/BrooklynJavascriptGuiLauncherTest.java
index e03652d..2896239 100644
--- a/launcher/src/test/java/org/apache/brooklyn/rest/jsgui/BrooklynJavascriptGuiLauncherTest.java
+++ b/launcher/src/test/java/org/apache/brooklyn/rest/jsgui/BrooklynJavascriptGuiLauncherTest.java
@@ -20,8 +20,9 @@ package org.apache.brooklyn.rest.jsgui;
 
 import org.apache.brooklyn.api.mgmt.ManagementContext;
 import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.server.BrooklynServiceAttributes;
 import org.apache.brooklyn.rest.BrooklynRestApiLauncherTestFixture;
-import org.apache.brooklyn.rest.util.OsgiCompat;
+import org.apache.brooklyn.util.core.osgi.Compat;
 import org.apache.brooklyn.util.http.HttpAsserts;
 import org.eclipse.jetty.server.NetworkConnector;
 import org.eclipse.jetty.server.Server;
@@ -75,7 +76,20 @@ public class BrooklynJavascriptGuiLauncherTest {
     }
 
     private ManagementContext getManagementContextFromJettyServerAttributes(Server server) {
-        return OsgiCompat.getManagementContext((ContextHandler) server.getHandler());
+        return getManagementContext((ContextHandler) server.getHandler());
+    }
+
+    /**
+     * Compatibility methods between karaf launcher and monolithic launcher.
+     *
+     * @todo Remove after transition to karaf launcher.
+     */
+    private static ManagementContext getManagementContext(ContextHandler jettyServerHandler) {
+        ManagementContext managementContext = Compat.getInstance().getManagementContext();
+        if (managementContext == null && jettyServerHandler != null) {
+            managementContext = (ManagementContext) jettyServerHandler.getAttribute(BrooklynServiceAttributes.BROOKLYN_MANAGEMENT_CONTEXT);
+        }
+        return managementContext;
     }
 
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/locations/jclouds/pom.xml
----------------------------------------------------------------------
diff --git a/locations/jclouds/pom.xml b/locations/jclouds/pom.xml
index 3338893..5db6ce7 100644
--- a/locations/jclouds/pom.xml
+++ b/locations/jclouds/pom.xml
@@ -98,6 +98,18 @@
         </dependency>
 
         <dependency>
+            <groupId>${jclouds.groupId}</groupId>
+            <artifactId>jclouds-core</artifactId>
+            <version>${jclouds.version}</version>
+            <exclusions>
+                <exclusion>
+                    <!-- Conflicts with javax.ws.rs-api -->
+                    <groupId>javax.ws.rs</groupId>
+                    <artifactId>jsr311-api</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
             <groupId>${jclouds.groupId}.driver</groupId>
             <artifactId>jclouds-slf4j</artifactId>
             <version>${jclouds.version}</version>

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/parent/pom.xml
----------------------------------------------------------------------
diff --git a/parent/pom.xml b/parent/pom.xml
index 2b1f4a5..d87baae 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -127,26 +127,14 @@
                 <version>${slf4j.version}</version>
             </dependency>
             <dependency>
-                <groupId>com.sun.jersey</groupId>
-                <artifactId>jersey-server</artifactId>
-                <version>${jersey.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>com.sun.jersey.contribs</groupId>
-                <artifactId>jersey-multipart</artifactId>
-                <version>${jersey.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>com.sun.jersey.jersey-test-framework</groupId>
-                <artifactId>jersey-test-framework-inmemory</artifactId>
-                <version>${jersey.version}</version>
-                <scope>test</scope>
+                <groupId>org.eclipse.jetty</groupId>
+                <artifactId>jetty-http</artifactId>
+                <version>${jetty.version}</version>
             </dependency>
             <dependency>
-                <groupId>com.sun.jersey.jersey-test-framework</groupId>
-                <artifactId>jersey-test-framework-grizzly2</artifactId>
-                <version>${jersey.version}</version>
-                <scope>test</scope>
+                <groupId>org.eclipse.jetty</groupId>
+                <artifactId>jetty-io</artifactId>
+                <version>${jetty.version}</version>
             </dependency>
             <dependency>
                 <groupId>org.apache.commons</groupId>
@@ -169,6 +157,26 @@
                 <version>${jetty.version}</version>
             </dependency>
             <dependency>
+                <groupId>org.eclipse.jetty</groupId>
+                <artifactId>jetty-security</artifactId>
+                <version>${jetty.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.eclipse.jetty</groupId>
+                <artifactId>jetty-webapp</artifactId>
+                <version>${jetty.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.eclipse.jetty</groupId>
+                <artifactId>jetty-io</artifactId>
+                <version>${jetty.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.eclipse.jetty</groupId>
+                <artifactId>jetty-http</artifactId>
+                <version>${jetty.version}</version>
+            </dependency>
+            <dependency>
                 <groupId>org.eclipse.jetty.toolchain</groupId>
                 <artifactId>jetty-schemas</artifactId>
                 <version>${jetty-schemas.version}</version>
@@ -230,16 +238,6 @@
                 <version>${javax-servlet.version}</version>
             </dependency>
             <dependency>
-                <groupId>org.eclipse.jetty</groupId>
-                <artifactId>jetty-security</artifactId>
-                <version>${jetty.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>com.sun.jersey</groupId>
-                <artifactId>jersey-core</artifactId>
-                <version>${jersey.version}</version>
-            </dependency>
-            <dependency>
                 <groupId>com.beust</groupId>
                 <artifactId>jcommander</artifactId>
                 <version>${jcommander.version}</version>
@@ -341,31 +339,11 @@
                 <version>${reflections.version}</version>
             </dependency>
             <dependency>
-                <groupId>com.sun.jersey</groupId>
-                <artifactId>jersey-client</artifactId>
-                <version>${jersey.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>org.eclipse.jetty</groupId>
-                <artifactId>jetty-webapp</artifactId>
-                <version>${jetty.version}</version>
-            </dependency>
-            <dependency>
                 <groupId>org.apache.felix</groupId>
                 <artifactId>org.apache.felix.framework</artifactId>
                 <version>${felix.framework.version}</version>
             </dependency>
             <dependency>
-                <groupId>com.sun.jersey</groupId>
-                <artifactId>jersey-servlet</artifactId>
-                <version>${jersey.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>javax.ws.rs</groupId>
-                <artifactId>jsr311-api</artifactId>
-                <version>${jsr311-api.version}</version>
-            </dependency>
-            <dependency>
                 <groupId>org.glassfish.external</groupId>
                 <artifactId>opendmk_jmxremote_optional_jar</artifactId>
                 <version>${opendmk_jmxremote_optional_jar.version}</version>
@@ -376,26 +354,6 @@
                 <version>${slf4j.version}</version>
             </dependency>
             <dependency>
-                <groupId>com.sun.jersey</groupId>
-                <artifactId>jersey-json</artifactId>
-                <version>${jersey.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>org.jboss.resteasy</groupId>
-                <artifactId>resteasy-jaxrs</artifactId>
-                <version>${resteasy.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>org.jboss.resteasy</groupId>
-                <artifactId>resteasy-jackson2-provider</artifactId>
-                <version>${resteasy.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>org.jboss.resteasy</groupId>
-                <artifactId>jaxrs-api</artifactId>
-                <version>${resteasy.version}</version>
-            </dependency>
-            <dependency>
                 <groupId>javax.validation</groupId>
                 <artifactId>validation-api</artifactId>
                 <version>${validation-api.version}</version>
@@ -510,6 +468,94 @@
                 <artifactId>asm</artifactId>
                 <version>${minidev.asm.version}</version>
             </dependency>
+
+            <!-- JAX-RS dependencies-->
+            <!--  JAX-RS 1.1 API -->
+            <dependency>
+                <groupId>javax.ws.rs</groupId>
+                <artifactId>jsr311-api</artifactId>
+                <version>${jsr311-api.version}</version>
+            </dependency>
+            <!--  JAX-RS 2.0 API -->
+            <dependency>
+                <groupId>javax.ws.rs</groupId>
+                <artifactId>javax.ws.rs-api</artifactId>
+                <version>${jax-rs-api.version}</version>
+            </dependency>
+            <!--  JAX-RS 2.0 Apache CXF Implementation -->
+            <dependency>
+                <groupId>org.apache.cxf</groupId>
+                <artifactId>cxf-rt-frontend-jaxrs</artifactId>
+                <version>${cxf.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.cxf</groupId>
+                <artifactId>cxf-rt-transports-local</artifactId>
+                <version>${cxf.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.cxf</groupId>
+                <artifactId>cxf-rt-transports-http-jetty</artifactId>
+                <version>${cxf.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.cxf</groupId>
+                <artifactId>cxf-rt-rs-client</artifactId>
+                <version>${cxf.version}</version>
+            </dependency>
+            <!--  JAX-RS 1.1 Jersey Implementation -->
+            <dependency>
+                <groupId>com.sun.jersey</groupId>
+                <artifactId>jersey-core</artifactId>
+                <version>${jersey.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.sun.jersey</groupId>
+                <artifactId>jersey-client</artifactId>
+                <version>${jersey.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.sun.jersey</groupId>
+                <artifactId>jersey-server</artifactId>
+                <version>${jersey.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.sun.jersey</groupId>
+                <artifactId>jersey-servlet</artifactId>
+                <version>${jersey.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.sun.jersey</groupId>
+                <artifactId>jersey-json</artifactId>
+                <version>${jersey.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.sun.jersey.jersey-test-framework</groupId>
+                <artifactId>jersey-test-framework-inmemory</artifactId>
+                <version>${jersey.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.sun.jersey.jersey-test-framework</groupId>
+                <artifactId>jersey-test-framework-grizzly2</artifactId>
+                <version>${jersey.version}</version>
+            </dependency>
+            <!--  JAX-RS 2.0 RESTEasy Implementation -->
+            <dependency>
+                <groupId>org.jboss.resteasy</groupId>
+                <artifactId>resteasy-jaxrs</artifactId>
+                <version>${resteasy.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.jboss.resteasy</groupId>
+                <artifactId>resteasy-jackson2-provider</artifactId>
+                <version>${resteasy.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.jboss.resteasy</groupId>
+                <artifactId>jaxrs-api</artifactId>
+                <version>${resteasy.version}</version>
+            </dependency>
+            <!-- / JAX-RS -->
         </dependencies>
     </dependencyManagement>
 

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 5bfe68a..588198a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -18,7 +18,7 @@
     under the License.
 -->
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 
     <modelVersion>4.0.0</modelVersion>
     <parent>
@@ -101,6 +101,7 @@
         <fasterxml.jackson.version>2.4.5</fasterxml.jackson.version>
         <resteasy.version>3.0.8.Final</resteasy.version>
         <jersey.version>1.19</jersey.version>
+        <cxf.version>3.1.4</cxf.version>
         <httpclient.version>4.4.1</httpclient.version>
         <commons-lang3.version>3.3.2</commons-lang3.version>
         <groovy.version>2.3.7</groovy.version> <!-- Version supported by https://github.com/groovy/groovy-eclipse/wiki/Groovy-Eclipse-2.9.1-Release-Notes -->
@@ -136,6 +137,7 @@
         <jopt.version>4.3</jopt.version>
         <concurrentlinkedhashmap.version>1.0_jdk5</concurrentlinkedhashmap.version>
         <javax-servlet.version>3.1.0</javax-servlet.version>
+        <javax-servlet-jsp.version>2.0</javax-servlet-jsp.version>
         <jcommander.version>1.27</jcommander.version>
         <xml-apis.version>1.0.b2</xml-apis.version>
         <jsr250-api.version>1.0</jsr250-api.version>
@@ -146,6 +148,7 @@
         <commons-lang.version>2.4</commons-lang.version>
         <hamcrest.version>1.1</hamcrest.version>
         <jsr311-api.version>1.1.1</jsr311-api.version>
+        <jax-rs-api.version>2.0.1</jax-rs-api.version>
         <maxmind.version>0.8.1</maxmind.version>
         <jna.version>4.0.0</jna.version>
         <winrm4j.version>0.3.1</winrm4j.version>
@@ -205,10 +208,12 @@
         <module>logging/logback-xml</module>
         <module>rest/rest-api</module>
         <module>rest/rest-client</module>
+	<module>rest/rest-resources</module>
         <module>rest/rest-server</module>
         <module>test-framework</module>
         <module>test-support</module>
 
+
         <module>utils/common</module>
         <module>utils/groovy</module>
         <module>utils/jmx/jmxmp-ssl-agent</module>

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-api/pom.xml
----------------------------------------------------------------------
diff --git a/rest/rest-api/pom.xml b/rest/rest-api/pom.xml
index fc61f97..9f50fee 100644
--- a/rest/rest-api/pom.xml
+++ b/rest/rest-api/pom.xml
@@ -50,10 +50,6 @@
             <version>${fasterxml.jackson.version}</version>
         </dependency>
         <dependency>
-            <groupId>com.sun.jersey.contribs</groupId>
-            <artifactId>jersey-multipart</artifactId>
-        </dependency>
-        <dependency>
             <groupId>javax.validation</groupId>
             <artifactId>validation-api</artifactId>
         </dependency>
@@ -74,8 +70,8 @@
             <artifactId>javax.servlet-api</artifactId>
         </dependency>
         <dependency>
-            <groupId>com.sun.jersey</groupId>
-            <artifactId>jersey-core</artifactId>
+            <groupId>javax.ws.rs</groupId>
+            <artifactId>javax.ws.rs-api</artifactId>
         </dependency>
 
         <dependency>
@@ -144,6 +140,19 @@
                     </execution>
                 </executions>
             </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <configuration>
+                    <instructions>
+                        <Import-Package>
+                            javax.ws.rs;version="[1.1,2.0]",
+                            javax.ws.rs.core;version="[1.1,2.0]",
+                            *
+                        </Import-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
         </plugins>
         <pluginManagement>
         	<plugins>

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/AccessApi.java
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/AccessApi.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/AccessApi.java
index b2af640..b0371ca 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/AccessApi.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/AccessApi.java
@@ -35,7 +35,7 @@ import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
 
 @Beta
-@Path("/v1/access")
+@Path("/access")
 @Api("Access Control")
 @Produces(MediaType.APPLICATION_JSON)
 @Consumes(MediaType.APPLICATION_JSON)

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ActivityApi.java
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ActivityApi.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ActivityApi.java
index de93c64..d131606 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ActivityApi.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ActivityApi.java
@@ -31,7 +31,7 @@ import io.swagger.annotations.ApiResponses;
 import javax.ws.rs.*;
 import javax.ws.rs.core.MediaType;
 
-@Path("/v1/activities")
+@Path("/activities")
 @Api("Activities")
 @Produces(MediaType.APPLICATION_JSON)
 @Consumes(MediaType.APPLICATION_JSON)

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ApplicationApi.java
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ApplicationApi.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ApplicationApi.java
index cd8f22e..f1e167f 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ApplicationApi.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ApplicationApi.java
@@ -45,7 +45,7 @@ import io.swagger.annotations.ApiResponses;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
 
-@Path("/v1/applications")
+@Path("/applications")
 @Api("Applications")
 @Produces(MediaType.APPLICATION_JSON)
 @Consumes(MediaType.APPLICATION_JSON)

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/CatalogApi.java
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/CatalogApi.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/CatalogApi.java
index cf18b89..f561759 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/CatalogApi.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/CatalogApi.java
@@ -18,7 +18,6 @@
  */
 package org.apache.brooklyn.rest.api;
 
-import java.io.InputStream;
 import java.util.List;
 
 import javax.validation.Valid;
@@ -39,29 +38,18 @@ import org.apache.brooklyn.rest.domain.CatalogItemSummary;
 import org.apache.brooklyn.rest.domain.CatalogLocationSummary;
 import org.apache.brooklyn.rest.domain.CatalogPolicySummary;
 
-import com.sun.jersey.core.header.FormDataContentDisposition;
-import com.sun.jersey.multipart.FormDataParam;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiResponse;
 import io.swagger.annotations.ApiResponses;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
 
-@Path("/v1/catalog")
+@Path("/catalog")
 @Api("Catalog")
 @Consumes(MediaType.APPLICATION_JSON)
 @Produces(MediaType.APPLICATION_JSON)
 public interface CatalogApi {
 
-    @POST
-    @ApiOperation(value = "Add a catalog item (e.g. new type of entity, policy or location) by uploading YAML descriptor from browser using multipart/form-data",
-        response = String.class)
-    @Consumes(MediaType.MULTIPART_FORM_DATA)
-    public Response createFromMultipart(
-        @ApiParam(name = "yaml", value = "multipart/form-data file input field")
-        @FormDataParam("yaml") InputStream uploadedInputStream,
-        @FormDataParam("yaml") FormDataContentDisposition fileDetail);
-
     @Consumes
     @POST
     @ApiOperation(value = "Add a catalog item (e.g. new type of entity, policy or location) by uploading YAML descriptor "
@@ -89,7 +77,7 @@ public interface CatalogApi {
     @ApiResponses(value = {
         @ApiResponse(code = 404, message = "Entity not found")
     })
-    public void deleteEntity(
+    public void deleteEntity_0_7_0(
         @ApiParam(name = "entityId", value = "The ID of the entity or template to delete", required = true)
         @PathParam("entityId") String entityId) throws Exception;
 
@@ -181,7 +169,7 @@ public interface CatalogApi {
     @ApiResponses(value = {
         @ApiResponse(code = 404, message = "Entity not found")
     })
-    public CatalogEntitySummary getEntity(
+    public CatalogEntitySummary getEntity_0_7_0(
         @ApiParam(name = "entityId", value = "The ID of the entity or template to retrieve", required = true)
         @PathParam("entityId") String entityId) throws Exception;
 
@@ -210,7 +198,7 @@ public interface CatalogApi {
     @ApiResponses(value = {
         @ApiResponse(code = 404, message = "Entity not found")
     })
-    public CatalogEntitySummary getApplication(
+    public CatalogEntitySummary getApplication_0_7_0(
         @ApiParam(name = "applicationId", value = "The ID of the application to retrieve", required = true)
         @PathParam("applicationId") String applicationId) throws Exception;
 
@@ -252,7 +240,7 @@ public interface CatalogApi {
     @ApiResponses(value = {
         @ApiResponse(code = 404, message = "Entity not found")
     })
-    public CatalogItemSummary getPolicy(
+    public CatalogItemSummary getPolicy_0_7_0(
         @ApiParam(name = "policyId", value = "The ID of the policy to retrieve", required = true)
         @PathParam("policyId") String policyId) throws Exception;
 
@@ -264,7 +252,7 @@ public interface CatalogApi {
     @ApiResponses(value = {
         @ApiResponse(code = 404, message = "Entity not found")
     })
-    public CatalogItemSummary getPolicy(
+    public CatalogPolicySummary getPolicy(
         @ApiParam(name = "policyId", value = "The ID of the policy to retrieve", required = true)
         @PathParam("policyId") String policyId,
         @ApiParam(name = "version", value = "The version identifier of the application to retrieve", required = true)
@@ -293,7 +281,7 @@ public interface CatalogApi {
     @ApiResponses(value = {
         @ApiResponse(code = 404, message = "Entity not found")
     })
-    public CatalogItemSummary getLocation(
+    public CatalogItemSummary getLocation_0_7_0(
         @ApiParam(name = "locationId", value = "The ID of the location to retrieve", required = true)
         @PathParam("locationId") String locationId) throws Exception;
 
@@ -320,7 +308,7 @@ public interface CatalogApi {
             @ApiResponse(code = 404, message = "Item not found")
         })
     @Produces("application/image")
-    public Response getIcon(
+    public Response getIcon_0_7_0(
         @ApiParam(name = "itemId", value = "ID of catalog item (application, entity, policy, location)")
         @PathParam("itemId") @DefaultValue("") String itemId);
 

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/EffectorApi.java
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/EffectorApi.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/EffectorApi.java
index e6fae45..2865223 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/EffectorApi.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/EffectorApi.java
@@ -32,7 +32,7 @@ import javax.ws.rs.core.Response;
 import java.util.List;
 import java.util.Map;
 
-@Path("/v1/applications/{application}/entities/{entity}/effectors")
+@Path("/applications/{application}/entities/{entity}/effectors")
 @Api("Entity Effectors")
 @Produces(MediaType.APPLICATION_JSON)
 @Consumes(MediaType.APPLICATION_JSON)

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/EntityApi.java
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/EntityApi.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/EntityApi.java
index d081526..e5683ed 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/EntityApi.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/EntityApi.java
@@ -35,7 +35,7 @@ import javax.ws.rs.core.Response;
 import java.util.List;
 import java.util.Map;
 
-@Path("/v1/applications/{application}/entities")
+@Path("/applications/{application}/entities")
 @Api("Entities")
 @Produces(MediaType.APPLICATION_JSON)
 @Consumes(MediaType.APPLICATION_JSON)
@@ -50,7 +50,7 @@ public interface EntityApi {
     })
     public List<EntitySummary> list(
             @ApiParam(value = "Application ID or name", required = true)
-            @PathParam("application") final String application) ;
+            @PathParam("application") final String application);
 
     @GET
     @Path("/{entity}")

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/EntityConfigApi.java
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/EntityConfigApi.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/EntityConfigApi.java
index af68eb3..4a5bbde 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/EntityConfigApi.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/EntityConfigApi.java
@@ -39,7 +39,7 @@ import io.swagger.annotations.ApiResponses;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
 
-@Path("/v1/applications/{application}/entities/{entity}/config")
+@Path("/applications/{application}/entities/{entity}/config")
 @Api("Entity Config")
 @Produces(MediaType.APPLICATION_JSON)
 @Consumes(MediaType.APPLICATION_JSON)
@@ -96,7 +96,7 @@ public interface EntityConfigApi {
     @ApiResponses(value = {
             @ApiResponse(code = 404, message = "Could not find application, entity or config key")
     })
-    @Produces(MediaType.TEXT_PLAIN)
+    @Produces(MediaType.TEXT_PLAIN + ";qs=0.9")
     public String getPlain(
             @ApiParam(value = "Application ID or name", required = true)
             @PathParam("application") String application,

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/LocationApi.java
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/LocationApi.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/LocationApi.java
index 5eb25ac..ea632d5 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/LocationApi.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/LocationApi.java
@@ -42,7 +42,7 @@ import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
 
 @SuppressWarnings("deprecation")
-@Path("/v1/locations")
+@Path("/locations")
 @Api("Locations")
 @Produces(MediaType.APPLICATION_JSON)
 @Consumes(MediaType.APPLICATION_JSON)

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/PolicyApi.java
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/PolicyApi.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/PolicyApi.java
index 983af7d..a698f7d 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/PolicyApi.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/PolicyApi.java
@@ -32,7 +32,7 @@ import javax.ws.rs.core.Response;
 import java.util.List;
 import java.util.Map;
 
-@Path("/v1/applications/{application}/entities/{entity}/policies")
+@Path("/applications/{application}/entities/{entity}/policies")
 @Api("Entity Policies")
 @Produces(MediaType.APPLICATION_JSON)
 @Consumes(MediaType.APPLICATION_JSON)

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/PolicyConfigApi.java
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/PolicyConfigApi.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/PolicyConfigApi.java
index 223c6ac..6c26228 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/PolicyConfigApi.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/PolicyConfigApi.java
@@ -31,7 +31,7 @@ import javax.ws.rs.core.Response;
 import java.util.List;
 import java.util.Map;
 
-@Path("/v1/applications/{application}/entities/{entity}/policies/{policy}/config")
+@Path("/applications/{application}/entities/{entity}/policies/{policy}/config")
 @Api("Entity Policy Config")
 @Produces(MediaType.APPLICATION_JSON)
 @Consumes(MediaType.APPLICATION_JSON)

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ScriptApi.java
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ScriptApi.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ScriptApi.java
index ed90618..72af2c3 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ScriptApi.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ScriptApi.java
@@ -31,7 +31,7 @@ import javax.ws.rs.Produces;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.MediaType;
 
-@Path("/v1/script")
+@Path("/script")
 @Api("Scripting")
 @Produces(MediaType.APPLICATION_JSON)
 @Consumes(MediaType.APPLICATION_JSON)

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/SensorApi.java
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/SensorApi.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/SensorApi.java
index 73c37cc..7947a80 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/SensorApi.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/SensorApi.java
@@ -40,7 +40,7 @@ import io.swagger.annotations.ApiResponses;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
 
-@Path("/v1/applications/{application}/entities/{entity}/sensors")
+@Path("/applications/{application}/entities/{entity}/sensors")
 @Api("Entity Sensors")
 @Produces(MediaType.APPLICATION_JSON)
 @Consumes(MediaType.APPLICATION_JSON)
@@ -76,6 +76,7 @@ public interface SensorApi {
     @ApiResponses(value = {
             @ApiResponse(code = 404, message = "Could not find application, entity or sensor")
     })
+    @Produces({MediaType.APPLICATION_JSON})
     public Object get(
             @ApiParam(value = "Application ID or name", required = true)
             @PathParam("application") final String application,
@@ -93,7 +94,7 @@ public interface SensorApi {
     @ApiResponses(value = {
             @ApiResponse(code = 404, message = "Could not find application, entity or sensor")
     })
-    @Produces(MediaType.TEXT_PLAIN)
+    @Produces(MediaType.TEXT_PLAIN + ";qs=0.9")
     public String getPlain(
             @ApiParam(value = "Application ID or name", required = true)
             @PathParam("application") final String application,

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ServerApi.java
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ServerApi.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ServerApi.java
index bc38c1f..90a195a 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ServerApi.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ServerApi.java
@@ -44,7 +44,7 @@ import io.swagger.annotations.ApiResponses;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
 
-@Path("/v1/server")
+@Path("/server")
 @Api("Server")
 @Produces(MediaType.APPLICATION_JSON)
 @Beta

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/UsageApi.java
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/UsageApi.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/UsageApi.java
index b483cf0..61e4cbf 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/UsageApi.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/UsageApi.java
@@ -36,7 +36,7 @@ import io.swagger.annotations.ApiResponses;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
 
-@Path("/v1/usage")
+@Path("/usage")
 @Api("Usage")
 @Produces(MediaType.APPLICATION_JSON)
 @Consumes(MediaType.APPLICATION_JSON)

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/VersionApi.java
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/VersionApi.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/VersionApi.java
index f1e4601..12dc76e 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/VersionApi.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/VersionApi.java
@@ -27,11 +27,11 @@ import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
 import javax.ws.rs.core.MediaType;
 
-@Path("/v1/version")
+@Path("/version")
 @Api("Version")
 @Produces(MediaType.APPLICATION_JSON)
 @Consumes(MediaType.APPLICATION_JSON)
-/** @deprecated since 0.7.0; use /v1/server/version */
+/** @deprecated since 0.7.0; use /server/version */
 @Deprecated
 public interface VersionApi {
 


[03/34] brooklyn-server git commit: [BROOKLYN-183] REST API using CXF JAX-RS 2.0 implementation

Posted by he...@apache.org.
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/EntityResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/EntityResourceTest.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/EntityResourceTest.java
deleted file mode 100644
index 1c091da..0000000
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/EntityResourceTest.java
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.rest.resources;
-
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
-import javax.annotation.Nullable;
-import javax.ws.rs.core.MediaType;
-
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.core.entity.Entities;
-import org.apache.brooklyn.core.entity.EntityInternal;
-import org.apache.brooklyn.core.test.entity.TestEntity;
-import org.apache.brooklyn.entity.stock.BasicApplication;
-import org.apache.brooklyn.rest.domain.ApplicationSpec;
-import org.apache.brooklyn.rest.domain.EntitySpec;
-import org.apache.brooklyn.rest.domain.TaskSummary;
-import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
-import org.apache.brooklyn.rest.testing.mocks.RestMockSimpleEntity;
-import org.apache.brooklyn.util.collections.MutableList;
-import org.apache.brooklyn.util.exceptions.Exceptions;
-import org.apache.brooklyn.util.http.HttpAsserts;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.Assert;
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.Test;
-
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.google.common.base.Predicate;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
-import com.sun.jersey.api.client.ClientResponse;
-
-@Test(singleThreaded = true)
-public class EntityResourceTest extends BrooklynRestResourceTest {
-
-    private static final Logger log = LoggerFactory.getLogger(EntityResourceTest.class);
-    
-    private final ApplicationSpec simpleSpec = ApplicationSpec.builder()
-            .name("simple-app")
-            .entities(ImmutableSet.of(new EntitySpec("simple-ent", RestMockSimpleEntity.class.getName())))
-            .locations(ImmutableSet.of("localhost"))
-            .build();
-
-    private EntityInternal entity;
-
-    private static final String entityEndpoint = "/v1/applications/simple-app/entities/simple-ent";
-
-    @BeforeClass(alwaysRun = true)
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-
-        // Deploy application
-        ClientResponse deploy = clientDeploy(simpleSpec);
-        waitForApplicationToBeRunning(deploy.getLocation());
-
-        // Add tag
-        entity = (EntityInternal) Iterables.find(getManagementContext().getEntityManager().getEntities(), new Predicate<Entity>() {
-            @Override
-            public boolean apply(@Nullable Entity input) {
-                return "RestMockSimpleEntity".equals(input.getEntityType().getSimpleName());
-            }
-        });
-    }
-
-    @Test
-    public void testTagsSanity() throws Exception {
-        entity.tags().addTag("foo");
-        
-        ClientResponse response = client().resource(entityEndpoint + "/tags")
-                .accept(MediaType.APPLICATION_JSON_TYPE)
-                .get(ClientResponse.class);
-        String data = response.getEntity(String.class);
-        
-        try {
-            List<Object> tags = new ObjectMapper().readValue(data, new TypeReference<List<Object>>() {});
-            Assert.assertTrue(tags.contains("foo"));
-            Assert.assertFalse(tags.contains("bar"));
-        } catch (Exception e) {
-            Exceptions.propagateIfFatal(e);
-            throw new IllegalStateException("Error with deserialization of tags list: "+e+"\n"+data, e);
-        }
-    }
-    
-    @Test
-    public void testRename() throws Exception {
-        try {
-            ClientResponse response = client().resource(entityEndpoint + "/name")
-                .queryParam("name", "New Name")
-                .post(ClientResponse.class);
-
-            HttpAsserts.assertHealthyStatusCode(response.getStatus());
-            Assert.assertTrue(entity.getDisplayName().equals("New Name"));
-        } finally {
-            // restore it for other tests!
-            entity.setDisplayName("simple-ent");
-        }
-    }
-    
-    @Test
-    public void testAddChild() throws Exception {
-        try {
-            // to test in GUI: 
-            // services: [ { type: org.apache.brooklyn.entity.stock.BasicEntity }]
-            ClientResponse response = client().resource(entityEndpoint + "/children?timeout=10s")
-                .entity("services: [ { type: "+TestEntity.class.getName()+" }]", "application/yaml")
-                .post(ClientResponse.class);
-
-            HttpAsserts.assertHealthyStatusCode(response.getStatus());
-            Assert.assertEquals(entity.getChildren().size(), 1);
-            Entity child = Iterables.getOnlyElement(entity.getChildren());
-            Assert.assertTrue(Entities.isManaged(child));
-            
-            TaskSummary task = response.getEntity(TaskSummary.class);
-            Assert.assertEquals(task.getResult(), MutableList.of(child.getId()));
-            
-        } finally {
-            // restore it for other tests
-            Collection<Entity> children = entity.getChildren();
-            if (!children.isEmpty()) Entities.unmanage(Iterables.getOnlyElement(children));
-        }
-    }
-    
-    @Test
-    public void testTagsDoNotSerializeTooMuch() throws Exception {
-        entity.tags().addTag("foo");
-        entity.tags().addTag(entity.getParent());
-
-        ClientResponse response = client().resource(entityEndpoint + "/tags")
-                .accept(MediaType.APPLICATION_JSON)
-                .get(ClientResponse.class);
-        String raw = response.getEntity(String.class);
-        log.info("TAGS raw: "+raw);
-        HttpAsserts.assertHealthyStatusCode(response.getStatus());
-        
-        Assert.assertTrue(raw.contains(entity.getParent().getId()), "unexpected app tag, does not include ID: "+raw);
-        
-        Assert.assertTrue(raw.length() < 1000, "unexpected app tag, includes too much mgmt info (len "+raw.length()+"): "+raw);
-        
-        Assert.assertFalse(raw.contains(entity.getManagementContext().getManagementNodeId()), "unexpected app tag, includes too much mgmt info: "+raw);
-        Assert.assertFalse(raw.contains("managementContext"), "unexpected app tag, includes too much mgmt info: "+raw);
-        Assert.assertFalse(raw.contains("localhost"), "unexpected app tag, includes too much mgmt info: "+raw);
-        Assert.assertFalse(raw.contains("catalog"), "unexpected app tag, includes too much mgmt info: "+raw);
-
-        @SuppressWarnings("unchecked")
-        List<Object> tags = mapper().readValue(raw, List.class);
-        log.info("TAGS are: "+tags);
-        
-        Assert.assertEquals(tags.size(), 2, "tags are: "+tags);
-
-        Assert.assertTrue(tags.contains("foo"));
-        Assert.assertFalse(tags.contains("bar"));
-        
-        MutableList<Object> appTags = MutableList.copyOf(tags);
-        appTags.remove("foo");
-        Object appTag = Iterables.getOnlyElement( appTags );
-        
-        // it's a map at this point, because there was no way to make it something stronger than Object
-        Assert.assertTrue(appTag instanceof Map, "Should have deserialized an entity: "+appTag);
-        // let's re-serialize it as an entity
-        appTag = mapper().readValue(mapper().writeValueAsString(appTag), Entity.class);
-        
-        Assert.assertTrue(appTag instanceof Entity, "Should have deserialized an entity: "+appTag);
-        Assert.assertEquals( ((Entity)appTag).getId(), entity.getApplicationId(), "Wrong ID: "+appTag);
-        Assert.assertTrue(appTag instanceof BasicApplication, "Should have deserialized BasicApplication: "+appTag);
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ErrorResponseTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ErrorResponseTest.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ErrorResponseTest.java
deleted file mode 100644
index 0979875..0000000
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ErrorResponseTest.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.rest.resources;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.assertTrue;
-
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response.Status;
-
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.Test;
-
-import org.apache.brooklyn.rest.domain.ApiError;
-import org.apache.brooklyn.rest.domain.ApplicationSpec;
-import org.apache.brooklyn.rest.domain.EntitySpec;
-import org.apache.brooklyn.rest.domain.PolicySummary;
-import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
-import org.apache.brooklyn.rest.testing.mocks.RestMockSimpleEntity;
-import org.apache.brooklyn.rest.testing.mocks.RestMockSimplePolicy;
-
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Maps;
-import com.sun.jersey.api.client.ClientResponse;
-
-public class ErrorResponseTest extends BrooklynRestResourceTest {
-
-    private final ApplicationSpec simpleSpec = ApplicationSpec.builder().name("simple-app").entities(
-            ImmutableSet.of(new EntitySpec("simple-ent", RestMockSimpleEntity.class.getName()))).locations(
-            ImmutableSet.of("localhost")).build();
-    private String policyId;
-
-    @BeforeClass(alwaysRun = true)
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-
-        ClientResponse aResponse = clientDeploy(simpleSpec);
-        waitForApplicationToBeRunning(aResponse.getLocation());
-
-        String policiesEndpoint = "/v1/applications/simple-app/entities/simple-ent/policies";
-
-        ClientResponse pResponse = client().resource(policiesEndpoint)
-                .queryParam("type", RestMockSimplePolicy.class.getCanonicalName())
-                .type(MediaType.APPLICATION_JSON_TYPE)
-                .post(ClientResponse.class, Maps.newHashMap());
-        PolicySummary response = pResponse.getEntity(PolicySummary.class);
-        assertNotNull(response.getId());
-        policyId = response.getId();
-    }
-
-    @Test
-    public void testResponseToBadRequest() {
-        String resource = "/v1/applications/simple-app/entities/simple-ent/policies/"+policyId+"/config/"
-                + RestMockSimplePolicy.INTEGER_CONFIG.getName() + "/set";
-
-        ClientResponse response = client().resource(resource)
-                .queryParam("value", "notanumber")
-                .post(ClientResponse.class);
-
-        assertEquals(response.getStatus(), Status.BAD_REQUEST.getStatusCode());
-        assertEquals(response.getHeaders().getFirst("Content-Type"), MediaType.APPLICATION_JSON);
-
-        ApiError error = response.getEntity(ApiError.class);
-        assertTrue(error.getMessage().toLowerCase().contains("cannot coerce"));
-    }
-
-    @Test
-    public void testResponseToWrongMethod() {
-        String resource = "/v1/applications/simple-app/entities/simple-ent/policies/"+policyId+"/config/"
-                + RestMockSimplePolicy.INTEGER_CONFIG.getName() + "/set";
-
-        // Should be POST, not GET
-        ClientResponse response = client().resource(resource)
-                .queryParam("value", "4")
-                .get(ClientResponse.class);
-
-        assertEquals(response.getStatus(), 405);
-        // Can we assert anything about the content type?
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/LocationResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/LocationResourceTest.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/LocationResourceTest.java
deleted file mode 100644
index d8897af..0000000
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/LocationResourceTest.java
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.rest.resources;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertTrue;
-
-import java.net.URI;
-import java.util.Map;
-import java.util.Set;
-
-import javax.annotation.Nullable;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.Assert;
-import org.testng.annotations.Test;
-
-import com.google.api.client.repackaged.com.google.common.base.Joiner;
-import com.google.common.base.Predicate;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Iterables;
-import com.sun.jersey.api.client.ClientResponse;
-import com.sun.jersey.api.client.GenericType;
-
-import org.apache.brooklyn.api.location.LocationSpec;
-import org.apache.brooklyn.core.location.SimulatedLocation;
-import org.apache.brooklyn.location.jclouds.JcloudsLocation;
-import org.apache.brooklyn.rest.domain.CatalogLocationSummary;
-import org.apache.brooklyn.rest.domain.LocationSummary;
-import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
-import org.apache.brooklyn.test.Asserts;
-
-@Test(singleThreaded = true)
-public class LocationResourceTest extends BrooklynRestResourceTest {
-
-    private static final Logger log = LoggerFactory.getLogger(LocationResourceTest.class);
-    private String legacyLocationName = "my-jungle-legacy";
-    private String legacyLocationVersion = "0.0.0.SNAPSHOT";
-    
-    private String locationName = "my-jungle";
-    private String locationVersion = "0.1.2";
-    
-    @Test
-    @Deprecated
-    public void testAddLegacyLocationDefinition() {
-        Map<String, String> expectedConfig = ImmutableMap.of(
-                "identity", "bob",
-                "credential", "CR3dential");
-        ClientResponse response = client().resource("/v1/locations")
-                .type(MediaType.APPLICATION_JSON_TYPE)
-                .post(ClientResponse.class, new org.apache.brooklyn.rest.domain.LocationSpec(legacyLocationName, "aws-ec2:us-east-1", expectedConfig));
-
-        URI addedLegacyLocationUri = response.getLocation();
-        log.info("added legacy, at: " + addedLegacyLocationUri);
-        LocationSummary location = client().resource(response.getLocation()).get(LocationSummary.class);
-        log.info(" contents: " + location);
-        assertEquals(location.getSpec(), "brooklyn.catalog:"+legacyLocationName+":"+legacyLocationVersion);
-        assertTrue(addedLegacyLocationUri.toString().startsWith("/v1/locations/"));
-
-        JcloudsLocation l = (JcloudsLocation) getManagementContext().getLocationRegistry().resolve(legacyLocationName);
-        Assert.assertEquals(l.getProvider(), "aws-ec2");
-        Assert.assertEquals(l.getRegion(), "us-east-1");
-        Assert.assertEquals(l.getIdentity(), "bob");
-        Assert.assertEquals(l.getCredential(), "CR3dential");
-    }
-
-    @SuppressWarnings("deprecation")
-    @Test
-    public void testAddNewLocationDefinition() {
-        String yaml = Joiner.on("\n").join(ImmutableList.of(
-                "brooklyn.catalog:",
-                "  symbolicName: "+locationName,
-                "  version: " + locationVersion,
-                "",
-                "brooklyn.locations:",
-                "- type: "+"aws-ec2:us-east-1",
-                "  brooklyn.config:",
-                "    identity: bob",
-                "    credential: CR3dential"));
-
-        
-        ClientResponse response = client().resource("/v1/catalog")
-                .post(ClientResponse.class, yaml);
-
-        assertEquals(response.getStatus(), Response.Status.CREATED.getStatusCode());
-        
-
-        URI addedCatalogItemUri = response.getLocation();
-        log.info("added, at: " + addedCatalogItemUri);
-        
-        // Ensure location definition exists
-        CatalogLocationSummary locationItem = client().resource("/v1/catalog/locations/"+locationName + "/" + locationVersion)
-                .get(CatalogLocationSummary.class);
-        log.info(" item: " + locationItem);
-        LocationSummary locationSummary = client().resource(URI.create("/v1/locations/"+locationName+"/")).get(LocationSummary.class);
-        log.info(" summary: " + locationSummary);
-        Assert.assertEquals(locationSummary.getSpec(), "brooklyn.catalog:"+locationName+":"+locationVersion);
-
-        // Ensure location is usable - can instantiate, and has right config
-        JcloudsLocation l = (JcloudsLocation) getManagementContext().getLocationRegistry().resolve(locationName);
-        Assert.assertEquals(l.getProvider(), "aws-ec2");
-        Assert.assertEquals(l.getRegion(), "us-east-1");
-        Assert.assertEquals(l.getIdentity(), "bob");
-        Assert.assertEquals(l.getCredential(), "CR3dential");
-    }
-
-    @SuppressWarnings("deprecation")
-    @Test(dependsOnMethods = { "testAddNewLocationDefinition" })
-    public void testListAllLocationDefinitions() {
-        Set<LocationSummary> locations = client().resource("/v1/locations")
-                .get(new GenericType<Set<LocationSummary>>() {});
-        Iterable<LocationSummary> matching = Iterables.filter(locations, new Predicate<LocationSummary>() {
-            @Override
-            public boolean apply(@Nullable LocationSummary l) {
-                return locationName.equals(l.getName());
-            }
-        });
-        LocationSummary location = Iterables.getOnlyElement(matching);
-        
-        URI expectedLocationUri = URI.create("/v1/locations/"+locationName);
-        Assert.assertEquals(location.getSpec(), "brooklyn.catalog:"+locationName+":"+locationVersion);
-        Assert.assertEquals(location.getLinks().get("self"), expectedLocationUri);
-    }
-
-    @SuppressWarnings("deprecation")
-    @Test(dependsOnMethods = { "testListAllLocationDefinitions" })
-    public void testGetSpecificLocation() {
-        URI expectedLocationUri = URI.create("/v1/locations/"+locationName);
-        LocationSummary location = client().resource(expectedLocationUri).get(LocationSummary.class);
-        assertEquals(location.getSpec(), "brooklyn.catalog:"+locationName+":"+locationVersion);
-    }
-
-    @SuppressWarnings("deprecation")
-    @Test
-    public void testGetLocationConfig() {
-        SimulatedLocation parentLoc = (SimulatedLocation) getManagementContext().getLocationManager().createLocation(LocationSpec.create(SimulatedLocation.class)
-                .configure("myParentKey", "myParentVal"));
-        SimulatedLocation loc = (SimulatedLocation) getManagementContext().getLocationManager().createLocation(LocationSpec.create(SimulatedLocation.class)
-                .parent(parentLoc)
-                .configure("mykey", "myval")
-                .configure("password", "mypassword"));
-    
-        // "full" means including-inherited, filtered to exclude secrets
-        URI uriFull = URI.create("/v1/locations/"+loc.getId()+"?full=true");
-        LocationSummary summaryFull = client().resource(uriFull).get(LocationSummary.class);
-        assertEquals(summaryFull.getConfig(), ImmutableMap.of("mykey", "myval", "myParentKey", "myParentVal"), "conf="+summaryFull.getConfig());
-        
-        // Default is local-only, filtered to exclude secrets
-        URI uriDefault = URI.create("/v1/locations/"+loc.getId());
-        LocationSummary summaryDefault = client().resource(uriDefault).get(LocationSummary.class);
-        assertEquals(summaryDefault.getConfig(), ImmutableMap.of("mykey", "myval"), "conf="+summaryDefault.getConfig());
-    }
-
-    @Test(dependsOnMethods = { "testAddLegacyLocationDefinition" })
-    @Deprecated
-    public void testDeleteLocation() {
-        final int size = getLocationRegistry().getDefinedLocations().size();
-        URI expectedLocationUri = URI.create("/v1/locations/"+legacyLocationName);
-
-        ClientResponse response = client().resource(expectedLocationUri).delete(ClientResponse.class);
-        assertEquals(response.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
-        Asserts.succeedsEventually(new Runnable() {
-            @Override
-            public void run() {
-                assertEquals(getLocationRegistry().getDefinedLocations().size(), size - 1);
-            }
-        });
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/PolicyResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/PolicyResourceTest.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/PolicyResourceTest.java
deleted file mode 100644
index 22dc86d..0000000
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/PolicyResourceTest.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.rest.resources;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.fail;
-
-import java.util.Map;
-import java.util.Set;
-
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.Test;
-
-import org.apache.brooklyn.rest.domain.ApplicationSpec;
-import org.apache.brooklyn.rest.domain.EntitySpec;
-import org.apache.brooklyn.rest.domain.PolicyConfigSummary;
-import org.apache.brooklyn.rest.domain.PolicySummary;
-import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
-import org.apache.brooklyn.rest.testing.mocks.RestMockSimpleEntity;
-import org.apache.brooklyn.rest.testing.mocks.RestMockSimplePolicy;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-import com.sun.jersey.api.client.ClientResponse;
-import com.sun.jersey.api.client.GenericType;
-
-@Test(singleThreaded = true)
-public class PolicyResourceTest extends BrooklynRestResourceTest {
-
-    @SuppressWarnings("unused")
-    private static final Logger log = LoggerFactory.getLogger(PolicyResourceTest.class);
-
-    private static final String ENDPOINT = "/v1/applications/simple-app/entities/simple-ent/policies/";
-
-    private final ApplicationSpec simpleSpec = ApplicationSpec.builder().name("simple-app").entities(
-            ImmutableSet.of(new EntitySpec("simple-ent", RestMockSimpleEntity.class.getName()))).locations(
-            ImmutableSet.of("localhost")).build();
-
-    private String policyId;
-
-    @BeforeClass(alwaysRun = true)
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-
-        ClientResponse aResponse = clientDeploy(simpleSpec);
-        waitForApplicationToBeRunning(aResponse.getLocation());
-
-        ClientResponse pResponse = client().resource(ENDPOINT)
-                .queryParam("type", RestMockSimplePolicy.class.getCanonicalName())
-                .type(MediaType.APPLICATION_JSON_TYPE)
-                .post(ClientResponse.class, Maps.newHashMap());
-
-        PolicySummary response = pResponse.getEntity(PolicySummary.class);
-        assertNotNull(response.getId());
-        policyId = response.getId();
-
-    }
-
-    @Test
-    public void testListConfig() throws Exception {
-        Set<PolicyConfigSummary> config = client().resource(ENDPOINT + policyId + "/config")
-                .get(new GenericType<Set<PolicyConfigSummary>>() {});
-        
-        Set<String> configNames = Sets.newLinkedHashSet();
-        for (PolicyConfigSummary conf : config) {
-            configNames.add(conf.getName());
-        }
-
-        assertEquals(configNames, ImmutableSet.of(
-                RestMockSimplePolicy.SAMPLE_CONFIG.getName(),
-                RestMockSimplePolicy.INTEGER_CONFIG.getName()));
-    }
-
-    @Test
-    public void testGetNonExistantConfigReturns404() throws Exception {
-        String invalidConfigName = "doesnotexist";
-        try {
-            PolicyConfigSummary summary = client().resource(ENDPOINT + policyId + "/config/" + invalidConfigName)
-                    .get(PolicyConfigSummary.class);
-            fail("Should have thrown 404, but got "+summary);
-        } catch (Exception e) {
-            if (!e.toString().contains("404")) throw e;
-        }
-    }
-
-    @Test
-    public void testGetDefaultValue() throws Exception {
-        String configName = RestMockSimplePolicy.SAMPLE_CONFIG.getName();
-        String expectedVal = RestMockSimplePolicy.SAMPLE_CONFIG.getDefaultValue();
-        
-        String configVal = client().resource(ENDPOINT + policyId + "/config/" + configName)
-                .get(String.class);
-        assertEquals(configVal, expectedVal);
-    }
-    
-    @Test(dependsOnMethods = "testGetDefaultValue")
-    public void testReconfigureConfig() throws Exception {
-        String configName = RestMockSimplePolicy.SAMPLE_CONFIG.getName();
-        
-        ClientResponse response = client().resource(ENDPOINT + policyId + "/config/" + configName + "/set")
-                .queryParam("value", "newval")
-                .post(ClientResponse.class);
-
-        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
-    }
-    
-    @Test(dependsOnMethods = "testReconfigureConfig")
-    public void testGetConfigValue() throws Exception {
-        String configName = RestMockSimplePolicy.SAMPLE_CONFIG.getName();
-        String expectedVal = "newval";
-        
-        Map<String, Object> allState = client().resource(ENDPOINT + policyId + "/config/current-state")
-                .get(new GenericType<Map<String, Object>>() {});
-        assertEquals(allState, ImmutableMap.of(configName, expectedVal));
-        
-        String configVal = client().resource(ENDPOINT + policyId + "/config/" + configName)
-                .get(String.class);
-        assertEquals(configVal, expectedVal);
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ScriptResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ScriptResourceTest.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ScriptResourceTest.java
deleted file mode 100644
index 08b9aa4..0000000
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ScriptResourceTest.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.rest.resources;
-
-import java.util.Collections;
-
-import org.apache.brooklyn.api.entity.Application;
-import org.apache.brooklyn.api.entity.EntitySpec;
-import org.apache.brooklyn.api.location.Location;
-import org.apache.brooklyn.api.mgmt.ManagementContext;
-import org.apache.brooklyn.core.entity.Entities;
-import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
-import org.apache.brooklyn.rest.domain.ScriptExecutionSummary;
-import org.apache.brooklyn.rest.testing.mocks.RestMockApp;
-import org.testng.Assert;
-import org.testng.annotations.Test;
-
-public class ScriptResourceTest {
-
-    @Test
-    public void testGroovy() {
-        ManagementContext mgmt = LocalManagementContextForTests.newInstance();
-        Application app = mgmt.getEntityManager().createEntity( EntitySpec.create(Application.class, RestMockApp.class) );
-        try {
-        
-            Entities.start(app, Collections.<Location>emptyList());
-
-            ScriptResource s = new ScriptResource();
-            s.setManagementContext(mgmt);
-
-            ScriptExecutionSummary result = s.groovy(null, "def apps = []; mgmt.applications.each { println 'app:'+it; apps << it.id }; apps");
-            Assert.assertEquals(Collections.singletonList(app.getId()).toString(), result.getResult());
-            Assert.assertTrue(result.getStdout().contains("app:RestMockApp"));
-        
-        } finally { Entities.destroyAll(mgmt); }
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/SensorResourceIntegrationTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/SensorResourceIntegrationTest.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/SensorResourceIntegrationTest.java
deleted file mode 100644
index f90b677..0000000
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/SensorResourceIntegrationTest.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.rest.resources;
-
-import java.net.URI;
-
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.entity.EntitySpec;
-import org.apache.brooklyn.api.mgmt.ManagementContext;
-import org.apache.brooklyn.core.entity.EntityInternal;
-import org.apache.brooklyn.core.entity.EntityPredicates;
-import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
-import org.apache.brooklyn.entity.stock.BasicApplication;
-import org.apache.brooklyn.rest.BrooklynRestApiLauncher;
-import org.apache.brooklyn.rest.BrooklynRestApiLauncherTestFixture;
-import org.apache.brooklyn.rest.testing.mocks.RestMockSimpleEntity;
-import org.apache.brooklyn.test.HttpTestUtils;
-import org.apache.brooklyn.util.collections.MutableList;
-import org.apache.brooklyn.util.http.HttpTool;
-import org.apache.brooklyn.util.http.HttpToolResponse;
-import org.apache.brooklyn.util.net.Urls;
-import org.apache.http.client.HttpClient;
-import org.eclipse.jetty.server.Server;
-import org.testng.Assert;
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.Test;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Iterables;
-
-public class SensorResourceIntegrationTest extends BrooklynRestApiLauncherTestFixture {
-
-    private Server server;
-    private ManagementContext mgmt;
-    private BasicApplication app;
-
-    @BeforeClass(alwaysRun = true)
-    protected void setUp() {
-        mgmt = LocalManagementContextForTests.newInstance();
-        server = useServerForTest(BrooklynRestApiLauncher.launcher()
-            .managementContext(mgmt)
-            .withoutJsgui()
-            .start());
-        app = mgmt.getEntityManager().createEntity(EntitySpec.create(BasicApplication.class).displayName("simple-app")
-            .child(EntitySpec.create(Entity.class, RestMockSimpleEntity.class).displayName("simple-ent")));
-        mgmt.getEntityManager().manage(app);
-        app.start(MutableList.of(mgmt.getLocationRegistry().resolve("localhost")));
-    }
-    
-    // marked integration because of time
-    @Test(groups = "Integration")
-    public void testSensorBytes() throws Exception {
-        EntityInternal entity = (EntityInternal) Iterables.find(mgmt.getEntityManager().getEntities(), EntityPredicates.displayNameEqualTo("simple-ent"));
-        SensorResourceTest.addAmphibianSensor(entity);
-        
-        String baseUri = getBaseUri(server);
-        URI url = URI.create(Urls.mergePaths(baseUri, SensorResourceTest.SENSORS_ENDPOINT, SensorResourceTest.SENSOR_NAME));
-        
-        // Uses explicit "application/json" because failed on jenkins as though "text/plain" was the default on Ubuntu jenkins! 
-        HttpClient client = HttpTool.httpClientBuilder().uri(baseUri).build();
-        HttpToolResponse response = HttpTool.httpGet(client, url, ImmutableMap.<String, String>of("Accept", "application/json"));
-        HttpTestUtils.assertHealthyStatusCode(response.getResponseCode());
-        Assert.assertEquals(response.getContentAsString(), "\"12345 frogs\"");
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/SensorResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/SensorResourceTest.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/SensorResourceTest.java
deleted file mode 100644
index 4d2f781..0000000
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/SensorResourceTest.java
+++ /dev/null
@@ -1,271 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.rest.resources;
-
-import static org.testng.Assert.assertEquals;
-
-import java.util.Map;
-
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-
-import org.apache.brooklyn.api.sensor.AttributeSensor;
-import org.apache.brooklyn.core.config.render.RendererHints;
-import org.apache.brooklyn.core.entity.EntityInternal;
-import org.apache.brooklyn.core.entity.EntityPredicates;
-import org.apache.brooklyn.core.sensor.Sensors;
-import org.apache.brooklyn.rest.api.SensorApi;
-import org.apache.brooklyn.rest.domain.ApplicationSpec;
-import org.apache.brooklyn.rest.domain.EntitySpec;
-import org.apache.brooklyn.rest.test.config.render.TestRendererHints;
-import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
-import org.apache.brooklyn.rest.testing.mocks.RestMockSimpleEntity;
-import org.apache.brooklyn.util.collections.MutableMap;
-import org.apache.brooklyn.util.http.HttpAsserts;
-import org.apache.brooklyn.util.stream.Streams;
-import org.apache.brooklyn.util.text.StringFunctions;
-import org.testng.annotations.AfterClass;
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.Test;
-
-import com.google.common.base.Functions;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
-import com.sun.jersey.api.client.ClientResponse;
-import com.sun.jersey.api.client.GenericType;
-import com.sun.jersey.api.client.WebResource;
-import com.sun.jersey.api.client.WebResource.Builder;
-
-/**
- * Test the {@link SensorApi} implementation.
- * <p>
- * Check that {@link SensorResource} correctly renders {@link AttributeSensor}
- * values, including {@link RendererHints.DisplayValue} hints.
- */
-@Test(singleThreaded = true)
-public class SensorResourceTest extends BrooklynRestResourceTest {
-
-    final static ApplicationSpec SIMPLE_SPEC = ApplicationSpec.builder()
-            .name("simple-app")
-            .entities(ImmutableSet.of(new EntitySpec("simple-ent", RestMockSimpleEntity.class.getName())))
-            .locations(ImmutableSet.of("localhost"))
-            .build();
-
-    static final String SENSORS_ENDPOINT = "/v1/applications/simple-app/entities/simple-ent/sensors";
-    static final String SENSOR_NAME = "amphibian.count";
-    static final AttributeSensor<Integer> SENSOR = Sensors.newIntegerSensor(SENSOR_NAME);
-
-    EntityInternal entity;
-
-    /**
-     * Sets up the application and entity.
-     * <p>
-     * Adds a sensor and sets its value to {@code 12345}. Configures a display value
-     * hint that appends {@code frogs} to the value of the sensor.
-     */
-    @BeforeClass(alwaysRun = true)
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-
-        // Deploy application
-        ClientResponse deploy = clientDeploy(SIMPLE_SPEC);
-        waitForApplicationToBeRunning(deploy.getLocation());
-
-        entity = (EntityInternal) Iterables.find(getManagementContext().getEntityManager().getEntities(), EntityPredicates.displayNameEqualTo("simple-ent"));
-        addAmphibianSensor(entity);
-    }
-
-    static void addAmphibianSensor(EntityInternal entity) {
-        // Add new sensor
-        entity.getMutableEntityType().addSensor(SENSOR);
-        entity.sensors().set(SENSOR, 12345);
-
-        // Register display value hint
-        RendererHints.register(SENSOR, RendererHints.displayValue(Functions.compose(StringFunctions.append(" frogs"), Functions.toStringFunction())));
-    }
-
-    @AfterClass(alwaysRun = true)
-    @Override
-    public void tearDown() throws Exception {
-        TestRendererHints.clearRegistry();
-        super.tearDown();
-    }
-
-    /** Check default is to use display value hint. */
-    @Test
-    public void testBatchSensorRead() throws Exception {
-        ClientResponse response = client().resource(SENSORS_ENDPOINT + "/current-state")
-                .accept(MediaType.APPLICATION_JSON)
-                .get(ClientResponse.class);
-        Map<String, ?> currentState = response.getEntity(new GenericType<Map<String,?>>(Map.class) {});
-
-        for (String sensor : currentState.keySet()) {
-            if (sensor.equals(SENSOR_NAME)) {
-                assertEquals(currentState.get(sensor), "12345 frogs");
-            }
-        }
-    }
-
-    /** Check setting {@code raw} to {@code true} ignores display value hint. */
-    @Test(dependsOnMethods = "testBatchSensorRead")
-    public void testBatchSensorReadRaw() throws Exception {
-        ClientResponse response = client().resource(SENSORS_ENDPOINT + "/current-state")
-                .queryParam("raw", "true")
-                .accept(MediaType.APPLICATION_JSON)
-                .get(ClientResponse.class);
-        Map<String, ?> currentState = response.getEntity(new GenericType<Map<String,?>>(Map.class) {});
-
-        for (String sensor : currentState.keySet()) {
-            if (sensor.equals(SENSOR_NAME)) {
-                assertEquals(currentState.get(sensor), Integer.valueOf(12345));
-            }
-        }
-    }
-
-    protected ClientResponse doSensorTest(Boolean raw, MediaType acceptsType, Object expectedValue) {
-        return doSensorTestUntyped(
-            raw==null ? null : (""+raw).toLowerCase(), 
-            acceptsType==null ? null : new String[] { acceptsType.getType() }, 
-            expectedValue);
-    }
-    protected ClientResponse doSensorTestUntyped(String raw, String[] acceptsTypes, Object expectedValue) {
-        WebResource req = client().resource(SENSORS_ENDPOINT + "/" + SENSOR_NAME);
-        if (raw!=null) req = req.queryParam("raw", raw);
-        ClientResponse response;
-        if (acceptsTypes!=null) {
-            Builder rb = req.accept(acceptsTypes);
-            response = rb.get(ClientResponse.class);
-        } else {
-            response = req.get(ClientResponse.class);
-        }
-        if (expectedValue!=null) {
-            HttpAsserts.assertHealthyStatusCode(response.getStatus());
-            Object value = response.getEntity(expectedValue.getClass());
-            assertEquals(value, expectedValue);
-        }
-        return response;
-    }
-    
-    /**
-     * Check we can get a sensor, explicitly requesting json; gives a string picking up the rendering hint.
-     * 
-     * If no "Accepts" header is given, then we don't control whether json or plain text comes back.
-     * It is dependent on the method order, which is compiler-specific.
-     */
-    @Test
-    public void testGetJson() throws Exception {
-        doSensorTest(null, MediaType.APPLICATION_JSON_TYPE, "\"12345 frogs\"");
-    }
-    
-    @Test
-    public void testGetJsonBytes() throws Exception {
-        ClientResponse response = doSensorTest(null, MediaType.APPLICATION_JSON_TYPE, null);
-        byte[] bytes = Streams.readFully(response.getEntityInputStream());
-        // assert we have one set of surrounding quotes
-        assertEquals(bytes.length, 13);
-    }
-
-    /** Check that plain returns a string without quotes, with the rendering hint */
-    @Test
-    public void testGetPlain() throws Exception {
-        doSensorTest(null, MediaType.TEXT_PLAIN_TYPE, "12345 frogs");
-    }
-
-    /** 
-     * Check that when we set {@code raw = true}, the result ignores the display value hint.
-     *
-     * If no "Accepts" header is given, then we don't control whether json or plain text comes back.
-     * It is dependent on the method order, which is compiler-specific.
-     */
-    @Test
-    public void testGetRawJson() throws Exception {
-        doSensorTest(true, MediaType.APPLICATION_JSON_TYPE, 12345);
-    }
-    
-    /** As {@link #testGetRaw()} but with plain set, returns the number */
-    @Test
-    public void testGetPlainRaw() throws Exception {
-        // have to pass a string because that's how PLAIN is deserialized
-        doSensorTest(true, MediaType.TEXT_PLAIN_TYPE, "12345");
-    }
-
-    /** Check explicitly setting {@code raw} to {@code false} is as before */
-    @Test
-    public void testGetPlainRawFalse() throws Exception {
-        doSensorTest(false, MediaType.TEXT_PLAIN_TYPE, "12345 frogs");
-    }
-
-    /** Check empty vaue for {@code raw} will revert to using default. */
-    @Test
-    public void testGetPlainRawEmpty() throws Exception {
-        doSensorTestUntyped("", new String[] { MediaType.TEXT_PLAIN }, "12345 frogs");
-    }
-
-    /** Check unparseable vaue for {@code raw} will revert to using default. */
-    @Test
-    public void testGetPlainRawError() throws Exception {
-        doSensorTestUntyped("biscuits", new String[] { MediaType.TEXT_PLAIN }, "12345 frogs");
-    }
-    
-    /** Check we can set a value */
-    @Test
-    public void testSet() throws Exception {
-        try {
-            ClientResponse response = client().resource(SENSORS_ENDPOINT + "/" + SENSOR_NAME)
-                .type(MediaType.APPLICATION_JSON_TYPE)
-                .post(ClientResponse.class, 67890);
-            assertEquals(response.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
-
-            assertEquals(entity.getAttribute(SENSOR), (Integer)67890);
-            
-            String value = client().resource(SENSORS_ENDPOINT + "/" + SENSOR_NAME).accept(MediaType.TEXT_PLAIN_TYPE).get(String.class);
-            assertEquals(value, "67890 frogs");
-
-        } finally { addAmphibianSensor(entity); }
-    }
-
-    @Test
-    public void testSetFromMap() throws Exception {
-        try {
-            ClientResponse response = client().resource(SENSORS_ENDPOINT)
-                .type(MediaType.APPLICATION_JSON_TYPE)
-                .post(ClientResponse.class, MutableMap.of(SENSOR_NAME, 67890));
-            assertEquals(response.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
-            
-            assertEquals(entity.getAttribute(SENSOR), (Integer)67890);
-
-        } finally { addAmphibianSensor(entity); }
-    }
-    
-    /** Check we can delete a value */
-    @Test
-    public void testDelete() throws Exception {
-        try {
-            ClientResponse response = client().resource(SENSORS_ENDPOINT + "/" + SENSOR_NAME)
-                .delete(ClientResponse.class);
-            assertEquals(response.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
-
-            String value = client().resource(SENSORS_ENDPOINT + "/" + SENSOR_NAME).accept(MediaType.TEXT_PLAIN_TYPE).get(String.class);
-            assertEquals(value, "");
-
-        } finally { addAmphibianSensor(entity); }
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ServerResourceIntegrationTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ServerResourceIntegrationTest.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ServerResourceIntegrationTest.java
index ce3fd37..74fbe3c 100644
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ServerResourceIntegrationTest.java
+++ b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ServerResourceIntegrationTest.java
@@ -32,7 +32,7 @@ import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
 import org.apache.brooklyn.rest.BrooklynRestApiLauncher;
 import org.apache.brooklyn.rest.BrooklynRestApiLauncherTestFixture;
 import org.apache.brooklyn.rest.security.provider.TestSecurityProvider;
-import org.apache.brooklyn.test.HttpTestUtils;
+import org.apache.brooklyn.util.http.HttpAsserts;
 import org.apache.brooklyn.util.http.HttpTool;
 import org.apache.brooklyn.util.http.HttpToolResponse;
 import org.apache.http.HttpStatus;
@@ -71,7 +71,7 @@ public class ServerResourceIntegrationTest extends BrooklynRestApiLauncherTestFi
             String baseUri = getBaseUri(server);
     
             HttpToolResponse response;
-            final URI uri = URI.create(getBaseUri() + "/v1/server/properties/reload");
+            final URI uri = URI.create(getBaseUri() + "/server/properties/reload");
             final Map<String, String> args = Collections.emptyMap();
     
             // Unauthorised when no credentials, and when default credentials.
@@ -85,12 +85,12 @@ public class ServerResourceIntegrationTest extends BrooklynRestApiLauncherTestFi
             // Accepts TestSecurityProvider credentials, and we reload.
             response = HttpTool.httpPost(httpClientBuilder().uri(baseUri).credentials(TestSecurityProvider.CREDENTIAL).build(),
                     uri, args, args);
-            HttpTestUtils.assertHealthyStatusCode(response.getResponseCode());
+            HttpAsserts.assertHealthyStatusCode(response.getResponseCode());
     
             // Has no gone back to credentials from brooklynProperties; TestSecurityProvider credentials no longer work
             response = HttpTool.httpPost(httpClientBuilder().uri(baseUri).credentials(defaultCredential).build(), 
                     uri, args, args);
-            HttpTestUtils.assertHealthyStatusCode(response.getResponseCode());
+            HttpAsserts.assertHealthyStatusCode(response.getResponseCode());
             
             response = HttpTool.httpPost(httpClientBuilder().uri(baseUri).credentials(TestSecurityProvider.CREDENTIAL).build(), 
                     uri, args, args);
@@ -116,9 +116,9 @@ public class ServerResourceIntegrationTest extends BrooklynRestApiLauncherTestFi
                 .credentials(TestSecurityProvider.CREDENTIAL)
                 .build();
         
-        HttpToolResponse response = HttpTool.httpGet(client, URI.create(getBaseUri(server) + "/v1/server/user"),
+        HttpToolResponse response = HttpTool.httpGet(client, URI.create(getBaseUri(server) + "/server/user"),
                 ImmutableMap.<String, String>of());
-        HttpTestUtils.assertHealthyStatusCode(response.getResponseCode());
+        HttpAsserts.assertHealthyStatusCode(response.getResponseCode());
         return response.getContentAsString();
     }
 

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ServerResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ServerResourceTest.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ServerResourceTest.java
deleted file mode 100644
index f84cb80..0000000
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ServerResourceTest.java
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.rest.resources;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNotNull;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import org.apache.brooklyn.api.entity.ImplementedBy;
-import org.apache.brooklyn.api.mgmt.ManagementContext;
-import org.apache.brooklyn.core.BrooklynVersion;
-import org.apache.brooklyn.core.internal.BrooklynProperties;
-import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
-import org.apache.brooklyn.entity.software.base.EmptySoftwareProcess;
-import org.apache.brooklyn.entity.software.base.EmptySoftwareProcessDriver;
-import org.apache.brooklyn.entity.software.base.EmptySoftwareProcessImpl;
-import org.apache.brooklyn.rest.domain.HighAvailabilitySummary;
-import org.apache.brooklyn.rest.domain.VersionSummary;
-import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
-import org.apache.brooklyn.test.Asserts;
-import org.apache.brooklyn.util.exceptions.Exceptions;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.annotations.Test;
-
-import com.google.common.collect.ImmutableSet;
-import com.sun.jersey.api.client.UniformInterfaceException;
-
-@Test(singleThreaded = true)
-public class ServerResourceTest extends BrooklynRestResourceTest {
-
-    private static final Logger log = LoggerFactory.getLogger(ServerResourceTest.class);
-    
-    @Test
-    public void testGetVersion() throws Exception {
-        VersionSummary version = client().resource("/v1/server/version").get(VersionSummary.class);
-        assertEquals(version.getVersion(), BrooklynVersion.get());
-    }
-
-    @Test
-    public void testGetStatus() throws Exception {
-        String status = client().resource("/v1/server/status").get(String.class);
-        assertEquals(status, "MASTER");
-    }
-
-    @Test
-    public void testGetHighAvailability() throws Exception {
-        // Note by default management context from super is started without HA enabled.
-        // Therefore can only assert a minimal amount of stuff.
-        HighAvailabilitySummary summary = client().resource("/v1/server/highAvailability").get(HighAvailabilitySummary.class);
-        log.info("HA summary is: "+summary);
-        
-        String ownNodeId = getManagementContext().getManagementNodeId();
-        assertEquals(summary.getOwnId(), ownNodeId);
-        assertEquals(summary.getMasterId(), ownNodeId);
-        assertEquals(summary.getNodes().keySet(), ImmutableSet.of(ownNodeId));
-        assertEquals(summary.getNodes().get(ownNodeId).getNodeId(), ownNodeId);
-        assertEquals(summary.getNodes().get(ownNodeId).getStatus(), "MASTER");
-        assertNotNull(summary.getNodes().get(ownNodeId).getLocalTimestamp());
-        // remote will also be non-null if there is no remote backend (local is re-used)
-        assertNotNull(summary.getNodes().get(ownNodeId).getRemoteTimestamp());
-        assertEquals(summary.getNodes().get(ownNodeId).getLocalTimestamp(), summary.getNodes().get(ownNodeId).getRemoteTimestamp());
-    }
-
-    @SuppressWarnings("serial")
-    @Test
-    public void testReloadsBrooklynProperties() throws Exception {
-        final AtomicInteger reloadCount = new AtomicInteger();
-        getManagementContext().addPropertiesReloadListener(new ManagementContext.PropertiesReloadListener() {
-            @Override public void reloaded() {
-                reloadCount.incrementAndGet();
-            }});
-        client().resource("/v1/server/properties/reload").post();
-        assertEquals(reloadCount.get(), 1);
-    }
-
-    @Test
-    void testGetConfig() throws Exception {
-        ((ManagementContextInternal)getManagementContext()).getBrooklynProperties().put("foo.bar.baz", "quux");
-        try {
-            assertEquals(client().resource("/v1/server/config/foo.bar.baz").get(String.class), "quux");
-        } finally {
-            ((ManagementContextInternal)getManagementContext()).getBrooklynProperties().remove("foo.bar.baz");
-        }
-    }
-
-    @Test
-    void testGetMissingConfigThrowsException() throws Exception {
-        final String key = "foo.bar.baz";
-        BrooklynProperties properties = ((ManagementContextInternal)getManagementContext()).getBrooklynProperties();
-        Object existingValue = null;
-        boolean keyAlreadyPresent = false;
-        String response = null;
-        if (properties.containsKey(key)) {
-            existingValue = properties.remove(key);
-            keyAlreadyPresent = true;
-        }
-        try {
-            response = client().resource("/v1/server/config/" + key).get(String.class);
-            Asserts.fail("Expected call to /v1/server/config/" + key + " to fail with status 404, instead server returned " + response);
-        } catch (UniformInterfaceException e) {
-            assertEquals(e.getResponse().getStatus(), 204);
-        } finally {
-            if (keyAlreadyPresent) {
-                properties.put(key, existingValue);
-            }
-        }
-    }
-
-    // Alternatively could reuse a blocking location, see org.apache.brooklyn.entity.software.base.SoftwareProcessEntityTest.ReleaseLatchLocation
-    @ImplementedBy(StopLatchEntityImpl.class)
-    public interface StopLatchEntity extends EmptySoftwareProcess {
-        public void unblock();
-        public boolean isBlocked();
-    }
-
-    public static class StopLatchEntityImpl extends EmptySoftwareProcessImpl implements StopLatchEntity {
-        private CountDownLatch lock = new CountDownLatch(1);
-        private volatile boolean isBlocked;
-
-        @Override
-        public void unblock() {
-            lock.countDown();
-        }
-
-        @Override
-        protected void postStop() {
-            super.preStop();
-            try {
-                isBlocked = true;
-                lock.await();
-                isBlocked = false;
-            } catch (InterruptedException e) {
-                throw Exceptions.propagate(e);
-            }
-        }
-
-        @Override
-        public Class<?> getDriverInterface() {
-            return EmptySoftwareProcessDriver.class;
-        }
-
-        @Override
-        public boolean isBlocked() {
-            return isBlocked;
-        }
-
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ServerShutdownTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ServerShutdownTest.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ServerShutdownTest.java
deleted file mode 100644
index dbe9afd..0000000
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ServerShutdownTest.java
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.rest.resources;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.assertNull;
-import static org.testng.Assert.assertTrue;
-
-import java.util.concurrent.atomic.AtomicReference;
-
-import javax.ws.rs.core.MultivaluedMap;
-
-import org.apache.brooklyn.api.entity.EntitySpec;
-import org.apache.brooklyn.api.mgmt.EntityManager;
-import org.apache.brooklyn.api.mgmt.Task;
-import org.apache.brooklyn.core.entity.Attributes;
-import org.apache.brooklyn.core.entity.EntityAsserts;
-import org.apache.brooklyn.core.entity.drivers.BasicEntityDriverManager;
-import org.apache.brooklyn.core.entity.drivers.ReflectiveEntityDriverFactory;
-import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
-import org.apache.brooklyn.core.entity.trait.Startable;
-import org.apache.brooklyn.core.test.entity.TestApplication;
-import org.apache.brooklyn.rest.resources.ServerResourceTest.StopLatchEntity;
-import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
-import org.apache.brooklyn.test.Asserts;
-import org.apache.brooklyn.util.exceptions.Exceptions;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.annotations.AfterClass;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.sun.jersey.core.util.MultivaluedMapImpl;
-
-public class ServerShutdownTest extends BrooklynRestResourceTest {
-    private static final Logger log = LoggerFactory.getLogger(ServerResourceTest.class);
-
-    // Need to initialise the ManagementContext before each test as it is destroyed.
-    @Override
-    @BeforeClass(alwaysRun = true)
-    public void setUp() throws Exception {
-    }
-
-    @Override
-    @AfterClass(alwaysRun = true)
-    public void tearDown() throws Exception {
-    }
-
-    @Override
-    @BeforeMethod(alwaysRun = true)
-    public void setUpMethod() {
-        setUpJersey();
-        super.setUpMethod();
-    }
-
-    @AfterMethod(alwaysRun = true)
-    public void tearDownMethod() {
-        tearDownJersey();
-        destroyManagementContext();
-    }
-
-    @Test
-    public void testShutdown() throws Exception {
-        assertTrue(getManagementContext().isRunning());
-        assertFalse(shutdownListener.isRequested());
-
-        MultivaluedMap<String, String> formData = new MultivaluedMapImpl();
-        formData.add("requestTimeout", "0");
-        formData.add("delayForHttpReturn", "0");
-        client().resource("/v1/server/shutdown").entity(formData).post();
-
-        Asserts.succeedsEventually(new Runnable() {
-            @Override
-            public void run() {
-                assertTrue(shutdownListener.isRequested());
-            }
-        });
-        Asserts.succeedsEventually(new Runnable() {
-            @Override public void run() {
-                assertFalse(getManagementContext().isRunning());
-            }});
-    }
-
-    @Test
-    public void testStopAppThenShutdownAndStopAppsWaitsForFirstStop() throws InterruptedException {
-        ReflectiveEntityDriverFactory f = ((BasicEntityDriverManager)getManagementContext().getEntityDriverManager()).getReflectiveDriverFactory();
-        f.addClassFullNameMapping("org.apache.brooklyn.entity.software.base.EmptySoftwareProcessDriver", "org.apache.brooklyn.rest.resources.ServerResourceTest$EmptySoftwareProcessTestDriver");
-
-        // Second stop on SoftwareProcess could return early, while the first stop is still in progress
-        // This causes the app to shutdown prematurely, leaking machines.
-        EntityManager emgr = getManagementContext().getEntityManager();
-        EntitySpec<TestApplication> appSpec = EntitySpec.create(TestApplication.class);
-        TestApplication app = emgr.createEntity(appSpec);
-        emgr.manage(app);
-        EntitySpec<StopLatchEntity> latchEntitySpec = EntitySpec.create(StopLatchEntity.class);
-        final StopLatchEntity entity = app.createAndManageChild(latchEntitySpec);
-        app.start(ImmutableSet.of(app.newLocalhostProvisioningLocation()));
-        EntityAsserts.assertAttributeEquals(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
-
-        try {
-            final Task<Void> firstStop = app.invoke(Startable.STOP, ImmutableMap.<String, Object>of());
-            Asserts.succeedsEventually(new Runnable() {
-                @Override
-                public void run() {
-                    assertTrue(entity.isBlocked());
-                }
-            });
-
-            final AtomicReference<Exception> shutdownError = new AtomicReference<>();
-            // Can't use ExecutionContext as it will be stopped on shutdown
-            Thread shutdownThread = new Thread() {
-                @Override
-                public void run() {
-                    try {
-                        MultivaluedMap<String, String> formData = new MultivaluedMapImpl();
-                        formData.add("stopAppsFirst", "true");
-                        formData.add("shutdownTimeout", "0");
-                        formData.add("requestTimeout", "0");
-                        formData.add("delayForHttpReturn", "0");
-                        client().resource("/v1/server/shutdown").entity(formData).post();
-                    } catch (Exception e) {
-                        log.error("Shutdown request error", e);
-                        shutdownError.set(e);
-                        throw Exceptions.propagate(e);
-                    }
-                }
-            };
-            shutdownThread.start();
-
-            //shutdown must wait until the first stop completes (or time out)
-            Asserts.succeedsContinually(new Runnable() {
-                @Override
-                public void run() {
-                    assertFalse(firstStop.isDone());
-                    assertEquals(getManagementContext().getApplications().size(), 1);
-                    assertFalse(shutdownListener.isRequested());
-                }
-            });
-
-            // NOTE test is not fully deterministic. Depending on thread scheduling this will
-            // execute before or after ServerResource.shutdown does the app stop loop. This
-            // means that the shutdown code might not see the app at all. In any case though
-            // the test must succeed.
-            entity.unblock();
-
-            Asserts.succeedsEventually(new Runnable() {
-                @Override
-                public void run() {
-                    assertTrue(firstStop.isDone());
-                    assertTrue(shutdownListener.isRequested());
-                    assertFalse(getManagementContext().isRunning());
-                }
-            });
-
-            shutdownThread.join();
-            assertNull(shutdownError.get(), "Shutdown request error, logged above");
-        } finally {
-            // Be sure we always unblock entity stop even in the case of an exception.
-            // In the success path the entity is already unblocked above.
-            entity.unblock();
-        }
-    }
-
-}


[09/34] brooklyn-server git commit: [BROOKLYN-183] REST API using CXF JAX-RS 2.0 implementation

Posted by he...@apache.org.
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ApplicationResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ApplicationResource.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ApplicationResource.java
deleted file mode 100644
index 1dbf1a8..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ApplicationResource.java
+++ /dev/null
@@ -1,449 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.rest.resources;
-
-import com.google.common.base.Throwables;
-import com.google.common.collect.FluentIterable;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-import org.apache.brooklyn.api.entity.Application;
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.entity.EntitySpec;
-import org.apache.brooklyn.api.entity.Group;
-import org.apache.brooklyn.api.location.Location;
-import org.apache.brooklyn.api.mgmt.Task;
-import org.apache.brooklyn.api.objs.BrooklynObject;
-import org.apache.brooklyn.api.sensor.AttributeSensor;
-import org.apache.brooklyn.api.sensor.Sensor;
-import org.apache.brooklyn.api.typereg.RegisteredType;
-import org.apache.brooklyn.core.config.ConstraintViolationException;
-import org.apache.brooklyn.core.entity.Attributes;
-import org.apache.brooklyn.core.entity.EntityPredicates;
-import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
-import org.apache.brooklyn.core.entity.trait.Startable;
-import org.apache.brooklyn.core.mgmt.EntityManagementUtils;
-import org.apache.brooklyn.core.mgmt.EntityManagementUtils.CreationResult;
-import org.apache.brooklyn.core.mgmt.entitlement.EntitlementPredicates;
-import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
-import org.apache.brooklyn.core.mgmt.entitlement.Entitlements.EntityAndItem;
-import org.apache.brooklyn.core.mgmt.entitlement.Entitlements.StringAndArgument;
-import org.apache.brooklyn.core.sensor.Sensors;
-import org.apache.brooklyn.core.typereg.RegisteredTypeLoadingContexts;
-import org.apache.brooklyn.core.typereg.RegisteredTypes;
-import org.apache.brooklyn.entity.group.AbstractGroup;
-import org.apache.brooklyn.rest.api.ApplicationApi;
-import org.apache.brooklyn.rest.domain.ApplicationSpec;
-import org.apache.brooklyn.rest.domain.ApplicationSummary;
-import org.apache.brooklyn.rest.domain.EntityDetail;
-import org.apache.brooklyn.rest.domain.EntitySummary;
-import org.apache.brooklyn.rest.domain.TaskSummary;
-import org.apache.brooklyn.rest.filter.HaHotStateRequired;
-import org.apache.brooklyn.rest.transform.ApplicationTransformer;
-import org.apache.brooklyn.rest.transform.EntityTransformer;
-import org.apache.brooklyn.rest.transform.TaskTransformer;
-import org.apache.brooklyn.rest.util.BrooklynRestResourceUtils;
-import org.apache.brooklyn.rest.util.WebResourceUtils;
-import org.apache.brooklyn.util.collections.MutableMap;
-import org.apache.brooklyn.util.core.ResourceUtils;
-import org.apache.brooklyn.util.exceptions.Exceptions;
-import org.apache.brooklyn.util.exceptions.UserFacingException;
-import org.apache.brooklyn.util.guava.Maybe;
-import org.apache.brooklyn.util.javalang.JavaClassNames;
-import org.apache.brooklyn.util.text.Strings;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.ws.rs.core.Context;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.Response.ResponseBuilder;
-import javax.ws.rs.core.UriInfo;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-import static javax.ws.rs.core.Response.Status.ACCEPTED;
-import static javax.ws.rs.core.Response.created;
-import static javax.ws.rs.core.Response.status;
-
-@HaHotStateRequired
-public class ApplicationResource extends AbstractBrooklynRestResource implements ApplicationApi {
-
-    private static final Logger log = LoggerFactory.getLogger(ApplicationResource.class);
-
-    @Context
-    private UriInfo uriInfo;
-
-    private EntityDetail fromEntity(Entity entity) {
-        Boolean serviceUp = entity.getAttribute(Attributes.SERVICE_UP);
-
-        Lifecycle serviceState = entity.getAttribute(Attributes.SERVICE_STATE_ACTUAL);
-
-        String iconUrl = entity.getIconUrl();
-        if (iconUrl!=null) {
-            if (brooklyn().isUrlServerSideAndSafe(iconUrl))
-                // route to server if it is a server-side url
-                iconUrl = EntityTransformer.entityUri(entity)+"/icon";
-        }
-
-        List<EntitySummary> children = Lists.newArrayList();
-        if (!entity.getChildren().isEmpty()) {
-            for (Entity child : entity.getChildren()) {
-                children.add(fromEntity(child));
-            }
-        }
-
-        String parentId = null;
-        if (entity.getParent()!= null) {
-            parentId = entity.getParent().getId();
-        }
-
-        List<String> groupIds = Lists.newArrayList();
-        if (!entity.groups().isEmpty()) {
-            groupIds.addAll(entitiesIdAsArray(entity.groups()));
-        }
-
-        List<Map<String, String>> members = Lists.newArrayList();
-        if (entity instanceof Group) {
-            // use attribute instead of method in case it is read-only
-            Collection<Entity> memberEntities = entity.getAttribute(AbstractGroup.GROUP_MEMBERS);
-            if (memberEntities != null && !memberEntities.isEmpty())
-                members.addAll(entitiesIdAndNameAsList(memberEntities));
-        }
-
-        return new EntityDetail(
-                entity.getApplicationId(),
-                entity.getId(),
-                parentId,
-                entity.getDisplayName(),
-                entity.getEntityType().getName(),
-                serviceUp,
-                serviceState,
-                iconUrl,
-                entity.getCatalogItemId(),
-                children,
-                groupIds,
-                members);
-    }
-
-    private List<Map<String, String>> entitiesIdAndNameAsList(Collection<? extends Entity> entities) {
-        List<Map<String, String>> members = Lists.newArrayList();
-        for (Entity entity : entities) {
-            if (Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY, entity)) {
-                members.add(ImmutableMap.of("id", entity.getId(), "name", entity.getDisplayName()));
-            }
-        }
-        return members;
-    }
-
-    private List<String> entitiesIdAsArray(Iterable<? extends Entity> entities) {
-        List<String> ids = Lists.newArrayList();
-        for (Entity entity : entities) {
-            if (Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY, entity)) {
-                ids.add(entity.getId());
-            }
-        }
-        return ids;
-    }
-
-    @Override
-    public List<EntityDetail> fetch(String entityIds) {
-
-        List<EntityDetail> entitySummaries = Lists.newArrayList();
-        for (Entity application : mgmt().getApplications()) {
-            entitySummaries.add(fromEntity(application));
-        }
-
-        if (entityIds != null) {
-            for (String entityId: entityIds.split(",")) {
-                Entity entity = mgmt().getEntityManager().getEntity(entityId.trim());
-                while (entity != null && entity.getParent() != null) {
-                    if (Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY, entity)) {
-                        entitySummaries.add(fromEntity(entity));
-                    }
-                    entity = entity.getParent();
-                }
-            }
-        }
-        return entitySummaries;
-    }
-
-    @Override
-    public List<ApplicationSummary> list(String typeRegex) {
-        if (Strings.isBlank(typeRegex)) {
-            typeRegex = ".*";
-        }
-        return FluentIterable
-                .from(mgmt().getApplications())
-                .filter(EntitlementPredicates.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY))
-                .filter(EntityPredicates.hasInterfaceMatching(typeRegex))
-                .transform(ApplicationTransformer.FROM_APPLICATION)
-                .toList();
-    }
-
-    @Override
-    public ApplicationSummary get(String application) {
-        return ApplicationTransformer.summaryFromApplication(brooklyn().getApplication(application));
-    }
-
-    public Response create(ApplicationSpec applicationSpec) {
-        return createFromAppSpec(applicationSpec);
-    }
-
-    /** @deprecated since 0.7.0 see #create */ @Deprecated
-    protected Response createFromAppSpec(ApplicationSpec applicationSpec) {
-        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.DEPLOY_APPLICATION, applicationSpec)) {
-            throw WebResourceUtils.forbidden("User '%s' is not authorized to start application %s",
-                Entitlements.getEntitlementContext().user(), applicationSpec);
-        }
-
-        checkApplicationTypesAreValid(applicationSpec);
-        checkLocationsAreValid(applicationSpec);
-        // TODO duplicate prevention
-        List<Location> locations = brooklyn().getLocations(applicationSpec);
-        Application app = brooklyn().create(applicationSpec);
-        Task<?> t = brooklyn().start(app, locations);
-        TaskSummary ts = TaskTransformer.FROM_TASK.apply(t);
-        URI ref = uriInfo.getBaseUriBuilder()
-                .path(ApplicationApi.class)
-                .path(ApplicationApi.class, "get")
-                .build(app.getApplicationId());
-        return created(ref).entity(ts).build();
-    }
-
-    @Override
-    public Response createFromYaml(String yaml) {
-        // First of all, see if it's a URL
-        URI uri;
-        try {
-            uri = new URI(yaml);
-        } catch (URISyntaxException e) {
-            // It's not a URI then...
-            uri = null;
-        }
-        if (uri != null) {
-            log.debug("Create app called with URI; retrieving contents: {}", uri);
-            yaml = ResourceUtils.create(mgmt()).getResourceAsString(uri.toString());
-        }
-
-        log.debug("Creating app from yaml:\n{}", yaml);
-        EntitySpec<? extends Application> spec = createEntitySpecForApplication(yaml);
-
-        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.DEPLOY_APPLICATION, spec)) {
-            throw WebResourceUtils.forbidden("User '%s' is not authorized to start application %s",
-                Entitlements.getEntitlementContext().user(), yaml);
-        }
-
-        return launch(yaml, spec);
-    }
-
-    private Response launch(String yaml, EntitySpec<? extends Application> spec) {
-        try {
-            Application app = EntityManagementUtils.createUnstarted(mgmt(), spec);
-            CreationResult<Application,Void> result = EntityManagementUtils.start(app);
-
-            boolean isEntitled = Entitlements.isEntitled(
-                    mgmt().getEntitlementManager(),
-                    Entitlements.INVOKE_EFFECTOR,
-                    EntityAndItem.of(app, StringAndArgument.of(Startable.START.getName(), null)));
-
-            if (!isEntitled) {
-                throw WebResourceUtils.forbidden("User '%s' is not authorized to start application %s",
-                    Entitlements.getEntitlementContext().user(), spec.getType());
-            }
-
-            log.info("Launched from YAML: " + yaml + " -> " + app + " (" + result.task() + ")");
-
-            URI ref = URI.create(app.getApplicationId());
-            ResponseBuilder response = created(ref);
-            if (result.task() != null)
-                response.entity(TaskTransformer.FROM_TASK.apply(result.task()));
-            return response.build();
-        } catch (ConstraintViolationException e) {
-            throw new UserFacingException(e);
-        } catch (Exception e) {
-            throw Exceptions.propagate(e);
-        }
-    }
-
-    @Override
-    public Response createPoly(byte[] inputToAutodetectType) {
-        log.debug("Creating app from autodetecting input");
-
-        boolean looksLikeLegacy = false;
-        Exception legacyFormatException = null;
-        // attempt legacy format
-        try {
-            ApplicationSpec appSpec = mapper().readValue(inputToAutodetectType, ApplicationSpec.class);
-            if (appSpec.getType() != null || appSpec.getEntities() != null) {
-                looksLikeLegacy = true;
-            }
-            return createFromAppSpec(appSpec);
-        } catch (Exception e) {
-            Exceptions.propagateIfFatal(e);
-            legacyFormatException = e;
-            log.debug("Input is not legacy ApplicationSpec JSON (will try others): "+e, e);
-        }
-
-        //TODO infer encoding from request
-        String potentialYaml = new String(inputToAutodetectType);
-        EntitySpec<? extends Application> spec = createEntitySpecForApplication(potentialYaml);
-
-        // TODO not json - try ZIP, etc
-
-        if (spec != null) {
-            return launch(potentialYaml, spec);
-        } else if (looksLikeLegacy) {
-            throw Throwables.propagate(legacyFormatException);
-        } else {
-            return Response.serverError().entity("Unsupported format; not able to autodetect.").build();
-        }
-    }
-
-    @Override
-    public Response createFromForm(String contents) {
-        log.debug("Creating app from form");
-        return createPoly(contents.getBytes());
-    }
-
-    @Override
-    public Response delete(String application) {
-        Application app = brooklyn().getApplication(application);
-        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.INVOKE_EFFECTOR, Entitlements.EntityAndItem.of(app,
-            StringAndArgument.of(Entitlements.LifecycleEffectors.DELETE, null)))) {
-            throw WebResourceUtils.forbidden("User '%s' is not authorized to delete application %s",
-                Entitlements.getEntitlementContext().user(), app);
-        }
-        Task<?> t = brooklyn().destroy(app);
-        TaskSummary ts = TaskTransformer.FROM_TASK.apply(t);
-        return status(ACCEPTED).entity(ts).build();
-    }
-
-    private EntitySpec<? extends Application> createEntitySpecForApplication(String potentialYaml) {
-        try {
-            return EntityManagementUtils.createEntitySpecForApplication(mgmt(), potentialYaml);
-        } catch (Exception e) {
-            // An IllegalArgumentException for creating the entity spec gets wrapped in a ISE, and possibly a Compound.
-            // But we want to return a 400 rather than 500, so ensure we throw IAE.
-            IllegalArgumentException iae = (IllegalArgumentException) Exceptions.getFirstThrowableOfType(e, IllegalArgumentException.class);
-            if (iae != null) {
-                throw new IllegalArgumentException("Cannot create spec for app: "+iae.getMessage(), e);
-            } else {
-                throw Exceptions.propagate(e);
-            }
-        }
-    }
-
-    private void checkApplicationTypesAreValid(ApplicationSpec applicationSpec) {
-        String appType = applicationSpec.getType();
-        if (appType != null) {
-            checkEntityTypeIsValid(appType);
-
-            if (applicationSpec.getEntities() != null) {
-                throw WebResourceUtils.preconditionFailed("Application given explicit type '%s' must not define entities", appType);
-            }
-            return;
-        }
-
-        for (org.apache.brooklyn.rest.domain.EntitySpec entitySpec : applicationSpec.getEntities()) {
-            String entityType = entitySpec.getType();
-            checkEntityTypeIsValid(checkNotNull(entityType, "entityType"));
-        }
-    }
-
-    private void checkSpecTypeIsValid(String type, Class<? extends BrooklynObject> subType) {
-        Maybe<RegisteredType> typeV = RegisteredTypes.tryValidate(mgmt().getTypeRegistry().get(type), RegisteredTypeLoadingContexts.spec(subType));
-        if (!typeV.isNull()) {
-            // found, throw if any problem
-            typeV.get();
-            return;
-        }
-
-        // not found, try classloading
-        try {
-            brooklyn().getCatalogClassLoader().loadClass(type);
-        } catch (ClassNotFoundException e) {
-            log.debug("Class not found for type '" + type + "'; reporting 404", e);
-            throw WebResourceUtils.notFound("Undefined type '%s'", type);
-        }
-        log.info(JavaClassNames.simpleClassName(subType)+" type '{}' not defined in catalog but is on classpath; continuing", type);
-    }
-
-    private void checkEntityTypeIsValid(String type) {
-        checkSpecTypeIsValid(type, Entity.class);
-    }
-
-    @SuppressWarnings("deprecation")
-    private void checkLocationsAreValid(ApplicationSpec applicationSpec) {
-        for (String locationId : applicationSpec.getLocations()) {
-            locationId = BrooklynRestResourceUtils.fixLocation(locationId);
-            if (!brooklyn().getLocationRegistry().canMaybeResolve(locationId) && brooklyn().getLocationRegistry().getDefinedLocationById(locationId)==null) {
-                throw WebResourceUtils.notFound("Undefined location '%s'", locationId);
-            }
-        }
-    }
-
-    @Override
-    public List<EntitySummary> getDescendants(String application, String typeRegex) {
-        return EntityTransformer.entitySummaries(brooklyn().descendantsOfType(application, application, typeRegex));
-    }
-
-    @Override
-    public Map<String, Object> getDescendantsSensor(String application, String sensor, String typeRegex) {
-        Iterable<Entity> descs = brooklyn().descendantsOfType(application, application, typeRegex);
-        return getSensorMap(sensor, descs);
-    }
-
-    public static Map<String, Object> getSensorMap(String sensor, Iterable<Entity> descs) {
-        if (Iterables.isEmpty(descs))
-            return Collections.emptyMap();
-        Map<String, Object> result = MutableMap.of();
-        Iterator<Entity> di = descs.iterator();
-        Sensor<?> s = null;
-        while (di.hasNext()) {
-            Entity potentialSource = di.next();
-            s = potentialSource.getEntityType().getSensor(sensor);
-            if (s!=null) break;
-        }
-        if (s==null)
-            s = Sensors.newSensor(Object.class, sensor);
-        if (!(s instanceof AttributeSensor<?>)) {
-            log.warn("Cannot retrieve non-attribute sensor "+s+" for entities; returning empty map");
-            return result;
-        }
-        for (Entity e: descs) {
-            Object v = null;
-            try {
-                v = e.getAttribute((AttributeSensor<?>)s);
-            } catch (Exception exc) {
-                Exceptions.propagateIfFatal(exc);
-                log.warn("Error retrieving sensor "+s+" for "+e+" (ignoring): "+exc);
-            }
-            if (v!=null)
-                result.put(e.getId(), v);
-        }
-        return result;
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java
deleted file mode 100644
index a26f1a1..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java
+++ /dev/null
@@ -1,521 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.rest.resources;
-
-import java.io.InputStream;
-import java.net.URI;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.NoSuchElementException;
-import java.util.Set;
-
-import javax.annotation.Nullable;
-import javax.ws.rs.Consumes;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.Response.Status;
-
-import org.apache.brooklyn.api.catalog.CatalogItem;
-import org.apache.brooklyn.api.entity.Application;
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.entity.EntitySpec;
-import org.apache.brooklyn.api.location.Location;
-import org.apache.brooklyn.api.location.LocationSpec;
-import org.apache.brooklyn.api.policy.Policy;
-import org.apache.brooklyn.api.policy.PolicySpec;
-import org.apache.brooklyn.api.typereg.RegisteredType;
-import org.apache.brooklyn.core.catalog.CatalogPredicates;
-import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog;
-import org.apache.brooklyn.core.catalog.internal.CatalogDto;
-import org.apache.brooklyn.core.catalog.internal.CatalogItemComparator;
-import org.apache.brooklyn.core.catalog.internal.CatalogUtils;
-import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
-import org.apache.brooklyn.core.mgmt.entitlement.Entitlements.StringAndArgument;
-import org.apache.brooklyn.core.typereg.RegisteredTypeLoadingContexts;
-import org.apache.brooklyn.core.typereg.RegisteredTypePredicates;
-import org.apache.brooklyn.core.typereg.RegisteredTypes;
-import org.apache.brooklyn.rest.api.CatalogApi;
-import org.apache.brooklyn.rest.domain.ApiError;
-import org.apache.brooklyn.rest.domain.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.filter.HaHotStateRequired;
-import org.apache.brooklyn.rest.transform.CatalogTransformer;
-import org.apache.brooklyn.rest.util.WebResourceUtils;
-import org.apache.brooklyn.util.collections.MutableMap;
-import org.apache.brooklyn.util.collections.MutableSet;
-import org.apache.brooklyn.util.core.ResourceUtils;
-import org.apache.brooklyn.util.exceptions.Exceptions;
-import org.apache.brooklyn.util.guava.Maybe;
-import org.apache.brooklyn.util.stream.Streams;
-import org.apache.brooklyn.util.text.StringPredicates;
-import org.apache.brooklyn.util.text.Strings;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.common.base.Function;
-import com.google.common.base.Predicate;
-import com.google.common.base.Predicates;
-import com.google.common.collect.FluentIterable;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import com.google.common.io.Files;
-import com.sun.jersey.core.header.FormDataContentDisposition;
-
-@HaHotStateRequired
-public class CatalogResource extends AbstractBrooklynRestResource implements CatalogApi {
-
-    private static final Logger log = LoggerFactory.getLogger(CatalogResource.class);
-    
-    @SuppressWarnings("rawtypes")
-    private final Function<CatalogItem, CatalogItemSummary> TO_CATALOG_ITEM_SUMMARY = new Function<CatalogItem, CatalogItemSummary>() {
-        @Override
-        public CatalogItemSummary apply(@Nullable CatalogItem input) {
-            return CatalogTransformer.catalogItemSummary(brooklyn(), input);
-        }
-    };
-
-    @Override
-    @Consumes(MediaType.MULTIPART_FORM_DATA)
-    public Response createFromMultipart(InputStream uploadedInputStream, FormDataContentDisposition fileDetail) {
-      return create(Streams.readFullyString(uploadedInputStream));
-    }
-
-    static Set<String> missingIcons = MutableSet.of();
-    
-    @Override
-    public Response create(String yaml) {
-        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.ADD_CATALOG_ITEM, yaml)) {
-            throw WebResourceUtils.forbidden("User '%s' is not authorized to add catalog item",
-                Entitlements.getEntitlementContext().user());
-        }
-        
-        Iterable<? extends CatalogItem<?, ?>> items; 
-        try {
-            items = brooklyn().getCatalog().addItems(yaml);
-        } catch (IllegalArgumentException e) {
-            return Response.status(Status.BAD_REQUEST)
-                    .type(MediaType.APPLICATION_JSON)
-                    .entity(ApiError.of(e))
-                    .build();
-        }
-
-        log.info("REST created catalog items: "+items);
-
-        Map<String,Object> result = MutableMap.of();
-        
-        for (CatalogItem<?,?> item: items) {
-            try {
-                result.put(item.getId(), CatalogTransformer.catalogItemSummary(brooklyn(), item));
-            } catch (Throwable t) {
-                log.warn("Error loading catalog item '"+item+"' (rethrowing): "+t);
-                throw Exceptions.propagate(t);
-            }
-        }
-        return Response.status(Status.CREATED).entity(result).build();
-    }
-
-    @SuppressWarnings("deprecation")
-    @Override
-    public Response resetXml(String xml, boolean ignoreErrors) {
-        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_CATALOG_ITEM, null) ||
-            !Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.ADD_CATALOG_ITEM, null)) {
-            throw WebResourceUtils.forbidden("User '%s' is not authorized to modify catalog",
-                Entitlements.getEntitlementContext().user());
-        }
-
-        ((BasicBrooklynCatalog)mgmt().getCatalog()).reset(CatalogDto.newDtoFromXmlContents(xml, "REST reset"), !ignoreErrors);
-        return Response.ok().build();
-    }
-    
-    @Override
-    @Deprecated
-    public void deleteEntity(String entityId) throws Exception {
-        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_CATALOG_ITEM, StringAndArgument.of(entityId, "delete"))) {
-            throw WebResourceUtils.forbidden("User '%s' is not authorized to modify catalog",
-                Entitlements.getEntitlementContext().user());
-        }
-        try {
-            Maybe<RegisteredType> item = RegisteredTypes.tryValidate(mgmt().getTypeRegistry().get(entityId), RegisteredTypeLoadingContexts.spec(Entity.class));
-            if (item.isNull()) {
-                throw WebResourceUtils.notFound("Entity with id '%s' not found", entityId);
-            }
-            if (item.isAbsent()) {
-                throw WebResourceUtils.notFound("Item with id '%s' is not an entity", entityId);
-            }
-            
-            brooklyn().getCatalog().deleteCatalogItem(item.get().getSymbolicName(), item.get().getVersion());
-            
-        } catch (NoSuchElementException e) {
-            // shouldn't come here
-            throw WebResourceUtils.notFound("Entity with id '%s' could not be deleted", entityId);
-            
-        }
-    }
-
-    @Override
-    public void deleteApplication(String symbolicName, String version) throws Exception {
-        deleteEntity(symbolicName, version);
-    }
-
-    @Override
-    public void deleteEntity(String symbolicName, String version) throws Exception {
-        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_CATALOG_ITEM, StringAndArgument.of(symbolicName+(Strings.isBlank(version) ? "" : ":"+version), "delete"))) {
-            throw WebResourceUtils.forbidden("User '%s' is not authorized to modify catalog",
-                Entitlements.getEntitlementContext().user());
-        }
-        
-        RegisteredType item = mgmt().getTypeRegistry().get(symbolicName, version);
-        if (item == null) {
-            throw WebResourceUtils.notFound("Entity with id '%s:%s' not found", symbolicName, version);
-        } else if (!RegisteredTypePredicates.IS_ENTITY.apply(item) && !RegisteredTypePredicates.IS_APPLICATION.apply(item)) {
-            throw WebResourceUtils.preconditionFailed("Item with id '%s:%s' not an entity", symbolicName, version);
-        } else {
-            brooklyn().getCatalog().deleteCatalogItem(item.getSymbolicName(), item.getVersion());
-        }
-    }
-
-    @Override
-    public void deletePolicy(String policyId, String version) throws Exception {
-        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_CATALOG_ITEM, StringAndArgument.of(policyId+(Strings.isBlank(version) ? "" : ":"+version), "delete"))) {
-            throw WebResourceUtils.forbidden("User '%s' is not authorized to modify catalog",
-                Entitlements.getEntitlementContext().user());
-        }
-        
-        RegisteredType item = mgmt().getTypeRegistry().get(policyId, version);
-        if (item == null) {
-            throw WebResourceUtils.notFound("Policy with id '%s:%s' not found", policyId, version);
-        } else if (!RegisteredTypePredicates.IS_POLICY.apply(item)) {
-            throw WebResourceUtils.preconditionFailed("Item with id '%s:%s' not a policy", policyId, version);
-        } else {
-            brooklyn().getCatalog().deleteCatalogItem(item.getSymbolicName(), item.getVersion());
-        }
-    }
-
-    @Override
-    public void deleteLocation(String locationId, String version) throws Exception {
-        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_CATALOG_ITEM, StringAndArgument.of(locationId+(Strings.isBlank(version) ? "" : ":"+version), "delete"))) {
-            throw WebResourceUtils.forbidden("User '%s' is not authorized to modify catalog",
-                Entitlements.getEntitlementContext().user());
-        }
-        
-        RegisteredType item = mgmt().getTypeRegistry().get(locationId, version);
-        if (item == null) {
-            throw WebResourceUtils.notFound("Location with id '%s:%s' not found", locationId, version);
-        } else if (!RegisteredTypePredicates.IS_LOCATION.apply(item)) {
-            throw WebResourceUtils.preconditionFailed("Item with id '%s:%s' not a location", locationId, version);
-        } else {
-            brooklyn().getCatalog().deleteCatalogItem(item.getSymbolicName(), item.getVersion());
-        }
-    }
-
-    @Override
-    public List<CatalogEntitySummary> listEntities(String regex, String fragment, boolean allVersions) {
-        Predicate<CatalogItem<Entity, EntitySpec<?>>> filter =
-                Predicates.and(
-                        CatalogPredicates.IS_ENTITY,
-                        CatalogPredicates.<Entity, EntitySpec<?>>disabled(false));
-        List<CatalogItemSummary> result = getCatalogItemSummariesMatchingRegexFragment(filter, regex, fragment, allVersions);
-        return castList(result, CatalogEntitySummary.class);
-    }
-
-    @Override
-    public List<CatalogItemSummary> listApplications(String regex, String fragment, boolean allVersions) {
-        @SuppressWarnings("unchecked")
-        Predicate<CatalogItem<Application, EntitySpec<? extends Application>>> filter =
-                Predicates.and(
-                        CatalogPredicates.IS_TEMPLATE,
-                        CatalogPredicates.<Application,EntitySpec<? extends Application>>deprecated(false),
-                        CatalogPredicates.<Application,EntitySpec<? extends Application>>disabled(false));
-        return getCatalogItemSummariesMatchingRegexFragment(filter, regex, fragment, allVersions);
-    }
-
-    @Override
-    @Deprecated
-    public CatalogEntitySummary getEntity(String entityId) {
-        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CATALOG_ITEM, entityId)) {
-            throw WebResourceUtils.forbidden("User '%s' is not authorized to see catalog entry",
-                Entitlements.getEntitlementContext().user());
-        }
-
-        CatalogItem<Entity,EntitySpec<?>> result =
-                CatalogUtils.getCatalogItemOptionalVersion(mgmt(), Entity.class, entityId);
-
-        if (result==null) {
-            throw WebResourceUtils.notFound("Entity with id '%s' not found", entityId);
-        }
-
-        return CatalogTransformer.catalogEntitySummary(brooklyn(), result);
-    }
-    
-    @Override
-    public CatalogEntitySummary getEntity(String symbolicName, String version) {
-        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CATALOG_ITEM, symbolicName+(Strings.isBlank(version)?"":":"+version))) {
-            throw WebResourceUtils.forbidden("User '%s' is not authorized to see catalog entry",
-                Entitlements.getEntitlementContext().user());
-        }
-
-        //TODO These casts are not pretty, we could just provide separate get methods for the different types?
-        //Or we could provide asEntity/asPolicy cast methods on the CataloItem doing a safety check internally
-        @SuppressWarnings("unchecked")
-        CatalogItem<Entity, EntitySpec<?>> result =
-              (CatalogItem<Entity, EntitySpec<?>>) brooklyn().getCatalog().getCatalogItem(symbolicName, version);
-
-        if (result==null) {
-            throw WebResourceUtils.notFound("Entity with id '%s:%s' not found", symbolicName, version);
-        }
-
-        return CatalogTransformer.catalogEntitySummary(brooklyn(), result);
-    }
-
-    @Override
-    @Deprecated
-    public CatalogEntitySummary getApplication(String applicationId) throws Exception {
-        return getEntity(applicationId);
-    }
-
-    @Override
-    public CatalogEntitySummary getApplication(String symbolicName, String version) {
-        return getEntity(symbolicName, version);
-    }
-
-    @Override
-    public List<CatalogPolicySummary> listPolicies(String regex, String fragment, boolean allVersions) {
-        Predicate<CatalogItem<Policy, PolicySpec<?>>> filter =
-                Predicates.and(
-                        CatalogPredicates.IS_POLICY,
-                        CatalogPredicates.<Policy, PolicySpec<?>>disabled(false));
-        List<CatalogItemSummary> result = getCatalogItemSummariesMatchingRegexFragment(filter, regex, fragment, allVersions);
-        return castList(result, CatalogPolicySummary.class);
-    }
-
-    @Override
-    @Deprecated
-    public CatalogPolicySummary getPolicy(String policyId) {
-        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CATALOG_ITEM, policyId)) {
-            throw WebResourceUtils.forbidden("User '%s' is not authorized to see catalog entry",
-                Entitlements.getEntitlementContext().user());
-        }
-
-        CatalogItem<? extends Policy, PolicySpec<?>> result =
-            CatalogUtils.getCatalogItemOptionalVersion(mgmt(), Policy.class, policyId);
-
-        if (result==null) {
-            throw WebResourceUtils.notFound("Policy with id '%s' not found", policyId);
-        }
-
-        return CatalogTransformer.catalogPolicySummary(brooklyn(), result);
-    }
-
-    @Override
-    public CatalogPolicySummary getPolicy(String policyId, String version) throws Exception {
-        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CATALOG_ITEM, policyId+(Strings.isBlank(version)?"":":"+version))) {
-            throw WebResourceUtils.forbidden("User '%s' is not authorized to see catalog entry",
-                Entitlements.getEntitlementContext().user());
-        }
-
-        @SuppressWarnings("unchecked")
-        CatalogItem<? extends Policy, PolicySpec<?>> result =
-                (CatalogItem<? extends Policy, PolicySpec<?>>)brooklyn().getCatalog().getCatalogItem(policyId, version);
-
-        if (result==null) {
-          throw WebResourceUtils.notFound("Policy with id '%s:%s' not found", policyId, version);
-        }
-
-        return CatalogTransformer.catalogPolicySummary(brooklyn(), result);
-    }
-
-    @Override
-    public List<CatalogLocationSummary> listLocations(String regex, String fragment, boolean allVersions) {
-        Predicate<CatalogItem<Location, LocationSpec<?>>> filter =
-                Predicates.and(
-                        CatalogPredicates.IS_LOCATION,
-                        CatalogPredicates.<Location, LocationSpec<?>>disabled(false));
-        List<CatalogItemSummary> result = getCatalogItemSummariesMatchingRegexFragment(filter, regex, fragment, allVersions);
-        return castList(result, CatalogLocationSummary.class);
-    }
-
-    @Override
-    @Deprecated
-    public CatalogLocationSummary getLocation(String locationId) {
-        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CATALOG_ITEM, locationId)) {
-            throw WebResourceUtils.forbidden("User '%s' is not authorized to see catalog entry",
-                Entitlements.getEntitlementContext().user());
-        }
-
-        CatalogItem<? extends Location, LocationSpec<?>> result =
-            CatalogUtils.getCatalogItemOptionalVersion(mgmt(), Location.class, locationId);
-
-        if (result==null) {
-            throw WebResourceUtils.notFound("Location with id '%s' not found", locationId);
-        }
-
-        return CatalogTransformer.catalogLocationSummary(brooklyn(), result);
-    }
-
-    @Override
-    public CatalogLocationSummary getLocation(String locationId, String version) throws Exception {
-        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CATALOG_ITEM, locationId+(Strings.isBlank(version)?"":":"+version))) {
-            throw WebResourceUtils.forbidden("User '%s' is not authorized to see catalog entry",
-                Entitlements.getEntitlementContext().user());
-        }
-
-        @SuppressWarnings("unchecked")
-        CatalogItem<? extends Location, LocationSpec<?>> result =
-                (CatalogItem<? extends Location, LocationSpec<?>>)brooklyn().getCatalog().getCatalogItem(locationId, version);
-
-        if (result==null) {
-          throw WebResourceUtils.notFound("Location with id '%s:%s' not found", locationId, version);
-        }
-
-        return CatalogTransformer.catalogLocationSummary(brooklyn(), result);
-    }
-
-    @SuppressWarnings({ "unchecked", "rawtypes" })
-    private <T,SpecT> List<CatalogItemSummary> getCatalogItemSummariesMatchingRegexFragment(Predicate<CatalogItem<T,SpecT>> type, String regex, String fragment, boolean allVersions) {
-        List filters = new ArrayList();
-        filters.add(type);
-        if (Strings.isNonEmpty(regex))
-            filters.add(CatalogPredicates.xml(StringPredicates.containsRegex(regex)));
-        if (Strings.isNonEmpty(fragment))
-            filters.add(CatalogPredicates.xml(StringPredicates.containsLiteralIgnoreCase(fragment)));
-        if (!allVersions)
-            filters.add(CatalogPredicates.isBestVersion(mgmt()));
-        
-        filters.add(CatalogPredicates.entitledToSee(mgmt()));
-
-        ImmutableList<CatalogItem<Object, Object>> sortedItems =
-                FluentIterable.from(brooklyn().getCatalog().getCatalogItems())
-                    .filter(Predicates.and(filters))
-                    .toSortedList(CatalogItemComparator.getInstance());
-        return Lists.transform(sortedItems, TO_CATALOG_ITEM_SUMMARY);
-    }
-
-    @Override
-    @Deprecated
-    public Response getIcon(String itemId) {
-        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CATALOG_ITEM, itemId)) {
-            throw WebResourceUtils.forbidden("User '%s' is not authorized to see catalog entry",
-                Entitlements.getEntitlementContext().user());
-        }
-
-        return getCatalogItemIcon( mgmt().getTypeRegistry().get(itemId) );
-    }
-
-    @Override
-    public Response getIcon(String itemId, String version) {
-        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CATALOG_ITEM, itemId+(Strings.isBlank(version)?"":":"+version))) {
-            throw WebResourceUtils.forbidden("User '%s' is not authorized to see catalog entry",
-                Entitlements.getEntitlementContext().user());
-        }
-        
-        return getCatalogItemIcon(mgmt().getTypeRegistry().get(itemId, version));
-    }
-
-    @Override
-    public void setDeprecatedLegacy(String itemId, boolean deprecated) {
-        log.warn("Use of deprecated \"/v1/catalog/entities/{itemId}/deprecated/{deprecated}\" for "+itemId
-                +"; use \"/v1/catalog/entities/{itemId}/deprecated\" with request body");
-        setDeprecated(itemId, deprecated);
-    }
-    
-    @SuppressWarnings("deprecation")
-    @Override
-    public void setDeprecated(String itemId, boolean deprecated) {
-        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_CATALOG_ITEM, StringAndArgument.of(itemId, "deprecated"))) {
-            throw WebResourceUtils.forbidden("User '%s' is not authorized to modify catalog",
-                    Entitlements.getEntitlementContext().user());
-        }
-        CatalogUtils.setDeprecated(mgmt(), itemId, deprecated);
-    }
-
-    @SuppressWarnings("deprecation")
-    @Override
-    public void setDisabled(String itemId, boolean disabled) {
-        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_CATALOG_ITEM, StringAndArgument.of(itemId, "disabled"))) {
-            throw WebResourceUtils.forbidden("User '%s' is not authorized to modify catalog",
-                    Entitlements.getEntitlementContext().user());
-        }
-        CatalogUtils.setDisabled(mgmt(), itemId, disabled);
-    }
-
-    private Response getCatalogItemIcon(RegisteredType result) {
-        String url = result.getIconUrl();
-        if (url==null) {
-            log.debug("No icon available for "+result+"; returning "+Status.NO_CONTENT);
-            return Response.status(Status.NO_CONTENT).build();
-        }
-        
-        if (brooklyn().isUrlServerSideAndSafe(url)) {
-            // classpath URL's we will serve IF they end with a recognised image format;
-            // paths (ie non-protocol) and 
-            // NB, for security, file URL's are NOT served
-            log.debug("Loading and returning "+url+" as icon for "+result);
-            
-            MediaType mime = WebResourceUtils.getImageMediaTypeFromExtension(Files.getFileExtension(url));
-            try {
-                Object content = ResourceUtils.create(CatalogUtils.newClassLoadingContext(mgmt(), result)).getResourceFromUrl(url);
-                return Response.ok(content, mime).build();
-            } catch (Exception e) {
-                Exceptions.propagateIfFatal(e);
-                synchronized (missingIcons) {
-                    if (missingIcons.add(url)) {
-                        // note: this can be quite common when running from an IDE, as resources may not be copied;
-                        // a mvn build should sort it out (the IDE will then find the resources, until you clean or maybe refresh...)
-                        log.warn("Missing icon data for "+result.getId()+", expected at: "+url+" (subsequent messages will log debug only)");
-                        log.debug("Trace for missing icon data at "+url+": "+e, e);
-                    } else {
-                        log.debug("Missing icon data for "+result.getId()+", expected at: "+url+" (already logged WARN and error details)");
-                    }
-                }
-                throw WebResourceUtils.notFound("Icon unavailable for %s", result.getId());
-            }
-        }
-        
-        log.debug("Returning redirect to "+url+" as icon for "+result);
-        
-        // for anything else we do a redirect (e.g. http / https; perhaps ftp)
-        return Response.temporaryRedirect(URI.create(url)).build();
-    }
-
-    // TODO Move to an appropriate utility class?
-    @SuppressWarnings("unchecked")
-    private static <T> List<T> castList(List<? super T> list, Class<T> elementType) {
-        List<T> result = Lists.newArrayList();
-        Iterator<? super T> li = list.iterator();
-        while (li.hasNext()) {
-            try {
-                result.add((T) li.next());
-            } catch (Throwable throwable) {
-                if (throwable instanceof NoClassDefFoundError) {
-                    // happens if class cannot be loaded for any reason during transformation - don't treat as fatal
-                } else {
-                    Exceptions.propagateIfFatal(throwable);
-                }
-                
-                // item cannot be transformed; we will have logged a warning earlier
-                log.debug("Ignoring invalid catalog item: "+throwable);
-            }
-        }
-        return result;
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/EffectorResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/EffectorResource.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/EffectorResource.java
deleted file mode 100644
index 710e2de..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/EffectorResource.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.rest.resources;
-
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-import javax.annotation.Nullable;
-import javax.ws.rs.core.Response;
-
-import org.apache.brooklyn.api.effector.Effector;
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.mgmt.Task;
-import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
-import org.apache.brooklyn.core.mgmt.entitlement.Entitlements.StringAndArgument;
-import org.apache.brooklyn.core.mgmt.internal.EffectorUtils;
-import org.apache.brooklyn.rest.api.EffectorApi;
-import org.apache.brooklyn.rest.domain.EffectorSummary;
-import org.apache.brooklyn.rest.domain.SummaryComparators;
-import org.apache.brooklyn.rest.filter.HaHotStateRequired;
-import org.apache.brooklyn.rest.transform.EffectorTransformer;
-import org.apache.brooklyn.rest.transform.TaskTransformer;
-import org.apache.brooklyn.rest.util.WebResourceUtils;
-import org.apache.brooklyn.util.exceptions.Exceptions;
-import org.apache.brooklyn.util.guava.Maybe;
-import org.apache.brooklyn.util.time.Time;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.common.base.Function;
-import com.google.common.base.Predicate;
-import com.google.common.collect.FluentIterable;
-
-@HaHotStateRequired
-public class EffectorResource extends AbstractBrooklynRestResource implements EffectorApi {
-
-    private static final Logger log = LoggerFactory.getLogger(EffectorResource.class);
-
-    @Override
-    public List<EffectorSummary> list(final String application, final String entityToken) {
-        final Entity entity = brooklyn().getEntity(application, entityToken);
-        return FluentIterable
-                .from(entity.getEntityType().getEffectors())
-                .filter(new Predicate<Effector<?>>() {
-                    @Override
-                    public boolean apply(@Nullable Effector<?> input) {
-                        return Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.INVOKE_EFFECTOR,
-                                Entitlements.EntityAndItem.of(entity, StringAndArgument.of(input.getName(), null)));
-                    }
-                })
-                .transform(new Function<Effector<?>, EffectorSummary>() {
-                    @Override
-                    public EffectorSummary apply(Effector<?> effector) {
-                        return EffectorTransformer.effectorSummary(entity, effector);
-                    }
-                })
-                .toSortedList(SummaryComparators.nameComparator());
-    }
-
-    @Override
-    public Response invoke(String application, String entityToken, String effectorName,
-            String timeout, Map<String, Object> parameters) {
-        final Entity entity = brooklyn().getEntity(application, entityToken);
-
-        // TODO check effectors?
-        Maybe<Effector<?>> effector = EffectorUtils.findEffectorDeclared(entity, effectorName);
-        if (effector.isAbsentOrNull()) {
-            throw WebResourceUtils.notFound("Entity '%s' has no effector with name '%s'", entityToken, effectorName);
-        } else if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.INVOKE_EFFECTOR,
-                Entitlements.EntityAndItem.of(entity, StringAndArgument.of(effector.get().getName(), null)))) {
-            throw WebResourceUtils.forbidden("User '%s' is not authorized to invoke effector %s on entity %s",
-                    Entitlements.getEntitlementContext().user(), effector.get().getName(), entity);
-        }
-        log.info("REST invocation of " + entity + "." + effector.get() + " " + parameters);
-        Task<?> t = entity.invoke(effector.get(), parameters);
-
-        try {
-            Object result;
-            if (timeout == null || timeout.isEmpty() || "never".equalsIgnoreCase(timeout)) {
-                result = t.get();
-            } else {
-                long timeoutMillis = "always".equalsIgnoreCase(timeout) ? 0 : Time.parseElapsedTime(timeout);
-                try {
-                    if (timeoutMillis == 0) throw new TimeoutException();
-                    result = t.get(timeoutMillis, TimeUnit.MILLISECONDS);
-                } catch (TimeoutException e) {
-                    result = TaskTransformer.taskSummary(t);
-                }
-            }
-            return Response.status(Response.Status.ACCEPTED).entity(result).build();
-        } catch (Exception e) {
-            throw Exceptions.propagate(e);
-        }
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/EntityConfigResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/EntityConfigResource.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/EntityConfigResource.java
deleted file mode 100644
index 7aaa3f7..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/EntityConfigResource.java
+++ /dev/null
@@ -1,206 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.rest.resources;
-
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.Callable;
-
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.mgmt.ManagementContext;
-import org.apache.brooklyn.config.ConfigKey;
-import org.apache.brooklyn.core.config.BasicConfigKey;
-import org.apache.brooklyn.core.entity.Entities;
-import org.apache.brooklyn.core.entity.EntityInternal;
-import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
-import org.apache.brooklyn.core.mgmt.entitlement.Entitlements.EntityAndItem;
-import org.apache.brooklyn.rest.api.EntityConfigApi;
-import org.apache.brooklyn.rest.domain.EntityConfigSummary;
-import org.apache.brooklyn.rest.filter.HaHotStateRequired;
-import org.apache.brooklyn.rest.transform.EntityTransformer;
-import org.apache.brooklyn.rest.util.WebResourceUtils;
-import org.apache.brooklyn.util.core.flags.TypeCoercions;
-import org.apache.brooklyn.util.core.task.Tasks;
-import org.apache.brooklyn.util.core.task.ValueResolver;
-import org.apache.brooklyn.util.text.Strings;
-import org.apache.brooklyn.util.time.Duration;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.common.base.Predicates;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-
-@HaHotStateRequired
-public class EntityConfigResource extends AbstractBrooklynRestResource implements EntityConfigApi {
-
-    private static final Logger LOG = LoggerFactory.getLogger(EntityConfigResource.class);
-
-    @Override
-    public List<EntityConfigSummary> list(final String application, final String entityToken) {
-        final Entity entity = brooklyn().getEntity(application, entityToken);
-        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY, entity)) {
-            throw WebResourceUtils.forbidden("User '%s' is not authorized to see entity '%s'",
-                    Entitlements.getEntitlementContext().user(), entity);
-        }
-
-        // TODO merge with keys which have values:
-        //      ((EntityInternal) entity).config().getBag().getAllConfigAsConfigKeyMap();
-        List<EntityConfigSummary> result = Lists.newArrayList();
-        
-        for (ConfigKey<?> key : entity.getEntityType().getConfigKeys()) {
-            // Exclude config that user is not allowed to see
-            if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CONFIG, new EntityAndItem<String>(entity, key.getName()))) {
-                LOG.trace("User {} not authorized to see config {} of entity {}; excluding from ConfigKey list results", 
-                        new Object[] {Entitlements.getEntitlementContext().user(), key.getName(), entity});
-                continue;
-            }
-            result.add(EntityTransformer.entityConfigSummary(entity, key));
-        }
-        
-        return result;
-    }
-
-    // TODO support parameters  ?show=value,summary&name=xxx &format={string,json,xml}
-    // (and in sensors class)
-    @Override
-    public Map<String, Object> batchConfigRead(String application, String entityToken, Boolean raw) {
-        // TODO: add test
-        Entity entity = brooklyn().getEntity(application, entityToken);
-        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY, entity)) {
-            throw WebResourceUtils.forbidden("User '%s' is not authorized to see entity '%s'",
-                    Entitlements.getEntitlementContext().user(), entity);
-        }
-
-        // wrap in a task for better runtime view
-        return Entities.submit(entity, Tasks.<Map<String,Object>>builder().displayName("REST API batch config read").body(new BatchConfigRead(mgmt(), this, entity, raw)).build()).getUnchecked();
-    }
-    
-    private static class BatchConfigRead implements Callable<Map<String,Object>> {
-        private final ManagementContext mgmt;
-        private final EntityConfigResource resource;
-        private final Entity entity;
-        private final Boolean raw;
-
-        public BatchConfigRead(ManagementContext mgmt, EntityConfigResource resource, Entity entity, Boolean raw) {
-            this.mgmt = mgmt;
-            this.resource = resource;
-            this.entity = entity;
-            this.raw = raw;
-        }
-
-        @Override
-        public Map<String, Object> call() throws Exception {
-            Map<ConfigKey<?>, ?> source = ((EntityInternal) entity).config().getBag().getAllConfigAsConfigKeyMap();
-            Map<String, Object> result = Maps.newLinkedHashMap();
-            for (Map.Entry<ConfigKey<?>, ?> ek : source.entrySet()) {
-                ConfigKey<?> key = ek.getKey();
-                Object value = ek.getValue();
-                
-                // Exclude config that user is not allowed to see
-                if (!Entitlements.isEntitled(mgmt.getEntitlementManager(), Entitlements.SEE_CONFIG, new EntityAndItem<String>(entity, ek.getKey().getName()))) {
-                    LOG.trace("User {} not authorized to see sensor {} of entity {}; excluding from current-state results", 
-                            new Object[] {Entitlements.getEntitlementContext().user(), ek.getKey().getName(), entity});
-                    continue;
-                }
-                result.put(key.getName(), 
-                    resource.resolving(value).preferJson(true).asJerseyOutermostReturnValue(false).raw(raw).context(entity).timeout(Duration.ZERO).renderAs(key).resolve());
-            }
-            return result;
-        }
-    }
-
-    @Override
-    public Object get(String application, String entityToken, String configKeyName, Boolean raw) {
-        return get(true, application, entityToken, configKeyName, raw);
-    }
-
-    @Override
-    public String getPlain(String application, String entityToken, String configKeyName, Boolean raw) {
-        return Strings.toString(get(false, application, entityToken, configKeyName, raw));
-    }
-
-    public Object get(boolean preferJson, String application, String entityToken, String configKeyName, Boolean raw) {
-        Entity entity = brooklyn().getEntity(application, entityToken);
-        ConfigKey<?> ck = findConfig(entity, configKeyName);
-        
-        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY, entity)) {
-            throw WebResourceUtils.forbidden("User '%s' is not authorized to see entity '%s'",
-                    Entitlements.getEntitlementContext().user(), entity);
-        }
-        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CONFIG, new EntityAndItem<String>(entity, ck.getName()))) {
-            throw WebResourceUtils.forbidden("User '%s' is not authorized to see entity '%s' config '%s'",
-                    Entitlements.getEntitlementContext().user(), entity, ck.getName());
-        }
-        
-        Object value = ((EntityInternal)entity).config().getRaw(ck).orNull();
-        return resolving(value).preferJson(preferJson).asJerseyOutermostReturnValue(true).raw(raw).context(entity).timeout(ValueResolver.PRETTY_QUICK_WAIT).renderAs(ck).resolve();
-    }
-
-    private ConfigKey<?> findConfig(Entity entity, String configKeyName) {
-        ConfigKey<?> ck = entity.getEntityType().getConfigKey(configKeyName);
-        if (ck == null)
-            ck = new BasicConfigKey<Object>(Object.class, configKeyName);
-        return ck;
-    }
-
-    @SuppressWarnings({ "rawtypes", "unchecked" })
-    @Override
-    public void setFromMap(String application, String entityToken, Boolean recurse, Map newValues) {
-        final Entity entity = brooklyn().getEntity(application, entityToken);
-        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_ENTITY, entity)) {
-            throw WebResourceUtils.forbidden("User '%s' is not authorized to modify entity '%s'",
-                    Entitlements.getEntitlementContext().user(), entity);
-        }
-
-        if (LOG.isDebugEnabled())
-            LOG.debug("REST user " + Entitlements.getEntitlementContext() + " setting configs " + newValues);
-        for (Object entry : newValues.entrySet()) {
-            String configName = Strings.toString(((Map.Entry) entry).getKey());
-            Object newValue = ((Map.Entry) entry).getValue();
-
-            ConfigKey ck = findConfig(entity, configName);
-            ((EntityInternal) entity).config().set(ck, TypeCoercions.coerce(newValue, ck.getTypeToken()));
-            if (Boolean.TRUE.equals(recurse)) {
-                for (Entity e2 : Entities.descendants(entity, Predicates.alwaysTrue(), false)) {
-                    ((EntityInternal) e2).config().set(ck, newValue);
-                }
-            }
-        }
-    }
-
-    @SuppressWarnings({ "rawtypes", "unchecked" })
-    @Override
-    public void set(String application, String entityToken, String configName, Boolean recurse, Object newValue) {
-        final Entity entity = brooklyn().getEntity(application, entityToken);
-        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_ENTITY, entity)) {
-            throw WebResourceUtils.forbidden("User '%s' is not authorized to modify entity '%s'",
-                    Entitlements.getEntitlementContext().user(), entity);
-        }
-
-        ConfigKey ck = findConfig(entity, configName);
-        LOG.debug("REST setting config " + configName + " on " + entity + " to " + newValue);
-        ((EntityInternal) entity).config().set(ck, TypeCoercions.coerce(newValue, ck.getTypeToken()));
-        if (Boolean.TRUE.equals(recurse)) {
-            for (Entity e2 : Entities.descendants(entity, Predicates.alwaysTrue(), false)) {
-                ((EntityInternal) e2).config().set(ck, newValue);
-            }
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/EntityResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/EntityResource.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/EntityResource.java
deleted file mode 100644
index c4df6d4..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/EntityResource.java
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.rest.resources;
-
-import static javax.ws.rs.core.Response.created;
-import static javax.ws.rs.core.Response.status;
-import static javax.ws.rs.core.Response.Status.ACCEPTED;
-
-import java.net.URI;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import javax.ws.rs.core.Context;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.Response.ResponseBuilder;
-import javax.ws.rs.core.Response.Status;
-import javax.ws.rs.core.UriInfo;
-
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.location.Location;
-import org.apache.brooklyn.api.mgmt.Task;
-import org.apache.brooklyn.core.mgmt.BrooklynTags;
-import org.apache.brooklyn.core.mgmt.BrooklynTags.NamedStringTag;
-import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
-import org.apache.brooklyn.core.mgmt.EntityManagementUtils;
-import org.apache.brooklyn.core.mgmt.EntityManagementUtils.CreationResult;
-import org.apache.brooklyn.core.mgmt.entitlement.EntitlementPredicates;
-import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
-import org.apache.brooklyn.rest.api.EntityApi;
-import org.apache.brooklyn.rest.domain.EntitySummary;
-import org.apache.brooklyn.rest.domain.LocationSummary;
-import org.apache.brooklyn.rest.domain.TaskSummary;
-import org.apache.brooklyn.rest.filter.HaHotStateRequired;
-import org.apache.brooklyn.rest.transform.EntityTransformer;
-import org.apache.brooklyn.rest.transform.LocationTransformer;
-import org.apache.brooklyn.rest.transform.LocationTransformer.LocationDetailLevel;
-import org.apache.brooklyn.rest.transform.TaskTransformer;
-import org.apache.brooklyn.rest.util.WebResourceUtils;
-import org.apache.brooklyn.util.collections.MutableList;
-import org.apache.brooklyn.util.core.ResourceUtils;
-import org.apache.brooklyn.util.time.Duration;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.common.collect.Collections2;
-import com.google.common.collect.FluentIterable;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-import com.google.common.io.Files;
-
-@HaHotStateRequired
-public class EntityResource extends AbstractBrooklynRestResource implements EntityApi {
-
-    private static final Logger log = LoggerFactory.getLogger(EntityResource.class);
-
-    @Context
-    private UriInfo uriInfo;
-    
-    @Override
-    public List<EntitySummary> list(final String application) {
-        return FluentIterable
-                .from(brooklyn().getApplication(application).getChildren())
-                .filter(EntitlementPredicates.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY))
-                .transform(EntityTransformer.FROM_ENTITY)
-                .toList();
-    }
-
-    @Override
-    public EntitySummary get(String application, String entityName) {
-        Entity entity = brooklyn().getEntity(application, entityName);
-        if (Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY, entity)) {
-            return EntityTransformer.entitySummary(entity);
-        }
-        throw WebResourceUtils.forbidden("User '%s' is not authorized to get entity '%s'",
-                Entitlements.getEntitlementContext().user(), entity);
-    }
-
-    @Override
-    public List<EntitySummary> getChildren(final String application, final String entity) {
-        return FluentIterable
-                .from(brooklyn().getEntity(application, entity).getChildren())
-                .filter(EntitlementPredicates.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY))
-                .transform(EntityTransformer.FROM_ENTITY)
-                .toList();
-    }
-
-    @Override
-    public List<EntitySummary> getChildrenOld(String application, String entity) {
-        log.warn("Using deprecated call to /entities when /children should be used");
-        return getChildren(application, entity);
-    }
-
-    @Override
-    public Response addChildren(String applicationToken, String entityToken, Boolean start, String timeoutS, String yaml) {
-        final Entity parent = brooklyn().getEntity(applicationToken, entityToken);
-        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_ENTITY, parent)) {
-            throw WebResourceUtils.forbidden("User '%s' is not authorized to modify entity '%s'",
-                    Entitlements.getEntitlementContext().user(), entityToken);
-        }
-        CreationResult<List<Entity>, List<String>> added = EntityManagementUtils.addChildren(parent, yaml, start)
-                .blockUntilComplete(timeoutS==null ? Duration.millis(20) : Duration.of(timeoutS));
-        ResponseBuilder response;
-        
-        if (added.get().size()==1) {
-            Entity child = Iterables.getOnlyElement(added.get());
-            URI ref = uriInfo.getBaseUriBuilder()
-                    .path(EntityApi.class)
-                    .path(EntityApi.class, "get")
-                    .build(child.getApplicationId(), child.getId());
-            response = created(ref);
-        } else {
-            response = Response.status(Status.CREATED);
-        }
-        return response.entity(TaskTransformer.taskSummary(added.task())).build();
-    }
-
-    @Override
-    public List<TaskSummary> listTasks(String applicationId, String entityId) {
-        Entity entity = brooklyn().getEntity(applicationId, entityId);
-        Set<Task<?>> tasks = BrooklynTaskTags.getTasksInEntityContext(mgmt().getExecutionManager(), entity);
-        return new LinkedList<TaskSummary>(Collections2.transform(tasks, TaskTransformer.FROM_TASK));
-    }
-
-    @Override
-    public TaskSummary getTask(final String application, final String entityToken, String taskId) {
-        // TODO deprecate in favour of ActivityApi.get ?
-        Task<?> t = mgmt().getExecutionManager().getTask(taskId);
-        if (t == null)
-            throw WebResourceUtils.notFound("Cannot find task '%s'", taskId);
-        return TaskTransformer.FROM_TASK.apply(t);
-    }
-
-    @SuppressWarnings("unchecked")
-    @Override
-    public List<Object> listTags(String applicationId, String entityId) {
-        Entity entity = brooklyn().getEntity(applicationId, entityId);
-        return (List<Object>) resolving(MutableList.copyOf(entity.tags().getTags())).preferJson(true).resolve();
-    }
-
-    @Override
-    public Response getIcon(String applicationId, String entityId) {
-        Entity entity = brooklyn().getEntity(applicationId, entityId);
-        String url = entity.getIconUrl();
-        if (url == null)
-            return Response.status(Status.NO_CONTENT).build();
-
-        if (brooklyn().isUrlServerSideAndSafe(url)) {
-            // classpath URL's we will serve IF they end with a recognised image format;
-            // paths (ie non-protocol) and
-            // NB, for security, file URL's are NOT served
-            MediaType mime = WebResourceUtils.getImageMediaTypeFromExtension(Files.getFileExtension(url));
-            Object content = ResourceUtils.create(brooklyn().getCatalogClassLoader()).getResourceFromUrl(url);
-            return Response.ok(content, mime).build();
-        }
-
-        // for anything else we do a redirect (e.g. http / https; perhaps ftp)
-        return Response.temporaryRedirect(URI.create(url)).build();
-    }
-
-    @Override
-    public Response rename(String application, String entity, String newName) {
-        Entity entityLocal = brooklyn().getEntity(application, entity);
-        entityLocal.setDisplayName(newName);
-        return status(Response.Status.OK).build();
-    }
-
-    @Override
-    public Response expunge(String application, String entity, boolean release) {
-        Entity entityLocal = brooklyn().getEntity(application, entity);
-        Task<?> task = brooklyn().expunge(entityLocal, release);
-        TaskSummary summary = TaskTransformer.FROM_TASK.apply(task);
-        return status(ACCEPTED).entity(summary).build();
-    }
-
-    @Override
-    public List<EntitySummary> getDescendants(String application, String entity, String typeRegex) {
-        return EntityTransformer.entitySummaries(brooklyn().descendantsOfType(application, entity, typeRegex));
-    }
-
-    @Override
-    public Map<String, Object> getDescendantsSensor(String application, String entity, String sensor, String typeRegex) {
-        Iterable<Entity> descs = brooklyn().descendantsOfType(application, entity, typeRegex);
-        return ApplicationResource.getSensorMap(sensor, descs);
-    }
-
-    @Override
-    public List<LocationSummary> getLocations(String application, String entity) {
-        List<LocationSummary> result = Lists.newArrayList();
-        Entity e = brooklyn().getEntity(application, entity);
-        for (Location l : e.getLocations()) {
-            result.add(LocationTransformer.newInstance(mgmt(), l, LocationDetailLevel.NONE));
-        }
-        return result;
-    }
-
-    @Override
-    public String getSpec(String applicationToken, String entityToken) {
-        Entity entity = brooklyn().getEntity(applicationToken, entityToken);
-        NamedStringTag spec = BrooklynTags.findFirst(BrooklynTags.YAML_SPEC_KIND, entity.tags().getTags());
-        if (spec == null)
-            return null;
-        return (String) WebResourceUtils.getValueForDisplay(spec.getContents(), true, true);
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/LocationResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/LocationResource.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/LocationResource.java
deleted file mode 100644
index 5922ebd..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/LocationResource.java
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.rest.resources;
-
-import java.net.URI;
-import java.util.Comparator;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import javax.ws.rs.core.Response;
-
-import org.apache.brooklyn.api.location.Location;
-import org.apache.brooklyn.api.location.LocationDefinition;
-import org.apache.brooklyn.api.typereg.RegisteredType;
-import org.apache.brooklyn.core.location.LocationConfigKeys;
-import org.apache.brooklyn.rest.api.LocationApi;
-import org.apache.brooklyn.rest.domain.LocationSpec;
-import org.apache.brooklyn.rest.domain.LocationSummary;
-import org.apache.brooklyn.rest.filter.HaHotStateRequired;
-import org.apache.brooklyn.rest.transform.LocationTransformer;
-import org.apache.brooklyn.rest.transform.LocationTransformer.LocationDetailLevel;
-import org.apache.brooklyn.rest.util.EntityLocationUtils;
-import org.apache.brooklyn.rest.util.WebResourceUtils;
-import org.apache.brooklyn.util.collections.MutableMap;
-import org.apache.brooklyn.util.exceptions.Exceptions;
-import org.apache.brooklyn.util.text.NaturalOrderComparator;
-import org.apache.brooklyn.util.text.Strings;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.common.base.Function;
-import com.google.common.base.Joiner;
-import com.google.common.collect.FluentIterable;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Sets;
-
-@SuppressWarnings("deprecation")
-@HaHotStateRequired
-public class LocationResource extends AbstractBrooklynRestResource implements LocationApi {
-
-    private static final Logger log = LoggerFactory.getLogger(LocationResource.class);
-
-    private final Set<String> specsWarnedOnException = Sets.newConcurrentHashSet();
-
-    @Override
-    public List<LocationSummary> list() {
-        Function<LocationDefinition, LocationSummary> transformer = new Function<LocationDefinition, LocationSummary>() {
-            @Override
-            public LocationSummary apply(LocationDefinition l) {
-                try {
-                    return LocationTransformer.newInstance(mgmt(), l, LocationDetailLevel.LOCAL_EXCLUDING_SECRET);
-                } catch (Exception e) {
-                    Exceptions.propagateIfFatal(e);
-                    String spec = l.getSpec();
-                    if (spec == null || specsWarnedOnException.add(spec)) {
-                        log.warn("Unable to find details of location {} in REST call to list (ignoring location): {}", l, e);
-                        if (log.isDebugEnabled()) log.debug("Error details for location " + l, e);
-                    } else {
-                        if (log.isTraceEnabled())
-                            log.trace("Unable again to find details of location {} in REST call to list (ignoring location): {}", l, e);
-                    }
-                    return null;
-                }
-            }
-        };
-        return FluentIterable.from(brooklyn().getLocationRegistry().getDefinedLocations().values())
-                .transform(transformer)
-                .filter(LocationSummary.class)
-                .toSortedList(nameOrSpecComparator());
-    }
-
-    private static NaturalOrderComparator COMPARATOR = new NaturalOrderComparator();
-    private static Comparator<LocationSummary> nameOrSpecComparator() {
-        return new Comparator<LocationSummary>() {
-            @Override
-            public int compare(LocationSummary o1, LocationSummary o2) {
-                return COMPARATOR.compare(getNameOrSpec(o1).toLowerCase(), getNameOrSpec(o2).toLowerCase());
-            }
-        };
-    }
-    private static String getNameOrSpec(LocationSummary o) {
-        if (Strings.isNonBlank(o.getName())) return o.getName();
-        if (Strings.isNonBlank(o.getSpec())) return o.getSpec();
-        return o.getId();
-    }
-
-    // this is here to support the web GUI's circles
-    @Override
-    public Map<String,Map<String,Object>> getLocatedLocations() {
-      Map<String,Map<String,Object>> result = new LinkedHashMap<String,Map<String,Object>>();
-      Map<Location, Integer> counts = new EntityLocationUtils(mgmt()).countLeafEntitiesByLocatedLocations();
-      for (Map.Entry<Location,Integer> count: counts.entrySet()) {
-          Location l = count.getKey();
-          Map<String,Object> m = MutableMap.<String,Object>of(
-                  "id", l.getId(),
-                  "name", l.getDisplayName(),
-                  "leafEntityCount", count.getValue(),
-                  "latitude", l.getConfig(LocationConfigKeys.LATITUDE),
-                  "longitude", l.getConfig(LocationConfigKeys.LONGITUDE)
-              );
-          result.put(l.getId(), m);
-      }
-      return result;
-    }
-
-    /** @deprecated since 0.7.0; REST call now handled by below (optional query parameter added) */
-    public LocationSummary get(String locationId) {
-        return get(locationId, false);
-    }
-
-    @Override
-    public LocationSummary get(String locationId, String fullConfig) {
-        return get(locationId, Boolean.valueOf(fullConfig));
-    }
-
-    public LocationSummary get(String locationId, boolean fullConfig) {
-        LocationDetailLevel configLevel = fullConfig ? LocationDetailLevel.FULL_EXCLUDING_SECRET : LocationDetailLevel.LOCAL_EXCLUDING_SECRET;
-        Location l1 = mgmt().getLocationManager().getLocation(locationId);
-        if (l1!=null) {
-            return LocationTransformer.newInstance(mgmt(), l1, configLevel);
-        }
-
-        LocationDefinition l2 = brooklyn().getLocationRegistry().getDefinedLocationById(locationId);
-        if (l2==null) throw WebResourceUtils.notFound("No location matching %s", locationId);
-        return LocationTransformer.newInstance(mgmt(), l2, configLevel);
-    }
-
-    @Override
-    public Response create(LocationSpec locationSpec) {
-        String name = locationSpec.getName();
-        ImmutableList.Builder<String> yaml = ImmutableList.<String>builder().add(
-                "brooklyn.catalog:",
-                "  symbolicName: "+name,
-                "",
-                "brooklyn.locations:",
-                "- type: "+locationSpec.getSpec());
-        if (locationSpec.getConfig().size() > 0) {
-            yaml.add("  brooklyn.config:");
-            for (Map.Entry<String, ?> entry : locationSpec.getConfig().entrySet()) {
-                yaml.add("    " + entry.getKey() + ": " + entry.getValue());
-            }
-        }
-
-        String locationBlueprint = Joiner.on("\n").join(yaml.build());
-        brooklyn().getCatalog().addItems(locationBlueprint);
-        LocationDefinition l = brooklyn().getLocationRegistry().getDefinedLocationByName(name);
-        return Response.created(URI.create(name))
-                .entity(LocationTransformer.newInstance(mgmt(), l, LocationDetailLevel.LOCAL_EXCLUDING_SECRET))
-                .build();
-    }
-
-    @Override
-    @Deprecated
-    public void delete(String locationId) {
-        // TODO make all locations be part of the catalog, then flip the JS GUI to use catalog api
-        if (deleteAllVersions(locationId)>0) return;
-        throw WebResourceUtils.notFound("No catalog item location matching %s; only catalog item locations can be deleted", locationId);
-    }
-    
-    private int deleteAllVersions(String locationId) {
-        RegisteredType item = mgmt().getTypeRegistry().get(locationId);
-        if (item==null) return 0; 
-        brooklyn().getCatalog().deleteCatalogItem(item.getSymbolicName(), item.getVersion());
-        return 1 + deleteAllVersions(locationId);
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/PolicyConfigResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/PolicyConfigResource.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/PolicyConfigResource.java
deleted file mode 100644
index fbb13a7..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/PolicyConfigResource.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.rest.resources;
-
-import java.util.List;
-import java.util.Map;
-
-import javax.ws.rs.core.Response;
-
-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.mgmt.entitlement.Entitlements;
-import org.apache.brooklyn.core.objs.BrooklynObjectInternal;
-import org.apache.brooklyn.rest.api.PolicyConfigApi;
-import org.apache.brooklyn.rest.domain.PolicyConfigSummary;
-import org.apache.brooklyn.rest.filter.HaHotStateRequired;
-import org.apache.brooklyn.rest.transform.PolicyTransformer;
-import org.apache.brooklyn.rest.util.BrooklynRestResourceUtils;
-import org.apache.brooklyn.rest.util.WebResourceUtils;
-import org.apache.brooklyn.util.core.flags.TypeCoercions;
-
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-
-@HaHotStateRequired
-public class PolicyConfigResource extends AbstractBrooklynRestResource implements PolicyConfigApi {
-
-    @Override
-    public List<PolicyConfigSummary> list(
-            final String application, final String entityToken, final String policyToken) {
-        Entity entity = brooklyn().getEntity(application, entityToken);
-        Policy policy = brooklyn().getPolicy(entity, policyToken);
-
-        List<PolicyConfigSummary> result = Lists.newArrayList();
-        for (ConfigKey<?> key : policy.getPolicyType().getConfigKeys()) {
-            result.add(PolicyTransformer.policyConfigSummary(brooklyn(), entity, policy, key));
-        }
-        return result;
-    }
-
-    // TODO support parameters  ?show=value,summary&name=xxx &format={string,json,xml}
-    // (and in sensors class)
-    @Override
-    public Map<String, Object> batchConfigRead(String application, String entityToken, String policyToken) {
-        // TODO: add test
-        Policy policy = brooklyn().getPolicy(application, entityToken, policyToken);
-        Map<String, Object> source = ((BrooklynObjectInternal)policy).config().getBag().getAllConfig();
-        Map<String, Object> result = Maps.newLinkedHashMap();
-        for (Map.Entry<String, Object> ek : source.entrySet()) {
-            result.put(ek.getKey(), getStringValueForDisplay(brooklyn(), policy, ek.getValue()));
-        }
-        return result;
-    }
-
-    @Override
-    public String get(String application, String entityToken, String policyToken, String configKeyName) {
-        Policy policy = brooklyn().getPolicy(application, entityToken, policyToken);
-        ConfigKey<?> ck = policy.getPolicyType().getConfigKey(configKeyName);
-        if (ck == null) throw WebResourceUtils.notFound("Cannot find config key '%s' in policy '%s' of entity '%s'", configKeyName, policy, entityToken);
-
-        return getStringValueForDisplay(brooklyn(), policy, policy.getConfig(ck));
-    }
-
-    @Override
-    @Deprecated
-    public Response set(String application, String entityToken, String policyToken, String configKeyName, String value) {
-        return set(application, entityToken, policyToken, configKeyName, (Object) value);
-    }
-
-    @SuppressWarnings({ "unchecked", "rawtypes" })
-    @Override
-    public Response set(String application, String entityToken, String policyToken, String configKeyName, Object value) {
-        Entity entity = brooklyn().getEntity(application, entityToken);
-        if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_ENTITY, entity)) {
-            throw WebResourceUtils.forbidden("User '%s' is not authorized to modify entity '%s'",
-                    Entitlements.getEntitlementContext().user(), entity);
-        }
-
-        Policy policy = brooklyn().getPolicy(application, entityToken, policyToken);
-        ConfigKey<?> ck = policy.getPolicyType().getConfigKey(configKeyName);
-        if (ck == null) throw WebResourceUtils.notFound("Cannot find config key '%s' in policy '%s' of entity '%s'", configKeyName, policy, entityToken);
-
-        policy.config().set((ConfigKey) ck, TypeCoercions.coerce(value, ck.getTypeToken()));
-
-        return Response.status(Response.Status.OK).build();
-    }
-
-    public static String getStringValueForDisplay(BrooklynRestResourceUtils utils, Policy policy, Object value) {
-        return utils.getStringValueForDisplay(value);
-    }
-}


[16/34] brooklyn-server git commit: [BROOKLYN-183] REST API using CXF JAX-RS 2.0 implementation

Posted by he...@apache.org.
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);
+    }
+}


[10/34] brooklyn-server git commit: [BROOKLYN-183] REST API using CXF JAX-RS 2.0 implementation

Posted by he...@apache.org.
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/util/HaHotStateCheckResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/util/HaHotStateCheckResource.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/util/HaHotStateCheckResource.java
new file mode 100644
index 0000000..5c9d4d1
--- /dev/null
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/util/HaHotStateCheckResource.java
@@ -0,0 +1,44 @@
+/*
+ * 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 javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import org.apache.brooklyn.rest.filter.HaHotStateRequired;
+
+@Path("/ha/method")
+@Produces(MediaType.APPLICATION_JSON)
+public class HaHotStateCheckResource {
+
+    @GET
+    @Path("ok")
+    public String ok() {
+        return "OK";
+    }
+
+    @GET
+    @Path("fail")
+    @HaHotStateRequired
+    public String fail() {
+        return "FAIL";
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/util/TestShutdownHandler.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/util/TestShutdownHandler.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/util/TestShutdownHandler.java
new file mode 100644
index 0000000..b5cb218
--- /dev/null
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/util/TestShutdownHandler.java
@@ -0,0 +1,37 @@
+/*
+ * 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;
+
+public class TestShutdownHandler implements ShutdownHandler {
+    private volatile boolean isRequested;
+
+    @Override
+    public void onShutdownRequest() {
+        isRequested = true;
+    }
+
+    public boolean isRequested() {
+        return isRequested;
+    }
+
+    public void reset() {
+        isRequested = false;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonSerializerTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonSerializerTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonSerializerTest.java
new file mode 100644
index 0000000..2dd5ae1
--- /dev/null
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonSerializerTest.java
@@ -0,0 +1,264 @@
+/*
+ * 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.json;
+
+import java.io.NotSerializableException;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.core.entity.Attributes;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
+import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
+import org.apache.brooklyn.util.collections.MutableList;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.stream.Streams;
+import org.apache.brooklyn.util.text.Strings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.MultimapBuilder;
+
+public class BrooklynJacksonSerializerTest {
+
+    private static final Logger log = LoggerFactory.getLogger(BrooklynJacksonSerializerTest.class);
+    
+    public static class SillyClassWithManagementContext {
+        @JsonProperty
+        ManagementContext mgmt;
+        @JsonProperty
+        String id;
+        
+        public SillyClassWithManagementContext() { }
+        
+        public SillyClassWithManagementContext(String id, ManagementContext mgmt) {
+            this.id = id;
+            this.mgmt = mgmt;
+        }
+
+        @Override
+        public String toString() {
+            return super.toString()+"[id="+id+";mgmt="+mgmt+"]";
+        }
+    }
+
+    @Test
+    public void testCustomSerializerWithSerializableSillyManagementExample() throws Exception {
+        ManagementContext mgmt = LocalManagementContextForTests.newInstance();
+        try {
+
+            ObjectMapper mapper = BrooklynJacksonJsonProvider.newPrivateObjectMapper(mgmt);
+
+            SillyClassWithManagementContext silly = new SillyClassWithManagementContext("123", mgmt);
+            log.info("silly is: "+silly);
+
+            String sillyS = mapper.writeValueAsString(silly);
+
+            log.info("silly json is: "+sillyS);
+
+            SillyClassWithManagementContext silly2 = mapper.readValue(sillyS, SillyClassWithManagementContext.class);
+            log.info("silly2 is: "+silly2);
+
+            Assert.assertEquals(silly.id, silly2.id);
+            
+        } finally {
+            Entities.destroyAll(mgmt);
+        }
+    }
+    
+    public static class SelfRefNonSerializableClass {
+        @JsonProperty
+        Object bogus = this;
+    }
+
+    @Test
+    public void testSelfReferenceFailsWhenStrict() {
+        checkNonSerializableWhenStrict(new SelfRefNonSerializableClass());
+    }
+    @Test
+    public void testSelfReferenceGeneratesErrorMapObject() throws Exception {
+        checkSerializesAsMapWithErrorAndToString(new SelfRefNonSerializableClass());
+    }
+    @Test
+    public void testNonSerializableInListIsShownInList() throws Exception {
+        List<?> result = checkSerializesAs(MutableList.of(1, new SelfRefNonSerializableClass()), List.class);
+        Assert.assertEquals( result.get(0), 1 );
+        Assert.assertEquals( ((Map<?,?>)result.get(1)).get("errorType"), NotSerializableException.class.getName() );
+    }
+    @Test
+    public void testNonSerializableInMapIsShownInMap() throws Exception {
+        Map<?,?> result = checkSerializesAs(MutableMap.of("x", new SelfRefNonSerializableClass()), Map.class);
+        Assert.assertEquals( ((Map<?,?>)result.get("x")).get("errorType"), NotSerializableException.class.getName() );
+    }
+    static class TupleWithNonSerializable {
+        String good = "bon";
+        SelfRefNonSerializableClass bad = new SelfRefNonSerializableClass();
+    }
+    @Test
+    public void testNonSerializableInObjectIsShownInMap() throws Exception {
+        String resultS = checkSerializesAs(new TupleWithNonSerializable(), null);
+        log.info("nested non-serializable json is "+resultS);
+        Assert.assertTrue(resultS.startsWith("{\"good\":\"bon\",\"bad\":{"), "expected a nested map for the error field, not "+resultS);
+        
+        Map<?,?> result = checkSerializesAs(new TupleWithNonSerializable(), Map.class);
+        Assert.assertEquals( result.get("good"), "bon" );
+        Assert.assertTrue( result.containsKey("bad"), "Should have had a key for field 'bad'" );
+        Assert.assertEquals( ((Map<?,?>)result.get("bad")).get("errorType"), NotSerializableException.class.getName() );
+    }
+    
+    public static class EmptyClass {
+    }
+
+    @Test
+    public void testEmptySerializesAsEmpty() throws Exception {
+        // deliberately, a class with no fields and no annotations serializes as an error,
+        // because the alternative, {}, is useless.  however if it *is* annotated, as below, then it will serialize fine.
+        checkSerializesAsMapWithErrorAndToString(new SelfRefNonSerializableClass());
+    }
+    @Test
+    public void testEmptyNonSerializableFailsWhenStrict() {
+        checkNonSerializableWhenStrict(new EmptyClass());
+    }
+
+    @JsonSerialize
+    public static class EmptyClassWithSerialize {
+    }
+
+    @Test
+    public void testEmptyAnnotatedSerializesAsEmptyEvenWhenStrict() throws Exception {
+        try {
+            BidiSerialization.setStrictSerialization(true);
+            testEmptyAnnotatedSerializesAsEmpty();
+        } finally {
+            BidiSerialization.clearStrictSerialization();
+        }
+    }
+    
+    @Test
+    public void testEmptyAnnotatedSerializesAsEmpty() throws Exception {
+        Map<?, ?> map = checkSerializesAs( new EmptyClassWithSerialize(), Map.class );
+        Assert.assertTrue(map.isEmpty(), "Expected an empty map; instead got: "+map);
+
+        String result = checkSerializesAs( MutableList.of(new EmptyClassWithSerialize()), null );
+        result = result.replaceAll(" ", "").trim();
+        Assert.assertEquals(result, "[{}]");
+    }
+
+    @Test
+    public void testSensorFailsWhenStrict() {
+        checkNonSerializableWhenStrict(MutableList.of(Attributes.HTTP_PORT));
+    }
+    @Test
+    public void testSensorSensible() throws Exception {
+        Map<?,?> result = checkSerializesAs(Attributes.HTTP_PORT, Map.class);
+        log.info("SENSOR json is: "+result);
+        Assert.assertFalse(result.toString().contains("error"), "Shouldn't have had an error, instead got: "+result);
+    }
+
+    @Test
+    public void testLinkedListSerialization() throws Exception {
+        LinkedList<Object> ll = new LinkedList<Object>();
+        ll.add(1); ll.add("two");
+        String result = checkSerializesAs(ll, null);
+        log.info("LLIST json is: "+result);
+        Assert.assertFalse(result.contains("error"), "Shouldn't have had an error, instead got: "+result);
+        Assert.assertEquals(Strings.collapseWhitespace(result, ""), "[1,\"two\"]");
+    }
+
+    @Test
+    public void testMultiMapSerialization() throws Exception {
+        Multimap<String, Integer> m = MultimapBuilder.hashKeys().arrayListValues().build();
+        m.put("bob", 24);
+        m.put("bob", 25);
+        String result = checkSerializesAs(m, null);
+        log.info("multimap serialized as: " + result);
+        Assert.assertFalse(result.contains("error"), "Shouldn't have had an error, instead got: "+result);
+        Assert.assertEquals(Strings.collapseWhitespace(result, ""), "{\"bob\":[24,25]}");
+    }
+
+    @Test
+    public void testSupplierSerialization() throws Exception {
+        String result = checkSerializesAs(Strings.toStringSupplier(Streams.byteArrayOfString("x")), null);
+        log.info("SUPPLIER json is: "+result);
+        Assert.assertFalse(result.contains("error"), "Shouldn't have had an error, instead got: "+result);
+    }
+
+    @Test
+    public void testWrappedStreamSerialization() throws Exception {
+        String result = checkSerializesAs(BrooklynTaskTags.tagForStream("TEST", Streams.byteArrayOfString("x")), null);
+        log.info("WRAPPED STREAM json is: "+result);
+        Assert.assertFalse(result.contains("error"), "Shouldn't have had an error, instead got: "+result);
+    }
+
+    @SuppressWarnings("unchecked")
+    protected <T> T checkSerializesAs(Object x, Class<T> type) {
+        ManagementContext mgmt = LocalManagementContextForTests.newInstance();
+        try {
+            ObjectMapper mapper = BrooklynJacksonJsonProvider.newPrivateObjectMapper(mgmt);
+            String tS = mapper.writeValueAsString(x);
+            log.debug("serialized "+x+" as "+tS);
+            Assert.assertTrue(tS.length() < 1000, "Data too long, size "+tS.length()+" for "+x);
+            if (type==null) return (T) tS;
+            return mapper.readValue(tS, type);
+        } catch (Exception e) {
+            throw Exceptions.propagate(e);
+        } finally {
+            Entities.destroyAll(mgmt);
+        }
+    }
+    protected Map<?,?> checkSerializesAsMapWithErrorAndToString(Object x) {
+        Map<?,?> rt = checkSerializesAs(x, Map.class);
+        Assert.assertEquals(rt.get("toString"), x.toString());
+        Assert.assertEquals(rt.get("error"), Boolean.TRUE);
+        return rt;
+    }
+    protected void checkNonSerializableWhenStrict(Object x) {
+        checkNonSerializable(x, true);
+    }
+    protected void checkNonSerializable(Object x, boolean strict) {
+        ManagementContext mgmt = LocalManagementContextForTests.newInstance();
+        try {
+            ObjectMapper mapper = BrooklynJacksonJsonProvider.newPrivateObjectMapper(mgmt);
+            if (strict)
+                BidiSerialization.setStrictSerialization(true);
+            
+            String tS = mapper.writeValueAsString(x);
+            Assert.fail("Should not have serialized "+x+"; instead gave: "+tS);
+            
+        } catch (Exception e) {
+            Exceptions.propagateIfFatal(e);
+            log.info("Got expected error, when serializing "+x+": "+e);
+            
+        } finally {
+            if (strict)
+                BidiSerialization.clearStrictSerialization();
+            Entities.destroyAll(mgmt);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/test/resources/META-INF/cxf/org.apache.cxf.Logger
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/resources/META-INF/cxf/org.apache.cxf.Logger b/rest/rest-resources/src/test/resources/META-INF/cxf/org.apache.cxf.Logger
new file mode 100644
index 0000000..180109e
--- /dev/null
+++ b/rest/rest-resources/src/test/resources/META-INF/cxf/org.apache.cxf.Logger
@@ -0,0 +1,18 @@
+# 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.
+#
+org.apache.cxf.common.logging.Slf4jLogger
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/test/resources/brooklyn/scanning.catalog.bom
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/resources/brooklyn/scanning.catalog.bom b/rest/rest-resources/src/test/resources/brooklyn/scanning.catalog.bom
new file mode 100644
index 0000000..cddb832
--- /dev/null
+++ b/rest/rest-resources/src/test/resources/brooklyn/scanning.catalog.bom
@@ -0,0 +1,19 @@
+# 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.
+#
+brooklyn.catalog:
+  scanJavaAnnotations: true

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/pom.xml
----------------------------------------------------------------------
diff --git a/rest/rest-server/pom.xml b/rest/rest-server/pom.xml
index 06c137b..66325e8 100644
--- a/rest/rest-server/pom.xml
+++ b/rest/rest-server/pom.xml
@@ -38,6 +38,12 @@
             <groupId>org.apache.brooklyn</groupId>
             <artifactId>brooklyn-rest-api</artifactId>
             <version>${project.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>javax.ws.rs</groupId>
+                    <artifactId>jsr311-api</artifactId>
+                </exclusion>
+            </exclusions>
         </dependency>
         <dependency>
             <groupId>org.apache.brooklyn</groupId>
@@ -66,7 +72,7 @@
         </dependency>
         <dependency>
             <groupId>org.apache.brooklyn</groupId>
-            <artifactId>brooklyn-utils-rest-swagger</artifactId>
+            <artifactId>brooklyn-rest-resources</artifactId>
             <version>${project.version}</version>
         </dependency>
 
@@ -87,10 +93,6 @@
             <artifactId>jsr305</artifactId>
         </dependency>
         <dependency>
-            <groupId>javax.ws.rs</groupId>
-            <artifactId>jsr311-api</artifactId>
-        </dependency>
-        <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
         </dependency>
@@ -99,18 +101,6 @@
             <artifactId>jackson-jaxrs-json-provider</artifactId>
         </dependency>
         <dependency>
-            <groupId>com.sun.jersey</groupId>
-            <artifactId>jersey-core</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>com.sun.jersey</groupId>
-            <artifactId>jersey-server</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>com.sun.jersey</groupId>
-            <artifactId>jersey-servlet</artifactId>
-        </dependency>
-        <dependency>
             <groupId>org.eclipse.jetty</groupId>
             <artifactId>jetty-webapp</artifactId>
         </dependency>
@@ -122,6 +112,14 @@
             <groupId>org.eclipse.jetty</groupId>
             <artifactId>jetty-servlet</artifactId>
         </dependency>
+        <dependency>
+            <groupId>javax.ws.rs</groupId>
+            <artifactId>javax.ws.rs-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cxf</groupId>
+            <artifactId>cxf-rt-frontend-jaxrs</artifactId>
+        </dependency>
 
         <dependency>
             <groupId>org.apache.brooklyn</groupId>
@@ -151,15 +149,21 @@
         </dependency>
         <dependency>
             <groupId>org.apache.brooklyn</groupId>
-            <artifactId>brooklyn-rest-api</artifactId>
+            <artifactId>brooklyn-locations-jclouds</artifactId>
             <version>${project.version}</version>
-            <classifier>tests</classifier>
             <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>javax.ws.rs</groupId>
+                    <artifactId>jsr311-api</artifactId>
+                </exclusion>
+            </exclusions>
         </dependency>
         <dependency>
             <groupId>org.apache.brooklyn</groupId>
-            <artifactId>brooklyn-locations-jclouds</artifactId>
+            <artifactId>brooklyn-rest-resources</artifactId>
             <version>${project.version}</version>
+            <classifier>tests</classifier>
             <scope>test</scope>
         </dependency>
         <dependency>
@@ -168,29 +172,37 @@
             <scope>test</scope>
         </dependency>
         <dependency>
-            <groupId>com.sun.jersey</groupId>
-            <artifactId>jersey-client</artifactId>
+            <groupId>org.testng</groupId>
+            <artifactId>testng</artifactId>
             <scope>test</scope>
         </dependency>
         <dependency>
-            <groupId>org.testng</groupId>
-            <artifactId>testng</artifactId>
+            <groupId>org.apache.brooklyn</groupId>
+            <artifactId>brooklyn-rt-osgi</artifactId>
+            <version>${project.version}</version>
+            <classifier>tests</classifier>
             <scope>test</scope>
         </dependency>
         <dependency>
-            <groupId>com.sun.jersey.jersey-test-framework</groupId>
-            <artifactId>jersey-test-framework-inmemory</artifactId>
+            <groupId>org.apache.cxf</groupId>
+            <artifactId>cxf-rt-transports-local</artifactId>
             <scope>test</scope>
         </dependency>
         <dependency>
-            <groupId>com.sun.jersey.jersey-test-framework</groupId>
-            <artifactId>jersey-test-framework-grizzly2</artifactId>
+            <groupId>org.apache.cxf</groupId>
+            <artifactId>cxf-rt-transports-http-jetty</artifactId>
+            <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <!-- version conflict -->
+                    <groupId>org.eclipse.jetty</groupId>
+                    <artifactId>jetty-http</artifactId>
+                </exclusion>
+            </exclusions>
         </dependency>
         <dependency>
-            <groupId>org.apache.brooklyn</groupId>
-            <artifactId>brooklyn-rt-osgi</artifactId>
-            <version>${project.version}</version>
-            <classifier>tests</classifier>
+            <groupId>org.apache.cxf</groupId>
+            <artifactId>cxf-rt-rs-client</artifactId>
             <scope>test</scope>
         </dependency>
     </dependencies>

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/BrooklynRestApi.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/BrooklynRestApi.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/BrooklynRestApi.java
deleted file mode 100644
index 6886f5e..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/BrooklynRestApi.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * 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;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.brooklyn.rest.resources.AbstractBrooklynRestResource;
-import org.apache.brooklyn.rest.resources.AccessResource;
-import org.apache.brooklyn.rest.resources.ActivityResource;
-import org.apache.brooklyn.rest.resources.ApidocResource;
-import org.apache.brooklyn.rest.resources.ApplicationResource;
-import org.apache.brooklyn.rest.resources.CatalogResource;
-import org.apache.brooklyn.rest.resources.EffectorResource;
-import org.apache.brooklyn.rest.resources.EntityConfigResource;
-import org.apache.brooklyn.rest.resources.EntityResource;
-import org.apache.brooklyn.rest.resources.LocationResource;
-import org.apache.brooklyn.rest.resources.PolicyConfigResource;
-import org.apache.brooklyn.rest.resources.PolicyResource;
-import org.apache.brooklyn.rest.resources.ScriptResource;
-import org.apache.brooklyn.rest.resources.SensorResource;
-import org.apache.brooklyn.rest.resources.ServerResource;
-import org.apache.brooklyn.rest.resources.UsageResource;
-import org.apache.brooklyn.rest.resources.VersionResource;
-import org.apache.brooklyn.rest.util.DefaultExceptionMapper;
-import org.apache.brooklyn.rest.util.FormMapProvider;
-import org.apache.brooklyn.rest.util.json.BrooklynJacksonJsonProvider;
-
-import com.google.common.collect.Iterables;
-import io.swagger.jaxrs.listing.SwaggerSerializers;
-
-@SuppressWarnings("deprecation")
-public class BrooklynRestApi {
-
-    public static Iterable<AbstractBrooklynRestResource> getBrooklynRestResources() {
-        List<AbstractBrooklynRestResource> resources = new ArrayList<AbstractBrooklynRestResource>();
-        resources.add(new LocationResource());
-        resources.add(new CatalogResource());
-        resources.add(new ApplicationResource());
-        resources.add(new EntityResource());
-        resources.add(new EntityConfigResource());
-        resources.add(new SensorResource());
-        resources.add(new EffectorResource());
-        resources.add(new PolicyResource());
-        resources.add(new PolicyConfigResource());
-        resources.add(new ActivityResource());
-        resources.add(new AccessResource());
-        resources.add(new ScriptResource());
-        resources.add(new ServerResource());
-        resources.add(new UsageResource());
-        resources.add(new VersionResource());
-        return resources;
-    }
-
-    public static Iterable<Object> getApidocResources() {
-        List<Object> resources = new ArrayList<Object>();
-        resources.add(new SwaggerSerializers());
-        resources.add(new ApidocResource());
-        return resources;
-    }
-
-    public static Iterable<Object> getMiscResources() {
-        List<Object> resources = new ArrayList<Object>();
-        resources.add(new DefaultExceptionMapper());
-        resources.add(new BrooklynJacksonJsonProvider());
-        resources.add(new FormMapProvider());
-        return resources;
-    }
-
-    public static Iterable<Object> getAllResources() {
-        return Iterables.concat(getBrooklynRestResources(), getApidocResources(), getMiscResources());
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/BrooklynWebConfig.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/BrooklynWebConfig.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/BrooklynWebConfig.java
deleted file mode 100644
index 601ff71..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/BrooklynWebConfig.java
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * 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;
-
-import org.apache.brooklyn.api.location.PortRange;
-import org.apache.brooklyn.config.ConfigKey;
-import org.apache.brooklyn.config.ConfigMap;
-import org.apache.brooklyn.core.config.ConfigKeys;
-import org.apache.brooklyn.core.config.ConfigPredicates;
-import org.apache.brooklyn.rest.security.provider.DelegatingSecurityProvider;
-import org.apache.brooklyn.rest.security.provider.ExplicitUsersSecurityProvider;
-import org.apache.brooklyn.rest.security.provider.SecurityProvider;
-
-public class BrooklynWebConfig {
-
-    public final static String BASE_NAME = "brooklyn.webconsole";
-    public final static String BASE_NAME_SECURITY = BASE_NAME+".security";
-
-    /**
-     * The security provider to be loaded by {@link DelegatingSecurityProvider}.
-     * e.g. <code>brooklyn.webconsole.security.provider=org.apache.brooklyn.rest.security.provider.AnyoneSecurityProvider</code>
-     * will allow anyone to log in.
-     */
-    public final static ConfigKey<String> SECURITY_PROVIDER_CLASSNAME = ConfigKeys.newStringConfigKey(
-            BASE_NAME_SECURITY+".provider", "class name of a Brooklyn SecurityProvider",
-            ExplicitUsersSecurityProvider.class.getCanonicalName());
-    public final static ConfigKey<SecurityProvider> SECURITY_PROVIDER_INSTANCE = ConfigKeys.newConfigKey(SecurityProvider.class,
-            SECURITY_PROVIDER_CLASSNAME.getName()+".internal.instance", "instance of a pre-configured security provider");
-    
-    /**
-     * Explicitly set the users/passwords, e.g. in brooklyn.properties:
-     * brooklyn.webconsole.security.users=admin,bob
-     * brooklyn.webconsole.security.user.admin.password=password
-     * brooklyn.webconsole.security.user.bob.password=bobspass
-     */
-    public final static ConfigKey<String> USERS = ConfigKeys.newStringConfigKey(BASE_NAME_SECURITY+".users");
-
-    public final static ConfigKey<String> PASSWORD_FOR_USER(String user) {
-        return ConfigKeys.newStringConfigKey(BASE_NAME_SECURITY + ".user." + user + ".password");
-    }
-    
-    public final static ConfigKey<String> SALT_FOR_USER(String user) {
-        return ConfigKeys.newStringConfigKey(BASE_NAME_SECURITY + ".user." + user + ".salt");
-    }
-    
-    public final static ConfigKey<String> SHA256_FOR_USER(String user) {
-        return ConfigKeys.newStringConfigKey(BASE_NAME_SECURITY + ".user." + user + ".sha256");
-    }
-    
-    public final static ConfigKey<String> LDAP_URL = ConfigKeys.newStringConfigKey(
-            BASE_NAME_SECURITY+".ldap.url");
-
-    public final static ConfigKey<String> LDAP_REALM = ConfigKeys.newStringConfigKey(
-            BASE_NAME_SECURITY+".ldap.realm");
-
-    public final static ConfigKey<String> LDAP_OU = ConfigKeys.newStringConfigKey(
-            BASE_NAME_SECURITY+".ldap.ou");
-
-    public final static ConfigKey<Boolean> HTTPS_REQUIRED = ConfigKeys.newBooleanConfigKey(
-            BASE_NAME+".security.https.required",
-            "Whether HTTPS is required; false here can be overridden by CLI option", false); 
-
-    public final static ConfigKey<PortRange> WEB_CONSOLE_PORT = ConfigKeys.newConfigKey(PortRange.class,
-        BASE_NAME+".port",
-        "Port/range for the web console to listen on; can be overridden by CLI option");
-
-    public final static ConfigKey<String> KEYSTORE_URL = ConfigKeys.newStringConfigKey(
-            BASE_NAME+".security.keystore.url",
-            "Keystore from which to take the certificate to present when running HTTPS; "
-            + "note that normally the password is also required, and an alias for the certificate if the keystore has more than one");
-
-    public final static ConfigKey<String> KEYSTORE_PASSWORD = ConfigKeys.newStringConfigKey(
-            BASE_NAME+".security.keystore.password",
-            "Password for the "+KEYSTORE_URL);
-
-    public final static ConfigKey<String> KEYSTORE_CERTIFICATE_ALIAS = ConfigKeys.newStringConfigKey(
-            BASE_NAME+".security.keystore.certificate.alias",
-            "Alias in "+KEYSTORE_URL+" for the certificate to use; defaults to the first if not supplied");
-
-    public final static ConfigKey<String> TRANSPORT_PROTOCOLS = ConfigKeys.newStringConfigKey(
-            BASE_NAME+".security.transport.protocols",
-            "SSL/TLS protocol versions to use for web console connections",
-            "TLSv1, TLSv1.1, TLSv1.2");
-
-    // https://wiki.mozilla.org/Security/Server_Side_TLS (v3.4)
-    // http://stackoverflow.com/questions/19846020/how-to-map-a-openssls-cipher-list-to-java-jsse
-    // list created on 05.05.2015, Intermediate config from first link
-    public final static ConfigKey<String> TRANSPORT_CIPHERS = ConfigKeys.newStringConfigKey(
-            BASE_NAME+".security.transport.ciphers",
-            "SSL/TLS cipher suites to use for web console connections",
-            "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256," +
-            "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384," +
-            "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,TLS_DHE_DSS_WITH_AES_128_GCM_SHA256," +
-            "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384,TLS_DHE_RSA_WITH_AES_256_GCM_SHA384," +
-            "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256," +
-            "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA," +
-            "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384," +
-            "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA," +
-            "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,TLS_DHE_RSA_WITH_AES_128_CBC_SHA," +
-            "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256,TLS_DHE_RSA_WITH_AES_256_CBC_SHA256," +
-            "TLS_DHE_DSS_WITH_AES_256_CBC_SHA,TLS_DHE_RSA_WITH_AES_256_CBC_SHA," +
-            "TLS_RSA_WITH_AES_128_GCM_SHA256,TLS_RSA_WITH_AES_256_GCM_SHA384," +
-            "TLS_RSA_WITH_AES_128_CBC_SHA256,TLS_RSA_WITH_AES_256_CBC_SHA256," +
-            "TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA," +
-            "TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA,TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA," +
-            "TLS_SRP_SHA_WITH_AES_256_CBC_SHA,TLS_DHE_DSS_WITH_AES_256_CBC_SHA256," +
-            "TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA,TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA," +
-            "TLS_SRP_SHA_WITH_AES_128_CBC_SHA,TLS_DHE_DSS_WITH_AES_128_CBC_SHA," +
-            "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA,TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA," +
-            "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA,TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA," +
-            "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA,TLS_RSA_WITH_CAMELLIA_128_CBC_SHA," +
-            "TLS_RSA_WITH_3DES_EDE_CBC_SHA," +
-            // Same as above but with SSL_ prefix, IBM Java compatibility (cipher is independent of protocol)
-            // https://www-01.ibm.com/support/knowledgecenter/SSYKE2_7.0.0/com.ibm.java.security.component.70.doc/security-component/jsse2Docs/ciphersuites.html
-            "SSL_ECDHE_RSA_WITH_AES_128_GCM_SHA256,SSL_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256," +
-            "SSL_ECDHE_RSA_WITH_AES_256_GCM_SHA384,SSL_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384," +
-            "SSL_DHE_RSA_WITH_AES_128_GCM_SHA256,SSL_DHE_DSS_WITH_AES_128_GCM_SHA256," +
-            "SSL_DHE_DSS_WITH_AES_256_GCM_SHA384,SSL_DHE_RSA_WITH_AES_256_GCM_SHA384," +
-            "SSL_ECDHE_RSA_WITH_AES_128_CBC_SHA256,SSL_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256," +
-            "SSL_ECDHE_RSA_WITH_AES_128_CBC_SHA,SSL_ECDHE_ECDSA_WITH_AES_128_CBC_SHA," +
-            "SSL_ECDHE_RSA_WITH_AES_256_CBC_SHA384,SSL_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384," +
-            "SSL_ECDHE_RSA_WITH_AES_256_CBC_SHA,SSL_ECDHE_ECDSA_WITH_AES_256_CBC_SHA," +
-            "SSL_DHE_RSA_WITH_AES_128_CBC_SHA256,SSL_DHE_RSA_WITH_AES_128_CBC_SHA," +
-            "SSL_DHE_DSS_WITH_AES_128_CBC_SHA256,SSL_DHE_RSA_WITH_AES_256_CBC_SHA256," +
-            "SSL_DHE_DSS_WITH_AES_256_CBC_SHA,SSL_DHE_RSA_WITH_AES_256_CBC_SHA," +
-            "SSL_RSA_WITH_AES_128_GCM_SHA256,SSL_RSA_WITH_AES_256_GCM_SHA384," +
-            "SSL_RSA_WITH_AES_128_CBC_SHA256,SSL_RSA_WITH_AES_256_CBC_SHA256," +
-            "SSL_RSA_WITH_AES_128_CBC_SHA,SSL_RSA_WITH_AES_256_CBC_SHA," +
-            "SSL_SRP_SHA_DSS_WITH_AES_256_CBC_SHA,SSL_SRP_SHA_RSA_WITH_AES_256_CBC_SHA," +
-            "SSL_SRP_SHA_WITH_AES_256_CBC_SHA,SSL_DHE_DSS_WITH_AES_256_CBC_SHA256," +
-            "SSL_SRP_SHA_DSS_WITH_AES_128_CBC_SHA,SSL_SRP_SHA_RSA_WITH_AES_128_CBC_SHA," +
-            "SSL_SRP_SHA_WITH_AES_128_CBC_SHA,SSL_DHE_DSS_WITH_AES_128_CBC_SHA," +
-            "SSL_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA,SSL_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA," +
-            "SSL_RSA_WITH_CAMELLIA_256_CBC_SHA,SSL_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA," +
-            "SSL_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA,SSL_RSA_WITH_CAMELLIA_128_CBC_SHA," +
-            "SSL_RSA_WITH_3DES_EDE_CBC_SHA");
-
-    public final static boolean hasNoSecurityOptions(ConfigMap config) {
-        return config.submap(ConfigPredicates.nameStartsWith(BASE_NAME_SECURITY)).isEmpty();
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/RestApiSetup.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/RestApiSetup.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/RestApiSetup.java
new file mode 100644
index 0000000..2a43aa1
--- /dev/null
+++ b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/RestApiSetup.java
@@ -0,0 +1,68 @@
+/*
+ * 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;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.EnumSet;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.Filter;
+
+import org.apache.brooklyn.rest.apidoc.RestApiResourceScanner;
+import org.apache.cxf.jaxrs.servlet.CXFNonSpringJaxrsServlet;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+
+import io.swagger.config.ScannerFactory;
+
+public class RestApiSetup {
+
+    public static ContextHandler installRestServlet(ServletContextHandler context, Object... providers) {
+        initSwagger();
+
+        BrooklynRestApp app = new BrooklynRestApp();
+        for (Object o : providers) {
+            app.singleton(o);
+        }
+
+        CXFNonSpringJaxrsServlet servlet = new CXFNonSpringJaxrsServlet(app);
+        final ServletHolder servletHolder = new ServletHolder(servlet);
+
+        context.addServlet(servletHolder, "/v1/*");
+        return context;
+    }
+
+    @SafeVarargs
+    public static void installServletFilters(ServletContextHandler context, Class<? extends Filter>... filters) {
+        installServletFilters(context, Arrays.asList(filters));
+    }
+
+    public static void installServletFilters(ServletContextHandler context, Collection<Class<? extends Filter>> filters) {
+        for (Class<? extends Filter> filter : filters) {
+            context.addFilter(filter, "/*", EnumSet.allOf(DispatcherType.class));
+        }
+    }
+    
+    public static void initSwagger() {
+        ScannerFactory.setScanner(new RestApiResourceScanner());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/BrooklynPropertiesSecurityFilter.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/BrooklynPropertiesSecurityFilter.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/BrooklynPropertiesSecurityFilter.java
index 07afa7a..6dd84e0 100644
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/BrooklynPropertiesSecurityFilter.java
+++ b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/BrooklynPropertiesSecurityFilter.java
@@ -31,16 +31,16 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import org.apache.brooklyn.api.mgmt.ManagementContext;
 import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
 import org.apache.brooklyn.core.mgmt.entitlement.WebEntitlementContext;
+import org.apache.brooklyn.rest.BrooklynWebConfig;
 import org.apache.brooklyn.rest.security.provider.DelegatingSecurityProvider;
-import org.apache.brooklyn.util.text.Strings;
-
-import com.sun.jersey.core.util.Base64;
 import org.apache.brooklyn.rest.util.OsgiCompat;
+import org.apache.brooklyn.util.text.Strings;
+import org.apache.commons.codec.binary.Base64;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Provides basic HTTP authentication.
@@ -58,8 +58,11 @@ public class BrooklynPropertiesSecurityFilter implements Filter {
     /**
      * The session attribute set to indicate the remote address of the HTTP request.
      * Corresponds to {@link javax.servlet.http.HttpServletRequest#getRemoteAddr()}.
+     * 
+     * @deprecated since 0.9.0, use {@link BrooklynWebConfig#REMOTE_ADDRESS_SESSION_ATTRIBUTE}
      */
-    public static final String REMOTE_ADDRESS_SESSION_ATTRIBUTE = "request.remoteAddress";
+    @Deprecated
+    public static final String REMOTE_ADDRESS_SESSION_ATTRIBUTE = BrooklynWebConfig.REMOTE_ADDRESS_SESSION_ATTRIBUTE;
 
     private static final Logger log = LoggerFactory.getLogger(BrooklynPropertiesSecurityFilter.class);
 
@@ -135,11 +138,11 @@ public class BrooklynPropertiesSecurityFilter implements Filter {
         if (provider.isAuthenticated(session)) {
             return true;
         }
-        session.setAttribute(REMOTE_ADDRESS_SESSION_ATTRIBUTE, request.getRemoteAddr());
+        session.setAttribute(BrooklynWebConfig.REMOTE_ADDRESS_SESSION_ATTRIBUTE, request.getRemoteAddr());
         String user = null, pass = null;
         String authorization = request.getHeader("Authorization");
         if (authorization != null) {
-            String userpass = Base64.base64Decode(authorization.substring(6));
+            String userpass = new String(Base64.decodeBase64(authorization.substring(6)));
             user = userpass.substring(0, userpass.indexOf(":"));
             pass = userpass.substring(userpass.indexOf(":") + 1);
         }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/HaHotCheckResourceFilter.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/HaHotCheckResourceFilter.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/HaHotCheckResourceFilter.java
deleted file mode 100644
index 05d4d93..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/HaHotCheckResourceFilter.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * 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.filter;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-
-import javax.ws.rs.WebApplicationException;
-import javax.ws.rs.core.Context;
-import javax.ws.rs.core.Response;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.apache.brooklyn.api.mgmt.ManagementContext;
-import org.apache.brooklyn.api.mgmt.ha.ManagementNodeState;
-import org.apache.brooklyn.rest.domain.ApiError;
-import org.apache.brooklyn.util.text.Strings;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.ImmutableSet;
-import com.sun.jersey.api.model.AbstractMethod;
-import com.sun.jersey.spi.container.ContainerRequest;
-import com.sun.jersey.spi.container.ContainerRequestFilter;
-import com.sun.jersey.spi.container.ContainerResponseFilter;
-import com.sun.jersey.spi.container.ResourceFilter;
-import com.sun.jersey.spi.container.ResourceFilterFactory;
-
-/** 
- * Checks that if the method or resource class corresponding to a request
- * has a {@link HaHotStateRequired} annotation,
- * that the server is in that state (and up). 
- * Requests with {@link #SKIP_CHECK_HEADER} set as a header skip this check.
- * <p>
- * This follows a different pattern to {@link HaMasterCheckFilter} 
- * as this needs to know the method being invoked. 
- */
-public class HaHotCheckResourceFilter implements ResourceFilterFactory {
-    
-    private static final Logger log = LoggerFactory.getLogger(HaHotCheckResourceFilter.class);
-    
-    private static final Set<ManagementNodeState> HOT_STATES = ImmutableSet.of(
-            ManagementNodeState.MASTER, ManagementNodeState.HOT_STANDBY, ManagementNodeState.HOT_BACKUP);
-
-    @Context
-    private ManagementContext mgmt;
-
-    public HaHotCheckResourceFilter() {}
-    
-    @VisibleForTesting
-    public HaHotCheckResourceFilter(ManagementContext mgmt) {
-        this.mgmt = mgmt;
-    }
-    
-    private static class MethodFilter implements ResourceFilter, ContainerRequestFilter {
-
-        private AbstractMethod am;
-        private ManagementContext mgmt;
-
-        public MethodFilter(AbstractMethod am, ManagementContext mgmt) {
-            this.am = am;
-            this.mgmt = mgmt;
-        }
-
-        @Override
-        public ContainerRequestFilter getRequestFilter() {
-            return this;
-        }
-
-        @Override
-        public ContainerResponseFilter getResponseFilter() {
-            return null;
-        }
-
-        private String lookForProblem(ContainerRequest request) {
-            if (isSkipCheckHeaderSet(request)) 
-                return null;
-            
-            if (!isHaHotStateRequired(request))
-                return null;
-            
-            String problem = HaMasterCheckFilter.lookForProblemIfServerNotRunning(mgmt);
-            if (Strings.isNonBlank(problem)) 
-                return problem;
-            
-            if (!isHaHotStatus())
-                return "server not in required HA hot state";
-            if (isStateNotYetValid())
-                return "server not yet completed loading data for required HA hot state";
-            
-            return null;
-        }
-        
-        @Override
-        public ContainerRequest filter(ContainerRequest request) {
-            String problem = lookForProblem(request);
-            if (Strings.isNonBlank(problem)) {
-                log.warn("Disallowing web request as "+problem+": "+request+"/"+am+" (caller should set '"+HaMasterCheckFilter.SKIP_CHECK_HEADER+"' to force)");
-                throw new WebApplicationException(ApiError.builder()
-                    .message("This request is only permitted against an active hot Brooklyn server")
-                    .errorCode(Response.Status.FORBIDDEN).build().asJsonResponse());
-            }
-            return request;
-        }
-
-        // Maybe there should be a separate state to indicate that we have switched state
-        // but still haven't finished rebinding. (Previously there was a time delay and an
-        // isRebinding check, but introducing RebindManager#isAwaitingInitialRebind() seems cleaner.)
-        private boolean isStateNotYetValid() {
-            return mgmt.getRebindManager().isAwaitingInitialRebind();
-        }
-
-        private boolean isHaHotStateRequired(ContainerRequest request) {
-            return (am.getAnnotation(HaHotStateRequired.class) != null ||
-                    am.getResource().getAnnotation(HaHotStateRequired.class) != null);
-        }
-
-        private boolean isSkipCheckHeaderSet(ContainerRequest request) {
-            return "true".equalsIgnoreCase(request.getHeaderValue(HaMasterCheckFilter.SKIP_CHECK_HEADER));
-        }
-
-        private boolean isHaHotStatus() {
-            ManagementNodeState state = mgmt.getHighAvailabilityManager().getNodeState();
-            return HOT_STATES.contains(state);
-        }
-
-    }
-
-    @Override
-    public List<ResourceFilter> create(AbstractMethod am) {
-        return Collections.<ResourceFilter>singletonList(new MethodFilter(am, mgmt));
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/HaHotStateRequired.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/HaHotStateRequired.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/HaHotStateRequired.java
deleted file mode 100644
index f64abdd..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/HaHotStateRequired.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * 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.filter;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * When a REST method (or its containing class) is marked with this annotation
- * requests to it will fail with a 403 response if the instance is not in MASTER
- * mode (or has recently switched or is still rebinding). Guards the method so
- * that when it returns the caller can be certain of the response. For example
- * if the response is 404, then the resource doesn't exist as opposed to
- * not being loaded from persistence store yet.
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.METHOD, ElementType.TYPE})
-public @interface HaHotStateRequired {}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/HaMasterCheckFilter.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/HaMasterCheckFilter.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/HaMasterCheckFilter.java
index 71c6c27..991d58c 100644
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/HaMasterCheckFilter.java
+++ b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/HaMasterCheckFilter.java
@@ -49,11 +49,11 @@ import org.apache.brooklyn.rest.util.OsgiCompat;
  * Post POSTs and PUTs are assumed to need master state, with the exception of shutdown.
  * Requests with {@link #SKIP_CHECK_HEADER} set as a header skip this check.
  */
+// TODO Merge with HaHotCheckResourceFilter so the functionality is available in Karaf
 public class HaMasterCheckFilter implements Filter {
 
     private static final Logger log = LoggerFactory.getLogger(HaMasterCheckFilter.class);
     
-    public static final String SKIP_CHECK_HEADER = "Brooklyn-Allow-Non-Master-Access";
     private static final Set<String> SAFE_STANDBY_METHODS = Sets.newHashSet("GET", "HEAD");
 
     protected ServletContext servletContext;
@@ -65,13 +65,6 @@ public class HaMasterCheckFilter implements Filter {
         mgmt = OsgiCompat.getManagementContext(servletContext);
     }
 
-    static String lookForProblemIfServerNotRunning(ManagementContext mgmt) {
-        if (mgmt==null) return "no management context available";
-        if (!mgmt.isRunning()) return "server no longer running";
-        if (!mgmt.isStartupComplete()) return "server not in required startup-completed state";
-        return null;
-    }
-    
     private String lookForProblem(ServletRequest request) {
         if (isSkipCheckHeaderSet(request)) 
             return null;
@@ -79,7 +72,7 @@ public class HaMasterCheckFilter implements Filter {
         if (!isMasterRequiredForRequest(request))
             return null;
         
-        String problem = lookForProblemIfServerNotRunning(mgmt);
+        String problem = HaHotCheckResourceFilter.lookForProblemIfServerNotRunning(mgmt);
         if (Strings.isNonBlank(problem)) 
             return problem;
         
@@ -93,7 +86,7 @@ public class HaMasterCheckFilter implements Filter {
     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
         String problem = lookForProblem(request);
         if (problem!=null) {
-            log.warn("Disallowing web request as "+problem+": "+request.getParameterMap()+" (caller should set '"+SKIP_CHECK_HEADER+"' to force)");
+            log.warn("Disallowing web request as "+problem+": "+request.getParameterMap()+" (caller should set '"+HaHotCheckResourceFilter.SKIP_CHECK_HEADER+"' to force)");
             WebResourceUtils.applyJsonResponse(servletContext, ApiError.builder()
                 .message("This request is only permitted against an active master Brooklyn server")
                 .errorCode(Response.Status.FORBIDDEN).build().asJsonResponse(), (HttpServletResponse)response);
@@ -121,7 +114,8 @@ public class HaMasterCheckFilter implements Filter {
             // explicitly allow calls to shutdown
             // (if stopAllApps is specified, the method itself will fail; but we do not want to consume parameters here, that breaks things!)
             // TODO combine with HaHotCheckResourceFilter and use an annotation HaAnyStateAllowed or similar
-            if ("/v1/server/shutdown".equals(httpRequest.getRequestURI())) return false;
+            if ("/v1/server/shutdown".equals(httpRequest.getRequestURI()) ||
+                    "/server/shutdown".equals(httpRequest.getRequestURI())) return false;
             
             // master required for everything else
             return true;
@@ -132,7 +126,7 @@ public class HaMasterCheckFilter implements Filter {
 
     private boolean isSkipCheckHeaderSet(ServletRequest httpRequest) {
         if (httpRequest instanceof HttpServletRequest)
-            return "true".equalsIgnoreCase(((HttpServletRequest)httpRequest).getHeader(SKIP_CHECK_HEADER));
+            return "true".equalsIgnoreCase(((HttpServletRequest)httpRequest).getHeader(HaHotCheckResourceFilter.SKIP_CHECK_HEADER));
         return false;
     }
 

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/LoggingFilter.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/LoggingFilter.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/LoggingFilter.java
index c6ddbf0..d5c9d70 100644
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/LoggingFilter.java
+++ b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/LoggingFilter.java
@@ -45,6 +45,7 @@ import com.google.common.collect.Sets;
 /**
  * Handles logging of request information.
  */
+// TODO Re-implement as JAX-RS filter
 public class LoggingFilter implements Filter {
 
     private static final Logger LOG = LoggerFactory.getLogger(BrooklynLogging.REST);
@@ -58,6 +59,7 @@ public class LoggingFilter implements Filter {
     /** Log all requests that take this time or longer to complete. */
     private static final Duration REQUEST_DURATION_LOG_POINT = Duration.FIVE_SECONDS;
 
+    @Override
     public void init(FilterConfig config) throws ServletException {
     }
 

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/NoCacheFilter.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/NoCacheFilter.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/NoCacheFilter.java
deleted file mode 100644
index 8a3c1c6..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/NoCacheFilter.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * 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.filter;
-
-import javax.ws.rs.core.HttpHeaders;
-import javax.ws.rs.core.MultivaluedMap;
-
-import com.sun.jersey.spi.container.ContainerRequest;
-import com.sun.jersey.spi.container.ContainerResponse;
-import com.sun.jersey.spi.container.ContainerResponseFilter;
-
-public class NoCacheFilter implements ContainerResponseFilter {
-
-    @Override
-    public ContainerResponse filter(ContainerRequest request, ContainerResponse response) {
-        //https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching_FAQ
-        MultivaluedMap<String, Object> headers = response.getHttpHeaders();
-        headers.putSingle(HttpHeaders.CACHE_CONTROL, "no-cache, no-store");
-        headers.putSingle("Pragma", "no-cache");
-        headers.putSingle(HttpHeaders.EXPIRES, "0");
-        return response;
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/RequestTaggingFilter.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/RequestTaggingFilter.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/RequestTaggingFilter.java
index 883fad8..3553aaa 100644
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/RequestTaggingFilter.java
+++ b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/RequestTaggingFilter.java
@@ -33,6 +33,7 @@ import org.apache.brooklyn.util.text.Identifiers;
  * Tags each request with a probabilistically unique id. Should be included before other
  * filters to make sense.
  */
+//TODO Re-implement as JAX-RS filter
 public class RequestTaggingFilter implements Filter {
 
     private static ThreadLocal<String> tag = new ThreadLocal<String>();

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/SwaggerFilter.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/SwaggerFilter.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/SwaggerFilter.java
deleted file mode 100644
index 5965c6c..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/SwaggerFilter.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * 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.filter;
-
-import io.swagger.config.ScannerFactory;
-import io.swagger.models.ExternalDocs;
-import io.swagger.models.Info;
-import io.swagger.models.License;
-import io.swagger.models.Swagger;
-import org.apache.brooklyn.rest.apidoc.RestApiResourceScanner;
-
-import javax.servlet.Filter;
-import javax.servlet.FilterChain;
-import javax.servlet.FilterConfig;
-import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import java.io.IOException;
-
-/**
- * Bootstraps swagger.
- * <p>
- * Swagger was intended to run as a servlet.
- *
- * @author Ciprian Ciubotariu <ch...@gmx.net>
- */
-public class SwaggerFilter implements Filter {
-
-    static Info info = new Info()
-            .title("Brooklyn API Documentation")
-            .version("v1") // API version, not BROOKLYN_VERSION
-            .license(new License()
-                    .name("Apache 2.0")
-                    .url("http://www.apache.org/licenses/LICENSE-2.0.html"));
-
-    @Override
-    public void init(FilterConfig filterConfig) throws ServletException {
-//        ReflectiveJaxrsScanner scanner = new ReflectiveJaxrsScanner();
-//        scanner.setResourcePackage("org.apache.brooklyn.rest.api,org.apache.brooklyn.rest.apidoc,org.apache.brooklyn.rest.resources");
-//        ScannerFactory.setScanner(scanner);
-        ScannerFactory.setScanner(new RestApiResourceScanner());
-
-        ServletContext context = filterConfig.getServletContext();
-        Swagger swagger = new Swagger()
-                .info(info)
-                .basePath("/");
-        context.setAttribute("swagger", swagger);
-    }
-
-    @Override
-    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
-        chain.doFilter(request, response);
-    }
-
-    @Override
-    public void destroy() {
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/AbstractBrooklynRestResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/AbstractBrooklynRestResource.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/AbstractBrooklynRestResource.java
deleted file mode 100644
index 9057a2c..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/AbstractBrooklynRestResource.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.rest.resources;
-
-import javax.annotation.Nullable;
-import javax.servlet.ServletContext;
-import javax.ws.rs.core.Context;
-
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.mgmt.ManagementContext;
-import org.apache.brooklyn.core.config.render.RendererHints;
-import org.apache.brooklyn.core.mgmt.ManagementContextInjectable;
-import org.apache.brooklyn.rest.util.BrooklynRestResourceUtils;
-import org.apache.brooklyn.rest.util.OsgiCompat;
-import org.apache.brooklyn.rest.util.WebResourceUtils;
-import org.apache.brooklyn.rest.util.json.BrooklynJacksonJsonProvider;
-import org.apache.brooklyn.util.core.task.Tasks;
-import org.apache.brooklyn.util.guava.Maybe;
-import org.apache.brooklyn.util.time.Duration;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-
-public abstract class AbstractBrooklynRestResource implements ManagementContextInjectable {
-
-    // can be injected by jersey when ManagementContext in not injected manually
-    // (seems there is no way to make this optional so note it _must_ be injected;
-    // most of the time that happens for free, but with test framework it doesn't,
-    // so we have set up a NullServletContextProvider in our tests) 
-    @Context ServletContext servletContext;
-    
-    private ManagementContext managementContext;
-    private BrooklynRestResourceUtils brooklynRestResourceUtils;
-    private ObjectMapper mapper;
-
-    public ManagementContext mgmt() {
-        return mgmtMaybe().get();
-    }
-    
-    protected synchronized Maybe<ManagementContext> mgmtMaybe() {
-        if (managementContext!=null) return Maybe.of(managementContext);
-        managementContext = OsgiCompat.getManagementContext(servletContext);
-        if (managementContext!=null) return Maybe.of(managementContext);
-        
-        return Maybe.absent("ManagementContext not available for Brooklyn Jersey Resource "+this);
-    }
-    
-    @Override
-    public void setManagementContext(ManagementContext managementContext) {
-        if (this.managementContext!=null) {
-            if (this.managementContext.equals(managementContext)) return;
-            throw new IllegalStateException("ManagementContext cannot be changed: specified twice for Brooklyn Jersey Resource "+this);
-        }
-        this.managementContext = managementContext;
-    }
-
-    public synchronized BrooklynRestResourceUtils brooklyn() {
-        if (brooklynRestResourceUtils!=null) return brooklynRestResourceUtils;
-        brooklynRestResourceUtils = new BrooklynRestResourceUtils(mgmt());
-        return brooklynRestResourceUtils;
-    }
-    
-    protected ObjectMapper mapper() {
-        if (mapper==null)
-            mapper = BrooklynJacksonJsonProvider.findAnyObjectMapper(servletContext, managementContext);
-        return mapper;
-    }
-
-    /** @deprecated since 0.7.0 use {@link #getValueForDisplay(Object, boolean, boolean, Boolean, EntityLocal, Duration)} */ @Deprecated
-    protected Object getValueForDisplay(Object value, boolean preferJson, boolean isJerseyReturnValue) {
-        return resolving(value).preferJson(preferJson).asJerseyOutermostReturnValue(isJerseyReturnValue).resolve();
-    }
-
-    protected RestValueResolver resolving(Object v) {
-        return new RestValueResolver(v).mapper(mapper());
-    }
-
-    public static class RestValueResolver {
-        final private Object valueToResolve;
-        private @Nullable ObjectMapper mapper;
-        private boolean preferJson;
-        private boolean isJerseyReturnValue;
-        private @Nullable Boolean raw; 
-        private @Nullable Entity entity;
-        private @Nullable Duration timeout;
-        private @Nullable Object rendererHintSource;
-        
-        public static RestValueResolver resolving(Object v) { return new RestValueResolver(v); }
-        
-        private RestValueResolver(Object v) { valueToResolve = v; }
-        
-        public RestValueResolver mapper(ObjectMapper mapper) { this.mapper = mapper; return this; }
-        
-        /** whether JSON is the ultimate product; 
-         * main effect here is to give null for null if true, else to give empty string 
-         * <p>
-         * conversion to JSON for complex types is done subsequently (often by the framework)
-         * <p>
-         * default is true */
-        public RestValueResolver preferJson(boolean preferJson) { this.preferJson = preferJson; return this; }
-        /** whether an outermost string must be wrapped in quotes, because a String return object is treated as
-         * already JSON-encoded
-         * <p>
-         * default is false */
-        public RestValueResolver asJerseyOutermostReturnValue(boolean asJerseyReturnJson) {
-            isJerseyReturnValue = asJerseyReturnJson;
-            return this;
-        }
-        public RestValueResolver raw(Boolean raw) { this.raw = raw; return this; }
-        public RestValueResolver context(Entity entity) { this.entity = entity; return this; }
-        public RestValueResolver timeout(Duration timeout) { this.timeout = timeout; return this; }
-        public RestValueResolver renderAs(Object rendererHintSource) { this.rendererHintSource = rendererHintSource; return this; }
-
-        public Object resolve() {
-            Object valueResult = getImmediateValue(valueToResolve, entity, timeout);
-            if (valueResult==UNRESOLVED) valueResult = valueToResolve;
-            if (rendererHintSource!=null && Boolean.FALSE.equals(raw)) {
-                valueResult = RendererHints.applyDisplayValueHintUnchecked(rendererHintSource, valueResult);
-            }
-            return WebResourceUtils.getValueForDisplay(mapper, valueResult, preferJson, isJerseyReturnValue);
-        }
-        
-        private static Object UNRESOLVED = "UNRESOLVED".toCharArray();
-        
-        private static Object getImmediateValue(Object value, @Nullable Entity context, @Nullable Duration timeout) {
-            return Tasks.resolving(value)
-                    .as(Object.class)
-                    .defaultValue(UNRESOLVED)
-                    .timeout(timeout)
-                    .context(context)
-                    .swallowExceptions()
-                    .get();
-        }
-
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/AccessResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/AccessResource.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/AccessResource.java
deleted file mode 100644
index abe4835..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/AccessResource.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.rest.resources;
-
-import javax.ws.rs.core.Response;
-
-import org.apache.brooklyn.core.mgmt.internal.AccessManager;
-import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
-import org.apache.brooklyn.rest.api.AccessApi;
-import org.apache.brooklyn.rest.domain.AccessSummary;
-import org.apache.brooklyn.rest.transform.AccessTransformer;
-
-import com.google.common.annotations.Beta;
-
-@Beta
-public class AccessResource extends AbstractBrooklynRestResource implements AccessApi {
-
-    @Override
-    public AccessSummary get() {
-        AccessManager accessManager = ((ManagementContextInternal) mgmt()).getAccessManager();
-        return AccessTransformer.accessSummary(accessManager);
-    }
-
-    @Override
-    public Response locationProvisioningAllowed(boolean allowed) {
-        AccessManager accessManager = ((ManagementContextInternal) mgmt()).getAccessManager();
-        accessManager.setLocationProvisioningAllowed(allowed);
-        return Response.status(Response.Status.OK).build();
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ActivityResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ActivityResource.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ActivityResource.java
deleted file mode 100644
index 0444188..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ActivityResource.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.rest.resources;
-
-import java.util.Collections;
-import java.util.LinkedList;
-import java.util.List;
-
-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.core.mgmt.entitlement.Entitlements;
-import org.apache.brooklyn.rest.api.ActivityApi;
-import org.apache.brooklyn.rest.domain.TaskSummary;
-import org.apache.brooklyn.rest.transform.TaskTransformer;
-import org.apache.brooklyn.rest.util.WebResourceUtils;
-
-import com.google.common.collect.Collections2;
-import com.google.common.collect.Lists;
-
-public class ActivityResource extends AbstractBrooklynRestResource implements ActivityApi {
-
-    @Override
-    public TaskSummary get(String taskId) {
-        Task<?> t = mgmt().getExecutionManager().getTask(taskId);
-        if (t == null) {
-            throw WebResourceUtils.notFound("Cannot find task '%s'", taskId);
-        }
-        checkEntityEntitled(t);
-        
-        return TaskTransformer.FROM_TASK.apply(t);
-    }
-
-    @Override
-    public List<TaskSummary> children(String taskId) {
-        Task<?> t = mgmt().getExecutionManager().getTask(taskId);
-        if (t == null) {
-            throw WebResourceUtils.notFound("Cannot find task '%s'", taskId);
-        }
-        checkEntityEntitled(t);
-        
-        if (!(t instanceof HasTaskChildren)) {
-            return Collections.emptyList();
-        }
-        return new LinkedList<TaskSummary>(Collections2.transform(Lists.newArrayList(((HasTaskChildren) t).getChildren()),
-                TaskTransformer.FROM_TASK));
-    }
-
-    @Override
-    public String stream(String taskId, String streamId) {
-        Task<?> t = mgmt().getExecutionManager().getTask(taskId);
-        if (t == null) {
-            throw WebResourceUtils.notFound("Cannot find task '%s'", taskId);
-        }
-        checkEntityEntitled(t);
-        checkStreamEntitled(t, streamId);
-        
-        WrappedStream stream = BrooklynTaskTags.stream(t, streamId);
-        if (stream == null) {
-            throw WebResourceUtils.notFound("Cannot find stream '%s' in task '%s'", streamId, taskId);
-        }
-        return stream.streamContents.get();
-    }
-    
-    protected void checkEntityEntitled(Task<?> task) {
-        Entity entity = BrooklynTaskTags.getContextEntity(task);
-        if (entity != null && !Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY, entity)) {
-            throw WebResourceUtils.forbidden("User '%s' is not authorized to see activity of entity '%s'",
-                    Entitlements.getEntitlementContext().user(), entity);
-        }
-    }
-    
-    protected void checkStreamEntitled(Task<?> task, String streamId) {
-        Entity entity = BrooklynTaskTags.getContextEntity(task);
-        Entitlements.TaskAndItem<String> item = new Entitlements.TaskAndItem<String>(task, streamId);
-        if (entity != null && !Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ACTIVITY_STREAMS, item)) {
-            throw WebResourceUtils.forbidden("User '%s' is not authorized to see activity stream of entity '%s'",
-                    Entitlements.getEntitlementContext().user(), entity);
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ApidocResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ApidocResource.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ApidocResource.java
deleted file mode 100644
index 95bbf59..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ApidocResource.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.rest.resources;
-
-import io.swagger.annotations.Api;
-import javax.ws.rs.Path;
-
-/**
- * @author Ciprian Ciubotariu <ch...@gmx.net>
- */
-@Api("API Documentation")
-@Path("/v1/apidoc")
-public class ApidocResource extends org.apache.brooklyn.rest.apidoc.ApiListingResource {
-
-}


[05/34] brooklyn-server git commit: [BROOKLYN-183] REST API using CXF JAX-RS 2.0 implementation

Posted by he...@apache.org.
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/json/MultimapSerializer.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/json/MultimapSerializer.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/json/MultimapSerializer.java
deleted file mode 100644
index c264e22..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/json/MultimapSerializer.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * 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.json;
-
-import java.io.IOException;
-import java.util.Collection;
-import java.util.Map;
-
-import com.fasterxml.jackson.core.JsonGenerator;
-import com.fasterxml.jackson.databind.SerializerProvider;
-import com.fasterxml.jackson.databind.ser.std.StdSerializer;
-
-import com.google.common.annotations.Beta;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Multimap;
-
-/**
- * Provides a serializer for {@link Multimap} instances.
- * <p>
- * When Brooklyn's Jackson dependency is updated from org.codehaus.jackson:1.9.13 to
- * com.fasterxml.jackson:2.3+ then this class should be replaced with a dependency on
- * jackson-datatype-guava and a GuavaModule registered with Brooklyn's ObjectMapper.
- */
-@Beta
-public class MultimapSerializer extends StdSerializer<Multimap<?, ?>> {
-
-    @SuppressWarnings({ "unchecked", "rawtypes" })
-    protected MultimapSerializer() {
-        super((Class<Multimap<?, ?>>) (Class) Multimap.class);
-    }
-
-    @Override
-    public void serialize(Multimap<?, ?> value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
-        jgen.writeStartObject();
-        writeEntries(value, jgen, provider);
-        jgen.writeEndObject();
-    }
-
-    private void writeEntries(Multimap<?, ?> value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
-        for (Map.Entry<?, ? extends Collection<?>> entry : value.asMap().entrySet()) {
-            provider.findKeySerializer(provider.constructType(String.class), null)
-                    .serialize(entry.getKey(), jgen, provider);
-            provider.defaultSerializeValue(Lists.newArrayList(entry.getValue()), jgen);
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/json/PossiblyStrictPreferringFieldsVisibilityChecker.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/json/PossiblyStrictPreferringFieldsVisibilityChecker.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/json/PossiblyStrictPreferringFieldsVisibilityChecker.java
deleted file mode 100644
index e474467..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/json/PossiblyStrictPreferringFieldsVisibilityChecker.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * 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.json;
-
-import java.lang.reflect.Field;
-import java.lang.reflect.Member;
-import java.lang.reflect.Method;
-
-import com.fasterxml.jackson.annotation.JsonAutoDetect;
-import com.fasterxml.jackson.annotation.PropertyAccessor;
-import com.fasterxml.jackson.databind.introspect.AnnotatedField;
-import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
-import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
-import com.fasterxml.jackson.databind.introspect.VisibilityChecker;
-
-import static com.fasterxml.jackson.annotation.JsonAutoDetect.*;
-
-/** a visibility checker which disables getters, but allows private access,
- * unless {@link BidiSerialization#isStrictSerialization()} is enabled in which case public fields or annotations must be used.
- * <p>
- * the reason for this change to visibility
- * is that getters might generate a copy, resulting in infinite loops, whereas field access should never do so.
- * (see e.g. test in {@link BrooklynJacksonSerializerTest} which uses a sensor+config object whose getTypeToken
- * causes infinite recursion)
- **/
-public class PossiblyStrictPreferringFieldsVisibilityChecker implements VisibilityChecker<PossiblyStrictPreferringFieldsVisibilityChecker> {
-    VisibilityChecker<?>
-        vizDefault = new VisibilityChecker.Std(Visibility.NONE, Visibility.NONE, Visibility.NONE, Visibility.ANY, Visibility.ANY),
-        vizStrict = new VisibilityChecker.Std(Visibility.NONE, Visibility.NONE, Visibility.NONE, Visibility.PUBLIC_ONLY, Visibility.PUBLIC_ONLY);
-    
-    @Override public PossiblyStrictPreferringFieldsVisibilityChecker with(JsonAutoDetect ann) { throw new UnsupportedOperationException(); }
-    @Override public PossiblyStrictPreferringFieldsVisibilityChecker with(Visibility v) { throw new UnsupportedOperationException(); }
-    @Override public PossiblyStrictPreferringFieldsVisibilityChecker withVisibility(PropertyAccessor method, Visibility v) { throw new UnsupportedOperationException(); }
-    @Override public PossiblyStrictPreferringFieldsVisibilityChecker withGetterVisibility(Visibility v) { throw new UnsupportedOperationException(); }
-    @Override public PossiblyStrictPreferringFieldsVisibilityChecker withIsGetterVisibility(Visibility v) { throw new UnsupportedOperationException(); }
-    @Override public PossiblyStrictPreferringFieldsVisibilityChecker withSetterVisibility(Visibility v) { throw new UnsupportedOperationException(); }
-    @Override public PossiblyStrictPreferringFieldsVisibilityChecker withCreatorVisibility(Visibility v) { throw new UnsupportedOperationException(); }
-    @Override public PossiblyStrictPreferringFieldsVisibilityChecker withFieldVisibility(Visibility v) { throw new UnsupportedOperationException(); }
-    
-    protected VisibilityChecker<?> viz() {
-        return BidiSerialization.isStrictSerialization() ? vizStrict : vizDefault;
-    }
-    
-    @Override public boolean isGetterVisible(Method m) { 
-        return viz().isGetterVisible(m);
-    }
-
-    @Override
-    public boolean isGetterVisible(AnnotatedMethod m) {
-        return isGetterVisible(m.getAnnotated());
-    }
-
-    @Override
-    public boolean isIsGetterVisible(Method m) {
-        return viz().isIsGetterVisible(m);
-    }
-
-    @Override
-    public boolean isIsGetterVisible(AnnotatedMethod m) {
-        return isIsGetterVisible(m.getAnnotated());
-    }
-
-    @Override
-    public boolean isSetterVisible(Method m) {
-        return viz().isSetterVisible(m);
-    }
-
-    @Override
-    public boolean isSetterVisible(AnnotatedMethod m) {
-        return isSetterVisible(m.getAnnotated());
-    }
-
-    @Override
-    public boolean isCreatorVisible(Member m) {
-        return viz().isCreatorVisible(m);
-    }
-
-    @Override
-    public boolean isCreatorVisible(AnnotatedMember m) {
-        return isCreatorVisible(m.getMember());
-    }
-
-    @Override
-    public boolean isFieldVisible(Field f) {
-        return viz().isFieldVisible(f);
-    }
-
-    @Override
-    public boolean isFieldVisible(AnnotatedField f) {
-        return isFieldVisible(f.getAnnotated());
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/webapp/WEB-INF/web.xml
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/webapp/WEB-INF/web.xml b/rest/rest-server/src/main/webapp/WEB-INF/web.xml
index ae98ff6..7ae55a0 100644
--- a/rest/rest-server/src/main/webapp/WEB-INF/web.xml
+++ b/rest/rest-server/src/main/webapp/WEB-INF/web.xml
@@ -1,6 +1,3 @@
-<!DOCTYPE web-app PUBLIC
-        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
-        "http://java.sun.com/dtd/web-app_2_3.dtd" >
 <!--
     Licensed to the Apache Software Foundation (ASF) under one
     or more contributor license agreements.  See the NOTICE file
@@ -19,7 +16,8 @@
     specific language governing permissions and limitations
     under the License.
 -->
-<web-app>
+<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
+         version="3.1">
     <display-name>Brooklyn REST API v1</display-name>
 
     <filter>
@@ -58,15 +56,6 @@
         <url-pattern>/*</url-pattern>
     </filter-mapping>
 
-    <filter>
-        <filter-name>Brooklyn Swagger Bootstrap</filter-name>
-        <filter-class>org.apache.brooklyn.rest.filter.SwaggerFilter</filter-class>
-    </filter>
-    <filter-mapping>
-        <filter-name>Brooklyn Swagger Bootstrap</filter-name>
-        <url-pattern>/*</url-pattern>
-    </filter-mapping>
-
     <!-- Brooklyn REST is usually run as a filter so static content can be placed in a webapp
          to which this is added; to run as a servlet directly, replace the filter tags 
          below (after the comment) with the servlet tags (commented out immediately below),
@@ -75,63 +64,44 @@
             <servlet-name>Brooklyn REST API v1 Servlet</servlet-name>
             <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
      -->
-    <filter>
-        <filter-name>Brooklyn REST API v1 Filter</filter-name>
-        <filter-class>com.sun.jersey.spi.container.servlet.ServletContainer</filter-class>
+    <servlet>
+        <servlet-name>Brooklyn REST API v1 Filter</servlet-name>
+        <servlet-class>org.apache.cxf.jaxrs.servlet.CXFNonSpringJaxrsServlet</servlet-class>
 
-        <!-- load our REST API jersey resources explicitly 
-            (the package scanner will only pick up classes with @Path annotations - doesn't look at implemented interfaces) 
-        -->
         <init-param>
-            <param-name>com.sun.jersey.config.property.resourceConfigClass</param-name>
-            <param-value>com.sun.jersey.api.core.ClassNamesResourceConfig</param-value>
-        </init-param>
-        <init-param>
-            <param-name>com.sun.jersey.config.property.classnames</param-name>
+            <param-name>jaxrs.serviceClasses</param-name>
             <param-value>
-                io.swagger.jaxrs.listing.SwaggerSerializers;
-                org.apache.brooklyn.rest.util.FormMapProvider;
-                com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
-                org.apache.brooklyn.rest.resources.ActivityResource;
-                org.apache.brooklyn.rest.resources.ApidocResource;
-                org.apache.brooklyn.rest.resources.ApplicationResource;
-                org.apache.brooklyn.rest.resources.CatalogResource;
-                org.apache.brooklyn.rest.resources.EffectorResource;
-                org.apache.brooklyn.rest.resources.EntityConfigResource;
-                org.apache.brooklyn.rest.resources.EntityResource;
-                org.apache.brooklyn.rest.resources.LocationResource;
-                org.apache.brooklyn.rest.resources.PolicyConfigResource;
-                org.apache.brooklyn.rest.resources.PolicyResource;
-                org.apache.brooklyn.rest.resources.ScriptResource;
-                org.apache.brooklyn.rest.resources.SensorResource;
-                org.apache.brooklyn.rest.resources.UsageResource;
-                org.apache.brooklyn.rest.resources.VersionResource;
+                org.apache.brooklyn.rest.resources.AccessResource,
+                org.apache.brooklyn.rest.resources.ActivityResource,
+                org.apache.brooklyn.rest.resources.ApidocResource,
+                org.apache.brooklyn.rest.resources.ApplicationResource,
+                org.apache.brooklyn.rest.resources.CatalogResource,
+                org.apache.brooklyn.rest.resources.EffectorResource,
+                org.apache.brooklyn.rest.resources.EntityConfigResource,
+                org.apache.brooklyn.rest.resources.EntityResource,
+                org.apache.brooklyn.rest.resources.LocationResource,
+                org.apache.brooklyn.rest.resources.PolicyConfigResource,
+                org.apache.brooklyn.rest.resources.PolicyResource,
+                org.apache.brooklyn.rest.resources.ScriptResource,
+                org.apache.brooklyn.rest.resources.SensorResource,
+                org.apache.brooklyn.rest.resources.ServerResource,
+                org.apache.brooklyn.rest.resources.UsageResource,
+                org.apache.brooklyn.rest.resources.VersionResource
             </param-value>
         </init-param>
 
         <init-param>
-            <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
-            <param-value>true</param-value>
-        </init-param>
-
-        <!-- no need for WADL. of course you can turn it back on it you want. -->
-        <init-param>
-            <param-name>com.sun.jersey.config.feature.DisableWADL</param-name>
-            <param-value>true</param-value>
+            <param-name>jaxrs.providers</param-name>
+            <param-value>
+                io.swagger.jaxrs.listing.SwaggerSerializers,
+                org.apache.brooklyn.rest.util.FormMapProvider,
+                com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider
+            </param-value>
         </init-param>
 
-    </filter>
-    <filter-mapping>
-        <filter-name>Brooklyn REST API v1 Filter</filter-name>
-        <url-pattern>/*</url-pattern>
-    </filter-mapping>
-    <!-- Brooklyn REST as a filter above; replace above 5 lines with those commented out below,
-         to run it as a servlet (see note above) 
-            <load-on-startup>1</load-on-startup>
-        </servlet>
-        <servlet-mapping>
-            <servlet-name>Brooklyn REST API v1 Servlet</servlet-name>
-            <url-pattern>/*</url-pattern>
-        </servlet-mapping>
-    -->
+    </servlet>
+    <servlet-mapping>
+        <servlet-name>Brooklyn REST API v1 Filter</servlet-name>
+        <url-pattern>/v1/*</url-pattern>
+    </servlet-mapping>
 </web-app>

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynPropertiesSecurityFilterTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynPropertiesSecurityFilterTest.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynPropertiesSecurityFilterTest.java
index e855841..6fe610b 100644
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynPropertiesSecurityFilterTest.java
+++ b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynPropertiesSecurityFilterTest.java
@@ -109,7 +109,7 @@ public class BrooklynPropertiesSecurityFilterTest extends BrooklynRestApiLaunche
                     .build();
             List<? extends NameValuePair> nvps = Lists.newArrayList(
                     new BasicNameValuePair("arg", "bar"));
-            String effector = String.format("/v1/applications/%s/entities/%s/effectors/identityEffector", appId, entityId);
+            String effector = String.format("/applications/%s/entities/%s/effectors/identityEffector", appId, entityId);
             HttpToolResponse response = HttpTool.httpPost(client, URI.create(getBaseUri() + effector),
                     ImmutableMap.of(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED.getMimeType()),
                     URLEncodedUtils.format(nvps, Charsets.UTF_8).getBytes());
@@ -129,7 +129,7 @@ public class BrooklynPropertiesSecurityFilterTest extends BrooklynRestApiLaunche
         HttpClient client = HttpTool.httpClientBuilder()
                 .uri(getBaseUri(server))
                 .build();
-        HttpToolResponse response = HttpTool.httpPost(client, URI.create(getBaseUri() + "/v1/applications"),
+        HttpToolResponse response = HttpTool.httpPost(client, URI.create(getBaseUri() + "/applications"),
                 ImmutableMap.of(HttpHeaders.CONTENT_TYPE, "application/x-yaml"),
                 blueprint.getBytes());
         assertTrue(HttpTool.isStatusCodeHealthy(response.getResponseCode()), "error creating app. response code=" + response.getResponseCode());
@@ -144,7 +144,7 @@ public class BrooklynPropertiesSecurityFilterTest extends BrooklynRestApiLaunche
                 .uri(getBaseUri(server))
                 .build();
         List entities = new ObjectMapper().readValue(
-                HttpTool.httpGet(client, URI.create(getBaseUri() + "/v1/applications/" + appId + "/entities"), MutableMap.<String, String>of()).getContent(), List.class);
+                HttpTool.httpGet(client, URI.create(getBaseUri() + "/applications/" + appId + "/entities"), MutableMap.<String, String>of()).getContent(), List.class);
         LOG.info((String) ((Map) entities.get(0)).get("id"));
         return (String) ((Map) entities.get(0)).get("id");
     }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncher.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncher.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncher.java
index becb882..0fe421e 100644
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncher.java
+++ b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncher.java
@@ -24,10 +24,8 @@ import java.io.File;
 import java.io.FilenameFilter;
 import java.io.IOException;
 import java.net.InetSocketAddress;
-import java.util.EnumSet;
 import java.util.List;
 
-import javax.servlet.DispatcherType;
 import javax.servlet.Filter;
 
 import org.apache.brooklyn.api.mgmt.ManagementContext;
@@ -38,12 +36,11 @@ import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
 import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
 import org.apache.brooklyn.core.server.BrooklynServerConfig;
 import org.apache.brooklyn.core.server.BrooklynServiceAttributes;
+import org.apache.brooklyn.rest.apidoc.RestApiResourceScanner;
 import org.apache.brooklyn.rest.filter.BrooklynPropertiesSecurityFilter;
 import org.apache.brooklyn.rest.filter.HaMasterCheckFilter;
 import org.apache.brooklyn.rest.filter.LoggingFilter;
-import org.apache.brooklyn.rest.filter.NoCacheFilter;
 import org.apache.brooklyn.rest.filter.RequestTaggingFilter;
-import org.apache.brooklyn.rest.filter.SwaggerFilter;
 import org.apache.brooklyn.rest.security.provider.AnyoneSecurityProvider;
 import org.apache.brooklyn.rest.security.provider.SecurityProvider;
 import org.apache.brooklyn.rest.util.ManagementContextProvider;
@@ -59,9 +56,7 @@ import org.apache.brooklyn.util.text.WildcardGlobs;
 import org.eclipse.jetty.server.NetworkConnector;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.handler.ContextHandler;
-import org.eclipse.jetty.servlet.FilterHolder;
 import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
 import org.eclipse.jetty.webapp.WebAppContext;
 import org.reflections.util.ClasspathHelper;
 import org.slf4j.Logger;
@@ -72,9 +67,8 @@ import com.google.common.base.Charsets;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 import com.google.common.io.Files;
-import com.sun.jersey.api.core.DefaultResourceConfig;
-import com.sun.jersey.api.core.ResourceConfig;
-import com.sun.jersey.spi.container.servlet.ServletContainer;
+
+import io.swagger.config.ScannerFactory;
 
 /** Convenience and demo for launching programmatically. Also used for automated tests.
  * <p>
@@ -96,20 +90,19 @@ public class BrooklynRestApiLauncher {
     public static final String SCANNING_CATALOG_BOM_URL = "classpath://brooklyn/scanning.catalog.bom";
 
     enum StartMode {
-        FILTER, SERVLET, /** web-xml is not fully supported */ @Beta WEB_XML
+        SERVLET, /** web-xml is not fully supported */ @Beta WEB_XML
     }
 
     public static final List<Class<? extends Filter>> DEFAULT_FILTERS = ImmutableList.of(
             RequestTaggingFilter.class,
             BrooklynPropertiesSecurityFilter.class,
             LoggingFilter.class,
-            HaMasterCheckFilter.class,
-            SwaggerFilter.class);
+            HaMasterCheckFilter.class);
 
     private boolean forceUseOfDefaultCatalogWithJavaClassPath = false;
     private Class<? extends SecurityProvider> securityProvider;
     private List<Class<? extends Filter>> filters = DEFAULT_FILTERS;
-    private StartMode mode = StartMode.FILTER;
+    private StartMode mode = StartMode.SERVLET;
     private ManagementContext mgmt;
     private ContextHandler customContext;
     private boolean deployJsgui = true;
@@ -182,18 +175,14 @@ public class BrooklynRestApiLauncher {
         String summary;
         if (customContext == null) {
             switch (mode) {
-            case SERVLET:
-                context = servletContextHandler(mgmt);
-                summary = "programmatic Jersey ServletContainer servlet";
-                break;
             case WEB_XML:
                 context = webXmlContextHandler(mgmt);
                 summary = "from WAR at " + ((WebAppContext) context).getWar();
                 break;
-            case FILTER:
+            case SERVLET:
             default:
-                context = filterContextHandler(mgmt);
-                summary = "programmatic Jersey ServletContainer filter on webapp at " + ((WebAppContext) context).getWar();
+                context = servletContextHandler(mgmt);
+                summary = "programmatic Jersey ServletContainer servlet";
                 break;
             }
         } else {
@@ -225,10 +214,22 @@ public class BrooklynRestApiLauncher {
         return server;
     }
 
-    private ContextHandler filterContextHandler(ManagementContext mgmt) {
+    private ContextHandler servletContextHandler(ManagementContext managementContext) {
         WebAppContext context = new WebAppContext();
-        context.setAttribute(BrooklynServiceAttributes.BROOKLYN_MANAGEMENT_CONTEXT, mgmt);
+
+        context.setAttribute(BrooklynServiceAttributes.BROOKLYN_MANAGEMENT_CONTEXT, managementContext);
+
+        installWar(context);
+        RestApiSetup.installRestServlet(context,
+                new ManagementContextProvider(managementContext),
+                new ShutdownHandlerProvider(shutdownListener));
+        RestApiSetup.installServletFilters(context, this.filters);
+
         context.setContextPath("/");
+        return context;
+    }
+
+    private void installWar(WebAppContext context) {
         // here we run with the JS GUI, for convenience, if we can find it, else set up an empty dir
         // TODO pretty sure there is an option to monitor this dir and load changes to static content
         // NOTE: When running Brooklyn from an IDE (i.e. by launching BrooklynJavascriptGuiLauncher.main())
@@ -243,8 +244,6 @@ public class BrooklynRestApiLauncher {
                 : ResourceUtils.create(this).doesUrlExist("classpath://brooklyn.war") 
                     ? Os.writeToTempFile(ResourceUtils.create(this).getResourceFromUrl("classpath://brooklyn.war"), "brooklyn", "war").getAbsolutePath()
                 : createTempWebDirWithIndexHtml("Brooklyn REST API <p> (gui not available)"));
-        installAsServletFilter(context, this.filters);
-        return context;
     }
 
     private ContextHandler servletContextHandler(ManagementContext managementContext) {
@@ -265,6 +264,7 @@ public class BrooklynRestApiLauncher {
 
     /** NB: not fully supported; use one of the other {@link StartMode}s */
     private ContextHandler webXmlContextHandler(ManagementContext mgmt) {
+        RestApiSetup.initSwagger();
         // TODO add security to web.xml
         WebAppContext context;
         if (findMatchingFile("src/main/webapp")!=null) {
@@ -328,16 +328,10 @@ public class BrooklynRestApiLauncher {
     }
 
     public static void main(String[] args) throws Exception {
-        startRestResourcesViaFilter();
+        startRestResourcesViaServlet();
         log.info("Press Ctrl-C to quit.");
     }
 
-    public static Server startRestResourcesViaFilter() {
-        return new BrooklynRestApiLauncher()
-                .mode(StartMode.FILTER)
-                .start();
-    }
-
     public static Server startRestResourcesViaServlet() throws Exception {
         return new BrooklynRestApiLauncher()
                 .mode(StartMode.SERVLET)

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTest.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTest.java
index 1bf756d..e73fab4 100644
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTest.java
+++ b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTest.java
@@ -18,7 +18,6 @@
  */
 package org.apache.brooklyn.rest;
 
-import static org.apache.brooklyn.rest.BrooklynRestApiLauncher.StartMode.FILTER;
 import static org.apache.brooklyn.rest.BrooklynRestApiLauncher.StartMode.SERVLET;
 import static org.apache.brooklyn.rest.BrooklynRestApiLauncher.StartMode.WEB_XML;
 
@@ -37,11 +36,6 @@ import org.testng.annotations.Test;
 public class BrooklynRestApiLauncherTest extends BrooklynRestApiLauncherTestFixture {
 
     @Test
-    public void testFilterStart() throws Exception {
-        checkRestCatalogEntities(useServerForTest(baseLauncher().mode(FILTER).start()));
-    }
-
-    @Test
     public void testServletStart() throws Exception {
         checkRestCatalogEntities(useServerForTest(baseLauncher().mode(SERVLET).start()));
     }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTestFixture.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTestFixture.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTestFixture.java
index b2c5031..4ca48df 100644
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTestFixture.java
+++ b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTestFixture.java
@@ -18,20 +18,21 @@
  */
 package org.apache.brooklyn.rest;
 
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.ContextHandler;
-import org.reflections.util.ClasspathHelper;
-import org.testng.Assert;
-import org.testng.annotations.AfterMethod;
 import org.apache.brooklyn.api.mgmt.ManagementContext;
 import org.apache.brooklyn.core.entity.Entities;
 import org.apache.brooklyn.core.internal.BrooklynProperties;
 import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
 import org.apache.brooklyn.core.server.BrooklynServerConfig;
+import org.apache.brooklyn.core.server.BrooklynServiceAttributes;
 import org.apache.brooklyn.rest.security.provider.AnyoneSecurityProvider;
-import org.apache.brooklyn.rest.util.OsgiCompat;
+import org.apache.brooklyn.util.core.osgi.Compat;
 import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.eclipse.jetty.server.NetworkConnector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.reflections.util.ClasspathHelper;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
 
 public abstract class BrooklynRestApiLauncherTestFixture {
 
@@ -104,7 +105,14 @@ public abstract class BrooklynRestApiLauncherTestFixture {
     }
 
     public static ManagementContext getManagementContextFromJettyServerAttributes(Server server) {
-        return OsgiCompat.getManagementContext((ContextHandler) server.getHandler());
+        return getManagementContext((ContextHandler) server.getHandler());
     }
     
+    public static ManagementContext getManagementContext(ContextHandler jettyServerHandler) {
+        ManagementContext managementContext = Compat.getInstance().getManagementContext();
+        if (managementContext == null && jettyServerHandler != null) {
+            managementContext = (ManagementContext) jettyServerHandler.getAttribute(BrooklynServiceAttributes.BROOKLYN_MANAGEMENT_CONTEXT);
+        }
+        return managementContext;
+    }
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/HaHotCheckTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/HaHotCheckTest.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/HaHotCheckTest.java
deleted file mode 100644
index 3fd5d38..0000000
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/HaHotCheckTest.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * 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;
-
-import static org.testng.Assert.assertEquals;
-
-import javax.ws.rs.core.MediaType;
-
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-import org.apache.brooklyn.api.mgmt.ha.HighAvailabilityManager;
-import org.apache.brooklyn.api.mgmt.ha.HighAvailabilityMode;
-import org.apache.brooklyn.api.mgmt.ha.ManagementNodeState;
-import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
-import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
-import org.apache.brooklyn.rest.filter.HaHotCheckResourceFilter;
-import org.apache.brooklyn.rest.filter.HaMasterCheckFilter;
-import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
-import org.apache.brooklyn.rest.util.HaHotStateCheckClassResource;
-import org.apache.brooklyn.rest.util.HaHotStateCheckResource;
-
-import com.sun.jersey.api.client.ClientResponse;
-import com.sun.jersey.api.client.WebResource.Builder;
-import com.sun.jersey.api.core.ResourceConfig;
-
-public class HaHotCheckTest extends BrooklynRestResourceTest {
-
-    // setup and teardown before/after each method
-    
-    @BeforeMethod(alwaysRun = true)
-    public void setUp() throws Exception { super.setUp(); }
-
-    @AfterMethod(alwaysRun = true)
-    public void tearDown() throws Exception { super.tearDown(); }
-    
-    @Override
-    protected void addBrooklynResources() {
-        config.getProperties().put(ResourceConfig.PROPERTY_RESOURCE_FILTER_FACTORIES, 
-            new HaHotCheckResourceFilter(getManagementContext()));
-        addResource(new HaHotStateCheckResource());
-        addResource(new HaHotStateCheckClassResource());
-        
-        ((LocalManagementContext)getManagementContext()).noteStartupComplete();
-    }
-
-    @Test
-    public void testHaCheck() {
-        HighAvailabilityManager ha = getManagementContext().getHighAvailabilityManager();
-        assertEquals(ha.getNodeState(), ManagementNodeState.MASTER);
-        testResourceFetch("/ha/method/ok", 200);
-        testResourceFetch("/ha/method/fail", 200);
-        testResourceFetch("/ha/class/fail", 200);
-
-        getManagementContext().getHighAvailabilityManager().changeMode(HighAvailabilityMode.STANDBY);
-        assertEquals(ha.getNodeState(), ManagementNodeState.STANDBY);
-
-        testResourceFetch("/ha/method/ok", 200);
-        testResourceFetch("/ha/method/fail", 403);
-        testResourceFetch("/ha/class/fail", 403);
-
-        ((ManagementContextInternal)getManagementContext()).terminate();
-        assertEquals(ha.getNodeState(), ManagementNodeState.TERMINATED);
-
-        testResourceFetch("/ha/method/ok", 200);
-        testResourceFetch("/ha/method/fail", 403);
-        testResourceFetch("/ha/class/fail", 403);
-    }
-
-    @Test
-    public void testHaCheckForce() {
-        HighAvailabilityManager ha = getManagementContext().getHighAvailabilityManager();
-        assertEquals(ha.getNodeState(), ManagementNodeState.MASTER);
-        testResourceForcedFetch("/ha/method/ok", 200);
-        testResourceForcedFetch("/ha/method/fail", 200);
-        testResourceForcedFetch("/ha/class/fail", 200);
-
-        getManagementContext().getHighAvailabilityManager().changeMode(HighAvailabilityMode.STANDBY);
-        assertEquals(ha.getNodeState(), ManagementNodeState.STANDBY);
-
-        testResourceForcedFetch("/ha/method/ok", 200);
-        testResourceForcedFetch("/ha/method/fail", 200);
-        testResourceForcedFetch("/ha/class/fail", 200);
-
-        ((ManagementContextInternal)getManagementContext()).terminate();
-        assertEquals(ha.getNodeState(), ManagementNodeState.TERMINATED);
-
-        testResourceForcedFetch("/ha/method/ok", 200);
-        testResourceForcedFetch("/ha/method/fail", 200);
-        testResourceForcedFetch("/ha/class/fail", 200);
-    }
-
-
-    private void testResourceFetch(String resourcePath, int code) {
-        testResourceFetch(resourcePath, false, code);
-    }
-
-    private void testResourceForcedFetch(String resourcePath, int code) {
-        testResourceFetch(resourcePath, true, code);
-    }
-
-    private void testResourceFetch(String resourcePath, boolean force, int code) {
-        Builder resource = client().resource(resourcePath)
-                .accept(MediaType.APPLICATION_JSON_TYPE);
-        if (force) {
-            resource.header(HaMasterCheckFilter.SKIP_CHECK_HEADER, "true");
-        }
-        ClientResponse response = resource
-                .get(ClientResponse.class);
-        assertEquals(response.getStatus(), code);
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/HaMasterCheckFilterTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/HaMasterCheckFilterTest.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/HaMasterCheckFilterTest.java
index 424c0c1..90e7957 100644
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/HaMasterCheckFilterTest.java
+++ b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/HaMasterCheckFilterTest.java
@@ -114,7 +114,7 @@ System.err.println("TEAR DOWN");
 
     private int getAppResponseCode() {
         HttpToolResponse response = HttpTool.httpGet(
-                client, URI.create(getBaseUri(server) + "/v1/applications/" + appId),
+                client, URI.create(getBaseUri(server) + "/applications/" + appId),
                 ImmutableMap.<String,String>of());
         return response.getResponseCode();
     }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/domain/ApplicationTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/domain/ApplicationTest.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/domain/ApplicationTest.java
deleted file mode 100644
index 8708fb1..0000000
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/domain/ApplicationTest.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.rest.domain;
-
-import static org.apache.brooklyn.rest.util.RestApiTestUtils.asJson;
-import static org.apache.brooklyn.rest.util.RestApiTestUtils.fromJson;
-import static org.apache.brooklyn.rest.util.RestApiTestUtils.jsonFixture;
-import static org.testng.Assert.assertEquals;
-
-import java.io.IOException;
-import java.net.URI;
-import java.util.Map;
-
-import org.apache.brooklyn.api.mgmt.ManagementContext;
-import org.apache.brooklyn.core.entity.Entities;
-import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
-import org.apache.brooklyn.core.test.entity.TestApplication;
-import org.apache.brooklyn.test.Asserts;
-import org.testng.annotations.Test;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-
-public class ApplicationTest {
-
-    final EntitySpec entitySpec = new EntitySpec("Vanilla Java App", "org.apache.brooklyn.entity.java.VanillaJavaApp",
-            ImmutableMap.of(
-                    "initialSize", "1",
-                    "creationScriptUrl", "http://my.brooklyn.io/storage/foo.sql"));
-
-    final ApplicationSpec applicationSpec = ApplicationSpec.builder().name("myapp")
-            .entities(ImmutableSet.of(entitySpec))
-            .locations(ImmutableSet.of("/v1/locations/1"))
-            .build();
-
-    final Map<String, URI> links = ImmutableMap.of(
-            "self", URI.create("/v1/applications/" + applicationSpec.getName()),
-            "entities", URI.create("fixtures/entity-summary-list.json"));
-    final ApplicationSummary application = new ApplicationSummary("myapp_id", applicationSpec, Status.STARTING, links);
-
-    @SuppressWarnings("serial")
-    @Test
-    public void testSerializeToJSON() throws IOException {
-        assertEquals(asJson(application), jsonFixture("fixtures/application.json"));
-    }
-
-    @Test
-    public void testDeserializeFromJSON() throws IOException {
-        assertEquals(fromJson(jsonFixture("fixtures/application.json"),
-                ApplicationSummary.class), application);
-    }
-
-    @Test
-    public void testTransitionToRunning() {
-        ApplicationSummary running = application.transitionTo(Status.RUNNING);
-        assertEquals(running.getStatus(), Status.RUNNING);
-    }
-
-    @Test
-    public void testAppInAppTest() throws IOException {
-        ManagementContext mgmt = LocalManagementContextForTests.newInstance();
-        try {
-            TestApplication app = mgmt.getEntityManager().createEntity(org.apache.brooklyn.api.entity.EntitySpec.create(TestApplication.class));
-            app.addChild(org.apache.brooklyn.api.entity.EntitySpec.create(TestApplication.class));
-            Asserts.assertEqualsIgnoringOrder(mgmt.getApplications(), ImmutableList.of(app));
-        } finally {
-            Entities.destroyAll(mgmt);
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/domain/SensorSummaryTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/domain/SensorSummaryTest.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/domain/SensorSummaryTest.java
deleted file mode 100644
index a3c6df2..0000000
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/domain/SensorSummaryTest.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.rest.domain;
-
-import static org.apache.brooklyn.rest.util.RestApiTestUtils.asJson;
-import static org.apache.brooklyn.rest.util.RestApiTestUtils.fromJson;
-import static org.apache.brooklyn.rest.util.RestApiTestUtils.jsonFixture;
-import static org.testng.Assert.assertEquals;
-
-import java.io.IOException;
-import java.net.URI;
-
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-import org.apache.brooklyn.api.entity.EntitySpec;
-import org.apache.brooklyn.api.mgmt.ManagementContext;
-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.core.entity.Entities;
-import org.apache.brooklyn.core.sensor.Sensors;
-import org.apache.brooklyn.core.test.entity.TestApplication;
-import org.apache.brooklyn.core.test.entity.TestEntity;
-import org.apache.brooklyn.rest.transform.SensorTransformer;
-
-import com.google.common.collect.ImmutableMap;
-
-public class SensorSummaryTest {
-
-    private SensorSummary sensorSummary = new SensorSummary("redis.uptime", "Integer",
-            "Description", ImmutableMap.of(
-            "self", URI.create("/v1/applications/redis-app/entities/redis-ent/sensors/redis.uptime")));
-
-    private TestApplication app;
-    private TestEntity entity;
-    private ManagementContext mgmt;
-
-    @BeforeMethod(alwaysRun = true)
-    public void setUp() throws Exception {
-        app = TestApplication.Factory.newManagedInstanceForTests();
-        mgmt = app.getManagementContext();
-        entity = app.createAndManageChild(EntitySpec.create(TestEntity.class));
-    }
-
-    @AfterMethod(alwaysRun = true)
-    public void tearDown() throws Exception {
-        if (mgmt != null) Entities.destroyAll(mgmt);
-    }
-
-    @Test
-    public void testSerializeToJSON() throws IOException {
-        assertEquals(asJson(sensorSummary), jsonFixture("fixtures/sensor-summary.json"));
-    }
-
-    @Test
-    public void testDeserializeFromJSON() throws IOException {
-        assertEquals(fromJson(jsonFixture("fixtures/sensor-summary.json"), SensorSummary.class), sensorSummary);
-    }
-
-    @Test
-    public void testEscapesUriForSensorName() throws IOException {
-        Sensor<String> sensor = Sensors.newStringSensor("name with space");
-        SensorSummary summary = SensorTransformer.sensorSummary(entity, sensor);
-        URI selfUri = summary.getLinks().get("self");
-
-        String expectedUri = "/v1/applications/" + entity.getApplicationId() + "/entities/" + entity.getId() + "/sensors/" + "name%20with%20space";
-
-        assertEquals(selfUri, URI.create(expectedUri));
-    }
-
-    // Previously failed because immutable-map builder threw exception if put same key multiple times,
-    // and the NamedActionWithUrl did not have equals/hashCode
-    @Test
-    public void testSensorWithMultipleOpenUrlActionsRegistered() throws IOException {
-        AttributeSensor<String> sensor = Sensors.newStringSensor("sensor1");
-        entity.sensors().set(sensor, "http://myval");
-        RendererHints.register(sensor, RendererHints.namedActionWithUrl());
-        RendererHints.register(sensor, RendererHints.namedActionWithUrl());
-
-        SensorSummary summary = SensorTransformer.sensorSummary(entity, sensor);
-
-        assertEquals(summary.getLinks().get("action:open"), URI.create("http://myval"));
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/AccessResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/AccessResourceTest.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/AccessResourceTest.java
deleted file mode 100644
index 0839ea5..0000000
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/AccessResourceTest.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.rest.resources;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.assertTrue;
-
-import javax.ws.rs.core.Response;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.annotations.Test;
-
-import org.apache.brooklyn.rest.domain.AccessSummary;
-import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
-
-import com.sun.jersey.api.client.ClientResponse;
-
-@Test(singleThreaded = true)
-public class AccessResourceTest extends BrooklynRestResourceTest {
-
-    @SuppressWarnings("unused")
-    private static final Logger log = LoggerFactory.getLogger(AccessResourceTest.class);
-
-    @Test
-    public void testGetAndSetAccessControl() throws Exception {
-        // Default is everything allowed
-        AccessSummary summary = client().resource("/v1/access").get(AccessSummary.class);
-        assertTrue(summary.isLocationProvisioningAllowed());
-
-        // Forbid location provisioning
-        ClientResponse response = client().resource(
-                "/v1/access/locationProvisioningAllowed")
-                .queryParam("allowed", "false")
-                .post(ClientResponse.class);
-        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
-
-        AccessSummary summary2 = client().resource("/v1/access").get(AccessSummary.class);
-        assertFalse(summary2.isLocationProvisioningAllowed());
-        
-        // Allow location provisioning
-        ClientResponse response2 = client().resource(
-                "/v1/access/locationProvisioningAllowed")
-                .queryParam("allowed", "true")
-                .post(ClientResponse.class);
-        assertEquals(response2.getStatus(), Response.Status.OK.getStatusCode());
-
-        AccessSummary summary3 = client().resource("/v1/access").get(AccessSummary.class);
-        assertTrue(summary3.isLocationProvisioningAllowed());
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ApidocResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ApidocResourceTest.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ApidocResourceTest.java
deleted file mode 100644
index 739d63f..0000000
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ApidocResourceTest.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.rest.resources;
-
-
-import com.google.common.base.Function;
-import com.google.common.base.Joiner;
-import com.google.common.base.Predicate;
-import com.google.common.collect.Collections2;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Iterables;
-import com.sun.jersey.api.core.ClassNamesResourceConfig;
-import com.sun.jersey.spi.container.servlet.ServletContainer;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.Assert;
-import org.testng.annotations.Test;
-
-import org.apache.brooklyn.rest.BrooklynRestApi;
-import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
-
-import com.sun.jersey.test.framework.AppDescriptor;
-import com.sun.jersey.test.framework.JerseyTest;
-import com.sun.jersey.test.framework.WebAppDescriptor;
-import com.sun.jersey.test.framework.spi.container.TestContainerException;
-import com.sun.jersey.test.framework.spi.container.TestContainerFactory;
-import com.sun.jersey.test.framework.spi.container.grizzly2.web.GrizzlyWebTestContainerFactory;
-import io.swagger.annotations.Api;
-import io.swagger.models.Info;
-import io.swagger.models.Operation;
-import io.swagger.models.Path;
-import io.swagger.models.Swagger;
-import java.util.Collection;
-import org.apache.brooklyn.rest.api.CatalogApi;
-import org.apache.brooklyn.rest.api.EffectorApi;
-import org.apache.brooklyn.rest.api.EntityApi;
-import org.apache.brooklyn.rest.filter.SwaggerFilter;
-import org.apache.brooklyn.rest.util.ShutdownHandlerProvider;
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertFalse;
-
-/**
- * @author Adam Lowe
- */
-@Test(singleThreaded = true)
-public class ApidocResourceTest extends BrooklynRestResourceTest {
-
-    private static final Logger log = LoggerFactory.getLogger(ApidocResourceTest.class);
-
-    @Override
-    protected JerseyTest createJerseyTest() {
-        return new JerseyTest() {
-            @Override
-            protected AppDescriptor configure() {
-                return new WebAppDescriptor.Builder(
-                        ImmutableMap.of(
-                                ServletContainer.RESOURCE_CONFIG_CLASS, ClassNamesResourceConfig.class.getName(),
-                                ClassNamesResourceConfig.PROPERTY_CLASSNAMES, getResourceClassnames()))
-                        .addFilter(SwaggerFilter.class, "SwaggerFilter").build();
-            }
-
-            @Override
-            protected TestContainerFactory getTestContainerFactory() throws TestContainerException {
-                return new GrizzlyWebTestContainerFactory();
-            }
-
-            private String getResourceClassnames() {
-                Iterable<String> classnames = Collections2.transform(config.getClasses(), new Function<Class, String>() {
-                    @Override
-                    public String apply(Class clazz) {
-                        return clazz.getName();
-                    }
-                });
-                classnames = Iterables.concat(classnames, Collections2.transform(config.getSingletons(), new Function<Object, String>() {
-                    @Override
-                    public String apply(Object singleton) {
-                        return singleton.getClass().getName();
-                    }
-                }));
-                return Joiner.on(';').join(classnames);
-            }
-        };
-    }
-
-    @Override
-    protected void addBrooklynResources() {
-        for (Object o : BrooklynRestApi.getApidocResources()) {
-            addResource(o);
-        }
-        super.addBrooklynResources();
-    }
-    
-    @Test(enabled = false)
-    public void testRootSerializesSensibly() throws Exception {
-        String data = resource("/v1/apidoc/swagger.json").get(String.class);
-        log.info("apidoc gives: "+data);
-        // make sure no scala gets in
-        assertFalse(data.contains("$"));
-        assertFalse(data.contains("scala"));
-        // make sure it's an appropriate swagger 2.0 json
-        Swagger swagger = resource("/v1/apidoc/swagger.json").get(Swagger.class);
-        assertEquals(swagger.getSwagger(), "2.0");
-    }
-    
-    @Test(enabled = false)
-    public void testCountRestResources() throws Exception {
-        Swagger swagger = resource("/v1/apidoc/swagger.json").get(Swagger.class);
-        assertEquals(swagger.getTags().size(), 1 + Iterables.size(BrooklynRestApi.getBrooklynRestResources()));
-    }
-
-    @Test(enabled = false)
-    public void testApiDocDetails() throws Exception {
-        Swagger swagger = resource("/v1/apidoc/swagger.json").get(Swagger.class);
-        Collection<Operation> operations = getTaggedOperations(swagger, ApidocResource.class.getAnnotation(Api.class).value());
-        assertEquals(operations.size(), 2, "ops="+operations);
-    }
-
-    @Test(enabled = false)
-    public void testEffectorDetails() throws Exception {
-        Swagger swagger = resource("/v1/apidoc/swagger.json").get(Swagger.class);
-        Collection<Operation> operations = getTaggedOperations(swagger, EffectorApi.class.getAnnotation(Api.class).value());
-        assertEquals(operations.size(), 2, "ops="+operations);
-    }
-
-    @Test(enabled = false)
-    public void testEntityDetails() throws Exception {
-        Swagger swagger = resource("/v1/apidoc/swagger.json").get(Swagger.class);
-        Collection<Operation> operations = getTaggedOperations(swagger, EntityApi.class.getAnnotation(Api.class).value());
-        assertEquals(operations.size(), 14, "ops="+operations);
-    }
-
-    @Test(enabled = false)
-    public void testCatalogDetails() throws Exception {
-        Swagger swagger = resource("/v1/apidoc/swagger.json").get(Swagger.class);
-        Collection<Operation> operations = getTaggedOperations(swagger, CatalogApi.class.getAnnotation(Api.class).value());
-        assertEquals(operations.size(), 22, "ops="+operations);
-    }
-
-    /**
-     * Retrieves all operations tagged the given tag from the given swagger spec.
-     */
-    private Collection<Operation> getTaggedOperations(Swagger swagger, final String requiredTag) {
-        Iterable<Operation> allOperations = Iterables.concat(Collections2.transform(swagger.getPaths().values(),
-                new Function<Path, Collection<Operation>>() {
-                    @Override
-                    public Collection<Operation> apply(Path path) {
-                        return path.getOperations();
-                    }
-                }));
-
-        return Collections2.filter(ImmutableList.copyOf(allOperations), new Predicate<Operation>() {
-            @Override
-            public boolean apply(Operation operation) {
-                return operation.getTags().contains(requiredTag);
-            }
-        });
-    }
-}
-

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceIntegrationTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceIntegrationTest.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceIntegrationTest.java
deleted file mode 100644
index 865b6f7..0000000
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceIntegrationTest.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.rest.resources;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertTrue;
-
-import java.net.URI;
-import java.util.Set;
-
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.annotations.Test;
-import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
-import org.apache.brooklyn.rest.domain.ApplicationSpec;
-import org.apache.brooklyn.rest.domain.ApplicationSummary;
-import org.apache.brooklyn.rest.domain.EntitySpec;
-import org.apache.brooklyn.rest.domain.EntitySummary;
-import org.apache.brooklyn.rest.domain.SensorSummary;
-import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
-import org.apache.brooklyn.test.Asserts;
-import org.apache.brooklyn.util.collections.MutableMap;
-
-import com.google.common.base.Predicate;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
-import com.sun.jersey.api.client.ClientResponse;
-import com.sun.jersey.api.client.GenericType;
-
-@Test(singleThreaded = true)
-public class ApplicationResourceIntegrationTest extends BrooklynRestResourceTest {
-
-    @SuppressWarnings("unused")
-    private static final Logger log = LoggerFactory.getLogger(ApplicationResourceIntegrationTest.class);
-
-    private final ApplicationSpec redisSpec = ApplicationSpec.builder().name("redis-app")
-            .entities(ImmutableSet.of(new EntitySpec("redis-ent", "org.apache.brooklyn.entity.nosql.redis.RedisStore")))
-            .locations(ImmutableSet.of("localhost"))
-            .build();
-
-    @Test(groups="Integration")
-    public void testDeployRedisApplication() throws Exception {
-        ClientResponse response = clientDeploy(redisSpec);
-
-        assertEquals(response.getStatus(), 201);
-        assertEquals(getManagementContext().getApplications().size(), 1);
-        assertTrue(response.getLocation().getPath().startsWith("/v1/applications/"), "path="+response.getLocation().getPath()); // path uses id, rather than app name
-
-        waitForApplicationToBeRunning(response.getLocation());
-    }
-
-    @Test(groups="Integration", dependsOnMethods = "testDeployRedisApplication")
-    public void testListEntities() {
-        Set<EntitySummary> entities = client().resource("/v1/applications/redis-app/entities")
-                .get(new GenericType<Set<EntitySummary>>() {});
-
-        for (EntitySummary entity : entities) {
-            client().resource(entity.getLinks().get("self")).get(ClientResponse.class);
-            // TODO assertions on the above call?
-
-            Set<EntitySummary> children = client().resource(entity.getLinks().get("children"))
-                    .get(new GenericType<Set<EntitySummary>>() {});
-            assertEquals(children.size(), 0);
-        }
-    }
-
-    @Test(groups="Integration", dependsOnMethods = "testDeployRedisApplication")
-    public void testListSensorsRedis() {
-        Set<SensorSummary> sensors = client().resource("/v1/applications/redis-app/entities/redis-ent/sensors")
-                .get(new GenericType<Set<SensorSummary>>() {});
-        assertTrue(sensors.size() > 0);
-        SensorSummary uptime = Iterables.find(sensors, new Predicate<SensorSummary>() {
-            @Override
-            public boolean apply(SensorSummary sensorSummary) {
-                return sensorSummary.getName().equals("redis.uptime");
-            }
-        });
-        assertEquals(uptime.getType(), "java.lang.Integer");
-    }
-
-    @Test(groups="Integration", dependsOnMethods = { "testListSensorsRedis", "testListEntities" })
-    public void testTriggerRedisStopEffector() throws Exception {
-        ClientResponse response = client().resource("/v1/applications/redis-app/entities/redis-ent/effectors/stop")
-                .type(MediaType.APPLICATION_JSON_TYPE)
-                .post(ClientResponse.class, ImmutableMap.of());
-        assertEquals(response.getStatus(), Response.Status.ACCEPTED.getStatusCode());
-
-        final URI stateSensor = URI.create("/v1/applications/redis-app/entities/redis-ent/sensors/service.state");
-        final String expectedStatus = Lifecycle.STOPPED.toString();
-        Asserts.succeedsEventually(MutableMap.of("timeout", 60 * 1000), new Runnable() {
-            public void run() {
-                // Accept with and without quotes; if don't specify "Accepts" header, then
-                // might get back json or plain text (depending on compiler / java runtime 
-                // used for SensorApi!)
-                String val = client().resource(stateSensor).get(String.class);
-                assertTrue(expectedStatus.equalsIgnoreCase(val) || ("\""+expectedStatus+"\"").equalsIgnoreCase(val), "state="+val);
-            }
-        });
-    }
-
-    @Test(groups="Integration", dependsOnMethods = "testTriggerRedisStopEffector" )
-    public void testDeleteRedisApplication() throws Exception {
-        int size = getManagementContext().getApplications().size();
-        ClientResponse response = client().resource("/v1/applications/redis-app")
-                .delete(ClientResponse.class);
-
-        waitForPageNotFoundResponse("/v1/applications/redis-app", ApplicationSummary.class);
-
-        assertEquals(response.getStatus(), Response.Status.ACCEPTED.getStatusCode());
-        assertEquals(getManagementContext().getApplications().size(), size-1);
-    }
-
-}


[23/34] brooklyn-server git commit: REST API optional Jersey compatibility

Posted by he...@apache.org.
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/EntityConfigResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/EntityConfigResourceTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/EntityConfigResourceTest.java
new file mode 100644
index 0000000..014053f
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/EntityConfigResourceTest.java
@@ -0,0 +1,172 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nullable;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+import org.apache.brooklyn.core.entity.EntityInternal;
+import org.apache.brooklyn.core.entity.EntityPredicates;
+import org.apache.brooklyn.rest.domain.ApplicationSpec;
+import org.apache.brooklyn.rest.domain.EntityConfigSummary;
+import org.apache.brooklyn.rest.domain.EntitySpec;
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+import org.apache.brooklyn.rest.testing.mocks.RestMockSimpleEntity;
+import org.apache.brooklyn.util.collections.MutableMap;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.sun.jersey.api.client.ClientResponse;
+import com.sun.jersey.api.client.GenericType;
+
+@Test(singleThreaded = true)
+public class EntityConfigResourceTest extends BrooklynRestResourceTest {
+    
+    private final static Logger log = LoggerFactory.getLogger(EntityConfigResourceTest.class);
+    private URI applicationUri;
+    private EntityInternal entity;
+
+    @BeforeClass(alwaysRun = true)
+    @Override
+    public void setUp() throws Exception {
+        super.setUp(); // We require that the superclass setup is done first, as we will be calling out to Jersey
+
+        // Deploy an application that we'll use to read the configuration of
+        final ApplicationSpec simpleSpec = ApplicationSpec.builder().name("simple-app").
+                  entities(ImmutableSet.of(new EntitySpec("simple-ent", RestMockSimpleEntity.class.getName(), ImmutableMap.of("install.version", "1.0.0")))).
+                  locations(ImmutableSet.of("localhost")).
+                  build();
+        
+        ClientResponse response = clientDeploy(simpleSpec);
+        int status = response.getStatus();
+        assertTrue(status >= 200 && status <= 299, "expected HTTP Response of 2xx but got " + status);
+        applicationUri = response.getLocation();
+        log.debug("Built app: application");
+        waitForApplicationToBeRunning(applicationUri);
+        
+        entity = (EntityInternal) Iterables.find(getManagementContext().getEntityManager().getEntities(), EntityPredicates.displayNameEqualTo("simple-ent"));
+    }
+
+    @Test
+    public void testList() throws Exception {
+        List<EntityConfigSummary> entityConfigSummaries = client().resource(
+                URI.create("/v1/applications/simple-app/entities/simple-ent/config"))
+                .get(new GenericType<List<EntityConfigSummary>>() {
+                });
+        
+        // Default entities have over a dozen config entries, but it's unnecessary to test them all; just pick one
+        // representative config key
+        Optional<EntityConfigSummary> configKeyOptional = Iterables.tryFind(entityConfigSummaries, new Predicate<EntityConfigSummary>() {
+            @Override
+            public boolean apply(@Nullable EntityConfigSummary input) {
+                return input != null && "install.version".equals(input.getName());
+            }
+        });
+        assertTrue(configKeyOptional.isPresent());
+        
+        assertEquals(configKeyOptional.get().getType(), "java.lang.String");
+        assertEquals(configKeyOptional.get().getDescription(), "Suggested version");
+        assertFalse(configKeyOptional.get().isReconfigurable());
+        assertNull(configKeyOptional.get().getDefaultValue());
+        assertNull(configKeyOptional.get().getLabel());
+        assertNull(configKeyOptional.get().getPriority());
+    }
+
+    @Test
+    public void testBatchConfigRead() throws Exception {
+        Map<String, Object> currentState = client().resource(
+                URI.create("/v1/applications/simple-app/entities/simple-ent/config/current-state"))
+                .get(new GenericType<Map<String, Object>>() {
+                });
+        assertTrue(currentState.containsKey("install.version"));
+        assertEquals(currentState.get("install.version"), "1.0.0");
+    }
+
+    @Test
+    public void testGetJson() throws Exception {
+        String configValue = client().resource(
+                URI.create("/v1/applications/simple-app/entities/simple-ent/config/install.version"))
+                .accept(MediaType.APPLICATION_JSON_TYPE)
+                .get(String.class);
+        assertEquals(configValue, "\"1.0.0\"");
+    }
+
+    @Test
+    public void testGetPlain() throws Exception {
+        String configValue = client().resource(
+                URI.create("/v1/applications/simple-app/entities/simple-ent/config/install.version"))
+                .accept(MediaType.TEXT_PLAIN_TYPE)
+                .get(String.class);
+        assertEquals(configValue, "1.0.0");
+    }
+
+    @Test
+    public void testSet() throws Exception {
+        try {
+            String uri = "/v1/applications/simple-app/entities/simple-ent/config/"+
+                RestMockSimpleEntity.SAMPLE_CONFIG.getName();
+            ClientResponse response = client().resource(uri)
+                .type(MediaType.APPLICATION_JSON_TYPE)
+                .post(ClientResponse.class, "\"hello world\"");
+            assertEquals(response.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
+
+            assertEquals(entity.getConfig(RestMockSimpleEntity.SAMPLE_CONFIG), "hello world");
+            
+            String value = client().resource(uri).accept(MediaType.APPLICATION_JSON_TYPE).get(String.class);
+            assertEquals(value, "\"hello world\"");
+
+        } finally { entity.config().set(RestMockSimpleEntity.SAMPLE_CONFIG, RestMockSimpleEntity.SAMPLE_CONFIG.getDefaultValue()); }
+    }
+
+    @Test
+    public void testSetFromMap() throws Exception {
+        try {
+            String uri = "/v1/applications/simple-app/entities/simple-ent/config";
+            ClientResponse response = client().resource(uri)
+                .type(MediaType.APPLICATION_JSON_TYPE)
+                .post(ClientResponse.class, MutableMap.of(
+                    RestMockSimpleEntity.SAMPLE_CONFIG.getName(), "hello world"));
+            assertEquals(response.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
+
+            assertEquals(entity.getConfig(RestMockSimpleEntity.SAMPLE_CONFIG), "hello world");
+            
+            String value = client().resource(uri+"/"+RestMockSimpleEntity.SAMPLE_CONFIG.getName()).accept(MediaType.APPLICATION_JSON_TYPE).get(String.class);
+            assertEquals(value, "\"hello world\"");
+
+        } finally { entity.config().set(RestMockSimpleEntity.SAMPLE_CONFIG, RestMockSimpleEntity.SAMPLE_CONFIG.getDefaultValue()); }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/EntityResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/EntityResourceTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/EntityResourceTest.java
new file mode 100644
index 0000000..1c091da
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/EntityResourceTest.java
@@ -0,0 +1,189 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nullable;
+import javax.ws.rs.core.MediaType;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.entity.EntityInternal;
+import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.apache.brooklyn.entity.stock.BasicApplication;
+import org.apache.brooklyn.rest.domain.ApplicationSpec;
+import org.apache.brooklyn.rest.domain.EntitySpec;
+import org.apache.brooklyn.rest.domain.TaskSummary;
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+import org.apache.brooklyn.rest.testing.mocks.RestMockSimpleEntity;
+import org.apache.brooklyn.util.collections.MutableList;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.http.HttpAsserts;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.sun.jersey.api.client.ClientResponse;
+
+@Test(singleThreaded = true)
+public class EntityResourceTest extends BrooklynRestResourceTest {
+
+    private static final Logger log = LoggerFactory.getLogger(EntityResourceTest.class);
+    
+    private final ApplicationSpec simpleSpec = ApplicationSpec.builder()
+            .name("simple-app")
+            .entities(ImmutableSet.of(new EntitySpec("simple-ent", RestMockSimpleEntity.class.getName())))
+            .locations(ImmutableSet.of("localhost"))
+            .build();
+
+    private EntityInternal entity;
+
+    private static final String entityEndpoint = "/v1/applications/simple-app/entities/simple-ent";
+
+    @BeforeClass(alwaysRun = true)
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        // Deploy application
+        ClientResponse deploy = clientDeploy(simpleSpec);
+        waitForApplicationToBeRunning(deploy.getLocation());
+
+        // Add tag
+        entity = (EntityInternal) Iterables.find(getManagementContext().getEntityManager().getEntities(), new Predicate<Entity>() {
+            @Override
+            public boolean apply(@Nullable Entity input) {
+                return "RestMockSimpleEntity".equals(input.getEntityType().getSimpleName());
+            }
+        });
+    }
+
+    @Test
+    public void testTagsSanity() throws Exception {
+        entity.tags().addTag("foo");
+        
+        ClientResponse response = client().resource(entityEndpoint + "/tags")
+                .accept(MediaType.APPLICATION_JSON_TYPE)
+                .get(ClientResponse.class);
+        String data = response.getEntity(String.class);
+        
+        try {
+            List<Object> tags = new ObjectMapper().readValue(data, new TypeReference<List<Object>>() {});
+            Assert.assertTrue(tags.contains("foo"));
+            Assert.assertFalse(tags.contains("bar"));
+        } catch (Exception e) {
+            Exceptions.propagateIfFatal(e);
+            throw new IllegalStateException("Error with deserialization of tags list: "+e+"\n"+data, e);
+        }
+    }
+    
+    @Test
+    public void testRename() throws Exception {
+        try {
+            ClientResponse response = client().resource(entityEndpoint + "/name")
+                .queryParam("name", "New Name")
+                .post(ClientResponse.class);
+
+            HttpAsserts.assertHealthyStatusCode(response.getStatus());
+            Assert.assertTrue(entity.getDisplayName().equals("New Name"));
+        } finally {
+            // restore it for other tests!
+            entity.setDisplayName("simple-ent");
+        }
+    }
+    
+    @Test
+    public void testAddChild() throws Exception {
+        try {
+            // to test in GUI: 
+            // services: [ { type: org.apache.brooklyn.entity.stock.BasicEntity }]
+            ClientResponse response = client().resource(entityEndpoint + "/children?timeout=10s")
+                .entity("services: [ { type: "+TestEntity.class.getName()+" }]", "application/yaml")
+                .post(ClientResponse.class);
+
+            HttpAsserts.assertHealthyStatusCode(response.getStatus());
+            Assert.assertEquals(entity.getChildren().size(), 1);
+            Entity child = Iterables.getOnlyElement(entity.getChildren());
+            Assert.assertTrue(Entities.isManaged(child));
+            
+            TaskSummary task = response.getEntity(TaskSummary.class);
+            Assert.assertEquals(task.getResult(), MutableList.of(child.getId()));
+            
+        } finally {
+            // restore it for other tests
+            Collection<Entity> children = entity.getChildren();
+            if (!children.isEmpty()) Entities.unmanage(Iterables.getOnlyElement(children));
+        }
+    }
+    
+    @Test
+    public void testTagsDoNotSerializeTooMuch() throws Exception {
+        entity.tags().addTag("foo");
+        entity.tags().addTag(entity.getParent());
+
+        ClientResponse response = client().resource(entityEndpoint + "/tags")
+                .accept(MediaType.APPLICATION_JSON)
+                .get(ClientResponse.class);
+        String raw = response.getEntity(String.class);
+        log.info("TAGS raw: "+raw);
+        HttpAsserts.assertHealthyStatusCode(response.getStatus());
+        
+        Assert.assertTrue(raw.contains(entity.getParent().getId()), "unexpected app tag, does not include ID: "+raw);
+        
+        Assert.assertTrue(raw.length() < 1000, "unexpected app tag, includes too much mgmt info (len "+raw.length()+"): "+raw);
+        
+        Assert.assertFalse(raw.contains(entity.getManagementContext().getManagementNodeId()), "unexpected app tag, includes too much mgmt info: "+raw);
+        Assert.assertFalse(raw.contains("managementContext"), "unexpected app tag, includes too much mgmt info: "+raw);
+        Assert.assertFalse(raw.contains("localhost"), "unexpected app tag, includes too much mgmt info: "+raw);
+        Assert.assertFalse(raw.contains("catalog"), "unexpected app tag, includes too much mgmt info: "+raw);
+
+        @SuppressWarnings("unchecked")
+        List<Object> tags = mapper().readValue(raw, List.class);
+        log.info("TAGS are: "+tags);
+        
+        Assert.assertEquals(tags.size(), 2, "tags are: "+tags);
+
+        Assert.assertTrue(tags.contains("foo"));
+        Assert.assertFalse(tags.contains("bar"));
+        
+        MutableList<Object> appTags = MutableList.copyOf(tags);
+        appTags.remove("foo");
+        Object appTag = Iterables.getOnlyElement( appTags );
+        
+        // it's a map at this point, because there was no way to make it something stronger than Object
+        Assert.assertTrue(appTag instanceof Map, "Should have deserialized an entity: "+appTag);
+        // let's re-serialize it as an entity
+        appTag = mapper().readValue(mapper().writeValueAsString(appTag), Entity.class);
+        
+        Assert.assertTrue(appTag instanceof Entity, "Should have deserialized an entity: "+appTag);
+        Assert.assertEquals( ((Entity)appTag).getId(), entity.getApplicationId(), "Wrong ID: "+appTag);
+        Assert.assertTrue(appTag instanceof BasicApplication, "Should have deserialized BasicApplication: "+appTag);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ErrorResponseTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ErrorResponseTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ErrorResponseTest.java
new file mode 100644
index 0000000..0979875
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ErrorResponseTest.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response.Status;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import org.apache.brooklyn.rest.domain.ApiError;
+import org.apache.brooklyn.rest.domain.ApplicationSpec;
+import org.apache.brooklyn.rest.domain.EntitySpec;
+import org.apache.brooklyn.rest.domain.PolicySummary;
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+import org.apache.brooklyn.rest.testing.mocks.RestMockSimpleEntity;
+import org.apache.brooklyn.rest.testing.mocks.RestMockSimplePolicy;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import com.sun.jersey.api.client.ClientResponse;
+
+public class ErrorResponseTest extends BrooklynRestResourceTest {
+
+    private final ApplicationSpec simpleSpec = ApplicationSpec.builder().name("simple-app").entities(
+            ImmutableSet.of(new EntitySpec("simple-ent", RestMockSimpleEntity.class.getName()))).locations(
+            ImmutableSet.of("localhost")).build();
+    private String policyId;
+
+    @BeforeClass(alwaysRun = true)
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        ClientResponse aResponse = clientDeploy(simpleSpec);
+        waitForApplicationToBeRunning(aResponse.getLocation());
+
+        String policiesEndpoint = "/v1/applications/simple-app/entities/simple-ent/policies";
+
+        ClientResponse pResponse = client().resource(policiesEndpoint)
+                .queryParam("type", RestMockSimplePolicy.class.getCanonicalName())
+                .type(MediaType.APPLICATION_JSON_TYPE)
+                .post(ClientResponse.class, Maps.newHashMap());
+        PolicySummary response = pResponse.getEntity(PolicySummary.class);
+        assertNotNull(response.getId());
+        policyId = response.getId();
+    }
+
+    @Test
+    public void testResponseToBadRequest() {
+        String resource = "/v1/applications/simple-app/entities/simple-ent/policies/"+policyId+"/config/"
+                + RestMockSimplePolicy.INTEGER_CONFIG.getName() + "/set";
+
+        ClientResponse response = client().resource(resource)
+                .queryParam("value", "notanumber")
+                .post(ClientResponse.class);
+
+        assertEquals(response.getStatus(), Status.BAD_REQUEST.getStatusCode());
+        assertEquals(response.getHeaders().getFirst("Content-Type"), MediaType.APPLICATION_JSON);
+
+        ApiError error = response.getEntity(ApiError.class);
+        assertTrue(error.getMessage().toLowerCase().contains("cannot coerce"));
+    }
+
+    @Test
+    public void testResponseToWrongMethod() {
+        String resource = "/v1/applications/simple-app/entities/simple-ent/policies/"+policyId+"/config/"
+                + RestMockSimplePolicy.INTEGER_CONFIG.getName() + "/set";
+
+        // Should be POST, not GET
+        ClientResponse response = client().resource(resource)
+                .queryParam("value", "4")
+                .get(ClientResponse.class);
+
+        assertEquals(response.getStatus(), 405);
+        // Can we assert anything about the content type?
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/LocationResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/LocationResourceTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/LocationResourceTest.java
new file mode 100644
index 0000000..fe78b83
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/LocationResourceTest.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.resources;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.net.URI;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.google.api.client.repackaged.com.google.common.base.Joiner;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.sun.jersey.api.client.ClientResponse;
+import com.sun.jersey.api.client.GenericType;
+
+import org.apache.brooklyn.api.location.LocationSpec;
+import org.apache.brooklyn.core.location.SimulatedLocation;
+import org.apache.brooklyn.location.jclouds.JcloudsLocation;
+import org.apache.brooklyn.rest.domain.CatalogLocationSummary;
+import org.apache.brooklyn.rest.domain.LocationSummary;
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+import org.apache.brooklyn.test.Asserts;
+
+@Test(singleThreaded = true)
+public class LocationResourceTest extends BrooklynRestResourceTest {
+
+    private static final Logger log = LoggerFactory.getLogger(LocationResourceTest.class);
+    private String legacyLocationName = "my-jungle-legacy";
+    private String legacyLocationVersion = "0.0.0.SNAPSHOT";
+    
+    private String locationName = "my-jungle";
+    private String locationVersion = "0.1.2";
+    
+    @Test
+    @Deprecated
+    public void testAddLegacyLocationDefinition() {
+        Map<String, String> expectedConfig = ImmutableMap.of(
+                "identity", "bob",
+                "credential", "CR3dential");
+        ClientResponse response = client().resource("/v1/locations")
+                .type(MediaType.APPLICATION_JSON_TYPE)
+                .post(ClientResponse.class, new org.apache.brooklyn.rest.domain.LocationSpec(legacyLocationName, "aws-ec2:us-east-1", expectedConfig));
+
+        URI addedLegacyLocationUri = response.getLocation();
+        log.info("added legacy, at: " + addedLegacyLocationUri);
+        LocationSummary location = client().resource(response.getLocation()).get(LocationSummary.class);
+        log.info(" contents: " + location);
+        assertEquals(location.getSpec(), "brooklyn.catalog:"+legacyLocationName+":"+legacyLocationVersion);
+        assertTrue(addedLegacyLocationUri.getPath().startsWith("/v1/locations/"));
+
+        JcloudsLocation l = (JcloudsLocation) getManagementContext().getLocationRegistry().resolve(legacyLocationName);
+        Assert.assertEquals(l.getProvider(), "aws-ec2");
+        Assert.assertEquals(l.getRegion(), "us-east-1");
+        Assert.assertEquals(l.getIdentity(), "bob");
+        Assert.assertEquals(l.getCredential(), "CR3dential");
+    }
+
+    @SuppressWarnings("deprecation")
+    @Test
+    public void testAddNewLocationDefinition() {
+        String yaml = Joiner.on("\n").join(ImmutableList.of(
+                "brooklyn.catalog:",
+                "  symbolicName: "+locationName,
+                "  version: " + locationVersion,
+                "",
+                "brooklyn.locations:",
+                "- type: "+"aws-ec2:us-east-1",
+                "  brooklyn.config:",
+                "    identity: bob",
+                "    credential: CR3dential"));
+
+        
+        ClientResponse response = client().resource("/v1/catalog")
+                .post(ClientResponse.class, yaml);
+
+        assertEquals(response.getStatus(), Response.Status.CREATED.getStatusCode());
+        
+
+        URI addedCatalogItemUri = response.getLocation();
+        log.info("added, at: " + addedCatalogItemUri);
+        
+        // Ensure location definition exists
+        CatalogLocationSummary locationItem = client().resource("/v1/catalog/locations/"+locationName + "/" + locationVersion)
+                .get(CatalogLocationSummary.class);
+        log.info(" item: " + locationItem);
+        LocationSummary locationSummary = client().resource(URI.create("/v1/locations/"+locationName+"/")).get(LocationSummary.class);
+        log.info(" summary: " + locationSummary);
+        Assert.assertEquals(locationSummary.getSpec(), "brooklyn.catalog:"+locationName+":"+locationVersion);
+
+        // Ensure location is usable - can instantiate, and has right config
+        JcloudsLocation l = (JcloudsLocation) getManagementContext().getLocationRegistry().resolve(locationName);
+        Assert.assertEquals(l.getProvider(), "aws-ec2");
+        Assert.assertEquals(l.getRegion(), "us-east-1");
+        Assert.assertEquals(l.getIdentity(), "bob");
+        Assert.assertEquals(l.getCredential(), "CR3dential");
+    }
+
+    @SuppressWarnings("deprecation")
+    @Test(dependsOnMethods = { "testAddNewLocationDefinition" })
+    public void testListAllLocationDefinitions() {
+        Set<LocationSummary> locations = client().resource("/v1/locations")
+                .get(new GenericType<Set<LocationSummary>>() {});
+        Iterable<LocationSummary> matching = Iterables.filter(locations, new Predicate<LocationSummary>() {
+            @Override
+            public boolean apply(@Nullable LocationSummary l) {
+                return locationName.equals(l.getName());
+            }
+        });
+        LocationSummary location = Iterables.getOnlyElement(matching);
+        
+        Assert.assertEquals(location.getSpec(), "brooklyn.catalog:"+locationName+":"+locationVersion);
+        Assert.assertEquals(location.getLinks().get("self").getPath(), "/v1/locations/"+locationName);
+    }
+
+    @SuppressWarnings("deprecation")
+    @Test(dependsOnMethods = { "testListAllLocationDefinitions" })
+    public void testGetSpecificLocation() {
+        URI expectedLocationUri = URI.create("/v1/locations/"+locationName);
+        LocationSummary location = client().resource(expectedLocationUri).get(LocationSummary.class);
+        assertEquals(location.getSpec(), "brooklyn.catalog:"+locationName+":"+locationVersion);
+    }
+
+    @SuppressWarnings("deprecation")
+    @Test
+    public void testGetLocationConfig() {
+        SimulatedLocation parentLoc = (SimulatedLocation) getManagementContext().getLocationManager().createLocation(LocationSpec.create(SimulatedLocation.class)
+                .configure("myParentKey", "myParentVal"));
+        SimulatedLocation loc = (SimulatedLocation) getManagementContext().getLocationManager().createLocation(LocationSpec.create(SimulatedLocation.class)
+                .parent(parentLoc)
+                .configure("mykey", "myval")
+                .configure("password", "mypassword"));
+    
+        // "full" means including-inherited, filtered to exclude secrets
+        URI uriFull = URI.create("/v1/locations/"+loc.getId()+"?full=true");
+        LocationSummary summaryFull = client().resource(uriFull).get(LocationSummary.class);
+        assertEquals(summaryFull.getConfig(), ImmutableMap.of("mykey", "myval", "myParentKey", "myParentVal"), "conf="+summaryFull.getConfig());
+        
+        // Default is local-only, filtered to exclude secrets
+        URI uriDefault = URI.create("/v1/locations/"+loc.getId());
+        LocationSummary summaryDefault = client().resource(uriDefault).get(LocationSummary.class);
+        assertEquals(summaryDefault.getConfig(), ImmutableMap.of("mykey", "myval"), "conf="+summaryDefault.getConfig());
+    }
+
+    @Test(dependsOnMethods = { "testAddLegacyLocationDefinition" })
+    @Deprecated
+    public void testDeleteLocation() {
+        final int size = getLocationRegistry().getDefinedLocations().size();
+        URI expectedLocationUri = URI.create("/v1/locations/"+legacyLocationName);
+
+        ClientResponse response = client().resource(expectedLocationUri).delete(ClientResponse.class);
+        assertEquals(response.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
+        Asserts.succeedsEventually(new Runnable() {
+            @Override
+            public void run() {
+                assertEquals(getLocationRegistry().getDefinedLocations().size(), size - 1);
+            }
+        });
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/PolicyResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/PolicyResourceTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/PolicyResourceTest.java
new file mode 100644
index 0000000..22dc86d
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/PolicyResourceTest.java
@@ -0,0 +1,145 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.fail;
+
+import java.util.Map;
+import java.util.Set;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import org.apache.brooklyn.rest.domain.ApplicationSpec;
+import org.apache.brooklyn.rest.domain.EntitySpec;
+import org.apache.brooklyn.rest.domain.PolicyConfigSummary;
+import org.apache.brooklyn.rest.domain.PolicySummary;
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+import org.apache.brooklyn.rest.testing.mocks.RestMockSimpleEntity;
+import org.apache.brooklyn.rest.testing.mocks.RestMockSimplePolicy;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.sun.jersey.api.client.ClientResponse;
+import com.sun.jersey.api.client.GenericType;
+
+@Test(singleThreaded = true)
+public class PolicyResourceTest extends BrooklynRestResourceTest {
+
+    @SuppressWarnings("unused")
+    private static final Logger log = LoggerFactory.getLogger(PolicyResourceTest.class);
+
+    private static final String ENDPOINT = "/v1/applications/simple-app/entities/simple-ent/policies/";
+
+    private final ApplicationSpec simpleSpec = ApplicationSpec.builder().name("simple-app").entities(
+            ImmutableSet.of(new EntitySpec("simple-ent", RestMockSimpleEntity.class.getName()))).locations(
+            ImmutableSet.of("localhost")).build();
+
+    private String policyId;
+
+    @BeforeClass(alwaysRun = true)
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        ClientResponse aResponse = clientDeploy(simpleSpec);
+        waitForApplicationToBeRunning(aResponse.getLocation());
+
+        ClientResponse pResponse = client().resource(ENDPOINT)
+                .queryParam("type", RestMockSimplePolicy.class.getCanonicalName())
+                .type(MediaType.APPLICATION_JSON_TYPE)
+                .post(ClientResponse.class, Maps.newHashMap());
+
+        PolicySummary response = pResponse.getEntity(PolicySummary.class);
+        assertNotNull(response.getId());
+        policyId = response.getId();
+
+    }
+
+    @Test
+    public void testListConfig() throws Exception {
+        Set<PolicyConfigSummary> config = client().resource(ENDPOINT + policyId + "/config")
+                .get(new GenericType<Set<PolicyConfigSummary>>() {});
+        
+        Set<String> configNames = Sets.newLinkedHashSet();
+        for (PolicyConfigSummary conf : config) {
+            configNames.add(conf.getName());
+        }
+
+        assertEquals(configNames, ImmutableSet.of(
+                RestMockSimplePolicy.SAMPLE_CONFIG.getName(),
+                RestMockSimplePolicy.INTEGER_CONFIG.getName()));
+    }
+
+    @Test
+    public void testGetNonExistantConfigReturns404() throws Exception {
+        String invalidConfigName = "doesnotexist";
+        try {
+            PolicyConfigSummary summary = client().resource(ENDPOINT + policyId + "/config/" + invalidConfigName)
+                    .get(PolicyConfigSummary.class);
+            fail("Should have thrown 404, but got "+summary);
+        } catch (Exception e) {
+            if (!e.toString().contains("404")) throw e;
+        }
+    }
+
+    @Test
+    public void testGetDefaultValue() throws Exception {
+        String configName = RestMockSimplePolicy.SAMPLE_CONFIG.getName();
+        String expectedVal = RestMockSimplePolicy.SAMPLE_CONFIG.getDefaultValue();
+        
+        String configVal = client().resource(ENDPOINT + policyId + "/config/" + configName)
+                .get(String.class);
+        assertEquals(configVal, expectedVal);
+    }
+    
+    @Test(dependsOnMethods = "testGetDefaultValue")
+    public void testReconfigureConfig() throws Exception {
+        String configName = RestMockSimplePolicy.SAMPLE_CONFIG.getName();
+        
+        ClientResponse response = client().resource(ENDPOINT + policyId + "/config/" + configName + "/set")
+                .queryParam("value", "newval")
+                .post(ClientResponse.class);
+
+        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
+    }
+    
+    @Test(dependsOnMethods = "testReconfigureConfig")
+    public void testGetConfigValue() throws Exception {
+        String configName = RestMockSimplePolicy.SAMPLE_CONFIG.getName();
+        String expectedVal = "newval";
+        
+        Map<String, Object> allState = client().resource(ENDPOINT + policyId + "/config/current-state")
+                .get(new GenericType<Map<String, Object>>() {});
+        assertEquals(allState, ImmutableMap.of(configName, expectedVal));
+        
+        String configVal = client().resource(ENDPOINT + policyId + "/config/" + configName)
+                .get(String.class);
+        assertEquals(configVal, expectedVal);
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ScriptResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ScriptResourceTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ScriptResourceTest.java
new file mode 100644
index 0000000..08b9aa4
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ScriptResourceTest.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import java.util.Collections;
+
+import org.apache.brooklyn.api.entity.Application;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
+import org.apache.brooklyn.rest.domain.ScriptExecutionSummary;
+import org.apache.brooklyn.rest.testing.mocks.RestMockApp;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class ScriptResourceTest {
+
+    @Test
+    public void testGroovy() {
+        ManagementContext mgmt = LocalManagementContextForTests.newInstance();
+        Application app = mgmt.getEntityManager().createEntity( EntitySpec.create(Application.class, RestMockApp.class) );
+        try {
+        
+            Entities.start(app, Collections.<Location>emptyList());
+
+            ScriptResource s = new ScriptResource();
+            s.setManagementContext(mgmt);
+
+            ScriptExecutionSummary result = s.groovy(null, "def apps = []; mgmt.applications.each { println 'app:'+it; apps << it.id }; apps");
+            Assert.assertEquals(Collections.singletonList(app.getId()).toString(), result.getResult());
+            Assert.assertTrue(result.getStdout().contains("app:RestMockApp"));
+        
+        } finally { Entities.destroyAll(mgmt); }
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/SensorResourceIntegrationTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/SensorResourceIntegrationTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/SensorResourceIntegrationTest.java
new file mode 100644
index 0000000..f90b677
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/SensorResourceIntegrationTest.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import java.net.URI;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.core.entity.EntityInternal;
+import org.apache.brooklyn.core.entity.EntityPredicates;
+import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
+import org.apache.brooklyn.entity.stock.BasicApplication;
+import org.apache.brooklyn.rest.BrooklynRestApiLauncher;
+import org.apache.brooklyn.rest.BrooklynRestApiLauncherTestFixture;
+import org.apache.brooklyn.rest.testing.mocks.RestMockSimpleEntity;
+import org.apache.brooklyn.test.HttpTestUtils;
+import org.apache.brooklyn.util.collections.MutableList;
+import org.apache.brooklyn.util.http.HttpTool;
+import org.apache.brooklyn.util.http.HttpToolResponse;
+import org.apache.brooklyn.util.net.Urls;
+import org.apache.http.client.HttpClient;
+import org.eclipse.jetty.server.Server;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+
+public class SensorResourceIntegrationTest extends BrooklynRestApiLauncherTestFixture {
+
+    private Server server;
+    private ManagementContext mgmt;
+    private BasicApplication app;
+
+    @BeforeClass(alwaysRun = true)
+    protected void setUp() {
+        mgmt = LocalManagementContextForTests.newInstance();
+        server = useServerForTest(BrooklynRestApiLauncher.launcher()
+            .managementContext(mgmt)
+            .withoutJsgui()
+            .start());
+        app = mgmt.getEntityManager().createEntity(EntitySpec.create(BasicApplication.class).displayName("simple-app")
+            .child(EntitySpec.create(Entity.class, RestMockSimpleEntity.class).displayName("simple-ent")));
+        mgmt.getEntityManager().manage(app);
+        app.start(MutableList.of(mgmt.getLocationRegistry().resolve("localhost")));
+    }
+    
+    // marked integration because of time
+    @Test(groups = "Integration")
+    public void testSensorBytes() throws Exception {
+        EntityInternal entity = (EntityInternal) Iterables.find(mgmt.getEntityManager().getEntities(), EntityPredicates.displayNameEqualTo("simple-ent"));
+        SensorResourceTest.addAmphibianSensor(entity);
+        
+        String baseUri = getBaseUri(server);
+        URI url = URI.create(Urls.mergePaths(baseUri, SensorResourceTest.SENSORS_ENDPOINT, SensorResourceTest.SENSOR_NAME));
+        
+        // Uses explicit "application/json" because failed on jenkins as though "text/plain" was the default on Ubuntu jenkins! 
+        HttpClient client = HttpTool.httpClientBuilder().uri(baseUri).build();
+        HttpToolResponse response = HttpTool.httpGet(client, url, ImmutableMap.<String, String>of("Accept", "application/json"));
+        HttpTestUtils.assertHealthyStatusCode(response.getResponseCode());
+        Assert.assertEquals(response.getContentAsString(), "\"12345 frogs\"");
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/SensorResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/SensorResourceTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/SensorResourceTest.java
new file mode 100644
index 0000000..4d2f781
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/SensorResourceTest.java
@@ -0,0 +1,271 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import static org.testng.Assert.assertEquals;
+
+import java.util.Map;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.core.config.render.RendererHints;
+import org.apache.brooklyn.core.entity.EntityInternal;
+import org.apache.brooklyn.core.entity.EntityPredicates;
+import org.apache.brooklyn.core.sensor.Sensors;
+import org.apache.brooklyn.rest.api.SensorApi;
+import org.apache.brooklyn.rest.domain.ApplicationSpec;
+import org.apache.brooklyn.rest.domain.EntitySpec;
+import org.apache.brooklyn.rest.test.config.render.TestRendererHints;
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+import org.apache.brooklyn.rest.testing.mocks.RestMockSimpleEntity;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.http.HttpAsserts;
+import org.apache.brooklyn.util.stream.Streams;
+import org.apache.brooklyn.util.text.StringFunctions;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Functions;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.sun.jersey.api.client.ClientResponse;
+import com.sun.jersey.api.client.GenericType;
+import com.sun.jersey.api.client.WebResource;
+import com.sun.jersey.api.client.WebResource.Builder;
+
+/**
+ * Test the {@link SensorApi} implementation.
+ * <p>
+ * Check that {@link SensorResource} correctly renders {@link AttributeSensor}
+ * values, including {@link RendererHints.DisplayValue} hints.
+ */
+@Test(singleThreaded = true)
+public class SensorResourceTest extends BrooklynRestResourceTest {
+
+    final static ApplicationSpec SIMPLE_SPEC = ApplicationSpec.builder()
+            .name("simple-app")
+            .entities(ImmutableSet.of(new EntitySpec("simple-ent", RestMockSimpleEntity.class.getName())))
+            .locations(ImmutableSet.of("localhost"))
+            .build();
+
+    static final String SENSORS_ENDPOINT = "/v1/applications/simple-app/entities/simple-ent/sensors";
+    static final String SENSOR_NAME = "amphibian.count";
+    static final AttributeSensor<Integer> SENSOR = Sensors.newIntegerSensor(SENSOR_NAME);
+
+    EntityInternal entity;
+
+    /**
+     * Sets up the application and entity.
+     * <p>
+     * Adds a sensor and sets its value to {@code 12345}. Configures a display value
+     * hint that appends {@code frogs} to the value of the sensor.
+     */
+    @BeforeClass(alwaysRun = true)
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        // Deploy application
+        ClientResponse deploy = clientDeploy(SIMPLE_SPEC);
+        waitForApplicationToBeRunning(deploy.getLocation());
+
+        entity = (EntityInternal) Iterables.find(getManagementContext().getEntityManager().getEntities(), EntityPredicates.displayNameEqualTo("simple-ent"));
+        addAmphibianSensor(entity);
+    }
+
+    static void addAmphibianSensor(EntityInternal entity) {
+        // Add new sensor
+        entity.getMutableEntityType().addSensor(SENSOR);
+        entity.sensors().set(SENSOR, 12345);
+
+        // Register display value hint
+        RendererHints.register(SENSOR, RendererHints.displayValue(Functions.compose(StringFunctions.append(" frogs"), Functions.toStringFunction())));
+    }
+
+    @AfterClass(alwaysRun = true)
+    @Override
+    public void tearDown() throws Exception {
+        TestRendererHints.clearRegistry();
+        super.tearDown();
+    }
+
+    /** Check default is to use display value hint. */
+    @Test
+    public void testBatchSensorRead() throws Exception {
+        ClientResponse response = client().resource(SENSORS_ENDPOINT + "/current-state")
+                .accept(MediaType.APPLICATION_JSON)
+                .get(ClientResponse.class);
+        Map<String, ?> currentState = response.getEntity(new GenericType<Map<String,?>>(Map.class) {});
+
+        for (String sensor : currentState.keySet()) {
+            if (sensor.equals(SENSOR_NAME)) {
+                assertEquals(currentState.get(sensor), "12345 frogs");
+            }
+        }
+    }
+
+    /** Check setting {@code raw} to {@code true} ignores display value hint. */
+    @Test(dependsOnMethods = "testBatchSensorRead")
+    public void testBatchSensorReadRaw() throws Exception {
+        ClientResponse response = client().resource(SENSORS_ENDPOINT + "/current-state")
+                .queryParam("raw", "true")
+                .accept(MediaType.APPLICATION_JSON)
+                .get(ClientResponse.class);
+        Map<String, ?> currentState = response.getEntity(new GenericType<Map<String,?>>(Map.class) {});
+
+        for (String sensor : currentState.keySet()) {
+            if (sensor.equals(SENSOR_NAME)) {
+                assertEquals(currentState.get(sensor), Integer.valueOf(12345));
+            }
+        }
+    }
+
+    protected ClientResponse doSensorTest(Boolean raw, MediaType acceptsType, Object expectedValue) {
+        return doSensorTestUntyped(
+            raw==null ? null : (""+raw).toLowerCase(), 
+            acceptsType==null ? null : new String[] { acceptsType.getType() }, 
+            expectedValue);
+    }
+    protected ClientResponse doSensorTestUntyped(String raw, String[] acceptsTypes, Object expectedValue) {
+        WebResource req = client().resource(SENSORS_ENDPOINT + "/" + SENSOR_NAME);
+        if (raw!=null) req = req.queryParam("raw", raw);
+        ClientResponse response;
+        if (acceptsTypes!=null) {
+            Builder rb = req.accept(acceptsTypes);
+            response = rb.get(ClientResponse.class);
+        } else {
+            response = req.get(ClientResponse.class);
+        }
+        if (expectedValue!=null) {
+            HttpAsserts.assertHealthyStatusCode(response.getStatus());
+            Object value = response.getEntity(expectedValue.getClass());
+            assertEquals(value, expectedValue);
+        }
+        return response;
+    }
+    
+    /**
+     * Check we can get a sensor, explicitly requesting json; gives a string picking up the rendering hint.
+     * 
+     * If no "Accepts" header is given, then we don't control whether json or plain text comes back.
+     * It is dependent on the method order, which is compiler-specific.
+     */
+    @Test
+    public void testGetJson() throws Exception {
+        doSensorTest(null, MediaType.APPLICATION_JSON_TYPE, "\"12345 frogs\"");
+    }
+    
+    @Test
+    public void testGetJsonBytes() throws Exception {
+        ClientResponse response = doSensorTest(null, MediaType.APPLICATION_JSON_TYPE, null);
+        byte[] bytes = Streams.readFully(response.getEntityInputStream());
+        // assert we have one set of surrounding quotes
+        assertEquals(bytes.length, 13);
+    }
+
+    /** Check that plain returns a string without quotes, with the rendering hint */
+    @Test
+    public void testGetPlain() throws Exception {
+        doSensorTest(null, MediaType.TEXT_PLAIN_TYPE, "12345 frogs");
+    }
+
+    /** 
+     * Check that when we set {@code raw = true}, the result ignores the display value hint.
+     *
+     * If no "Accepts" header is given, then we don't control whether json or plain text comes back.
+     * It is dependent on the method order, which is compiler-specific.
+     */
+    @Test
+    public void testGetRawJson() throws Exception {
+        doSensorTest(true, MediaType.APPLICATION_JSON_TYPE, 12345);
+    }
+    
+    /** As {@link #testGetRaw()} but with plain set, returns the number */
+    @Test
+    public void testGetPlainRaw() throws Exception {
+        // have to pass a string because that's how PLAIN is deserialized
+        doSensorTest(true, MediaType.TEXT_PLAIN_TYPE, "12345");
+    }
+
+    /** Check explicitly setting {@code raw} to {@code false} is as before */
+    @Test
+    public void testGetPlainRawFalse() throws Exception {
+        doSensorTest(false, MediaType.TEXT_PLAIN_TYPE, "12345 frogs");
+    }
+
+    /** Check empty vaue for {@code raw} will revert to using default. */
+    @Test
+    public void testGetPlainRawEmpty() throws Exception {
+        doSensorTestUntyped("", new String[] { MediaType.TEXT_PLAIN }, "12345 frogs");
+    }
+
+    /** Check unparseable vaue for {@code raw} will revert to using default. */
+    @Test
+    public void testGetPlainRawError() throws Exception {
+        doSensorTestUntyped("biscuits", new String[] { MediaType.TEXT_PLAIN }, "12345 frogs");
+    }
+    
+    /** Check we can set a value */
+    @Test
+    public void testSet() throws Exception {
+        try {
+            ClientResponse response = client().resource(SENSORS_ENDPOINT + "/" + SENSOR_NAME)
+                .type(MediaType.APPLICATION_JSON_TYPE)
+                .post(ClientResponse.class, 67890);
+            assertEquals(response.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
+
+            assertEquals(entity.getAttribute(SENSOR), (Integer)67890);
+            
+            String value = client().resource(SENSORS_ENDPOINT + "/" + SENSOR_NAME).accept(MediaType.TEXT_PLAIN_TYPE).get(String.class);
+            assertEquals(value, "67890 frogs");
+
+        } finally { addAmphibianSensor(entity); }
+    }
+
+    @Test
+    public void testSetFromMap() throws Exception {
+        try {
+            ClientResponse response = client().resource(SENSORS_ENDPOINT)
+                .type(MediaType.APPLICATION_JSON_TYPE)
+                .post(ClientResponse.class, MutableMap.of(SENSOR_NAME, 67890));
+            assertEquals(response.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
+            
+            assertEquals(entity.getAttribute(SENSOR), (Integer)67890);
+
+        } finally { addAmphibianSensor(entity); }
+    }
+    
+    /** Check we can delete a value */
+    @Test
+    public void testDelete() throws Exception {
+        try {
+            ClientResponse response = client().resource(SENSORS_ENDPOINT + "/" + SENSOR_NAME)
+                .delete(ClientResponse.class);
+            assertEquals(response.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
+
+            String value = client().resource(SENSORS_ENDPOINT + "/" + SENSOR_NAME).accept(MediaType.TEXT_PLAIN_TYPE).get(String.class);
+            assertEquals(value, "");
+
+        } finally { addAmphibianSensor(entity); }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ServerResourceIntegrationTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ServerResourceIntegrationTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ServerResourceIntegrationTest.java
new file mode 100644
index 0000000..ce3fd37
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ServerResourceIntegrationTest.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.resources;
+
+import static org.apache.brooklyn.util.http.HttpTool.httpClientBuilder;
+import static org.testng.Assert.assertEquals;
+
+import java.net.URI;
+import java.util.Collections;
+import java.util.Map;
+
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.core.internal.BrooklynProperties;
+import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
+import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
+import org.apache.brooklyn.rest.BrooklynRestApiLauncher;
+import org.apache.brooklyn.rest.BrooklynRestApiLauncherTestFixture;
+import org.apache.brooklyn.rest.security.provider.TestSecurityProvider;
+import org.apache.brooklyn.test.HttpTestUtils;
+import org.apache.brooklyn.util.http.HttpTool;
+import org.apache.brooklyn.util.http.HttpToolResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.HttpClient;
+import org.eclipse.jetty.server.Server;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableMap;
+
+public class ServerResourceIntegrationTest extends BrooklynRestApiLauncherTestFixture {
+
+    /**
+     * [sam] Other tests rely on brooklyn.properties not containing security properties so ..
+     * I think the best way to test this is to set a security provider, then reload properties
+     * and check no authentication is required.
+     * 
+     * [aled] Changing this test so doesn't rely on brooklyn.properties having no security
+     * provider (that can lead to failures locally when running just this test). Asserts 
+     */
+    @Test(groups = "Integration")
+    public void testSecurityProviderUpdatesWhenPropertiesReloaded() {
+        BrooklynProperties brooklynProperties = BrooklynProperties.Factory.newEmpty();
+        brooklynProperties.put("brooklyn.webconsole.security.users", "admin");
+        brooklynProperties.put("brooklyn.webconsole.security.user.admin.password", "mypassword");
+        UsernamePasswordCredentials defaultCredential = new UsernamePasswordCredentials("admin", "mypassword");
+
+        ManagementContext mgmt = new LocalManagementContext(brooklynProperties);
+        
+        try {
+            Server server = useServerForTest(BrooklynRestApiLauncher.launcher()
+                    .managementContext(mgmt)
+                    .withoutJsgui()
+                    .securityProvider(TestSecurityProvider.class)
+                    .start());
+            String baseUri = getBaseUri(server);
+    
+            HttpToolResponse response;
+            final URI uri = URI.create(getBaseUri() + "/v1/server/properties/reload");
+            final Map<String, String> args = Collections.emptyMap();
+    
+            // Unauthorised when no credentials, and when default credentials.
+            response = HttpTool.httpPost(httpClientBuilder().uri(baseUri).build(), uri, args, args);
+            assertEquals(response.getResponseCode(), HttpStatus.SC_UNAUTHORIZED);
+    
+            response = HttpTool.httpPost(httpClientBuilder().uri(baseUri).credentials(defaultCredential).build(), 
+                    uri, args, args);
+            assertEquals(response.getResponseCode(), HttpStatus.SC_UNAUTHORIZED);
+
+            // Accepts TestSecurityProvider credentials, and we reload.
+            response = HttpTool.httpPost(httpClientBuilder().uri(baseUri).credentials(TestSecurityProvider.CREDENTIAL).build(),
+                    uri, args, args);
+            HttpTestUtils.assertHealthyStatusCode(response.getResponseCode());
+    
+            // Has no gone back to credentials from brooklynProperties; TestSecurityProvider credentials no longer work
+            response = HttpTool.httpPost(httpClientBuilder().uri(baseUri).credentials(defaultCredential).build(), 
+                    uri, args, args);
+            HttpTestUtils.assertHealthyStatusCode(response.getResponseCode());
+            
+            response = HttpTool.httpPost(httpClientBuilder().uri(baseUri).credentials(TestSecurityProvider.CREDENTIAL).build(), 
+                    uri, args, args);
+            assertEquals(response.getResponseCode(), HttpStatus.SC_UNAUTHORIZED);
+    
+        } finally {
+            ((ManagementContextInternal)mgmt).terminate();
+        }
+    }
+
+    @Test(groups = "Integration")
+    public void testGetUser() throws Exception {
+        Server server = useServerForTest(BrooklynRestApiLauncher.launcher()
+                .securityProvider(TestSecurityProvider.class)
+                .withoutJsgui()
+                .start());
+        assertEquals(getServerUser(server), TestSecurityProvider.USER);
+    }
+
+    private String getServerUser(Server server) throws Exception {
+        HttpClient client = httpClientBuilder()
+                .uri(getBaseUri(server))
+                .credentials(TestSecurityProvider.CREDENTIAL)
+                .build();
+        
+        HttpToolResponse response = HttpTool.httpGet(client, URI.create(getBaseUri(server) + "/v1/server/user"),
+                ImmutableMap.<String, String>of());
+        HttpTestUtils.assertHealthyStatusCode(response.getResponseCode());
+        return response.getContentAsString();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ServerResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ServerResourceTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ServerResourceTest.java
new file mode 100644
index 0000000..f84cb80
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ServerResourceTest.java
@@ -0,0 +1,168 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.brooklyn.api.entity.ImplementedBy;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.core.BrooklynVersion;
+import org.apache.brooklyn.core.internal.BrooklynProperties;
+import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
+import org.apache.brooklyn.entity.software.base.EmptySoftwareProcess;
+import org.apache.brooklyn.entity.software.base.EmptySoftwareProcessDriver;
+import org.apache.brooklyn.entity.software.base.EmptySoftwareProcessImpl;
+import org.apache.brooklyn.rest.domain.HighAvailabilitySummary;
+import org.apache.brooklyn.rest.domain.VersionSummary;
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+import org.apache.brooklyn.test.Asserts;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableSet;
+import com.sun.jersey.api.client.UniformInterfaceException;
+
+@Test(singleThreaded = true)
+public class ServerResourceTest extends BrooklynRestResourceTest {
+
+    private static final Logger log = LoggerFactory.getLogger(ServerResourceTest.class);
+    
+    @Test
+    public void testGetVersion() throws Exception {
+        VersionSummary version = client().resource("/v1/server/version").get(VersionSummary.class);
+        assertEquals(version.getVersion(), BrooklynVersion.get());
+    }
+
+    @Test
+    public void testGetStatus() throws Exception {
+        String status = client().resource("/v1/server/status").get(String.class);
+        assertEquals(status, "MASTER");
+    }
+
+    @Test
+    public void testGetHighAvailability() throws Exception {
+        // Note by default management context from super is started without HA enabled.
+        // Therefore can only assert a minimal amount of stuff.
+        HighAvailabilitySummary summary = client().resource("/v1/server/highAvailability").get(HighAvailabilitySummary.class);
+        log.info("HA summary is: "+summary);
+        
+        String ownNodeId = getManagementContext().getManagementNodeId();
+        assertEquals(summary.getOwnId(), ownNodeId);
+        assertEquals(summary.getMasterId(), ownNodeId);
+        assertEquals(summary.getNodes().keySet(), ImmutableSet.of(ownNodeId));
+        assertEquals(summary.getNodes().get(ownNodeId).getNodeId(), ownNodeId);
+        assertEquals(summary.getNodes().get(ownNodeId).getStatus(), "MASTER");
+        assertNotNull(summary.getNodes().get(ownNodeId).getLocalTimestamp());
+        // remote will also be non-null if there is no remote backend (local is re-used)
+        assertNotNull(summary.getNodes().get(ownNodeId).getRemoteTimestamp());
+        assertEquals(summary.getNodes().get(ownNodeId).getLocalTimestamp(), summary.getNodes().get(ownNodeId).getRemoteTimestamp());
+    }
+
+    @SuppressWarnings("serial")
+    @Test
+    public void testReloadsBrooklynProperties() throws Exception {
+        final AtomicInteger reloadCount = new AtomicInteger();
+        getManagementContext().addPropertiesReloadListener(new ManagementContext.PropertiesReloadListener() {
+            @Override public void reloaded() {
+                reloadCount.incrementAndGet();
+            }});
+        client().resource("/v1/server/properties/reload").post();
+        assertEquals(reloadCount.get(), 1);
+    }
+
+    @Test
+    void testGetConfig() throws Exception {
+        ((ManagementContextInternal)getManagementContext()).getBrooklynProperties().put("foo.bar.baz", "quux");
+        try {
+            assertEquals(client().resource("/v1/server/config/foo.bar.baz").get(String.class), "quux");
+        } finally {
+            ((ManagementContextInternal)getManagementContext()).getBrooklynProperties().remove("foo.bar.baz");
+        }
+    }
+
+    @Test
+    void testGetMissingConfigThrowsException() throws Exception {
+        final String key = "foo.bar.baz";
+        BrooklynProperties properties = ((ManagementContextInternal)getManagementContext()).getBrooklynProperties();
+        Object existingValue = null;
+        boolean keyAlreadyPresent = false;
+        String response = null;
+        if (properties.containsKey(key)) {
+            existingValue = properties.remove(key);
+            keyAlreadyPresent = true;
+        }
+        try {
+            response = client().resource("/v1/server/config/" + key).get(String.class);
+            Asserts.fail("Expected call to /v1/server/config/" + key + " to fail with status 404, instead server returned " + response);
+        } catch (UniformInterfaceException e) {
+            assertEquals(e.getResponse().getStatus(), 204);
+        } finally {
+            if (keyAlreadyPresent) {
+                properties.put(key, existingValue);
+            }
+        }
+    }
+
+    // Alternatively could reuse a blocking location, see org.apache.brooklyn.entity.software.base.SoftwareProcessEntityTest.ReleaseLatchLocation
+    @ImplementedBy(StopLatchEntityImpl.class)
+    public interface StopLatchEntity extends EmptySoftwareProcess {
+        public void unblock();
+        public boolean isBlocked();
+    }
+
+    public static class StopLatchEntityImpl extends EmptySoftwareProcessImpl implements StopLatchEntity {
+        private CountDownLatch lock = new CountDownLatch(1);
+        private volatile boolean isBlocked;
+
+        @Override
+        public void unblock() {
+            lock.countDown();
+        }
+
+        @Override
+        protected void postStop() {
+            super.preStop();
+            try {
+                isBlocked = true;
+                lock.await();
+                isBlocked = false;
+            } catch (InterruptedException e) {
+                throw Exceptions.propagate(e);
+            }
+        }
+
+        @Override
+        public Class<?> getDriverInterface() {
+            return EmptySoftwareProcessDriver.class;
+        }
+
+        @Override
+        public boolean isBlocked() {
+            return isBlocked;
+        }
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ServerShutdownTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ServerShutdownTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ServerShutdownTest.java
new file mode 100644
index 0000000..dbe9afd
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ServerShutdownTest.java
@@ -0,0 +1,185 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.ws.rs.core.MultivaluedMap;
+
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.mgmt.EntityManager;
+import org.apache.brooklyn.api.mgmt.Task;
+import org.apache.brooklyn.core.entity.Attributes;
+import org.apache.brooklyn.core.entity.EntityAsserts;
+import org.apache.brooklyn.core.entity.drivers.BasicEntityDriverManager;
+import org.apache.brooklyn.core.entity.drivers.ReflectiveEntityDriverFactory;
+import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
+import org.apache.brooklyn.core.entity.trait.Startable;
+import org.apache.brooklyn.core.test.entity.TestApplication;
+import org.apache.brooklyn.rest.resources.ServerResourceTest.StopLatchEntity;
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+import org.apache.brooklyn.test.Asserts;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.sun.jersey.core.util.MultivaluedMapImpl;
+
+public class ServerShutdownTest extends BrooklynRestResourceTest {
+    private static final Logger log = LoggerFactory.getLogger(ServerResourceTest.class);
+
+    // Need to initialise the ManagementContext before each test as it is destroyed.
+    @Override
+    @BeforeClass(alwaysRun = true)
+    public void setUp() throws Exception {
+    }
+
+    @Override
+    @AfterClass(alwaysRun = true)
+    public void tearDown() throws Exception {
+    }
+
+    @Override
+    @BeforeMethod(alwaysRun = true)
+    public void setUpMethod() {
+        setUpJersey();
+        super.setUpMethod();
+    }
+
+    @AfterMethod(alwaysRun = true)
+    public void tearDownMethod() {
+        tearDownJersey();
+        destroyManagementContext();
+    }
+
+    @Test
+    public void testShutdown() throws Exception {
+        assertTrue(getManagementContext().isRunning());
+        assertFalse(shutdownListener.isRequested());
+
+        MultivaluedMap<String, String> formData = new MultivaluedMapImpl();
+        formData.add("requestTimeout", "0");
+        formData.add("delayForHttpReturn", "0");
+        client().resource("/v1/server/shutdown").entity(formData).post();
+
+        Asserts.succeedsEventually(new Runnable() {
+            @Override
+            public void run() {
+                assertTrue(shutdownListener.isRequested());
+            }
+        });
+        Asserts.succeedsEventually(new Runnable() {
+            @Override public void run() {
+                assertFalse(getManagementContext().isRunning());
+            }});
+    }
+
+    @Test
+    public void testStopAppThenShutdownAndStopAppsWaitsForFirstStop() throws InterruptedException {
+        ReflectiveEntityDriverFactory f = ((BasicEntityDriverManager)getManagementContext().getEntityDriverManager()).getReflectiveDriverFactory();
+        f.addClassFullNameMapping("org.apache.brooklyn.entity.software.base.EmptySoftwareProcessDriver", "org.apache.brooklyn.rest.resources.ServerResourceTest$EmptySoftwareProcessTestDriver");
+
+        // Second stop on SoftwareProcess could return early, while the first stop is still in progress
+        // This causes the app to shutdown prematurely, leaking machines.
+        EntityManager emgr = getManagementContext().getEntityManager();
+        EntitySpec<TestApplication> appSpec = EntitySpec.create(TestApplication.class);
+        TestApplication app = emgr.createEntity(appSpec);
+        emgr.manage(app);
+        EntitySpec<StopLatchEntity> latchEntitySpec = EntitySpec.create(StopLatchEntity.class);
+        final StopLatchEntity entity = app.createAndManageChild(latchEntitySpec);
+        app.start(ImmutableSet.of(app.newLocalhostProvisioningLocation()));
+        EntityAsserts.assertAttributeEquals(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+
+        try {
+            final Task<Void> firstStop = app.invoke(Startable.STOP, ImmutableMap.<String, Object>of());
+            Asserts.succeedsEventually(new Runnable() {
+                @Override
+                public void run() {
+                    assertTrue(entity.isBlocked());
+                }
+            });
+
+            final AtomicReference<Exception> shutdownError = new AtomicReference<>();
+            // Can't use ExecutionContext as it will be stopped on shutdown
+            Thread shutdownThread = new Thread() {
+                @Override
+                public void run() {
+                    try {
+                        MultivaluedMap<String, String> formData = new MultivaluedMapImpl();
+                        formData.add("stopAppsFirst", "true");
+                        formData.add("shutdownTimeout", "0");
+                        formData.add("requestTimeout", "0");
+                        formData.add("delayForHttpReturn", "0");
+                        client().resource("/v1/server/shutdown").entity(formData).post();
+                    } catch (Exception e) {
+                        log.error("Shutdown request error", e);
+                        shutdownError.set(e);
+                        throw Exceptions.propagate(e);
+                    }
+                }
+            };
+            shutdownThread.start();
+
+            //shutdown must wait until the first stop completes (or time out)
+            Asserts.succeedsContinually(new Runnable() {
+                @Override
+                public void run() {
+                    assertFalse(firstStop.isDone());
+                    assertEquals(getManagementContext().getApplications().size(), 1);
+                    assertFalse(shutdownListener.isRequested());
+                }
+            });
+
+            // NOTE test is not fully deterministic. Depending on thread scheduling this will
+            // execute before or after ServerResource.shutdown does the app stop loop. This
+            // means that the shutdown code might not see the app at all. In any case though
+            // the test must succeed.
+            entity.unblock();
+
+            Asserts.succeedsEventually(new Runnable() {
+                @Override
+                public void run() {
+                    assertTrue(firstStop.isDone());
+                    assertTrue(shutdownListener.isRequested());
+                    assertFalse(getManagementContext().isRunning());
+                }
+            });
+
+            shutdownThread.join();
+            assertNull(shutdownError.get(), "Shutdown request error, logged above");
+        } finally {
+            // Be sure we always unblock entity stop even in the case of an exception.
+            // In the success path the entity is already unblocked above.
+            entity.unblock();
+        }
+    }
+
+}


[25/34] brooklyn-server git commit: REST API optional Jersey compatibility

Posted by he...@apache.org.
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/HaMasterCheckFilterTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/HaMasterCheckFilterTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/HaMasterCheckFilterTest.java
new file mode 100644
index 0000000..424c0c1
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/HaMasterCheckFilterTest.java
@@ -0,0 +1,218 @@
+/*
+ * 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;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.File;
+import java.net.URI;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeoutException;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.mgmt.EntityManager;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.api.mgmt.ha.HighAvailabilityMode;
+import org.apache.brooklyn.api.mgmt.ha.ManagementNodeState;
+import org.apache.brooklyn.camp.brooklyn.BrooklynCampPlatformLauncherNoServer;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.mgmt.rebind.RebindTestUtils;
+import org.apache.brooklyn.entity.stock.BasicApplication;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.HttpClient;
+import org.eclipse.jetty.server.Server;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.Test;
+import org.apache.brooklyn.rest.security.provider.AnyoneSecurityProvider;
+import org.apache.brooklyn.test.Asserts;
+import org.apache.brooklyn.util.http.HttpTool;
+import org.apache.brooklyn.util.http.HttpToolResponse;
+import org.apache.brooklyn.util.os.Os;
+import org.apache.brooklyn.util.time.Duration;
+
+import com.google.common.base.Predicates;
+import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableMap;
+
+public class HaMasterCheckFilterTest extends BrooklynRestApiLauncherTestFixture {
+    private static final Duration TIMEOUT = Duration.THIRTY_SECONDS;
+
+    private File mementoDir;
+    private ManagementContext writeMgmt;
+    private ManagementContext readMgmt;
+    private String appId;
+    private Server server;
+    private HttpClient client;
+
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws Exception {
+System.err.println("TEAR DOWN");
+        server.stop();
+        Entities.destroyAll(writeMgmt);
+        Entities.destroyAll(readMgmt);
+        Os.deleteRecursively(mementoDir);
+    }
+
+    @Test(groups = "Integration")
+    public void testEntitiesExistOnDisabledHA() throws Exception {
+        initHaCluster(HighAvailabilityMode.DISABLED, HighAvailabilityMode.DISABLED);
+        assertReadIsMaster();
+        assertEntityExists(new ReturnCodeCheck());
+    }
+
+    @Test(groups = "Integration")
+    public void testEntitiesExistOnMasterPromotion() throws Exception {
+        initHaCluster(HighAvailabilityMode.AUTO, HighAvailabilityMode.AUTO);
+        stopWriteNode();
+        assertEntityExists(new ReturnCodeCheck());
+        assertReadIsMaster();
+    }
+
+    @Test(groups = "Integration")
+    public void testEntitiesExistOnHotStandbyAndPromotion() throws Exception {
+        initHaCluster(HighAvailabilityMode.AUTO, HighAvailabilityMode.HOT_STANDBY);
+        assertEntityExists(new ReturnCodeCheck());
+        stopWriteNode();
+        assertEntityExists(new ReturnCodeAndNodeState());
+        assertReadIsMaster();
+    }
+
+    @Test(groups = "Integration")
+    public void testEntitiesExistOnHotBackup() throws Exception {
+        initHaCluster(HighAvailabilityMode.AUTO, HighAvailabilityMode.HOT_BACKUP);
+        Asserts.continually(
+                ImmutableMap.<String,Object>of(
+                        "timeout", Duration.THIRTY_SECONDS,
+                        "period", Duration.ZERO),
+                new ReturnCodeSupplier(),
+                Predicates.or(Predicates.equalTo(200), Predicates.equalTo(403)));
+    }
+
+    private HttpClient getClient(Server server) {
+        HttpClient client = HttpTool.httpClientBuilder()
+                .uri(getBaseUri(server))
+                .build();
+        return client;
+    }
+
+    private int getAppResponseCode() {
+        HttpToolResponse response = HttpTool.httpGet(
+                client, URI.create(getBaseUri(server) + "/v1/applications/" + appId),
+                ImmutableMap.<String,String>of());
+        return response.getResponseCode();
+    }
+
+    private String createApp(ManagementContext mgmt) {
+        EntityManager entityMgr = mgmt.getEntityManager();
+        Entity app = entityMgr.createEntity(EntitySpec.create(BasicApplication.class));
+        entityMgr.manage(app);
+        return app.getId();
+    }
+
+    private ManagementContext createManagementContext(File mementoDir, HighAvailabilityMode mode) {
+        ManagementContext mgmt = RebindTestUtils.managementContextBuilder(mementoDir, getClass().getClassLoader())
+                .persistPeriodMillis(1)
+                .forLive(false)
+                .emptyCatalog(true)
+                .buildUnstarted();
+
+        if (mode == HighAvailabilityMode.DISABLED) {
+            mgmt.getHighAvailabilityManager().disabled();
+        } else {
+            mgmt.getHighAvailabilityManager().start(mode);
+        }
+
+        new BrooklynCampPlatformLauncherNoServer()
+            .useManagementContext(mgmt)
+            .launch();
+
+        return mgmt;
+    }
+
+    private void initHaCluster(HighAvailabilityMode writeMode, HighAvailabilityMode readMode) throws InterruptedException, TimeoutException {
+        mementoDir = Os.newTempDir(getClass());
+
+        writeMgmt = createManagementContext(mementoDir, writeMode);
+        appId = createApp(writeMgmt);
+        writeMgmt.getRebindManager().waitForPendingComplete(TIMEOUT, true);
+
+        if (readMode == HighAvailabilityMode.DISABLED) {
+            //no HA, one node only
+            readMgmt = writeMgmt;
+        } else {
+            readMgmt = createManagementContext(mementoDir, readMode);
+        }
+
+        server = useServerForTest(BrooklynRestApiLauncher.launcher()
+                .managementContext(readMgmt)
+                .securityProvider(AnyoneSecurityProvider.class)
+                .forceUseOfDefaultCatalogWithJavaClassPath(true)
+                .withoutJsgui()
+                .disableHighAvailability(false)
+                .start());
+        client = getClient(server);
+    }
+
+    private void assertEntityExists(Callable<Integer> c) {
+        assertEquals((int)Asserts.succeedsEventually(c), 200);
+    }
+
+    private void assertReadIsMaster() {
+        assertEquals(readMgmt.getHighAvailabilityManager().getNodeState(), ManagementNodeState.MASTER);
+    }
+
+    private void stopWriteNode() {
+        writeMgmt.getHighAvailabilityManager().stop();
+    }
+
+    private class ReturnCodeCheck implements Callable<Integer> {
+        @Override
+        public Integer call() {
+            int retCode = getAppResponseCode();
+            if (retCode == 403) {
+                throw new RuntimeException("Not ready, retry. Response - " + retCode);
+            } else {
+                return retCode;
+            }
+        }
+    }
+
+    private class ReturnCodeAndNodeState extends ReturnCodeCheck {
+        @Override
+        public Integer call() {
+            Integer ret = super.call();
+            if (ret == HttpStatus.SC_OK) {
+                ManagementNodeState state = readMgmt.getHighAvailabilityManager().getNodeState();
+                if (state != ManagementNodeState.MASTER) {
+                    throw new RuntimeException("Not master yet " + state);
+                }
+            }
+            return ret;
+        }
+    }
+
+    private class ReturnCodeSupplier implements Supplier<Integer> {
+        @Override
+        public Integer get() {
+            return getAppResponseCode();
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/domain/AbstractDomainTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/domain/AbstractDomainTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/domain/AbstractDomainTest.java
new file mode 100644
index 0000000..dc1131d
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/domain/AbstractDomainTest.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.domain;
+
+import java.io.IOException;
+
+import org.testng.annotations.Test;
+
+import static org.apache.brooklyn.rest.util.RestApiTestUtils.asJson;
+import static org.apache.brooklyn.rest.util.RestApiTestUtils.fromJson;
+import static org.apache.brooklyn.rest.util.RestApiTestUtils.jsonFixture;
+import static org.testng.Assert.assertEquals;
+
+public abstract class AbstractDomainTest {
+
+    protected abstract String getPath();
+    protected abstract Object getDomainObject();
+
+    @Test
+    public void testSerializeToJSON() throws IOException {
+        assertEquals(asJson(getDomainObject()), jsonFixture(getPath()));
+    }
+
+    @Test
+    public void testDeserializeFromJSON() throws IOException {
+        assertEquals(fromJson(jsonFixture(getPath()), getDomainObject().getClass()), getDomainObject());
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/domain/ApiErrorTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/domain/ApiErrorTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/domain/ApiErrorTest.java
new file mode 100644
index 0000000..f73bd30
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/domain/ApiErrorTest.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.domain;
+
+import static org.apache.brooklyn.rest.util.RestApiTestUtils.asJson;
+import static org.apache.brooklyn.rest.util.RestApiTestUtils.fromJson;
+import static org.apache.brooklyn.rest.util.RestApiTestUtils.jsonFixture;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+
+import java.io.IOException;
+import java.net.URI;
+
+import org.testng.annotations.Test;
+import org.testng.util.Strings;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+public class ApiErrorTest extends AbstractDomainTest {
+
+    @Override
+    protected String getPath() {
+        return "fixtures/api-error-basic.json";
+    }
+
+    @Override
+    protected Object getDomainObject() {
+        return ApiError.builder()
+                .message("explanatory message")
+                .details("accompanying details")
+                .build();
+    }
+
+    @Test
+    public void testSerializeApiErrorFromThrowable() throws IOException {
+        Exception e = new Exception("error");
+        e.setStackTrace(Thread.currentThread().getStackTrace());
+
+        ApiError error = ApiError.builderFromThrowable(e).build();
+        ApiError deserialised = fromJson(asJson(error), ApiError.class);
+
+        assertFalse(Strings.isNullOrEmpty(deserialised.getDetails()), "Expected details to contain exception stack trace");
+        assertEquals(deserialised, error);
+    }
+
+    @Test
+    public void testSerializeApiErrorWithoutDetails() throws IOException {
+        ApiError error = ApiError.builder()
+                .message("explanatory message")
+                .build();
+        assertEquals(asJson(error), jsonFixture("fixtures/api-error-no-details.json"));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/domain/ApplicationSpecTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/domain/ApplicationSpecTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/domain/ApplicationSpecTest.java
new file mode 100644
index 0000000..b2690d6
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/domain/ApplicationSpecTest.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.domain;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+public class ApplicationSpecTest extends AbstractDomainTest {
+
+    @Override
+    protected String getPath() {
+        return "fixtures/application-spec.json";
+    }
+
+    @Override
+    protected Object getDomainObject() {
+        EntitySpec entitySpec = new EntitySpec("Vanilla Java App", "org.apache.brooklyn.entity.java.VanillaJavaApp",
+                ImmutableMap.of("initialSize", "1", "creationScriptUrl", "http://my.brooklyn.io/storage/foo.sql"));
+        return ApplicationSpec.builder().name("myapp")
+                .entities(ImmutableSet.of(entitySpec)).locations(ImmutableSet.of("/v1/locations/1"))
+                .build();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/domain/ApplicationTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/domain/ApplicationTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/domain/ApplicationTest.java
new file mode 100644
index 0000000..8708fb1
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/domain/ApplicationTest.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.domain;
+
+import static org.apache.brooklyn.rest.util.RestApiTestUtils.asJson;
+import static org.apache.brooklyn.rest.util.RestApiTestUtils.fromJson;
+import static org.apache.brooklyn.rest.util.RestApiTestUtils.jsonFixture;
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.Map;
+
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
+import org.apache.brooklyn.core.test.entity.TestApplication;
+import org.apache.brooklyn.test.Asserts;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+public class ApplicationTest {
+
+    final EntitySpec entitySpec = new EntitySpec("Vanilla Java App", "org.apache.brooklyn.entity.java.VanillaJavaApp",
+            ImmutableMap.of(
+                    "initialSize", "1",
+                    "creationScriptUrl", "http://my.brooklyn.io/storage/foo.sql"));
+
+    final ApplicationSpec applicationSpec = ApplicationSpec.builder().name("myapp")
+            .entities(ImmutableSet.of(entitySpec))
+            .locations(ImmutableSet.of("/v1/locations/1"))
+            .build();
+
+    final Map<String, URI> links = ImmutableMap.of(
+            "self", URI.create("/v1/applications/" + applicationSpec.getName()),
+            "entities", URI.create("fixtures/entity-summary-list.json"));
+    final ApplicationSummary application = new ApplicationSummary("myapp_id", applicationSpec, Status.STARTING, links);
+
+    @SuppressWarnings("serial")
+    @Test
+    public void testSerializeToJSON() throws IOException {
+        assertEquals(asJson(application), jsonFixture("fixtures/application.json"));
+    }
+
+    @Test
+    public void testDeserializeFromJSON() throws IOException {
+        assertEquals(fromJson(jsonFixture("fixtures/application.json"),
+                ApplicationSummary.class), application);
+    }
+
+    @Test
+    public void testTransitionToRunning() {
+        ApplicationSummary running = application.transitionTo(Status.RUNNING);
+        assertEquals(running.getStatus(), Status.RUNNING);
+    }
+
+    @Test
+    public void testAppInAppTest() throws IOException {
+        ManagementContext mgmt = LocalManagementContextForTests.newInstance();
+        try {
+            TestApplication app = mgmt.getEntityManager().createEntity(org.apache.brooklyn.api.entity.EntitySpec.create(TestApplication.class));
+            app.addChild(org.apache.brooklyn.api.entity.EntitySpec.create(TestApplication.class));
+            Asserts.assertEqualsIgnoringOrder(mgmt.getApplications(), ImmutableList.of(app));
+        } finally {
+            Entities.destroyAll(mgmt);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/domain/EffectorSummaryTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/domain/EffectorSummaryTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/domain/EffectorSummaryTest.java
new file mode 100644
index 0000000..71cb64d
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/domain/EffectorSummaryTest.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.domain;
+
+import java.net.URI;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+public class EffectorSummaryTest extends AbstractDomainTest {
+
+    @Override
+    protected String getPath() {
+        return "fixtures/effector-summary.json";
+    }
+
+    @Override
+    protected Object getDomainObject() {
+        return new EffectorSummary(
+                "stop",
+                "void",
+                ImmutableSet.<EffectorSummary.ParameterSummary<?>>of(),
+                "Effector description",
+                ImmutableMap.of(
+                        "self", URI.create("/v1/applications/redis-app/entities/redis-ent/effectors/stop")));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/domain/EntitySpecTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/domain/EntitySpecTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/domain/EntitySpecTest.java
new file mode 100644
index 0000000..1beb078
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/domain/EntitySpecTest.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.domain;
+
+import static org.apache.brooklyn.rest.util.RestApiTestUtils.fromJson;
+import static org.apache.brooklyn.rest.util.RestApiTestUtils.jsonFixture;
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+
+import org.testng.annotations.Test;
+
+public class EntitySpecTest extends AbstractDomainTest {
+
+    @Override
+    protected String getPath() {
+        return "fixtures/entity.json";
+    }
+
+    @Override
+    protected Object getDomainObject() {
+        EntitySpec entitySpec = new EntitySpec("Vanilla Java App", "org.apache.brooklyn.entity.java.VanillaJavaApp");
+        return new EntitySpec[] { entitySpec };
+    }
+
+    @Test
+    public void testDeserializeFromJSONOnlyWithType() throws IOException {
+        EntitySpec actual = fromJson(jsonFixture("fixtures/entity-only-type.json"), EntitySpec.class);
+        assertEquals(actual.getType(), "org.apache.brooklyn.entity.java.VanillaJavaApp");
+        assertEquals(actual.getConfig().size(), 0);
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/domain/EntitySummaryTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/domain/EntitySummaryTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/domain/EntitySummaryTest.java
new file mode 100644
index 0000000..2d39cc9
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/domain/EntitySummaryTest.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.domain;
+
+import java.net.URI;
+import java.util.Map;
+
+import com.google.common.collect.Maps;
+
+public class EntitySummaryTest extends AbstractDomainTest {
+
+    @Override
+    protected String getPath() {
+        return "fixtures/entity-summary.json";
+    }
+
+    @Override
+    protected Object getDomainObject() {
+        Map<String, URI> links = Maps.newLinkedHashMap();
+        links.put("self", URI.create("/v1/applications/tesr/entities/zQsqdXzi"));
+        links.put("catalog", URI.create("/v1/catalog/entities/org.apache.brooklyn.entity.webapp.tomcat.TomcatServer"));
+        links.put("application", URI.create("/v1/applications/tesr"));
+        links.put("children", URI.create("/v1/applications/tesr/entities/zQsqdXzi/children"));
+        links.put("effectors", URI.create("fixtures/effector-summary-list.json"));
+        links.put("sensors", URI.create("fixtures/sensor-summary-list.json"));
+        links.put("activities", URI.create("fixtures/task-summary-list.json"));
+
+        return new EntitySummary("zQsqdXzi", "MyTomcat",
+                "org.apache.brooklyn.entity.webapp.tomcat.TomcatServer", null, links);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/domain/LocationSpecTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/domain/LocationSpecTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/domain/LocationSpecTest.java
new file mode 100644
index 0000000..0e653c5
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/domain/LocationSpecTest.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.domain;
+
+import static org.apache.brooklyn.rest.util.RestApiTestUtils.asJson;
+import static org.apache.brooklyn.rest.util.RestApiTestUtils.fromJson;
+import static org.apache.brooklyn.rest.util.RestApiTestUtils.jsonFixture;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
+
+import java.io.IOException;
+
+import org.testng.annotations.Test;
+
+@Deprecated
+public class LocationSpecTest {
+
+    // TODO when removing the deprecated class this tests, change the tests here to point at LocationSummary
+    
+    final LocationSpec locationSpec = LocationSpec.localhost();
+
+    @Test
+    public void testSerializeToJSON() throws IOException {
+        assertEquals(asJson(locationSpec), jsonFixture("fixtures/location.json"));
+    }
+
+    @Test
+    public void testDeserializeFromJSON() throws IOException {
+        assertEquals(fromJson(jsonFixture("fixtures/location.json"), LocationSpec.class), locationSpec);
+    }
+
+    @Test
+    public void testDeserializeFromJSONWithNoCredential() throws IOException {
+        LocationSpec loaded = fromJson(jsonFixture("fixtures/location-without-credential.json"), LocationSpec.class);
+
+        assertEquals(loaded.getSpec(), locationSpec.getSpec());
+
+        assertEquals(loaded.getConfig().size(), 1);
+        assertEquals(loaded.getConfig().get("identity"), "bob");
+        assertNull(loaded.getConfig().get("credential"));
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/domain/LocationSummaryTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/domain/LocationSummaryTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/domain/LocationSummaryTest.java
new file mode 100644
index 0000000..6279546
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/domain/LocationSummaryTest.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.domain;
+
+import java.net.URI;
+import java.util.Map;
+
+import com.google.common.collect.Maps;
+
+public class LocationSummaryTest extends AbstractDomainTest {
+
+    @Override
+    protected String getPath() {
+        return "fixtures/location-summary.json";
+    }
+
+    @Override
+    protected Object getDomainObject() {
+        Map<String, URI> links = Maps.newLinkedHashMap();
+        links.put("self", URI.create("/v1/locations/123"));
+
+        return new LocationSummary("123", "localhost", "localhost", null, null, links);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/domain/SensorSummaryTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/domain/SensorSummaryTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/domain/SensorSummaryTest.java
new file mode 100644
index 0000000..106d737
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/domain/SensorSummaryTest.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.domain;
+
+import static org.apache.brooklyn.rest.util.RestApiTestUtils.asJson;
+import static org.apache.brooklyn.rest.util.RestApiTestUtils.fromJson;
+import static org.apache.brooklyn.rest.util.RestApiTestUtils.jsonFixture;
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+import java.net.URI;
+
+import javax.ws.rs.core.UriBuilder;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+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.core.entity.Entities;
+import org.apache.brooklyn.core.sensor.Sensors;
+import org.apache.brooklyn.core.test.entity.TestApplication;
+import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.apache.brooklyn.rest.transform.SensorTransformer;
+
+import com.google.common.collect.ImmutableMap;
+
+public class SensorSummaryTest {
+
+    private SensorSummary sensorSummary = new SensorSummary("redis.uptime", "Integer",
+            "Description", ImmutableMap.of(
+            "self", URI.create("/v1/applications/redis-app/entities/redis-ent/sensors/redis.uptime")));
+
+    private TestApplication app;
+    private TestEntity entity;
+    private ManagementContext mgmt;
+
+    @BeforeMethod(alwaysRun = true)
+    public void setUp() throws Exception {
+        app = TestApplication.Factory.newManagedInstanceForTests();
+        mgmt = app.getManagementContext();
+        entity = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+    }
+
+    @AfterMethod(alwaysRun = true)
+    public void tearDown() throws Exception {
+        if (mgmt != null) Entities.destroyAll(mgmt);
+    }
+
+    @Test
+    public void testSerializeToJSON() throws IOException {
+        assertEquals(asJson(sensorSummary), jsonFixture("fixtures/sensor-summary.json"));
+    }
+
+    @Test
+    public void testDeserializeFromJSON() throws IOException {
+        assertEquals(fromJson(jsonFixture("fixtures/sensor-summary.json"), SensorSummary.class), sensorSummary);
+    }
+
+    @Test
+    public void testEscapesUriForSensorName() throws IOException {
+        Sensor<String> sensor = Sensors.newStringSensor("name with space");
+        SensorSummary summary = SensorTransformer.sensorSummary(entity, sensor, UriBuilder.fromPath("/v1"));
+        URI selfUri = summary.getLinks().get("self");
+
+        String expectedUri = "/v1/applications/" + entity.getApplicationId() + "/entities/" + entity.getId() + "/sensors/" + "name%20with%20space";
+
+        assertEquals(selfUri, URI.create(expectedUri));
+    }
+
+    // Previously failed because immutable-map builder threw exception if put same key multiple times,
+    // and the NamedActionWithUrl did not have equals/hashCode
+    @Test
+    public void testSensorWithMultipleOpenUrlActionsRegistered() throws IOException {
+        AttributeSensor<String> sensor = Sensors.newStringSensor("sensor1");
+        entity.sensors().set(sensor, "http://myval");
+        RendererHints.register(sensor, RendererHints.namedActionWithUrl());
+        RendererHints.register(sensor, RendererHints.namedActionWithUrl());
+
+        SensorSummary summary = SensorTransformer.sensorSummary(entity, sensor, UriBuilder.fromPath("/"));
+
+        assertEquals(summary.getLinks().get("action:open"), URI.create("http://myval"));
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/domain/VersionSummaryTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/domain/VersionSummaryTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/domain/VersionSummaryTest.java
new file mode 100644
index 0000000..7bed51b
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/domain/VersionSummaryTest.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.brooklyn.rest.domain;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+public class VersionSummaryTest  extends AbstractDomainTest {
+
+    @Override
+    protected String getPath() {
+        return "fixtures/server-version.json";
+    }
+
+    @Override
+    protected Object getDomainObject() {
+        BrooklynFeatureSummary features = new BrooklynFeatureSummary(
+                "Sample Brooklyn Project com.acme.sample:brooklyn-sample v0.1.0-SNAPSHOT",
+                "com.acme.sample.brooklyn-sample",
+                "0.1.0.SNAPSHOT",
+                "523305000",
+                ImmutableMap.of("Brooklyn-Feature-Build-Id", "e0fee1adf795c84eec4735f039503eb18d9c35cc")
+        );
+        return new VersionSummary(
+                "0.7.0-SNAPSHOT",
+                "cb4f0a3af2f5042222dd176edc102bfa64e7e0b5",
+                "versions",
+                ImmutableList.of(features)
+        );
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/entitlement/AbstractRestApiEntitlementsTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/entitlement/AbstractRestApiEntitlementsTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/entitlement/AbstractRestApiEntitlementsTest.java
new file mode 100644
index 0000000..73449ea
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/entitlement/AbstractRestApiEntitlementsTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.entitlement;
+
+import static org.apache.brooklyn.util.http.HttpTool.httpClientBuilder;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.net.URI;
+
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.internal.BrooklynProperties;
+import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
+import org.apache.brooklyn.core.mgmt.entitlement.PerUserEntitlementManager;
+import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
+import org.apache.brooklyn.core.test.entity.TestApplication;
+import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.apache.brooklyn.rest.BrooklynRestApiLauncher;
+import org.apache.brooklyn.rest.BrooklynRestApiLauncherTestFixture;
+import org.apache.brooklyn.util.http.HttpAsserts;
+import org.apache.brooklyn.util.http.HttpTool;
+import org.apache.brooklyn.util.http.HttpToolResponse;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.HttpClient;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+
+/**
+ * Sets up the REST api with some standard users, ready for testing entitlements.
+ */
+public abstract class AbstractRestApiEntitlementsTest extends BrooklynRestApiLauncherTestFixture {
+
+    protected ManagementContext mgmt;
+    protected TestApplication app;
+    protected TestEntity entity;
+
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        BrooklynProperties props = BrooklynProperties.Factory.newEmpty();
+        props.put(Entitlements.GLOBAL_ENTITLEMENT_MANAGER.getName(), PerUserEntitlementManager.class.getName());
+        props.put(PerUserEntitlementManager.PER_USER_ENTITLEMENTS_CONFIG_PREFIX+".myRoot", "root");
+        props.put(PerUserEntitlementManager.PER_USER_ENTITLEMENTS_CONFIG_PREFIX+".myReadonly", "readonly");
+        props.put(PerUserEntitlementManager.PER_USER_ENTITLEMENTS_CONFIG_PREFIX+".myMinimal", "minimal");
+        props.put(PerUserEntitlementManager.PER_USER_ENTITLEMENTS_CONFIG_PREFIX+".myCustom", StaticDelegatingEntitlementManager.class.getName());
+        
+        mgmt = LocalManagementContextForTests.builder(true).useProperties(props).build();
+        app = mgmt.getEntityManager().createEntity(EntitySpec.create(TestApplication.class)
+                .child(EntitySpec.create(TestEntity.class))
+                        .configure(TestEntity.CONF_NAME, "myname"));
+        entity = (TestEntity) Iterables.getOnlyElement(app.getChildren());
+        
+        useServerForTest(BrooklynRestApiLauncher.launcher()
+                .managementContext(mgmt)
+                .forceUseOfDefaultCatalogWithJavaClassPath(true)
+                .securityProvider(AuthenticateAnyoneSecurityProvider.class)
+                .start());
+    }
+
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws Exception {
+        if (mgmt != null) Entities.destroyAll(mgmt);
+    }
+    
+    protected HttpClient newClient(String user) throws Exception {
+        return httpClientBuilder()
+                .uri(getBaseUri())
+                .credentials(new UsernamePasswordCredentials(user, "ignoredPassword"))
+                .build();
+    }
+
+    protected String httpGet(String user, String path) throws Exception {
+        HttpToolResponse response = HttpTool.httpGet(newClient(user), URI.create(getBaseUri()).resolve(path), ImmutableMap.<String, String>of());
+        assertTrue(HttpAsserts.isHealthyStatusCode(response.getResponseCode()), "code="+response.getResponseCode()+"; reason="+response.getReasonPhrase());
+        return response.getContentAsString();
+    }
+    
+    protected String assertPermitted(String user, String path) throws Exception {
+        return httpGet(user, path);
+    }
+
+    protected void assertForbidden(String user, String path) throws Exception {
+        HttpToolResponse response = HttpTool.httpGet(newClient(user), URI.create(getBaseUri()).resolve(path), ImmutableMap.<String, String>of());
+        assertEquals(response.getResponseCode(), 403, "code="+response.getResponseCode()+"; reason="+response.getReasonPhrase()+"; content="+response.getContentAsString());
+    }
+
+    protected void assert404(String user, String path) throws Exception {
+        HttpToolResponse response = HttpTool.httpGet(newClient(user), URI.create(getBaseUri()).resolve(path), ImmutableMap.<String, String>of());
+        assertEquals(response.getResponseCode(), 404, "code="+response.getResponseCode()+"; reason="+response.getReasonPhrase()+"; content="+response.getContentAsString());
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/entitlement/ActivityApiEntitlementsTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/entitlement/ActivityApiEntitlementsTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/entitlement/ActivityApiEntitlementsTest.java
new file mode 100644
index 0000000..bb10197
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/entitlement/ActivityApiEntitlementsTest.java
@@ -0,0 +1,123 @@
+/*
+ * 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.entitlement;
+
+import static org.testng.Assert.assertEquals;
+
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.mgmt.Task;
+import org.apache.brooklyn.api.mgmt.entitlement.EntitlementClass;
+import org.apache.brooklyn.api.mgmt.entitlement.EntitlementContext;
+import org.apache.brooklyn.api.mgmt.entitlement.EntitlementManager;
+import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
+import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
+import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
+import org.apache.brooklyn.entity.machine.MachineEntity;
+import org.apache.brooklyn.entity.software.base.AbstractSoftwareProcessStreamsTest;
+import org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation;
+import org.apache.brooklyn.util.core.task.TaskPredicates;
+import org.apache.brooklyn.util.text.StringPredicates;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Maps;
+
+@Test(singleThreaded = true)
+public class ActivityApiEntitlementsTest extends AbstractRestApiEntitlementsTest {
+
+    protected MachineEntity machineEntity;
+    protected Task<?> subTask;
+    protected Map<String, String> streams;
+    
+    @BeforeMethod(alwaysRun=true)
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        
+        LocalhostMachineProvisioningLocation loc = app.newLocalhostProvisioningLocation();
+        machineEntity = app.addChild(EntitySpec.create(MachineEntity.class)
+                .configure(BrooklynConfigKeys.SKIP_ON_BOX_BASE_DIR_RESOLUTION, true)
+                .location(loc));
+        machineEntity.start(ImmutableList.<Location>of());
+        
+        machineEntity.execCommand("echo myval");
+        
+        Set<Task<?>> tasks = BrooklynTaskTags.getTasksInEntityContext(mgmt.getExecutionManager(), machineEntity);
+        String taskNameRegex = "ssh: echo myval";
+        
+        streams = Maps.newLinkedHashMap();
+        subTask = AbstractSoftwareProcessStreamsTest.findTaskOrSubTask(tasks, TaskPredicates.displayNameSatisfies(StringPredicates.matchesRegex(taskNameRegex))).get();
+        for (String streamId : ImmutableList.of("stdin", "stdout", "stderr")) {
+            streams.put(streamId, AbstractSoftwareProcessStreamsTest.getStreamOrFail(subTask, streamId));
+        }
+    }
+    
+    @Test(groups = "Integration")
+    public void testGetTask() throws Exception {
+        String path = "/v1/activities/"+subTask.getId();
+        assertPermitted("myRoot", path);
+        assertPermitted("myReadonly", path);
+        assertForbidden("myMinimal", path);
+        assertForbidden("unrecognisedUser", path);
+    }
+    
+    @Test(groups = "Integration")
+    public void testGetStream() throws Exception {
+        String pathPrefix = "/v1/activities/"+subTask.getId()+"/stream/";
+        for (Map.Entry<String, String> entry : streams.entrySet()) {
+            String streamId = entry.getKey();
+            String expectedStream = entry.getValue();
+
+            assertEquals(httpGet("myRoot", pathPrefix+streamId), expectedStream);
+            assertEquals(httpGet("myReadonly", pathPrefix+streamId), expectedStream);
+            assertForbidden("myMinimal", pathPrefix+streamId);
+            assertForbidden("unrecognisedUser", pathPrefix+streamId);
+            
+            StaticDelegatingEntitlementManager.setDelegate(new SeeSelectiveStreams(streamId));
+            assertEquals(httpGet("myCustom", pathPrefix+streamId), expectedStream);
+            
+            StaticDelegatingEntitlementManager.setDelegate(new SeeSelectiveStreams("differentStreamId"));
+            assertForbidden("myCustom", pathPrefix+streamId);
+        }
+    }
+    
+    public static class SeeSelectiveStreams implements EntitlementManager {
+        private final String regex;
+        
+        public SeeSelectiveStreams(String regex) {
+            this.regex = regex;
+        }
+        @Override 
+        @SuppressWarnings("unchecked")
+        public <T> boolean isEntitled(EntitlementContext context, EntitlementClass<T> entitlementClass, T entitlementClassArgument) {
+            String type = entitlementClass.entitlementClassIdentifier();
+            if (Entitlements.SEE_ACTIVITY_STREAMS.entitlementClassIdentifier().equals(type)) {
+                Entitlements.TaskAndItem<String> pair = (Entitlements.TaskAndItem<String>) entitlementClassArgument;
+                return pair.getItem().matches(regex);
+            } else {
+                return true;
+            }
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/entitlement/AuthenticateAnyoneSecurityProvider.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/entitlement/AuthenticateAnyoneSecurityProvider.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/entitlement/AuthenticateAnyoneSecurityProvider.java
new file mode 100644
index 0000000..b7264b2
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/entitlement/AuthenticateAnyoneSecurityProvider.java
@@ -0,0 +1,41 @@
+/*
+ * 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.entitlement;
+
+import javax.servlet.http.HttpSession;
+
+import org.apache.brooklyn.rest.security.provider.SecurityProvider;
+
+public class AuthenticateAnyoneSecurityProvider implements SecurityProvider {
+
+    @Override
+    public boolean isAuthenticated(HttpSession session) {
+        return false;
+    }
+
+    @Override
+    public boolean authenticate(HttpSession session, String user, String password) {
+        return user != null;
+    }
+
+    @Override
+    public boolean logout(HttpSession session) {
+        return false;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/entitlement/EntityConfigApiEntitlementsTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/entitlement/EntityConfigApiEntitlementsTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/entitlement/EntityConfigApiEntitlementsTest.java
new file mode 100644
index 0000000..cbda515
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/entitlement/EntityConfigApiEntitlementsTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.entitlement;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.mgmt.entitlement.EntitlementClass;
+import org.apache.brooklyn.api.mgmt.entitlement.EntitlementContext;
+import org.apache.brooklyn.api.mgmt.entitlement.EntitlementManager;
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.core.config.render.RendererHints;
+import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
+import org.apache.brooklyn.core.mgmt.entitlement.Entitlements.EntityAndItem;
+import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.apache.brooklyn.rest.api.SensorApi;
+import org.apache.brooklyn.rest.resources.SensorResource;
+import org.apache.brooklyn.test.Asserts;
+import org.testng.annotations.Test;
+
+/**
+ * Test the {@link SensorApi} implementation.
+ * <p>
+ * Check that {@link SensorResource} correctly renders {@link AttributeSensor}
+ * values, including {@link RendererHints.DisplayValue} hints.
+ */
+@Test(singleThreaded = true)
+public class EntityConfigApiEntitlementsTest extends AbstractRestApiEntitlementsTest {
+
+    @Test(groups = "Integration")
+    public void testGet() throws Exception {
+        String path = "/v1/applications/"+app.getId()+"/entities/"+entity.getId()+"/config/"+TestEntity.CONF_NAME.getName();
+        String val = "\"myname\"";
+        
+        assertEquals(httpGet("myRoot", path), val);
+        assertEquals(httpGet("myReadonly", path), val);
+        assert404("myMinimal", path); // can't see app, to retrieve entity
+        assert404("unrecognisedUser", path);
+
+        StaticDelegatingEntitlementManager.setDelegate(new SeeSelectiveConfig(entity, TestEntity.CONF_NAME.getName()));
+        assertEquals(httpGet("myCustom", path), val);
+        
+        StaticDelegatingEntitlementManager.setDelegate(new SeeSelectiveConfig(entity, "differentConfName"));
+        assertForbidden("myCustom", path);
+    }
+    
+    @Test(groups = "Integration")
+    public void testCurrentState() throws Exception {
+        String path = "/v1/applications/"+app.getId()+"/entities/"+entity.getId()+"/config/current-state";
+        String confName = TestEntity.CONF_NAME.getName();
+        String regex = ".*"+confName+".*myname.*";
+        
+        Asserts.assertStringMatchesRegex(httpGet("myRoot", path), regex);
+        Asserts.assertStringMatchesRegex(httpGet("myReadonly", path), regex);
+        assert404("myMinimal", path); // can't see app, to retrieve entity
+        assert404("unrecognisedUser", path);
+
+        StaticDelegatingEntitlementManager.setDelegate(new SeeSelectiveConfig(entity, confName));
+        Asserts.assertStringMatchesRegex(httpGet("myCustom", path), regex);
+        
+        StaticDelegatingEntitlementManager.setDelegate(new SeeSelectiveConfig(entity, "differentConfName"));
+        String resp = httpGet("myCustom", path);
+        assertFalse(resp.matches(regex), "resp="+resp);
+    }
+    
+    public static class SeeSelectiveConfig implements EntitlementManager {
+        private final Entity entity;
+        private final String regex;
+        
+        public SeeSelectiveConfig(Entity entity, String regex) {
+            this.entity = entity;
+            this.regex = regex;
+        }
+        @Override 
+        @SuppressWarnings("unchecked")
+        public <T> boolean isEntitled(EntitlementContext context, EntitlementClass<T> entitlementClass, T entitlementClassArgument) {
+            String type = entitlementClass.entitlementClassIdentifier();
+            if (Entitlements.SEE_CONFIG.entitlementClassIdentifier().equals(type)) {
+                EntityAndItem<String> entityAndItem = (EntityAndItem<String>) entitlementClassArgument;
+                return entity.equals(entityAndItem.getEntity()) && entityAndItem.getItem().matches(regex);
+            } else {
+                return true;
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/entitlement/SensorApiEntitlementsTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/entitlement/SensorApiEntitlementsTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/entitlement/SensorApiEntitlementsTest.java
new file mode 100644
index 0000000..dab72ec
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/entitlement/SensorApiEntitlementsTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.entitlement;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.mgmt.entitlement.EntitlementClass;
+import org.apache.brooklyn.api.mgmt.entitlement.EntitlementContext;
+import org.apache.brooklyn.api.mgmt.entitlement.EntitlementManager;
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.core.config.render.RendererHints;
+import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
+import org.apache.brooklyn.core.mgmt.entitlement.Entitlements.EntityAndItem;
+import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.apache.brooklyn.rest.api.SensorApi;
+import org.apache.brooklyn.rest.resources.SensorResource;
+import org.apache.brooklyn.test.Asserts;
+import org.testng.annotations.Test;
+
+/**
+ * Test the {@link SensorApi} implementation.
+ * <p>
+ * Check that {@link SensorResource} correctly renders {@link AttributeSensor}
+ * values, including {@link RendererHints.DisplayValue} hints.
+ */
+@Test(singleThreaded = true)
+public class SensorApiEntitlementsTest extends AbstractRestApiEntitlementsTest {
+
+    @Test(groups = "Integration")
+    public void testGet() throws Exception {
+        entity.sensors().set(TestEntity.NAME, "myval");
+        
+        String sensorName = TestEntity.NAME.getName();
+        String path = "/v1/applications/"+app.getId()+"/entities/"+entity.getId()+"/sensors/"+sensorName;
+        String val = "\"myval\"";
+        
+        assertEquals(httpGet("myRoot", path), val);
+        assertEquals(httpGet("myReadonly", path), val);
+        assert404("myMinimal", path); // can't see app, to retrieve entity
+        assert404("unrecognisedUser", path);
+
+        StaticDelegatingEntitlementManager.setDelegate(new SeeSelectiveSensor(entity, sensorName));
+        assertEquals(httpGet("myCustom", path), val);
+        
+        StaticDelegatingEntitlementManager.setDelegate(new SeeSelectiveSensor(entity, "differentConfName"));
+        assertForbidden("myCustom", path);
+    }
+    
+    @Test(groups = "Integration")
+    public void testCurrentState() throws Exception {
+        entity.sensors().set(TestEntity.NAME, "myval");
+        
+        String path = "/v1/applications/"+app.getId()+"/entities/"+entity.getId()+"/sensors/current-state";
+        String sensorName = TestEntity.NAME.getName();
+        String regex = ".*"+sensorName+".*myval.*";
+        
+        Asserts.assertStringMatchesRegex(httpGet("myRoot", path), regex);
+        Asserts.assertStringMatchesRegex(httpGet("myReadonly", path), regex);
+        assert404("myMinimal", path); // can't see app, to retrieve entity
+        assert404("unrecognisedUser", path);
+
+        StaticDelegatingEntitlementManager.setDelegate(new SeeSelectiveSensor(entity, sensorName));
+        Asserts.assertStringMatchesRegex(httpGet("myCustom", path), regex);
+        
+        StaticDelegatingEntitlementManager.setDelegate(new SeeSelectiveSensor(entity, "differentSensorName"));
+        String resp = httpGet("myCustom", path);
+        assertFalse(resp.matches(regex), "resp="+resp);
+    }
+    
+    public static class SeeSelectiveSensor implements EntitlementManager {
+        private final Entity entity;
+        private final String regex;
+        
+        public SeeSelectiveSensor(Entity entity, String regex) {
+            this.entity = entity;
+            this.regex = regex;
+        }
+        @Override 
+        @SuppressWarnings("unchecked")
+        public <T> boolean isEntitled(EntitlementContext context, EntitlementClass<T> entitlementClass, T entitlementClassArgument) {
+            String type = entitlementClass.entitlementClassIdentifier();
+            if (Entitlements.SEE_SENSOR.entitlementClassIdentifier().equals(type)) {
+                EntityAndItem<String> entityAndItem = (EntityAndItem<String>) entitlementClassArgument;
+                return entity.equals(entityAndItem.getEntity()) && entityAndItem.getItem().matches(regex);
+            } else {
+                return true;
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/entitlement/ServerApiEntitlementsTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/entitlement/ServerApiEntitlementsTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/entitlement/ServerApiEntitlementsTest.java
new file mode 100644
index 0000000..afa42cb
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/entitlement/ServerApiEntitlementsTest.java
@@ -0,0 +1,34 @@
+/*
+ * 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.entitlement;
+
+import org.testng.annotations.Test;
+
+@Test(singleThreaded = true)
+public class ServerApiEntitlementsTest extends AbstractRestApiEntitlementsTest {
+
+    @Test(groups = "Integration")
+    public void testGetHealthy() throws Exception {
+        String path = "/v1/server/up";
+        assertPermitted("myRoot", path);
+        assertForbidden("myReadonly", path);
+        assertForbidden("myMinimal", path);
+        assertForbidden("unrecognisedUser", path);
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/entitlement/StaticDelegatingEntitlementManager.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/entitlement/StaticDelegatingEntitlementManager.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/entitlement/StaticDelegatingEntitlementManager.java
new file mode 100644
index 0000000..4370ad6
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/entitlement/StaticDelegatingEntitlementManager.java
@@ -0,0 +1,37 @@
+/*
+ * 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.entitlement;
+
+import org.apache.brooklyn.api.mgmt.entitlement.EntitlementClass;
+import org.apache.brooklyn.api.mgmt.entitlement.EntitlementContext;
+import org.apache.brooklyn.api.mgmt.entitlement.EntitlementManager;
+
+public class StaticDelegatingEntitlementManager implements EntitlementManager {
+
+    private static volatile EntitlementManager delegate;
+    
+    public static void setDelegate(EntitlementManager val) {
+        delegate = val;
+    }
+    
+    @Override
+    public <T> boolean isEntitled(EntitlementContext context, EntitlementClass<T> entitlementClass, T entitlementClassArgument) {
+        return (delegate != null) ? delegate.isEntitled(context, entitlementClass, entitlementClassArgument) : false;
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/AccessResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/AccessResourceTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/AccessResourceTest.java
new file mode 100644
index 0000000..0839ea5
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/AccessResourceTest.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import javax.ws.rs.core.Response;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.Test;
+
+import org.apache.brooklyn.rest.domain.AccessSummary;
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+
+import com.sun.jersey.api.client.ClientResponse;
+
+@Test(singleThreaded = true)
+public class AccessResourceTest extends BrooklynRestResourceTest {
+
+    @SuppressWarnings("unused")
+    private static final Logger log = LoggerFactory.getLogger(AccessResourceTest.class);
+
+    @Test
+    public void testGetAndSetAccessControl() throws Exception {
+        // Default is everything allowed
+        AccessSummary summary = client().resource("/v1/access").get(AccessSummary.class);
+        assertTrue(summary.isLocationProvisioningAllowed());
+
+        // Forbid location provisioning
+        ClientResponse response = client().resource(
+                "/v1/access/locationProvisioningAllowed")
+                .queryParam("allowed", "false")
+                .post(ClientResponse.class);
+        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
+
+        AccessSummary summary2 = client().resource("/v1/access").get(AccessSummary.class);
+        assertFalse(summary2.isLocationProvisioningAllowed());
+        
+        // Allow location provisioning
+        ClientResponse response2 = client().resource(
+                "/v1/access/locationProvisioningAllowed")
+                .queryParam("allowed", "true")
+                .post(ClientResponse.class);
+        assertEquals(response2.getStatus(), Response.Status.OK.getStatusCode());
+
+        AccessSummary summary3 = client().resource("/v1/access").get(AccessSummary.class);
+        assertTrue(summary3.isLocationProvisioningAllowed());
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ApidocResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ApidocResourceTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ApidocResourceTest.java
new file mode 100644
index 0000000..739d63f
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ApidocResourceTest.java
@@ -0,0 +1,177 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.sun.jersey.api.core.ClassNamesResourceConfig;
+import com.sun.jersey.spi.container.servlet.ServletContainer;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import org.apache.brooklyn.rest.BrooklynRestApi;
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+
+import com.sun.jersey.test.framework.AppDescriptor;
+import com.sun.jersey.test.framework.JerseyTest;
+import com.sun.jersey.test.framework.WebAppDescriptor;
+import com.sun.jersey.test.framework.spi.container.TestContainerException;
+import com.sun.jersey.test.framework.spi.container.TestContainerFactory;
+import com.sun.jersey.test.framework.spi.container.grizzly2.web.GrizzlyWebTestContainerFactory;
+import io.swagger.annotations.Api;
+import io.swagger.models.Info;
+import io.swagger.models.Operation;
+import io.swagger.models.Path;
+import io.swagger.models.Swagger;
+import java.util.Collection;
+import org.apache.brooklyn.rest.api.CatalogApi;
+import org.apache.brooklyn.rest.api.EffectorApi;
+import org.apache.brooklyn.rest.api.EntityApi;
+import org.apache.brooklyn.rest.filter.SwaggerFilter;
+import org.apache.brooklyn.rest.util.ShutdownHandlerProvider;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+
+/**
+ * @author Adam Lowe
+ */
+@Test(singleThreaded = true)
+public class ApidocResourceTest extends BrooklynRestResourceTest {
+
+    private static final Logger log = LoggerFactory.getLogger(ApidocResourceTest.class);
+
+    @Override
+    protected JerseyTest createJerseyTest() {
+        return new JerseyTest() {
+            @Override
+            protected AppDescriptor configure() {
+                return new WebAppDescriptor.Builder(
+                        ImmutableMap.of(
+                                ServletContainer.RESOURCE_CONFIG_CLASS, ClassNamesResourceConfig.class.getName(),
+                                ClassNamesResourceConfig.PROPERTY_CLASSNAMES, getResourceClassnames()))
+                        .addFilter(SwaggerFilter.class, "SwaggerFilter").build();
+            }
+
+            @Override
+            protected TestContainerFactory getTestContainerFactory() throws TestContainerException {
+                return new GrizzlyWebTestContainerFactory();
+            }
+
+            private String getResourceClassnames() {
+                Iterable<String> classnames = Collections2.transform(config.getClasses(), new Function<Class, String>() {
+                    @Override
+                    public String apply(Class clazz) {
+                        return clazz.getName();
+                    }
+                });
+                classnames = Iterables.concat(classnames, Collections2.transform(config.getSingletons(), new Function<Object, String>() {
+                    @Override
+                    public String apply(Object singleton) {
+                        return singleton.getClass().getName();
+                    }
+                }));
+                return Joiner.on(';').join(classnames);
+            }
+        };
+    }
+
+    @Override
+    protected void addBrooklynResources() {
+        for (Object o : BrooklynRestApi.getApidocResources()) {
+            addResource(o);
+        }
+        super.addBrooklynResources();
+    }
+    
+    @Test(enabled = false)
+    public void testRootSerializesSensibly() throws Exception {
+        String data = resource("/v1/apidoc/swagger.json").get(String.class);
+        log.info("apidoc gives: "+data);
+        // make sure no scala gets in
+        assertFalse(data.contains("$"));
+        assertFalse(data.contains("scala"));
+        // make sure it's an appropriate swagger 2.0 json
+        Swagger swagger = resource("/v1/apidoc/swagger.json").get(Swagger.class);
+        assertEquals(swagger.getSwagger(), "2.0");
+    }
+    
+    @Test(enabled = false)
+    public void testCountRestResources() throws Exception {
+        Swagger swagger = resource("/v1/apidoc/swagger.json").get(Swagger.class);
+        assertEquals(swagger.getTags().size(), 1 + Iterables.size(BrooklynRestApi.getBrooklynRestResources()));
+    }
+
+    @Test(enabled = false)
+    public void testApiDocDetails() throws Exception {
+        Swagger swagger = resource("/v1/apidoc/swagger.json").get(Swagger.class);
+        Collection<Operation> operations = getTaggedOperations(swagger, ApidocResource.class.getAnnotation(Api.class).value());
+        assertEquals(operations.size(), 2, "ops="+operations);
+    }
+
+    @Test(enabled = false)
+    public void testEffectorDetails() throws Exception {
+        Swagger swagger = resource("/v1/apidoc/swagger.json").get(Swagger.class);
+        Collection<Operation> operations = getTaggedOperations(swagger, EffectorApi.class.getAnnotation(Api.class).value());
+        assertEquals(operations.size(), 2, "ops="+operations);
+    }
+
+    @Test(enabled = false)
+    public void testEntityDetails() throws Exception {
+        Swagger swagger = resource("/v1/apidoc/swagger.json").get(Swagger.class);
+        Collection<Operation> operations = getTaggedOperations(swagger, EntityApi.class.getAnnotation(Api.class).value());
+        assertEquals(operations.size(), 14, "ops="+operations);
+    }
+
+    @Test(enabled = false)
+    public void testCatalogDetails() throws Exception {
+        Swagger swagger = resource("/v1/apidoc/swagger.json").get(Swagger.class);
+        Collection<Operation> operations = getTaggedOperations(swagger, CatalogApi.class.getAnnotation(Api.class).value());
+        assertEquals(operations.size(), 22, "ops="+operations);
+    }
+
+    /**
+     * Retrieves all operations tagged the given tag from the given swagger spec.
+     */
+    private Collection<Operation> getTaggedOperations(Swagger swagger, final String requiredTag) {
+        Iterable<Operation> allOperations = Iterables.concat(Collections2.transform(swagger.getPaths().values(),
+                new Function<Path, Collection<Operation>>() {
+                    @Override
+                    public Collection<Operation> apply(Path path) {
+                        return path.getOperations();
+                    }
+                }));
+
+        return Collections2.filter(ImmutableList.copyOf(allOperations), new Predicate<Operation>() {
+            @Override
+            public boolean apply(Operation operation) {
+                return operation.getTags().contains(requiredTag);
+            }
+        });
+    }
+}
+


[06/34] brooklyn-server git commit: [BROOKLYN-183] REST API using CXF JAX-RS 2.0 implementation

Posted by he...@apache.org.
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/BrooklynRestResourceUtils.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/BrooklynRestResourceUtils.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/BrooklynRestResourceUtils.java
deleted file mode 100644
index 829b7cb..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/BrooklynRestResourceUtils.java
+++ /dev/null
@@ -1,608 +0,0 @@
-/*
- * 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 com.google.common.collect.Iterables.transform;
-import static org.apache.brooklyn.rest.util.WebResourceUtils.notFound;
-
-import java.lang.reflect.Constructor;
-import java.util.ArrayList;
-import java.util.Collection;
-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 org.apache.brooklyn.api.catalog.BrooklynCatalog;
-import org.apache.brooklyn.api.catalog.CatalogItem;
-import org.apache.brooklyn.api.entity.Application;
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.location.Location;
-import org.apache.brooklyn.api.location.LocationRegistry;
-import org.apache.brooklyn.api.mgmt.ManagementContext;
-import org.apache.brooklyn.api.mgmt.Task;
-import org.apache.brooklyn.api.policy.Policy;
-import org.apache.brooklyn.api.typereg.RegisteredType;
-import org.apache.brooklyn.camp.brooklyn.BrooklynCampConstants;
-import org.apache.brooklyn.camp.brooklyn.spi.dsl.methods.DslComponent;
-import org.apache.brooklyn.camp.brooklyn.spi.dsl.methods.DslComponent.Scope;
-import org.apache.brooklyn.config.ConfigKey;
-import org.apache.brooklyn.core.catalog.CatalogPredicates;
-import org.apache.brooklyn.core.catalog.internal.CatalogItemComparator;
-import org.apache.brooklyn.core.catalog.internal.CatalogUtils;
-import org.apache.brooklyn.core.entity.Attributes;
-import org.apache.brooklyn.core.entity.Entities;
-import org.apache.brooklyn.core.entity.EntityInternal;
-import org.apache.brooklyn.core.entity.trait.Startable;
-import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
-import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
-import org.apache.brooklyn.core.mgmt.entitlement.Entitlements.StringAndArgument;
-import org.apache.brooklyn.core.objs.BrooklynTypes;
-import org.apache.brooklyn.core.typereg.RegisteredTypes;
-import org.apache.brooklyn.enricher.stock.Enrichers;
-import org.apache.brooklyn.entity.stock.BasicApplication;
-import org.apache.brooklyn.rest.domain.ApplicationSpec;
-import org.apache.brooklyn.rest.domain.EntitySpec;
-import org.apache.brooklyn.util.collections.MutableMap;
-import org.apache.brooklyn.util.collections.MutableSet;
-import org.apache.brooklyn.util.core.flags.TypeCoercions;
-import org.apache.brooklyn.util.exceptions.Exceptions;
-import org.apache.brooklyn.util.javalang.Reflections;
-import org.apache.brooklyn.util.net.Urls;
-import org.apache.brooklyn.util.text.Strings;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.common.base.Function;
-import com.google.common.base.Preconditions;
-import com.google.common.base.Predicate;
-import com.google.common.base.Predicates;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.ImmutableSortedSet;
-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.policies()) {
-            if (policy.equals(p.getId())) return p;
-        }
-        for (Policy p: entity.policies()) {
-            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 Entity getEntity(String application, String entity) {
-        if (entity==null) return null;
-        Application app = application!=null ? getApplication(application) : null;
-        Entity e = 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 Entity searchForEntityNamed(Entity root, String entity) {
-        if (root.getId().equals(entity) || entity.equals(root.getDisplayName())) return root;
-        for (Entity child: root.getChildren()) {
-            Entity result = searchForEntityNamed(child, entity);
-            if (result!=null) return result;
-        }
-        return null;
-    }
-
-    private class FindItemAndClass {
-        String catalogItemId;
-        Class<? extends Entity> clazz;
-        
-        @SuppressWarnings({ "unchecked" })
-        private FindItemAndClass inferFrom(String type) {
-            RegisteredType item = mgmt.getTypeRegistry().get(type);
-            if (item==null) {
-                // deprecated attempt to load an item not in the type registry
-                
-                // although the method called was deprecated in 0.7.0, its use here was not warned until 0.9.0;
-                // therefore this behaviour should not be changed until after 0.9.0;
-                // at which point it should try a pojo load (see below)
-                item = getCatalogItemForType(type);
-                if (item!=null) {
-                    log.warn("Creating application for requested type `"+type+" using item "+item+"; "
-                        + "the registered type name ("+item.getSymbolicName()+") should be used from the spec instead, "
-                        + "or the type registered under its own name. "
-                        + "Future versions will likely change semantics to attempt a POJO load of the type instead.");
-                }
-            }
-            
-            if (item != null) {
-                return setAs(
-                    mgmt.getTypeRegistry().createSpec(item, null, org.apache.brooklyn.api.entity.EntitySpec.class).getType(),
-                    item.getId());
-            } else {
-                try {
-                    setAs(
-                        (Class<? extends Entity>) getCatalog().getRootClassLoader().loadClass(type),
-                        null);
-                    log.info("Catalog does not contain item for type {}; loaded class directly instead", type);
-                    return this;
-                } 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);
-                }
-            }
-        }
-
-        private FindItemAndClass setAs(Class<? extends Entity> clazz, String catalogItemId) {
-            this.clazz = clazz;
-            this.catalogItemId = catalogItemId;
-            return this;
-        }
-        
-        @Deprecated // see caller
-        private RegisteredType getCatalogItemForType(String typeName) {
-            final RegisteredType resultI;
-            if (CatalogUtils.looksLikeVersionedId(typeName)) {
-                //All catalog identifiers of the form xxxx:yyyy are composed of symbolicName+version.
-                //No javaType is allowed as part of the identifier.
-                resultI = mgmt.getTypeRegistry().get(typeName);
-            } else {
-                //Usually for catalog items with javaType (that is items from catalog.xml)
-                //the symbolicName and javaType match because symbolicName (was ID)
-                //is not specified explicitly. But could be the case that there is an item
-                //whose symbolicName is explicitly set to be different from the javaType.
-                //Note that in the XML the attribute is called registeredTypeName.
-                Iterable<CatalogItem<Object,Object>> resultL = mgmt.getCatalog().getCatalogItems(CatalogPredicates.javaType(Predicates.equalTo(typeName)));
-                if (!Iterables.isEmpty(resultL)) {
-                    //Push newer versions in front of the list (not that there should
-                    //be more than one considering the items are coming from catalog.xml).
-                    resultI = RegisteredTypes.of(sortVersionsDesc(resultL).iterator().next());
-                    if (log.isDebugEnabled() && Iterables.size(resultL)>1) {
-                        log.debug("Found "+Iterables.size(resultL)+" matches in catalog for type "+typeName+"; returning the result with preferred version, "+resultI);
-                    }
-                } else {
-                    //As a last resort try searching for items with the same symbolicName supposedly
-                    //different from the javaType.
-                    resultI = mgmt.getTypeRegistry().get(typeName, BrooklynCatalog.DEFAULT_VERSION);
-                    if (resultI != null) {
-                        if (resultI.getSuperTypes().isEmpty()) {
-                            //Catalog items scanned from the classpath (using reflection and annotations) now
-                            //get yaml spec rather than a java type. Can't use those when creating apps from
-                            //the legacy app spec format.
-                            log.warn("Unable to find catalog item for type "+typeName +
-                                    ". There is an existing catalog item with ID " + resultI.getId() +
-                                    " but it doesn't define a class type.");
-                            return null;
-                        }
-                    }
-                }
-            }
-            return resultI;
-        }
-        private <T,SpecT> Collection<CatalogItem<T,SpecT>> sortVersionsDesc(Iterable<CatalogItem<T,SpecT>> versions) {
-            return ImmutableSortedSet.orderedBy(CatalogItemComparator.<T,SpecT>getInstance()).addAll(versions).build();
-        }
-    }
-    
-    @SuppressWarnings({ "deprecation" })
-    public Application create(ApplicationSpec spec) {
-        log.warn("Using deprecated functionality (as of 0.9.0), ApplicationSpec style (pre CAMP plans). " +
-                    "Transition to actively supported spec plans.");
-        log.debug("REST creating application instance for {}", spec);
-        
-        if (!Entitlements.isEntitled(mgmt.getEntitlementManager(), Entitlements.DEPLOY_APPLICATION, spec)) {
-            throw WebResourceUtils.forbidden("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
-        FindItemAndClass itemAndClass;
-        if (Strings.isEmpty(type)) {
-            itemAndClass = new FindItemAndClass().setAs(BasicApplication.class, null);
-        } else {
-            itemAndClass = new FindItemAndClass().inferFrom(type);
-        }
-        
-        if (!Entitlements.isEntitled(mgmt.getEntitlementManager(), Entitlements.INVOKE_EFFECTOR, null)) {
-            throw WebResourceUtils.forbidden("User '%s' is not authorized to create application from applicationSpec %s",
-                Entitlements.getEntitlementContext().user(), spec);
-        }
-
-        try {
-            if (org.apache.brooklyn.core.entity.factory.ApplicationBuilder.class.isAssignableFrom(itemAndClass.clazz)) {
-                // warning only added in 0.9.0
-                log.warn("Using deprecated ApplicationBuilder "+itemAndClass.clazz+"; callers must migrate to use of Application");
-                Constructor<?> constructor = itemAndClass.clazz.getConstructor();
-                org.apache.brooklyn.core.entity.factory.ApplicationBuilder appBuilder = (org.apache.brooklyn.core.entity.factory.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(itemAndClass.clazz)) {
-                org.apache.brooklyn.api.entity.EntitySpec<?> coreSpec = toCoreEntitySpec(itemAndClass.clazz, name, configO, itemAndClass.catalogItemId);
-                configureRenderingMetadata(spec, coreSpec);
-                for (EntitySpec entitySpec : entities) {
-                    log.info("REST creating instance for entity {}", entitySpec.getType());
-                    coreSpec.child(toCoreEntitySpec(entitySpec));
-                }
-
-                log.info("REST placing '{}' under management", spec.getName() != null ? spec.getName() : spec);
-                instance = (Application) mgmt.getEntityManager().createEntity(coreSpec);
-
-            } else if (Entity.class.isAssignableFrom(itemAndClass.clazz)) {
-                if (entities.size() > 0)
-                    log.warn("Cannot supply additional entities when using a non-application entity; ignoring in spec {}", spec);
-
-                org.apache.brooklyn.api.entity.EntitySpec<?> coreSpec = toCoreEntitySpec(BasicApplication.class, name, configO, itemAndClass.catalogItemId);
-                configureRenderingMetadata(spec, coreSpec);
-
-                coreSpec.child(toCoreEntitySpec(itemAndClass.clazz, name, configO, itemAndClass.catalogItemId)
-                    .configure(BrooklynCampConstants.PLAN_ID, "soleChildId"));
-                coreSpec.enricher(Enrichers.builder()
-                    .propagatingAllBut(Attributes.SERVICE_UP, Attributes.SERVICE_NOT_UP_INDICATORS, 
-                        Attributes.SERVICE_STATE_ACTUAL, Attributes.SERVICE_STATE_EXPECTED, 
-                        Attributes.SERVICE_PROBLEMS)
-                        .from(new DslComponent(Scope.CHILD, "soleChildId").newTask())
-                        .build());
-
-                log.info("REST placing '{}' under management", spec.getName());
-                instance = (Application) mgmt.getEntityManager().createEntity(coreSpec);
-
-            } else {
-                throw new IllegalArgumentException("Class " + itemAndClass.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);
-        }
-    }
-    
-    public Task<?> start(Application app, ApplicationSpec spec) {
-        return start(app, getLocations(spec));
-    }
-
-    public Task<?> start(Application app, List<? extends Location> locations) {
-        return Entities.invokeEffector(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;
-    }
-
-    private org.apache.brooklyn.api.entity.EntitySpec<? extends Entity> toCoreEntitySpec(org.apache.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());
-
-        FindItemAndClass itemAndClass = new FindItemAndClass().inferFrom(type);
-        
-        final Class<? extends Entity> clazz = itemAndClass.clazz;
-        org.apache.brooklyn.api.entity.EntitySpec<? extends Entity> result;
-        if (clazz.isInterface()) {
-            result = org.apache.brooklyn.api.entity.EntitySpec.create(clazz);
-        } else {
-            result = org.apache.brooklyn.api.entity.EntitySpec.create(Entity.class).impl(clazz).additionalInterfaces(Reflections.getAllInterfaces(clazz));
-        }
-        result.catalogItemId(itemAndClass.catalogItemId);
-        if (!Strings.isEmpty(name)) result.displayName(name);
-        result.configure( convertFlagsToKeys(result.getType(), config) );
-        configureRenderingMetadata(spec, result);
-        return result;
-    }
-    
-    @SuppressWarnings("deprecation")
-    protected void configureRenderingMetadata(ApplicationSpec spec, org.apache.brooklyn.core.entity.factory.ApplicationBuilder appBuilder) {
-        appBuilder.configure(getRenderingConfigurationFor(spec.getType()));
-    }
-
-    protected void configureRenderingMetadata(ApplicationSpec input, org.apache.brooklyn.api.entity.EntitySpec<?> entity) {
-        entity.configure(getRenderingConfigurationFor(input.getType()));
-    }
-
-    protected void configureRenderingMetadata(EntitySpec input, org.apache.brooklyn.api.entity.EntitySpec<?> entity) {
-        entity.configure(getRenderingConfigurationFor(input.getType()));
-    }
-
-    protected Map<?, ?> getRenderingConfigurationFor(String catalogId) {
-        MutableMap<Object, Object> result = MutableMap.of();
-        RegisteredType item = mgmt.getTypeRegistry().get(catalogId);
-        if (item==null) return result;
-        
-        result.addIfNotNull("iconUrl", item.getIconUrl());
-        return result;
-    }
-    
-    @SuppressWarnings({ "rawtypes", "unchecked" })
-    private <T extends Entity> org.apache.brooklyn.api.entity.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);
-        
-        org.apache.brooklyn.api.entity.EntitySpec<? extends Entity> result;
-        if (clazz.isInterface()) {
-            result = org.apache.brooklyn.api.entity.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 = org.apache.brooklyn.api.entity.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))))) {
-            Map<String, Object> flags = MutableMap.<String, Object>of("displayName", "expunging " + entity, "description", "REST call to expunge entity "
-                    + entity.getDisplayName() + " (" + entity + ")");
-            if (Entitlements.getEntitlementContext() != null) {
-                flags.put("tags", MutableSet.of(BrooklynTaskTags.tagForEntitlement(Entitlements.getEntitlementContext())));
-            }
-            return mgmt.getExecutionManager().submit(
-                    flags, new Runnable() {
-                        @Override
-                        public void run() {
-                            if (release)
-                                Entities.destroyCatching(entity);
-                            else
-                                mgmt.getEntityManager().unmanage(entity);
-                        }
-                    });
-        }
-        throw WebResourceUtils.forbidden("User '%s' is not authorized to expunge entity %s",
-                    Entitlements.getEntitlementContext().user(), entity);
-    }
-
-    @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();
-        Entity 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();
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/DefaultExceptionMapper.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/DefaultExceptionMapper.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/DefaultExceptionMapper.java
deleted file mode 100644
index 978755b..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/DefaultExceptionMapper.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * 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 java.util.Set;
-
-import javax.ws.rs.WebApplicationException;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.Response.Status;
-import javax.ws.rs.ext.ExceptionMapper;
-import javax.ws.rs.ext.Provider;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.yaml.snakeyaml.error.YAMLException;
-import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
-import org.apache.brooklyn.rest.domain.ApiError;
-import org.apache.brooklyn.rest.domain.ApiError.Builder;
-import org.apache.brooklyn.util.collections.MutableSet;
-import org.apache.brooklyn.util.core.flags.ClassCoercionException;
-import org.apache.brooklyn.util.exceptions.Exceptions;
-import org.apache.brooklyn.util.exceptions.UserFacingException;
-import org.apache.brooklyn.util.text.Strings;
-
-@Provider
-public class DefaultExceptionMapper implements ExceptionMapper<Throwable> {
-
-    private static final Logger LOG = LoggerFactory.getLogger(DefaultExceptionMapper.class);
-
-    static Set<Class<?>> warnedUnknownExceptions = MutableSet.of();
-    
-    /**
-     * Maps a throwable to a response.
-     * <p/>
-     * Returns {@link WebApplicationException#getResponse} if the exception is an instance of
-     * {@link WebApplicationException}. Otherwise maps known exceptions to responses. If no
-     * mapping is found a {@link Status#INTERNAL_SERVER_ERROR} is assumed.
-     */
-    @Override
-    public Response toResponse(Throwable throwable1) {
-
-        LOG.debug("REST request running as {} threw: {}", Entitlements.getEntitlementContext(), 
-            Exceptions.collapse(throwable1));
-        if (LOG.isTraceEnabled()) {
-            LOG.trace("Full details of "+Entitlements.getEntitlementContext()+" "+throwable1, throwable1);
-        }
-
-        Throwable throwable2 = Exceptions.getFirstInteresting(throwable1);
-        // Some methods will throw this, which gets converted automatically
-        if (throwable2 instanceof WebApplicationException) {
-            WebApplicationException wae = (WebApplicationException) throwable2;
-            return wae.getResponse();
-        }
-
-        // The nicest way for methods to provide errors, wrap as this, and the stack trace will be suppressed
-        if (throwable2 instanceof UserFacingException) {
-            return ApiError.of(throwable2.getMessage()).asBadRequestResponseJson();
-        }
-
-        // For everything else, a trace is supplied
-        
-        // Assume ClassCoercionExceptions are caused by TypeCoercions from input parameters gone wrong
-        // And IllegalArgumentException for malformed input parameters.
-        if (throwable2 instanceof ClassCoercionException || throwable2 instanceof IllegalArgumentException) {
-            return ApiError.of(throwable2).asBadRequestResponseJson();
-        }
-
-        // YAML exception 
-        if (throwable2 instanceof YAMLException) {
-            return ApiError.builder().message(throwable2.getMessage()).prefixMessage("Invalid YAML").build().asBadRequestResponseJson();
-        }
-
-        if (!Exceptions.isPrefixBoring(throwable2)) {
-            if ( warnedUnknownExceptions.add( throwable2.getClass() )) {
-                LOG.warn("REST call generated exception type "+throwable2.getClass()+" unrecognized in "+getClass()+" (subsequent occurrences will be logged debug only): " + throwable2, throwable2);
-            }
-        }
-        
-        Throwable throwable3 = Exceptions.collapse(throwable2);
-        Builder rb = ApiError.builderFromThrowable(throwable3);
-        if (Strings.isBlank(rb.getMessage()))
-            rb.message("Internal error. Contact server administrator to consult logs for more details.");
-        if (Exceptions.isPrefixImportant(throwable3))
-            rb.message(Exceptions.getPrefixText(throwable3)+": "+rb.getMessage());
-        return rb.build().asResponse(Status.INTERNAL_SERVER_ERROR, MediaType.APPLICATION_JSON_TYPE);
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/EntityLocationUtils.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/EntityLocationUtils.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/EntityLocationUtils.java
deleted file mode 100644
index 32bb66d..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/EntityLocationUtils.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * 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 java.util.LinkedHashMap;
-import java.util.Map;
-
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.location.Location;
-import org.apache.brooklyn.api.mgmt.ManagementContext;
-import org.apache.brooklyn.core.location.LocationConfigKeys;
-
-public class EntityLocationUtils {
-
-    protected final ManagementContext context;
-
-    public EntityLocationUtils(ManagementContext ctx) {
-        this.context = ctx;
-    }
-    
-    /* Returns the number of entites at each location for which the geographic coordinates are known. */
-    public Map<Location, Integer> countLeafEntitiesByLocatedLocations() {
-        Map<Location, Integer> result = new LinkedHashMap<Location, Integer>();
-        for (Entity e: context.getApplications()) {
-            countLeafEntitiesByLocatedLocations(e, null, result);
-        }
-        return result;
-    }
-
-    protected void countLeafEntitiesByLocatedLocations(Entity target, Entity locatedParent, Map<Location, Integer> result) {
-        if (isLocatedLocation(target))
-            locatedParent = target;
-        if (!target.getChildren().isEmpty()) {
-            // non-leaf - inspect children
-            for (Entity child: target.getChildren()) 
-                countLeafEntitiesByLocatedLocations(child, locatedParent, result);
-        } else {
-            // leaf node - increment location count
-            if (locatedParent!=null) {
-                for (Location l: locatedParent.getLocations()) {
-                    Location ll = getMostGeneralLocatedLocation(l);
-                    if (ll!=null) {
-                        Integer count = result.get(ll);
-                        if (count==null) count = 1;
-                        else count++;
-                        result.put(ll, count);
-                    }
-                }
-            }
-        }
-    }
-
-    protected Location getMostGeneralLocatedLocation(Location l) {
-        if (l==null) return null;
-        if (!isLocatedLocation(l)) return null;
-        Location ll = getMostGeneralLocatedLocation(l.getParent());
-        if (ll!=null) return ll;
-        return l;
-    }
-
-    protected boolean isLocatedLocation(Entity target) {
-        for (Location l: target.getLocations())
-            if (isLocatedLocation(l)) return true;
-        return false;
-    }
-    protected boolean isLocatedLocation(Location l) {
-        return l.getConfig(LocationConfigKeys.LATITUDE)!=null && l.getConfig(LocationConfigKeys.LONGITUDE)!=null;
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/FormMapProvider.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/FormMapProvider.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/FormMapProvider.java
deleted file mode 100644
index 2b5c19b..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/FormMapProvider.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * 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 java.io.IOException;
-import java.io.InputStream;
-import java.lang.annotation.Annotation;
-import java.lang.reflect.ParameterizedType;
-import java.lang.reflect.Type;
-import java.util.List;
-import java.util.Map;
-import javax.ws.rs.Consumes;
-import javax.ws.rs.WebApplicationException;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.MultivaluedMap;
-import javax.ws.rs.ext.MessageBodyReader;
-import javax.ws.rs.ext.Provider;
-
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.sun.jersey.core.impl.provider.entity.FormMultivaluedMapProvider;
-import com.sun.jersey.core.util.MultivaluedMapImpl;
-
-/**
- * A MessageBodyReader producing a <code>Map&lt;String, Object&gt;</code>, where Object
- * is either a <code>String</code>, a <code>List&lt;String&gt;</code> or null.
- */
-@Provider
-@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
-public class FormMapProvider implements MessageBodyReader<Map<String, Object>> {
-
-    @Override
-    public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
-        if (!Map.class.equals(type) || !(genericType instanceof ParameterizedType)) {
-            return false;
-        }
-        ParameterizedType parameterized = (ParameterizedType) genericType;
-        return parameterized.getActualTypeArguments().length == 2 &&
-                parameterized.getActualTypeArguments()[0] == String.class &&
-                parameterized.getActualTypeArguments()[1] == Object.class;
-    }
-
-    @Override
-    public Map<String, Object> readFrom(Class<Map<String, Object>> type, Type genericType, Annotation[] annotations,
-            MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream)
-            throws IOException, WebApplicationException {
-        FormMultivaluedMapProvider delegate = new FormMultivaluedMapProvider();
-        MultivaluedMap<String, String> multi = new MultivaluedMapImpl();
-        multi = delegate.readFrom(multi, mediaType, entityStream);
-
-        Map<String, Object> map = Maps.newHashMapWithExpectedSize(multi.keySet().size());
-        for (String key : multi.keySet()) {
-            List<String> value = multi.get(key);
-            if (value.size() > 1) {
-                map.put(key, Lists.newArrayList(value));
-            } else if (value.size() == 1) {
-                map.put(key, Iterables.getOnlyElement(value));
-            } else {
-                map.put(key, null);
-            }
-        }
-        return map;
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/ManagementContextProvider.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/ManagementContextProvider.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/ManagementContextProvider.java
deleted file mode 100644
index 1e15f44..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/ManagementContextProvider.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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 javax.ws.rs.core.Context;
-
-import org.apache.brooklyn.api.mgmt.ManagementContext;
-
-import com.sun.jersey.spi.inject.SingletonTypeInjectableProvider;
-
-public class ManagementContextProvider extends SingletonTypeInjectableProvider<Context, ManagementContext> {
-
-    public ManagementContextProvider(ManagementContext instance) {
-        super(ManagementContext.class, instance);
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/OsgiCompat.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/OsgiCompat.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/OsgiCompat.java
deleted file mode 100644
index 1570efc..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/OsgiCompat.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2015 The Apache Software Foundation.
- *
- * Licensed 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 javax.servlet.ServletContext;
-import org.apache.brooklyn.api.mgmt.ManagementContext;
-import org.apache.brooklyn.core.server.BrooklynServiceAttributes;
-import org.apache.brooklyn.util.core.osgi.Compat;
-import org.eclipse.jetty.server.handler.ContextHandler;
-
-/**
- * Compatibility methods between karaf launcher and monolithic launcher.
- *
- * @todo Remove after transition to karaf launcher.
- */
-public class OsgiCompat {
-
-    public static ManagementContext getManagementContext(ServletContext servletContext) {
-        ManagementContext managementContext = Compat.getInstance().getManagementContext();
-        if (managementContext == null && servletContext != null) {
-            managementContext = (ManagementContext) servletContext.getAttribute(BrooklynServiceAttributes.BROOKLYN_MANAGEMENT_CONTEXT);
-        }
-        return managementContext;
-    }
-
-    public static ManagementContext getManagementContext(ContextHandler jettyServerHandler) {
-        ManagementContext managementContext = Compat.getInstance().getManagementContext();
-        if (managementContext == null && jettyServerHandler != null) {
-            managementContext = (ManagementContext) jettyServerHandler.getAttribute(BrooklynServiceAttributes.BROOKLYN_MANAGEMENT_CONTEXT);
-        }
-        return managementContext;
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/ShutdownHandler.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/ShutdownHandler.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/ShutdownHandler.java
deleted file mode 100644
index e573bf6..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/ShutdownHandler.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * 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;
-
-public interface ShutdownHandler {
-    void onShutdownRequest();
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/ShutdownHandlerProvider.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/ShutdownHandlerProvider.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/ShutdownHandlerProvider.java
deleted file mode 100644
index f499ca0..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/ShutdownHandlerProvider.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * 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 javax.annotation.Nullable;
-import javax.ws.rs.core.Context;
-
-import com.sun.jersey.spi.inject.SingletonTypeInjectableProvider;
-
-public class ShutdownHandlerProvider extends SingletonTypeInjectableProvider<Context, ShutdownHandler> {
-    public ShutdownHandlerProvider(@Nullable ShutdownHandler instance) {
-        super(ShutdownHandler.class, instance);
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/URLParamEncoder.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/URLParamEncoder.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/URLParamEncoder.java
deleted file mode 100644
index 8c25fda..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/URLParamEncoder.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * 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;
-
-
-/**
- * @deprecated since 0.7.0 use {@link org.apache.brooklyn.util.net.URLParamEncoder}
- */
-public class URLParamEncoder extends org.apache.brooklyn.util.net.URLParamEncoder {
-
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/WebResourceUtils.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/WebResourceUtils.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/WebResourceUtils.java
deleted file mode 100644
index da72c6f..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/WebResourceUtils.java
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * 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 java.io.IOException;
-import java.util.Map;
-
-import javax.servlet.ServletContext;
-import javax.servlet.http.HttpServletResponse;
-import javax.ws.rs.WebApplicationException;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.apache.brooklyn.core.catalog.internal.CatalogUtils;
-import org.apache.brooklyn.rest.domain.ApiError;
-import org.apache.brooklyn.rest.util.json.BrooklynJacksonJsonProvider;
-import org.apache.brooklyn.util.exceptions.Exceptions;
-import org.apache.brooklyn.util.net.Urls;
-import org.apache.brooklyn.util.text.StringEscapes.JavaStringEscapes;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.google.common.collect.ImmutableMap;
-import com.sun.jersey.spi.container.ContainerResponse;
-
-public class WebResourceUtils {
-
-    private static final Logger log = LoggerFactory.getLogger(WebResourceUtils.class);
-
-    /** @throws WebApplicationException with an ApiError as its body and the given status as its response code. */
-    public static WebApplicationException throwWebApplicationException(Response.Status status, String format, Object... args) {
-        String msg = String.format(format, args);
-        if (log.isDebugEnabled()) {
-            log.debug("responding {} {} ({})",
-                    new Object[]{status.getStatusCode(), status.getReasonPhrase(), msg});
-        }
-        ApiError apiError = ApiError.builder().message(msg).errorCode(status).build();
-        // including a Throwable is the only way to include a message with the WebApplicationException - ugly!
-        throw new WebApplicationException(new Throwable(apiError.toString()), apiError.asJsonResponse());
-    }
-
-    /** @throws WebApplicationException With code 500 internal server error */
-    public static WebApplicationException serverError(String format, Object... args) {
-        return throwWebApplicationException(Response.Status.INTERNAL_SERVER_ERROR, format, args);
-    }
-
-    /** @throws WebApplicationException With code 400 bad request */
-    public static WebApplicationException badRequest(String format, Object... args) {
-        return throwWebApplicationException(Response.Status.BAD_REQUEST, format, args);
-    }
-
-    /** @throws WebApplicationException With code 401 unauthorized */
-    public static WebApplicationException unauthorized(String format, Object... args) {
-        return throwWebApplicationException(Response.Status.UNAUTHORIZED, format, args);
-    }
-
-    /** @throws WebApplicationException With code 403 forbidden */
-    public static WebApplicationException forbidden(String format, Object... args) {
-        return throwWebApplicationException(Response.Status.FORBIDDEN, format, args);
-    }
-
-    /** @throws WebApplicationException With code 404 not found */
-    public static WebApplicationException notFound(String format, Object... args) {
-        return throwWebApplicationException(Response.Status.NOT_FOUND, format, args);
-    }
-
-    /** @throws WebApplicationException With code 412 precondition failed */
-    public static WebApplicationException preconditionFailed(String format, Object... args) {
-        return throwWebApplicationException(Response.Status.PRECONDITION_FAILED, format, args);
-    }
-
-    public final static Map<String,com.google.common.net.MediaType> IMAGE_FORMAT_MIME_TYPES = ImmutableMap.<String, com.google.common.net.MediaType>builder()
-            .put("jpg", com.google.common.net.MediaType.JPEG)
-            .put("jpeg", com.google.common.net.MediaType.JPEG)
-            .put("png", com.google.common.net.MediaType.PNG)
-            .put("gif", com.google.common.net.MediaType.GIF)
-            .put("svg", com.google.common.net.MediaType.SVG_UTF_8)
-            .build();
-    
-    public static MediaType getImageMediaTypeFromExtension(String extension) {
-        com.google.common.net.MediaType mime = IMAGE_FORMAT_MIME_TYPES.get(extension.toLowerCase());
-        if (mime==null) return null;
-        try {
-            return MediaType.valueOf(mime.toString());
-        } catch (Exception e) {
-            log.warn("Unparseable MIME type "+mime+"; ignoring ("+e+")");
-            Exceptions.propagateIfFatal(e);
-            return null;
-        }
-    }
-
-    /** as {@link #getValueForDisplay(ObjectMapper, Object, boolean, boolean)} with no mapper
-     * (so will only handle a subset of types) */
-    public static Object getValueForDisplay(Object value, boolean preferJson, boolean isJerseyReturnValue) {
-        return getValueForDisplay(null, value, preferJson, isJerseyReturnValue);
-    }
-    
-    /** returns an object which jersey will handle nicely, converting to json,
-     * sometimes wrapping in quotes if needed (for outermost json return types);
-     * if json is not preferred, this simply applies a toString-style rendering */ 
-    public static Object getValueForDisplay(ObjectMapper mapper, Object value, boolean preferJson, boolean isJerseyReturnValue) {
-        if (preferJson) {
-            if (value==null) return null;
-            Object result = value;
-            // no serialization checks required, with new smart-mapper which does toString
-            // (note there is more sophisticated logic in git history however)
-            result = value;
-            
-            if (isJerseyReturnValue) {
-                if (result instanceof String) {
-                    // Jersey does not do json encoding if the return type is a string,
-                    // expecting the returner to do the json encoding himself
-                    // cf discussion at https://github.com/dropwizard/dropwizard/issues/231
-                    result = JavaStringEscapes.wrapJavaString((String)result);
-                }
-            }
-            
-            return result;
-        } else {
-            if (value==null) return "";
-            return value.toString();            
-        }
-    }
-
-    public static String getPathFromVersionedId(String versionedId) {
-        if (CatalogUtils.looksLikeVersionedId(versionedId)) {
-            String symbolicName = CatalogUtils.getSymbolicNameFromVersionedId(versionedId);
-            String version = CatalogUtils.getVersionFromVersionedId(versionedId);
-            return Urls.encode(symbolicName) + "/" + Urls.encode(version);
-        } else {
-            return Urls.encode(versionedId);
-        }
-    }
-
-    /** Sets the {@link HttpServletResponse} target (last argument) from the given source {@link Response};
-     * useful in filters where we might have a {@link Response} and need to set up an {@link HttpServletResponse}. 
-     * Similar to {@link ContainerResponse#setResponse(Response)}; nothing like that seems to be available for {@link HttpServletResponse}. */
-    public static void applyJsonResponse(ServletContext servletContext, Response source, HttpServletResponse target) throws IOException {
-        target.setStatus(source.getStatus());
-        target.setContentType(MediaType.APPLICATION_JSON);
-        target.setCharacterEncoding("UTF-8");
-        target.getWriter().write(BrooklynJacksonJsonProvider.findAnyObjectMapper(servletContext, null).writeValueAsString(source.getEntity()));
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/json/BidiSerialization.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/json/BidiSerialization.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/json/BidiSerialization.java
deleted file mode 100644
index 93cae3f..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/json/BidiSerialization.java
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * 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.json;
-
-import java.io.IOException;
-import java.util.Map;
-
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.location.Location;
-import org.apache.brooklyn.api.mgmt.ManagementContext;
-import org.apache.brooklyn.api.objs.BrooklynObject;
-
-import com.fasterxml.jackson.core.JsonGenerator;
-import com.fasterxml.jackson.core.JsonParser;
-import com.fasterxml.jackson.databind.DeserializationContext;
-import com.fasterxml.jackson.databind.JsonDeserializer;
-import com.fasterxml.jackson.databind.JsonSerializer;
-import com.fasterxml.jackson.databind.SerializerProvider;
-import com.fasterxml.jackson.databind.module.SimpleModule;
-
-public class BidiSerialization {
-
-    protected final static ThreadLocal<Boolean> STRICT_SERIALIZATION = new ThreadLocal<Boolean>(); 
-
-    /**
-     * Sets strict serialization on, or off (the default), for the current thread.
-     * Recommended to be used in a <code>try { ... } finally { ... }</code> block
-     * with {@link #clearStrictSerialization()} at the end.
-     * <p>
-     * With strict serialization, classes must have public fields or annotated fields, else they will not be serialized.
-     */
-    public static void setStrictSerialization(Boolean value) {
-        STRICT_SERIALIZATION.set(value);
-    }
-
-    public static void clearStrictSerialization() {
-        STRICT_SERIALIZATION.remove();
-    }
-
-    public static boolean isStrictSerialization() {
-        Boolean result = STRICT_SERIALIZATION.get();
-        if (result!=null) return result;
-        return false;
-    }
-
-
-    public abstract static class AbstractWithManagementContextSerialization<T> {
-
-        protected class Serializer extends JsonSerializer<T> {
-            @Override
-            public void serialize(T value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
-                AbstractWithManagementContextSerialization.this.serialize(value, jgen, provider);
-            }
-        }
-        
-        protected class Deserializer extends JsonDeserializer<T> {
-            @Override
-            public T deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
-                return AbstractWithManagementContextSerialization.this.deserialize(jp, ctxt);
-            }
-        }
-        
-        protected final Serializer serializer = new Serializer();
-        protected final Deserializer deserializer = new Deserializer();
-        protected final Class<T> type;
-        protected final ManagementContext mgmt;
-        
-        public AbstractWithManagementContextSerialization(Class<T> type, ManagementContext mgmt) {
-            this.type = type;
-            this.mgmt = mgmt;
-        }
-        
-        public JsonSerializer<T> getSerializer() {
-            return serializer;
-        }
-        
-        public JsonDeserializer<T> getDeserializer() {
-            return deserializer;
-        }
-
-        public void serialize(T value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
-            jgen.writeStartObject();
-            writeBody(value, jgen, provider);
-            jgen.writeEndObject();
-        }
-
-        protected void writeBody(T value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
-            jgen.writeStringField("type", value.getClass().getCanonicalName());
-            customWriteBody(value, jgen, provider);
-        }
-
-        public abstract void customWriteBody(T value, JsonGenerator jgen, SerializerProvider provider) throws IOException;
-
-        public T deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
-            @SuppressWarnings("unchecked")
-            Map<Object,Object> values = jp.readValueAs(Map.class);
-            String type = (String) values.get("type");
-            return customReadBody(type, values, jp, ctxt);
-        }
-
-        protected abstract T customReadBody(String type, Map<Object, Object> values, JsonParser jp, DeserializationContext ctxt) throws IOException;
-
-        public void install(SimpleModule module) {
-            module.addSerializer(type, serializer);
-            module.addDeserializer(type, deserializer);
-        }
-    }
-    
-    public static class ManagementContextSerialization extends AbstractWithManagementContextSerialization<ManagementContext> {
-        public ManagementContextSerialization(ManagementContext mgmt) { super(ManagementContext.class, mgmt); }
-        @Override
-        public void customWriteBody(ManagementContext value, JsonGenerator jgen, SerializerProvider provider) throws IOException {}
-        @Override
-        protected ManagementContext customReadBody(String type, Map<Object, Object> values, JsonParser jp, DeserializationContext ctxt) throws IOException {
-            return mgmt;
-        }
-    }
-    
-    public abstract static class AbstractBrooklynObjectSerialization<T extends BrooklynObject> extends AbstractWithManagementContextSerialization<T> {
-        public AbstractBrooklynObjectSerialization(Class<T> type, ManagementContext mgmt) { 
-            super(type, mgmt);
-        }
-        @Override
-        protected void writeBody(T value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
-            jgen.writeStringField("type", type.getCanonicalName());
-            customWriteBody(value, jgen, provider);
-        }
-        @Override
-        public void customWriteBody(T value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
-            jgen.writeStringField("id", value.getId());
-        }
-        @Override
-        protected T customReadBody(String type, Map<Object, Object> values, JsonParser jp, DeserializationContext ctxt) throws IOException {
-            return getInstanceFromId((String) values.get("id"));
-        }
-        protected abstract T getInstanceFromId(String id);
-    }
-
-    public static class EntitySerialization extends AbstractBrooklynObjectSerialization<Entity> {
-        public EntitySerialization(ManagementContext mgmt) { super(Entity.class, mgmt); }
-        @Override protected Entity getInstanceFromId(String id) { return mgmt.getEntityManager().getEntity(id); }
-    }
-    public static class LocationSerialization extends AbstractBrooklynObjectSerialization<Location> {
-        public LocationSerialization(ManagementContext mgmt) { super(Location.class, mgmt); }
-        @Override protected Location getInstanceFromId(String id) { return mgmt.getLocationManager().getLocation(id); }
-    }
-    // TODO how to look up policies and enrichers? (not essential...)
-//    public static class PolicySerialization extends AbstractBrooklynObjectSerialization<Policy> {
-//        public EntitySerialization(ManagementContext mgmt) { super(Policy.class, mgmt); }
-//        @Override protected Policy getKind(String id) { return mgmt.getEntityManager().getEntity(id); }
-//    }
-//    public static class EnricherSerialization extends AbstractBrooklynObjectSerialization<Enricher> {
-//        public EntitySerialization(ManagementContext mgmt) { super(Entity.class, mgmt); }
-//        @Override protected Enricher getKind(String id) { return mgmt.getEntityManager().getEntity(id); }
-//    }
-
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonJsonProvider.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonJsonProvider.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonJsonProvider.java
deleted file mode 100644
index 08265c7..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonJsonProvider.java
+++ /dev/null
@@ -1,165 +0,0 @@
-/*
- * 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.json;
-
-import javax.servlet.ServletContext;
-import javax.ws.rs.core.Context;
-import javax.ws.rs.core.MediaType;
-
-import org.apache.brooklyn.api.mgmt.ManagementContext;
-import org.apache.brooklyn.config.ConfigKey;
-import org.apache.brooklyn.core.config.ConfigKeys;
-import org.apache.brooklyn.core.internal.BrooklynProperties;
-import org.apache.brooklyn.core.mgmt.ManagementContextInjectable;
-import org.apache.brooklyn.core.server.BrooklynServiceAttributes;
-import org.apache.brooklyn.rest.util.OsgiCompat;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.fasterxml.jackson.core.Version;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.module.SimpleModule;
-import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
-
-public class BrooklynJacksonJsonProvider extends JacksonJsonProvider implements ManagementContextInjectable {
-
-    private static final Logger log = LoggerFactory.getLogger(BrooklynJacksonJsonProvider.class);
-
-    public static final String BROOKLYN_REST_OBJECT_MAPPER = BrooklynServiceAttributes.BROOKLYN_REST_OBJECT_MAPPER;
-
-    @Context protected ServletContext servletContext;
-
-    protected ObjectMapper ourMapper;
-    protected boolean notFound = false;
-
-    private ManagementContext mgmt;
-
-    public ObjectMapper locateMapper(Class<?> type, MediaType mediaType) {
-        if (ourMapper != null)
-            return ourMapper;
-
-        findSharedMapper();
-
-        if (ourMapper != null)
-            return ourMapper;
-
-        if (!notFound) {
-            log.warn("Management context not available; using default ObjectMapper in "+this);
-            notFound = true;
-        }
-
-        return super.locateMapper(Object.class, MediaType.APPLICATION_JSON_TYPE);
-    }
-
-    protected synchronized ObjectMapper findSharedMapper() {
-        if (ourMapper != null || notFound)
-            return ourMapper;
-
-        ourMapper = findSharedObjectMapper(servletContext, mgmt);
-        if (ourMapper == null) return null;
-
-        if (notFound) {
-            notFound = false;
-        }
-        log.debug("Found mapper "+ourMapper+" for "+this+", creating custom Brooklyn mapper");
-
-        return ourMapper;
-    }
-
-    /**
-     * Finds a shared {@link ObjectMapper} or makes a new one, stored against the servlet context;
-     * returns null if a shared instance cannot be created.
-     */
-    public static ObjectMapper findSharedObjectMapper(ServletContext servletContext, ManagementContext mgmt) {
-        if (servletContext != null) {
-            synchronized (servletContext) {
-                ObjectMapper mapper = (ObjectMapper) servletContext.getAttribute(BROOKLYN_REST_OBJECT_MAPPER);
-                if (mapper != null) return mapper;
-
-                mapper = newPrivateObjectMapper(getManagementContext(servletContext));
-                servletContext.setAttribute(BROOKLYN_REST_OBJECT_MAPPER, mapper);
-                return mapper;
-            }
-        }
-        if (mgmt != null) {
-            synchronized (mgmt) {
-                ConfigKey<ObjectMapper> key = ConfigKeys.newConfigKey(ObjectMapper.class, BROOKLYN_REST_OBJECT_MAPPER);
-                ObjectMapper mapper = mgmt.getConfig().getConfig(key);
-                if (mapper != null) return mapper;
-
-                mapper = newPrivateObjectMapper(mgmt);
-                log.debug("Storing new ObjectMapper against "+mgmt+" because no ServletContext available: "+mapper);
-                ((BrooklynProperties)mgmt.getConfig()).put(key, mapper);
-                return mapper;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Like {@link #findSharedObjectMapper(ServletContext, ManagementContext)} but will create a private
-     * ObjectMapper if it can, from the servlet context and/or the management context, or else fail
-     */
-    public static ObjectMapper findAnyObjectMapper(ServletContext servletContext, ManagementContext mgmt) {
-        ObjectMapper mapper = findSharedObjectMapper(servletContext, mgmt);
-        if (mapper != null) return mapper;
-
-        if (mgmt == null && servletContext != null) {
-            mgmt = getManagementContext(servletContext);
-        }
-        return newPrivateObjectMapper(mgmt);
-    }
-
-    /**
-     * @return A new Brooklyn-specific ObjectMapper.
-     *   Normally {@link #findSharedObjectMapper(ServletContext, ManagementContext)} is preferred
-     */
-    public static ObjectMapper newPrivateObjectMapper(ManagementContext mgmt) {
-        if (mgmt == null) {
-            throw new IllegalStateException("No management context available for creating ObjectMapper");
-        }
-
-        ConfigurableSerializerProvider sp = new ConfigurableSerializerProvider();
-        sp.setUnknownTypeSerializer(new ErrorAndToStringUnknownTypeSerializer());
-
-        ObjectMapper mapper = new ObjectMapper();
-        mapper.setSerializerProvider(sp);
-        mapper.setVisibilityChecker(new PossiblyStrictPreferringFieldsVisibilityChecker());
-
-        SimpleModule mapperModule = new SimpleModule("Brooklyn", new Version(0, 0, 0, "ignored"));
-
-        new BidiSerialization.ManagementContextSerialization(mgmt).install(mapperModule);
-        new BidiSerialization.EntitySerialization(mgmt).install(mapperModule);
-        new BidiSerialization.LocationSerialization(mgmt).install(mapperModule);
-
-        mapperModule.addSerializer(new MultimapSerializer());
-        mapper.registerModule(mapperModule);
-
-        return mapper;
-    }
-
-    public static ManagementContext getManagementContext(ServletContext servletContext) {
-        return OsgiCompat.getManagementContext(servletContext);
-    }
-
-    @Override
-    public void setManagementContext(ManagementContext mgmt) {
-        this.mgmt = mgmt;
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/json/ConfigurableSerializerProvider.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/json/ConfigurableSerializerProvider.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/json/ConfigurableSerializerProvider.java
deleted file mode 100644
index a84c695..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/json/ConfigurableSerializerProvider.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * 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.json;
-
-import java.io.IOException;
-
-import org.apache.brooklyn.util.exceptions.Exceptions;
-
-import com.fasterxml.jackson.core.JsonGenerator;
-import com.fasterxml.jackson.core.JsonStreamContext;
-import com.fasterxml.jackson.databind.JavaType;
-import com.fasterxml.jackson.databind.JsonSerializer;
-import com.fasterxml.jackson.databind.SerializationConfig;
-import com.fasterxml.jackson.databind.ser.DefaultSerializerProvider;
-import com.fasterxml.jackson.databind.ser.SerializerFactory;
-
-/** allows the serializer-of-last-resort to be customized, ie used for unknown-types */
-final class ConfigurableSerializerProvider extends DefaultSerializerProvider {
-
-    protected JsonSerializer<Object> unknownTypeSerializer;
-
-    public ConfigurableSerializerProvider() {}
-
-    @Override
-    public DefaultSerializerProvider createInstance(SerializationConfig config, SerializerFactory jsf) {
-        return new ConfigurableSerializerProvider(config, this, jsf);
-    }
-
-    public ConfigurableSerializerProvider(SerializationConfig config, ConfigurableSerializerProvider src, SerializerFactory jsf) {
-        super(src, config, jsf);
-        unknownTypeSerializer = src.unknownTypeSerializer;
-    }
-
-    public JsonSerializer<Object> getUnknownTypeSerializer(Class<?> unknownType) {
-        if (unknownTypeSerializer!=null) return unknownTypeSerializer;
-        return super.getUnknownTypeSerializer(unknownType);
-    }
-
-    public void setUnknownTypeSerializer(JsonSerializer<Object> unknownTypeSerializer) {
-        this.unknownTypeSerializer = unknownTypeSerializer;
-    }
-
-    @Override
-    public void serializeValue(JsonGenerator jgen, Object value) throws IOException {
-        JsonStreamContext ctxt = jgen.getOutputContext();
-        try {
-            super.serializeValue(jgen, value);
-        } catch (Exception e) {
-            onSerializationException(ctxt, jgen, value, e);
-        }
-    }
-
-    @Override
-    public void serializeValue(JsonGenerator jgen, Object value, JavaType rootType) throws IOException {
-        JsonStreamContext ctxt = jgen.getOutputContext();
-        try {
-            super.serializeValue(jgen, value, rootType);
-        } catch (Exception e) {
-            onSerializationException(ctxt, jgen, value, e);
-        }
-    }
-
-    protected void onSerializationException(JsonStreamContext ctxt, JsonGenerator jgen, Object value, Exception e) throws IOException {
-        Exceptions.propagateIfFatal(e);
-
-        JsonSerializer<Object> unknownTypeSerializer = getUnknownTypeSerializer(value.getClass());
-        if (unknownTypeSerializer instanceof ErrorAndToStringUnknownTypeSerializer) {
-            ((ErrorAndToStringUnknownTypeSerializer)unknownTypeSerializer).serializeFromError(ctxt, e, value, jgen, this);
-        } else {
-            unknownTypeSerializer.serialize(value, jgen, this);
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/json/ErrorAndToStringUnknownTypeSerializer.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/json/ErrorAndToStringUnknownTypeSerializer.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/json/ErrorAndToStringUnknownTypeSerializer.java
deleted file mode 100644
index e529ec9..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/json/ErrorAndToStringUnknownTypeSerializer.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * 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.json;
-
-import java.io.IOException;
-import java.io.NotSerializableException;
-import java.util.Collections;
-import java.util.Set;
-
-import javax.annotation.Nullable;
-
-import org.apache.brooklyn.util.collections.MutableSet;
-import org.apache.brooklyn.util.javalang.Reflections;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.fasterxml.jackson.core.JsonGenerator;
-import com.fasterxml.jackson.core.JsonStreamContext;
-import com.fasterxml.jackson.databind.JsonMappingException;
-import com.fasterxml.jackson.databind.SerializerProvider;
-import com.fasterxml.jackson.databind.annotation.JsonSerialize;
-import com.fasterxml.jackson.databind.ser.impl.UnknownSerializer;
-
-/**
- * for non-json-serializable classes (quite a lot of them!) simply provide a sensible error message and a toString.
- * TODO maybe we want to attempt to serialize fields instead?  (but being careful not to be self-referential!)
- */
-public class ErrorAndToStringUnknownTypeSerializer extends UnknownSerializer {
-
-    private static final Logger log = LoggerFactory.getLogger(ErrorAndToStringUnknownTypeSerializer.class);
-    private static Set<String> WARNED_CLASSES = Collections.synchronizedSet(MutableSet.<String>of());
-
-    @Override
-    public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
-        if (BidiSerialization.isStrictSerialization())
-            throw new JsonMappingException("Cannot serialize object containing "+value.getClass().getName()+" when strict serialization requested");
-
-        serializeFromError(jgen.getOutputContext(), null, value, jgen, provider);
-    }
-
-    public void serializeFromError(JsonStreamContext ctxt, @Nullable Exception error, Object value, JsonGenerator jgen, SerializerProvider configurableSerializerProvider) throws IOException {
-        if (log.isDebugEnabled())
-            log.debug("Recovering from json serialization error, serializing "+value+": "+error);
-
-        if (BidiSerialization.isStrictSerialization())
-            throw new JsonMappingException("Cannot serialize "
-                + (ctxt!=null && !ctxt.inRoot() ? "object containing " : "")
-                + value.getClass().getName()+" when strict serialization requested");
-
-        if (WARNED_CLASSES.add(value.getClass().getCanonicalName())) {
-            log.warn("Standard serialization not possible for "+value.getClass()+" ("+value+")", error);
-        }
-        JsonStreamContext newCtxt = jgen.getOutputContext();
-
-        // very odd, but flush seems necessary when working with large objects; presumably a buffer which is allowed to clear itself?
-        // without this, when serializing the large (1.5M) Server json object from BrooklynJacksonSerializerTest creates invalid json,
-        // containing:  "foo":false,"{"error":true,...
-        jgen.flush();
-
-        boolean createObject = !newCtxt.inObject() || newCtxt.getCurrentName()!=null;
-        if (createObject) {
-            jgen.writeStartObject();
-        }
-
-        if (allowEmpty(value.getClass())) {
-            // write nothing
-        } else {
-
-            jgen.writeFieldName("error");
-            jgen.writeBoolean(true);
-
-            jgen.writeFieldName("errorType");
-            jgen.writeString(NotSerializableException.class.getCanonicalName());
-
-            jgen.writeFieldName("type");
-            jgen.writeString(value.getClass().getCanonicalName());
-
-            jgen.writeFieldName("toString");
-            jgen.writeString(value.toString());
-
-            if (error!=null) {
-                jgen.writeFieldName("causedByError");
-                jgen.writeString(error.toString());
-            }
-
-        }
-
-        if (createObject) {
-            jgen.writeEndObject();
-        }
-
-        while (newCtxt!=null && !newCtxt.equals(ctxt)) {
-            if (jgen.getOutputContext().inArray()) { jgen.writeEndArray(); continue; }
-            if (jgen.getOutputContext().inObject()) { jgen.writeEndObject(); continue; }
-            break;
-        }
-
-    }
-
-    protected boolean allowEmpty(Class<? extends Object> clazz) {
-        if (clazz.getAnnotation(JsonSerialize.class)!=null && Reflections.hasNoNonObjectFields(clazz)) {
-            return true;
-        } else {
-            return false;
-        }
-    }
-}


[33/34] brooklyn-server git commit: remove deprecation warnings in launcher

Posted by he...@apache.org.
remove deprecation warnings in launcher


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

Branch: refs/heads/master
Commit: bdd502fbd8d858b9051f5a9d9874600c78b6b20c
Parents: 96d804a
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Thu Feb 18 14:24:33 2016 +0000
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Thu Feb 18 14:24:33 2016 +0000

----------------------------------------------------------------------
 .../brooklyn/launcher/BrooklynLauncher.java     | 27 ++++++++++++--------
 1 file changed, 17 insertions(+), 10 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/bdd502fb/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynLauncher.java
----------------------------------------------------------------------
diff --git a/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynLauncher.java b/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynLauncher.java
index e5d4c01..1a2224e 100644
--- a/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynLauncher.java
+++ b/launcher/src/main/java/org/apache/brooklyn/launcher/BrooklynLauncher.java
@@ -27,11 +27,11 @@ import java.net.InetAddress;
 import java.nio.file.Files;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.LinkedHashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.TimeoutException;
+
 import javax.annotation.Nullable;
 
 import org.apache.brooklyn.api.entity.Application;
@@ -55,7 +55,6 @@ import org.apache.brooklyn.core.catalog.internal.CatalogInitialization;
 import org.apache.brooklyn.core.config.ConfigPredicates;
 import org.apache.brooklyn.core.entity.Entities;
 import org.apache.brooklyn.core.entity.StartableApplication;
-import org.apache.brooklyn.core.entity.factory.ApplicationBuilder;
 import org.apache.brooklyn.core.entity.trait.Startable;
 import org.apache.brooklyn.core.internal.BrooklynProperties;
 import org.apache.brooklyn.core.location.PortRanges;
@@ -135,7 +134,8 @@ public class BrooklynLauncher {
     private final List<Location> locations = new ArrayList<Location>();
 
     private final List<Application> appsToManage = new ArrayList<Application>();
-    private final List<ApplicationBuilder> appBuildersToManage = new ArrayList<ApplicationBuilder>();
+    @SuppressWarnings("deprecation") // TODO convert to EntitySpec; should be easy when users not allowed to pass in a builder
+    private final List<org.apache.brooklyn.core.entity.factory.ApplicationBuilder> appBuildersToManage = new ArrayList<org.apache.brooklyn.core.entity.factory.ApplicationBuilder>();
     private final List<String> yamlAppsToManage = new ArrayList<String>();
     private final List<Application> apps = new ArrayList<Application>();
     
@@ -211,9 +211,13 @@ public class BrooklynLauncher {
      * The application will not be started as part of this call (callers can
      * subsequently call {@link #start()} or {@link #getApplications()}.
      * 
-     * @see #application(Application)
+     * @see #application(EntitySpec)
+     * 
+     * @deprecated since 0.9.0; instead use {@link #application(String)} for YAML apps, or {@link #application(EntitySpec)}.
+     *             Note that apps are now auto-managed on construction through EntitySpec/YAML.
      */
-    public BrooklynLauncher application(ApplicationBuilder appBuilder) {
+    public BrooklynLauncher application(org.apache.brooklyn.core.entity.factory.ApplicationBuilder appBuilder) {
+        LOG.warn("Caller supplied ApplicationBuilder; convert to EntitySpec as this style builder may not be supported in future.");
         appBuildersToManage.add(checkNotNull(appBuilder, "appBuilder"));
         return this;
     }
@@ -226,8 +230,9 @@ public class BrooklynLauncher {
      * 
      * @see #application(Application)
      */
+    @SuppressWarnings("deprecation")  // when appsToManage is EntitySpec this will no longer be needed
     public BrooklynLauncher application(EntitySpec<? extends StartableApplication> appSpec) {
-        appBuildersToManage.add(new ApplicationBuilder(checkNotNull(appSpec, "appSpec")) {
+        appBuildersToManage.add(new org.apache.brooklyn.core.entity.factory.ApplicationBuilder(checkNotNull(appSpec, "appSpec")) {
                 @Override protected void doBuild() {
                 }});
         return this;
@@ -802,7 +807,7 @@ public class BrooklynLauncher {
                     BrooklynWebConfig.SECURITY_PROVIDER_INSTANCE,
                     new BrooklynUserWithRandomPasswordSecurityProvider(managementContext));
         } else {
-            LOG.debug("Starting Brooklyn using security properties: "+brooklynProperties.submap(ConfigPredicates.startingWith(BrooklynWebConfig.BASE_NAME_SECURITY)).asMapWithStringKeys());
+            LOG.debug("Starting Brooklyn using security properties: "+brooklynProperties.submap(ConfigPredicates.nameStartsWith(BrooklynWebConfig.BASE_NAME_SECURITY)).asMapWithStringKeys());
         }
         if (bindAddress == null) bindAddress = Networking.ANY_NIC;
 
@@ -937,8 +942,9 @@ public class BrooklynLauncher {
         rebindManager.startPersistence();
     }
 
+    @SuppressWarnings("deprecation")
     protected void createApps() {
-        for (ApplicationBuilder appBuilder : appBuildersToManage) {
+        for (org.apache.brooklyn.core.entity.factory.ApplicationBuilder appBuilder : appBuildersToManage) {
             StartableApplication app = appBuilder.manage(managementContext);
             apps.add(app);
         }
@@ -953,6 +959,8 @@ public class BrooklynLauncher {
         }
     }
 
+    @SuppressWarnings("deprecation")
+    // TODO convert to spec -- should be easy
     protected void startBrooklynNode() {
         final String classpath = System.getenv("INITIAL_CLASSPATH");
         if (Strings.isBlank(classpath)) {
@@ -963,8 +971,7 @@ public class BrooklynLauncher {
             LOG.info("Skipping BrooklynNode entity creation, BrooklynWebServer not running");
             return;
         }
-        ApplicationBuilder brooklyn = new ApplicationBuilder() {
-            @SuppressWarnings("deprecation")
+        org.apache.brooklyn.core.entity.factory.ApplicationBuilder brooklyn = new org.apache.brooklyn.core.entity.factory.ApplicationBuilder() {
             @Override
             protected void doBuild() {
                 addChild(EntitySpec.create(LocalBrooklynNode.class)


[04/34] brooklyn-server git commit: [BROOKLYN-183] REST API using CXF JAX-RS 2.0 implementation

Posted by he...@apache.org.
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java
deleted file mode 100644
index 8b49763..0000000
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java
+++ /dev/null
@@ -1,701 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.rest.resources;
-
-import static com.google.common.collect.Iterables.find;
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.assertTrue;
-
-import java.io.IOException;
-import java.net.URI;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.TimeoutException;
-
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.MultivaluedMap;
-import javax.ws.rs.core.Response;
-
-import org.apache.brooklyn.api.entity.Application;
-import org.apache.brooklyn.core.entity.Attributes;
-import org.apache.brooklyn.core.entity.Entities;
-import org.apache.brooklyn.core.entity.EntityFunctions;
-import org.apache.brooklyn.core.entity.EntityPredicates;
-import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
-import org.apache.brooklyn.core.location.AbstractLocation;
-import org.apache.brooklyn.core.location.LocationConfigKeys;
-import org.apache.brooklyn.core.location.geo.HostGeoInfo;
-import org.apache.brooklyn.core.location.internal.LocationInternal;
-import org.apache.brooklyn.entity.stock.BasicApplication;
-import org.apache.brooklyn.entity.stock.BasicEntity;
-import org.apache.brooklyn.rest.domain.ApiError;
-import org.apache.brooklyn.rest.domain.ApplicationSpec;
-import org.apache.brooklyn.rest.domain.ApplicationSummary;
-import org.apache.brooklyn.rest.domain.CatalogEntitySummary;
-import org.apache.brooklyn.rest.domain.CatalogItemSummary;
-import org.apache.brooklyn.rest.domain.EffectorSummary;
-import org.apache.brooklyn.rest.domain.EntityConfigSummary;
-import org.apache.brooklyn.rest.domain.EntitySpec;
-import org.apache.brooklyn.rest.domain.EntitySummary;
-import org.apache.brooklyn.rest.domain.PolicySummary;
-import org.apache.brooklyn.rest.domain.SensorSummary;
-import org.apache.brooklyn.rest.domain.Status;
-import org.apache.brooklyn.rest.domain.TaskSummary;
-import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
-import org.apache.brooklyn.rest.testing.mocks.CapitalizePolicy;
-import org.apache.brooklyn.rest.testing.mocks.NameMatcherGroup;
-import org.apache.brooklyn.rest.testing.mocks.RestMockApp;
-import org.apache.brooklyn.rest.testing.mocks.RestMockAppBuilder;
-import org.apache.brooklyn.rest.testing.mocks.RestMockSimpleEntity;
-import org.apache.brooklyn.test.Asserts;
-import org.apache.brooklyn.util.collections.CollectionFunctionals;
-import org.apache.brooklyn.util.exceptions.Exceptions;
-import org.apache.brooklyn.util.http.HttpAsserts;
-import org.apache.brooklyn.util.text.Strings;
-import org.apache.brooklyn.util.time.Duration;
-import org.apache.http.HttpHeaders;
-import org.apache.http.entity.ContentType;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.Assert;
-import org.testng.annotations.Test;
-
-import com.google.common.base.Predicate;
-import com.google.common.base.Predicates;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Maps;
-import com.sun.jersey.api.client.ClientResponse;
-import com.sun.jersey.api.client.GenericType;
-import com.sun.jersey.api.client.UniformInterfaceException;
-import com.sun.jersey.api.client.WebResource;
-import com.sun.jersey.core.util.MultivaluedMapImpl;
-
-@Test(singleThreaded = true)
-public class ApplicationResourceTest extends BrooklynRestResourceTest {
-
-    /*
-     * In simpleSpec, not using EverythingGroup because caused problems! The group is a child of the
-     * app, and the app is a member of the group. It failed in jenkins with:
-     *   BasicApplicationImpl{id=GSPjBCe4} GSPjBCe4
-     *     service.isUp: true
-     *     service.problems: {service-lifecycle-indicators-from-children-and-members=Required entity not healthy: EverythingGroupImpl{id=KQ4mSEOJ}}
-     *     service.state: on-fire
-     *     service.state.expected: running @ 1412003485617 / Mon Sep 29 15:11:25 UTC 2014
-     *   EverythingGroupImpl{id=KQ4mSEOJ} KQ4mSEOJ
-     *     service.isUp: true
-     *     service.problems: {service-lifecycle-indicators-from-children-and-members=Required entities not healthy: BasicApplicationImpl{id=GSPjBCe4}, EverythingGroupImpl{id=KQ4mSEOJ}}
-     *     service.state: on-fire
-     * I'm guessing there's a race: the app was not yet healthy because EverythingGroup hadn't set itself to running; 
-     * but then the EverythingGroup would never transition to healthy because one of its members was not healthy.
-     */
-
-    private static final Logger log = LoggerFactory.getLogger(ApplicationResourceTest.class);
-    
-    private final ApplicationSpec simpleSpec = ApplicationSpec.builder().name("simple-app")
-          .entities(ImmutableSet.of(
-                  new EntitySpec("simple-ent", RestMockSimpleEntity.class.getName()),
-                  new EntitySpec("simple-group", NameMatcherGroup.class.getName(), ImmutableMap.of("namematchergroup.regex", "simple-ent"))
-          ))
-          .locations(ImmutableSet.of("localhost"))
-          .build();
-
-    // Convenience for finding an EntitySummary within a collection, based on its name
-    private static Predicate<EntitySummary> withName(final String name) {
-        return new Predicate<EntitySummary>() {
-            public boolean apply(EntitySummary input) {
-                return name.equals(input.getName());
-            }
-        };
-    }
-
-    // Convenience for finding a Map within a collection, based on the value of one of its keys
-    private static Predicate<? super Map<?,?>> withValueForKey(final Object key, final Object value) {
-        return new Predicate<Object>() {
-            public boolean apply(Object input) {
-                if (!(input instanceof Map)) return false;
-                return value.equals(((Map<?, ?>) input).get(key));
-            }
-        };
-    }
-
-    @Test
-    public void testGetUndefinedApplication() {
-        try {
-            client().resource("/v1/applications/dummy-not-found").get(ApplicationSummary.class);
-        } catch (UniformInterfaceException e) {
-            assertEquals(e.getResponse().getStatus(), 404);
-        }
-    }
-
-    private static void assertRegexMatches(String actual, String patternExpected) {
-        if (actual==null) Assert.fail("Actual value is null; expected "+patternExpected);
-        if (!actual.matches(patternExpected)) {
-            Assert.fail("Text '"+actual+"' does not match expected pattern "+patternExpected);
-        }
-    }
-
-    @Test
-    public void testDeployApplication() throws Exception {
-        ClientResponse response = clientDeploy(simpleSpec);
-
-        HttpAsserts.assertHealthyStatusCode(response.getStatus());
-        assertEquals(getManagementContext().getApplications().size(), 1);
-        assertRegexMatches(response.getLocation().getPath(), "/v1/applications/.*");
-        // Object taskO = response.getEntity(Object.class);
-        TaskSummary task = response.getEntity(TaskSummary.class);
-        log.info("deployed, got " + task);
-        assertEquals(task.getEntityId(), getManagementContext().getApplications().iterator().next().getApplicationId());
-
-        waitForApplicationToBeRunning(response.getLocation());
-    }
-
-    @Test(dependsOnMethods = { "testDeleteApplication" })
-    // this must happen after we've deleted the main application, as testLocatedLocations assumes a single location
-    public void testDeployApplicationImpl() throws Exception {
-    ApplicationSpec spec = ApplicationSpec.builder()
-            .type(RestMockApp.class.getCanonicalName())
-            .name("simple-app-impl")
-            .locations(ImmutableSet.of("localhost"))
-            .build();
-
-        ClientResponse response = clientDeploy(spec);
-        assertTrue(response.getStatus() / 100 == 2, "response is " + response);
-
-        // Expect app to be running
-        URI appUri = response.getLocation();
-        waitForApplicationToBeRunning(response.getLocation());
-        assertEquals(client().resource(appUri).get(ApplicationSummary.class).getSpec().getName(), "simple-app-impl");
-    }
-
-    @Test(dependsOnMethods = { "testDeployApplication", "testLocatedLocation" })
-    public void testDeployApplicationFromInterface() throws Exception {
-        ApplicationSpec spec = ApplicationSpec.builder()
-                .type(BasicApplication.class.getCanonicalName())
-                .name("simple-app-interface")
-                .locations(ImmutableSet.of("localhost"))
-                .build();
-
-        ClientResponse response = clientDeploy(spec);
-        assertTrue(response.getStatus() / 100 == 2, "response is " + response);
-
-        // Expect app to be running
-        URI appUri = response.getLocation();
-        waitForApplicationToBeRunning(response.getLocation());
-        assertEquals(client().resource(appUri).get(ApplicationSummary.class).getSpec().getName(), "simple-app-interface");
-    }
-
-    @Test(dependsOnMethods = { "testDeployApplication", "testLocatedLocation" })
-    public void testDeployApplicationFromBuilder() throws Exception {
-        ApplicationSpec spec = ApplicationSpec.builder()
-                .type(RestMockAppBuilder.class.getCanonicalName())
-                .name("simple-app-builder")
-                .locations(ImmutableSet.of("localhost"))
-                .build();
-
-        ClientResponse response = clientDeploy(spec);
-        assertTrue(response.getStatus() / 100 == 2, "response is " + response);
-
-        // Expect app to be running
-        URI appUri = response.getLocation();
-        waitForApplicationToBeRunning(response.getLocation(), Duration.TEN_SECONDS);
-        assertEquals(client().resource(appUri).get(ApplicationSummary.class).getSpec().getName(), "simple-app-builder");
-
-        // Expect app to have the child-entity
-        Set<EntitySummary> entities = client().resource(appUri.toString() + "/entities")
-                .get(new GenericType<Set<EntitySummary>>() {});
-        assertEquals(entities.size(), 1);
-        assertEquals(Iterables.getOnlyElement(entities).getName(), "child1");
-        assertEquals(Iterables.getOnlyElement(entities).getType(), RestMockSimpleEntity.class.getCanonicalName());
-    }
-
-    @Test(dependsOnMethods = { "testDeployApplication", "testLocatedLocation" })
-    public void testDeployApplicationYaml() throws Exception {
-        String yaml = "{ name: simple-app-yaml, location: localhost, services: [ { serviceType: "+BasicApplication.class.getCanonicalName()+" } ] }";
-
-        ClientResponse response = client().resource("/v1/applications")
-                .entity(yaml, "application/x-yaml")
-                .post(ClientResponse.class);
-        assertTrue(response.getStatus()/100 == 2, "response is "+response);
-
-        // Expect app to be running
-        URI appUri = response.getLocation();
-        waitForApplicationToBeRunning(response.getLocation());
-        assertEquals(client().resource(appUri).get(ApplicationSummary.class).getSpec().getName(), "simple-app-yaml");
-    }
-
-    @Test
-    public void testReferenceCatalogEntity() throws Exception {
-        getManagementContext().getCatalog().addItems("{ name: "+BasicEntity.class.getName()+", "
-            + "services: [ { type: "+BasicEntity.class.getName()+" } ] }");
-
-        String yaml = "{ name: simple-app-yaml, location: localhost, services: [ { type: " + BasicEntity.class.getName() + " } ] }";
-
-        ClientResponse response = client().resource("/v1/applications")
-                .entity(yaml, "application/x-yaml")
-                .post(ClientResponse.class);
-        assertTrue(response.getStatus()/100 == 2, "response is "+response);
-
-        // Expect app to be running
-        URI appUri = response.getLocation();
-        waitForApplicationToBeRunning(response.getLocation());
-        assertEquals(client().resource(appUri).get(ApplicationSummary.class).getSpec().getName(), "simple-app-yaml");
-
-        ClientResponse response2 = client().resource(appUri.getPath())
-                .delete(ClientResponse.class);
-        assertEquals(response2.getStatus(), Response.Status.ACCEPTED.getStatusCode());
-    }
-
-    @Test
-    public void testDeployWithInvalidEntityType() {
-        try {
-            clientDeploy(ApplicationSpec.builder()
-                    .name("invalid-app")
-                    .entities(ImmutableSet.of(new EntitySpec("invalid-ent", "not.existing.entity")))
-                    .locations(ImmutableSet.of("localhost"))
-                    .build());
-
-        } catch (UniformInterfaceException e) {
-            ApiError error = e.getResponse().getEntity(ApiError.class);
-            assertEquals(error.getMessage(), "Undefined type 'not.existing.entity'");
-        }
-    }
-
-    @Test
-    public void testDeployWithInvalidLocation() {
-        try {
-            clientDeploy(ApplicationSpec.builder()
-                    .name("invalid-app")
-                    .entities(ImmutableSet.of(new EntitySpec("simple-ent", RestMockSimpleEntity.class.getName())))
-                    .locations(ImmutableSet.of("3423"))
-                    .build());
-
-        } catch (UniformInterfaceException e) {
-            ApiError error = e.getResponse().getEntity(ApiError.class);
-            assertEquals(error.getMessage(), "Undefined location '3423'");
-        }
-    }
-
-    @Test(dependsOnMethods = "testDeployApplication")
-    public void testListEntities() {
-        Set<EntitySummary> entities = client().resource("/v1/applications/simple-app/entities")
-                .get(new GenericType<Set<EntitySummary>>() {});
-
-        assertEquals(entities.size(), 2);
-
-        EntitySummary entity = Iterables.find(entities, withName("simple-ent"), null);
-        EntitySummary group = Iterables.find(entities, withName("simple-group"), null);
-        Assert.assertNotNull(entity);
-        Assert.assertNotNull(group);
-
-        client().resource(entity.getLinks().get("self")).get(ClientResponse.class);
-
-        Set<EntitySummary> children = client().resource(entity.getLinks().get("children"))
-                .get(new GenericType<Set<EntitySummary>>() {});
-        assertEquals(children.size(), 0);
-    }
-
-    @Test(dependsOnMethods = "testDeployApplication")
-    public void testListApplications() {
-        Set<ApplicationSummary> applications = client().resource("/v1/applications")
-                .get(new GenericType<Set<ApplicationSummary>>() { });
-        log.info("Applications listed are: " + applications);
-        for (ApplicationSummary app : applications) {
-            if (simpleSpec.getName().equals(app.getSpec().getName())) return;
-        }
-        Assert.fail("simple-app not found in list of applications: "+applications);
-    }
-
-    @Test(dependsOnMethods = "testDeployApplication")
-    public void testGetApplicationOnFire() {
-        Application app = Iterables.find(manager.getApplications(), EntityPredicates.displayNameEqualTo(simpleSpec.getName()));
-        Lifecycle origState = app.getAttribute(Attributes.SERVICE_STATE_ACTUAL);
-        
-        ApplicationSummary summary = client().resource("/v1/applications/"+app.getId())
-                .get(ApplicationSummary.class);
-        assertEquals(summary.getStatus(), Status.RUNNING);
-
-        app.sensors().set(Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
-        try {
-            ApplicationSummary summary2 = client().resource("/v1/applications/"+app.getId())
-                    .get(ApplicationSummary.class);
-            log.info("Application: " + summary2);
-            assertEquals(summary2.getStatus(), Status.ERROR);
-            
-        } finally {
-            app.sensors().set(Attributes.SERVICE_STATE_ACTUAL, origState);
-        }
-    }
-
-    @SuppressWarnings({ "rawtypes", "unchecked" })
-    @Test(dependsOnMethods = "testDeployApplication")
-    public void testFetchApplicationsAndEntity() {
-        Collection apps = client().resource("/v1/applications/fetch").get(Collection.class);
-        log.info("Applications fetched are: " + apps);
-
-        Map app = null;
-        for (Object appI : apps) {
-            Object name = ((Map) appI).get("name");
-            if ("simple-app".equals(name)) {
-                app = (Map) appI;
-            }
-            if (ImmutableSet.of("simple-ent", "simple-group").contains(name))
-                Assert.fail(name + " should not be listed at high level: " + apps);
-        }
-
-        Assert.assertNotNull(app);
-        Assert.assertFalse(Strings.isBlank((String) app.get("applicationId")),
-                "expected value for applicationId, was: " + app.get("applicationId"));
-        Assert.assertFalse(Strings.isBlank((String) app.get("serviceState")),
-                "expected value for serviceState, was: " + app.get("serviceState"));
-        Assert.assertNotNull(app.get("serviceUp"), "expected non-null value for serviceUp");
-
-        Collection children = (Collection) app.get("children");
-        Assert.assertEquals(children.size(), 2);
-
-        Map entitySummary = (Map) Iterables.find(children, withValueForKey("name", "simple-ent"), null);
-        Map groupSummary = (Map) Iterables.find(children, withValueForKey("name", "simple-group"), null);
-        Assert.assertNotNull(entitySummary);
-        Assert.assertNotNull(groupSummary);
-
-        String itemIds = app.get("id") + "," + entitySummary.get("id") + "," + groupSummary.get("id");
-        Collection entities = client().resource("/v1/applications/fetch?items="+itemIds)
-                .get(Collection.class);
-        log.info("Applications+Entities fetched are: " + entities);
-
-        Assert.assertEquals(entities.size(), apps.size() + 2);
-        Map entityDetails = (Map) Iterables.find(entities, withValueForKey("name", "simple-ent"), null);
-        Map groupDetails = (Map) Iterables.find(entities, withValueForKey("name", "simple-group"), null);
-        Assert.assertNotNull(entityDetails);
-        Assert.assertNotNull(groupDetails);
-
-        Assert.assertEquals(entityDetails.get("parentId"), app.get("id"));
-        Assert.assertNull(entityDetails.get("children"));
-        Assert.assertEquals(groupDetails.get("parentId"), app.get("id"));
-        Assert.assertNull(groupDetails.get("children"));
-
-        Collection entityGroupIds = (Collection) entityDetails.get("groupIds");
-        Assert.assertNotNull(entityGroupIds);
-        Assert.assertEquals(entityGroupIds.size(), 1);
-        Assert.assertEquals(entityGroupIds.iterator().next(), groupDetails.get("id"));
-
-        Collection groupMembers = (Collection) groupDetails.get("members");
-        Assert.assertNotNull(groupMembers);
-
-        for (Application appi : getManagementContext().getApplications()) {
-            Entities.dumpInfo(appi);
-        }
-        log.info("MEMBERS: " + groupMembers);
-
-        Assert.assertEquals(groupMembers.size(), 1);
-        Map entityMemberDetails = (Map) Iterables.find(groupMembers, withValueForKey("name", "simple-ent"), null);
-        Assert.assertNotNull(entityMemberDetails);
-        Assert.assertEquals(entityMemberDetails.get("id"), entityDetails.get("id"));
-    }
-
-    @Test(dependsOnMethods = "testDeployApplication")
-    public void testListSensors() {
-        Set<SensorSummary> sensors = client().resource("/v1/applications/simple-app/entities/simple-ent/sensors")
-                .get(new GenericType<Set<SensorSummary>>() { });
-        assertTrue(sensors.size() > 0);
-        SensorSummary sample = Iterables.find(sensors, new Predicate<SensorSummary>() {
-            @Override
-            public boolean apply(SensorSummary sensorSummary) {
-                return sensorSummary.getName().equals(RestMockSimpleEntity.SAMPLE_SENSOR.getName());
-            }
-        });
-        assertEquals(sample.getType(), "java.lang.String");
-    }
-
-    @Test(dependsOnMethods = "testDeployApplication")
-    public void testListConfig() {
-        Set<EntityConfigSummary> config = client().resource("/v1/applications/simple-app/entities/simple-ent/config")
-                .get(new GenericType<Set<EntityConfigSummary>>() { });
-        assertTrue(config.size() > 0);
-        System.out.println(("CONFIG: " + config));
-    }
-
-    @Test(dependsOnMethods = "testListConfig")
-    public void testListConfig2() {
-        Set<EntityConfigSummary> config = client().resource("/v1/applications/simple-app/entities/simple-ent/config")
-                .get(new GenericType<Set<EntityConfigSummary>>() {});
-        assertTrue(config.size() > 0);
-        System.out.println(("CONFIG: " + config));
-    }
-
-    @Test(dependsOnMethods = "testDeployApplication")
-    public void testListEffectors() {
-        Set<EffectorSummary> effectors = client().resource("/v1/applications/simple-app/entities/simple-ent/effectors")
-                .get(new GenericType<Set<EffectorSummary>>() {});
-
-        assertTrue(effectors.size() > 0);
-
-        EffectorSummary sampleEffector = find(effectors, new Predicate<EffectorSummary>() {
-            @Override
-            public boolean apply(EffectorSummary input) {
-                return input.getName().equals("sampleEffector");
-            }
-        });
-        assertEquals(sampleEffector.getReturnType(), "java.lang.String");
-    }
-
-    @Test(dependsOnMethods = "testListSensors")
-    public void testTriggerSampleEffector() throws InterruptedException, IOException {
-        ClientResponse response = client()
-                .resource("/v1/applications/simple-app/entities/simple-ent/effectors/"+RestMockSimpleEntity.SAMPLE_EFFECTOR.getName())
-                .type(MediaType.APPLICATION_JSON_TYPE)
-                .post(ClientResponse.class, ImmutableMap.of("param1", "foo", "param2", 4));
-
-        assertEquals(response.getStatus(), Response.Status.ACCEPTED.getStatusCode());
-
-        String result = response.getEntity(String.class);
-        assertEquals(result, "foo4");
-    }
-
-    @Test(dependsOnMethods = "testListSensors")
-    public void testTriggerSampleEffectorWithFormData() throws InterruptedException, IOException {
-        MultivaluedMap<String, String> data = new MultivaluedMapImpl();
-        data.add("param1", "foo");
-        data.add("param2", "4");
-        ClientResponse response = client()
-                .resource("/v1/applications/simple-app/entities/simple-ent/effectors/"+RestMockSimpleEntity.SAMPLE_EFFECTOR.getName())
-                .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE)
-                .post(ClientResponse.class, data);
-
-        assertEquals(response.getStatus(), Response.Status.ACCEPTED.getStatusCode());
-
-        String result = response.getEntity(String.class);
-        assertEquals(result, "foo4");
-    }
-
-    @Test(dependsOnMethods = "testTriggerSampleEffector")
-    public void testBatchSensorValues() {
-        WebResource resource = client().resource("/v1/applications/simple-app/entities/simple-ent/sensors/current-state");
-        Map<String, Object> sensors = resource.get(new GenericType<Map<String, Object>>() {});
-        assertTrue(sensors.size() > 0);
-        assertEquals(sensors.get(RestMockSimpleEntity.SAMPLE_SENSOR.getName()), "foo4");
-    }
-
-    @Test(dependsOnMethods = "testBatchSensorValues")
-    public void testReadEachSensor() {
-    Set<SensorSummary> sensors = client().resource("/v1/applications/simple-app/entities/simple-ent/sensors")
-            .get(new GenericType<Set<SensorSummary>>() {});
-
-        Map<String, String> readings = Maps.newHashMap();
-        for (SensorSummary sensor : sensors) {
-            try {
-                readings.put(sensor.getName(), client().resource(sensor.getLinks().get("self")).accept(MediaType.TEXT_PLAIN).get(String.class));
-            } catch (UniformInterfaceException uie) {
-                if (uie.getResponse().getStatus() == 204) { // no content
-                    readings.put(sensor.getName(), null);
-                } else {
-                    Exceptions.propagate(uie);
-                }
-            }
-        }
-
-        assertEquals(readings.get(RestMockSimpleEntity.SAMPLE_SENSOR.getName()), "foo4");
-    }
-
-    @Test(dependsOnMethods = "testTriggerSampleEffector")
-    public void testPolicyWhichCapitalizes() {
-        String policiesEndpoint = "/v1/applications/simple-app/entities/simple-ent/policies";
-        Set<PolicySummary> policies = client().resource(policiesEndpoint).get(new GenericType<Set<PolicySummary>>(){});
-        assertEquals(policies.size(), 0);
-
-        ClientResponse response = client().resource(policiesEndpoint)
-                .queryParam("type", CapitalizePolicy.class.getCanonicalName())
-                .type(MediaType.APPLICATION_JSON_TYPE)
-                .post(ClientResponse.class, Maps.newHashMap());
-        assertEquals(response.getStatus(), 200);
-        PolicySummary policy = response.getEntity(PolicySummary.class);
-        assertNotNull(policy.getId());
-        String newPolicyId = policy.getId();
-        log.info("POLICY CREATED: " + newPolicyId);
-        policies = client().resource(policiesEndpoint).get(new GenericType<Set<PolicySummary>>() {});
-        assertEquals(policies.size(), 1);
-
-        Lifecycle status = client().resource(policiesEndpoint + "/" + newPolicyId).get(Lifecycle.class);
-        log.info("POLICY STATUS: " + status);
-
-        response = client().resource(policiesEndpoint+"/"+newPolicyId+"/start")
-                .post(ClientResponse.class);
-        assertEquals(response.getStatus(), 204);
-        status = client().resource(policiesEndpoint + "/" + newPolicyId).get(Lifecycle.class);
-        assertEquals(status, Lifecycle.RUNNING);
-
-        response = client().resource(policiesEndpoint+"/"+newPolicyId+"/stop")
-                .post(ClientResponse.class);
-        assertEquals(response.getStatus(), 204);
-        status = client().resource(policiesEndpoint + "/" + newPolicyId).get(Lifecycle.class);
-        assertEquals(status, Lifecycle.STOPPED);
-
-        response = client().resource(policiesEndpoint+"/"+newPolicyId+"/destroy")
-                .post(ClientResponse.class);
-        assertEquals(response.getStatus(), 204);
-
-        response = client().resource(policiesEndpoint+"/"+newPolicyId).get(ClientResponse.class);
-        log.info("POLICY STATUS RESPONSE AFTER DESTROY: " + response.getStatus());
-        assertEquals(response.getStatus(), 404);
-
-        policies = client().resource(policiesEndpoint).get(new GenericType<Set<PolicySummary>>() {});
-        assertEquals(0, policies.size());
-    }
-
-    @SuppressWarnings({ "rawtypes" })
-    @Test(dependsOnMethods = "testDeployApplication")
-    public void testLocatedLocation() {
-        log.info("starting testLocatedLocations");
-        testListApplications();
-
-        LocationInternal l = (LocationInternal) getManagementContext().getApplications().iterator().next().getLocations().iterator().next();
-        if (l.config().getLocalRaw(LocationConfigKeys.LATITUDE).isAbsent()) {
-            log.info("Supplying fake locations for localhost because could not be autodetected");
-            ((AbstractLocation) l).setHostGeoInfo(new HostGeoInfo("localhost", "localhost", 50, 0));
-        }
-        Map result = client().resource("/v1/locations/usage/LocatedLocations")
-                .get(Map.class);
-        log.info("LOCATIONS: " + result);
-        Assert.assertEquals(result.size(), 1);
-        Map details = (Map) result.values().iterator().next();
-        assertEquals(details.get("leafEntityCount"), 2);
-    }
-
-    @Test(dependsOnMethods = {"testListEffectors", "testFetchApplicationsAndEntity", "testTriggerSampleEffector", "testListApplications","testReadEachSensor","testPolicyWhichCapitalizes","testLocatedLocation"})
-    public void testDeleteApplication() throws TimeoutException, InterruptedException {
-        waitForPageFoundResponse("/v1/applications/simple-app", ApplicationSummary.class);
-        Collection<Application> apps = getManagementContext().getApplications();
-        log.info("Deleting simple-app from " + apps);
-        int size = apps.size();
-
-        ClientResponse response = client().resource("/v1/applications/simple-app")
-                .delete(ClientResponse.class);
-
-        assertEquals(response.getStatus(), Response.Status.ACCEPTED.getStatusCode());
-        TaskSummary task = response.getEntity(TaskSummary.class);
-        assertTrue(task.getDescription().toLowerCase().contains("destroy"), task.getDescription());
-        assertTrue(task.getDescription().toLowerCase().contains("simple-app"), task.getDescription());
-
-        waitForPageNotFoundResponse("/v1/applications/simple-app", ApplicationSummary.class);
-
-        log.info("App appears gone, apps are: " + getManagementContext().getApplications());
-        // more logging above, for failure in the check below
-
-        Asserts.eventually(
-                EntityFunctions.applications(getManagementContext()),
-                Predicates.compose(Predicates.equalTo(size-1), CollectionFunctionals.sizeFunction()) );
-    }
-
-    @Test
-    public void testDisabledApplicationCatalog() throws TimeoutException, InterruptedException {
-        String itemSymbolicName = "my.catalog.item.id.for.disabling";
-        String itemVersion = "1.0";
-        String serviceType = "org.apache.brooklyn.entity.stock.BasicApplication";
-        
-        // Deploy the catalog item
-        addTestCatalogItem(itemSymbolicName, "template", itemVersion, serviceType);
-        List<CatalogEntitySummary> itemSummaries = client().resource("/v1/catalog/applications")
-                .queryParam("fragment", itemSymbolicName).queryParam("allVersions", "true").get(new GenericType<List<CatalogEntitySummary>>() {});
-        CatalogItemSummary itemSummary = Iterables.getOnlyElement(itemSummaries);
-        String itemVersionedId = String.format("%s:%s", itemSummary.getSymbolicName(), itemSummary.getVersion());
-        assertEquals(itemSummary.getId(), itemVersionedId);
-
-        try {
-            // Create an app before disabling: this should work
-            String yaml = "{ name: my-app, location: localhost, services: [ { type: \""+itemVersionedId+"\" } ] }";
-            ClientResponse response = client().resource("/v1/applications")
-                    .entity(yaml, "application/x-yaml")
-                    .post(ClientResponse.class);
-            HttpAsserts.assertHealthyStatusCode(response.getStatus());
-            waitForPageFoundResponse("/v1/applications/my-app", ApplicationSummary.class);
-    
-            // Deprecate
-            deprecateCatalogItem(itemSymbolicName, itemVersion, true);
-
-            // Create an app when deprecated: this should work
-            String yaml2 = "{ name: my-app2, location: localhost, services: [ { type: \""+itemVersionedId+"\" } ] }";
-            ClientResponse response2 = client().resource("/v1/applications")
-                    .entity(yaml2, "application/x-yaml")
-                    .post(ClientResponse.class);
-            HttpAsserts.assertHealthyStatusCode(response2.getStatus());
-            waitForPageFoundResponse("/v1/applications/my-app2", ApplicationSummary.class);
-    
-            // Disable
-            disableCatalogItem(itemSymbolicName, itemVersion, true);
-
-            // Now try creating an app; this should fail because app is disabled
-            String yaml3 = "{ name: my-app3, location: localhost, services: [ { type: \""+itemVersionedId+"\" } ] }";
-            ClientResponse response3 = client().resource("/v1/applications")
-                    .entity(yaml3, "application/x-yaml")
-                    .post(ClientResponse.class);
-            HttpAsserts.assertClientErrorStatusCode(response3.getStatus());
-            assertTrue(response3.getEntity(String.class).contains("cannot be matched"));
-            waitForPageNotFoundResponse("/v1/applications/my-app3", ApplicationSummary.class);
-            
-        } finally {
-            client().resource("/v1/applications/my-app")
-                    .delete(ClientResponse.class);
-
-            client().resource("/v1/applications/my-app2")
-                    .delete(ClientResponse.class);
-
-            client().resource("/v1/applications/my-app3")
-                    .delete(ClientResponse.class);
-
-            client().resource("/v1/catalog/entities/"+itemVersionedId+"/"+itemVersion)
-                    .delete(ClientResponse.class);
-        }
-    }
-
-    private void deprecateCatalogItem(String symbolicName, String version, boolean deprecated) {
-        String id = String.format("%s:%s", symbolicName, version);
-        ClientResponse response = client().resource(String.format("/v1/catalog/entities/%s/deprecated", id))
-                    .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
-                    .post(ClientResponse.class, deprecated);
-        assertEquals(response.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
-    }
-    
-    private void disableCatalogItem(String symbolicName, String version, boolean disabled) {
-        String id = String.format("%s:%s", symbolicName, version);
-        ClientResponse response = client().resource(String.format("/v1/catalog/entities/%s/disabled", id))
-                .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
-                .post(ClientResponse.class, disabled);
-        assertEquals(response.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
-    }
-
-    private void addTestCatalogItem(String catalogItemId, String itemType, String version, String service) {
-        String yaml =
-                "brooklyn.catalog:\n"+
-                "  id: " + catalogItemId + "\n"+
-                "  name: My Catalog App\n"+
-                (itemType!=null ? "  item_type: "+itemType+"\n" : "")+
-                "  description: My description\n"+
-                "  icon_url: classpath:///redis-logo.png\n"+
-                "  version: " + version + "\n"+
-                "\n"+
-                "services:\n"+
-                "- type: " + service + "\n";
-
-        client().resource("/v1/catalog").post(yaml);
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/CatalogResetTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/CatalogResetTest.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/CatalogResetTest.java
deleted file mode 100644
index cadea54..0000000
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/CatalogResetTest.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.rest.resources;
-
-import static org.testng.Assert.assertNotNull;
-
-import javax.ws.rs.core.MediaType;
-
-import org.apache.http.HttpStatus;
-import org.testng.annotations.AfterClass;
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.Test;
-import org.apache.brooklyn.api.catalog.BrooklynCatalog;
-import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry;
-import org.apache.brooklyn.test.http.TestHttpRequestHandler;
-import org.apache.brooklyn.test.http.TestHttpServer;
-import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
-import org.apache.brooklyn.util.core.ResourceUtils;
-
-import com.sun.jersey.api.client.UniformInterfaceException;
-
-public class CatalogResetTest extends BrooklynRestResourceTest {
-
-    private TestHttpServer server;
-    private String serverUrl;
-
-    @BeforeClass(alwaysRun=true)
-    @Override
-    public void setUp() throws Exception {
-        useLocalScannedCatalog();
-        super.setUp();
-        server = new TestHttpServer()
-            .handler("/404", new TestHttpRequestHandler().code(HttpStatus.SC_NOT_FOUND).response("Not Found"))
-            .handler("/200", new TestHttpRequestHandler().response("OK"))
-            .start();
-        serverUrl = server.getUrl();
-    }
-
-    @Override
-    protected void addBrooklynResources() {
-        addResource(new CatalogResource());
-    }
-
-    @AfterClass(alwaysRun=true)
-    @Override
-    public void tearDown() throws Exception {
-        super.tearDown();
-        server.stop();
-    }
-
-    @Test(expectedExceptions=UniformInterfaceException.class, expectedExceptionsMessageRegExp="Client response status: 500")
-    public void testConnectionError() throws Exception {
-        reset("http://0.0.0.0/can-not-connect", false);
-    }
-
-    @Test
-    public void testConnectionErrorIgnore() throws Exception {
-        reset("http://0.0.0.0/can-not-connect", true);
-    }
-
-    @Test(expectedExceptions=UniformInterfaceException.class, expectedExceptionsMessageRegExp="Client response status: 500")
-    public void testResourceMissingError() throws Exception {
-        reset(serverUrl + "/404", false);
-    }
-
-    @Test
-    public void testResourceMissingIgnore() throws Exception {
-        reset(serverUrl + "/404", true);
-    }
-
-    @Test(expectedExceptions=UniformInterfaceException.class, expectedExceptionsMessageRegExp="Client response status: 500")
-    public void testResourceInvalidError() throws Exception {
-        reset(serverUrl + "/200", false);
-    }
-
-    @Test
-    public void testResourceInvalidIgnore() throws Exception {
-        reset(serverUrl + "/200", true);
-    }
-
-    private void reset(String bundleLocation, boolean ignoreErrors) throws Exception {
-        String xml = ResourceUtils.create(this).getResourceAsString("classpath://reset-catalog.xml");
-        client().resource("/v1/catalog/reset")
-            .queryParam("ignoreErrors", Boolean.toString(ignoreErrors))
-            .header("Content-type", MediaType.APPLICATION_XML)
-            .post(xml.replace("${bundle-location}", bundleLocation));
-        //if above succeeds assert catalog contents
-        assertItems();
-    }
-    
-    private void assertItems() {
-        BrooklynTypeRegistry types = getManagementContext().getTypeRegistry();
-        assertNotNull(types.get("org.apache.brooklyn.entity.stock.BasicApplication", BrooklynCatalog.DEFAULT_VERSION));
-        assertNotNull(types.get("org.apache.brooklyn.test.osgi.entities.SimpleApplication", BrooklynCatalog.DEFAULT_VERSION));
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/CatalogResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/CatalogResourceTest.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/CatalogResourceTest.java
deleted file mode 100644
index 921d6fc..0000000
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/CatalogResourceTest.java
+++ /dev/null
@@ -1,512 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.rest.resources;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertTrue;
-
-import java.awt.*;
-import java.io.IOException;
-import java.net.URI;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-
-import org.apache.brooklyn.api.typereg.OsgiBundleWithUrl;
-import org.apache.brooklyn.api.typereg.RegisteredType;
-import org.apache.brooklyn.core.catalog.internal.CatalogUtils;
-import org.apache.brooklyn.core.mgmt.osgi.OsgiStandaloneTest;
-import org.apache.brooklyn.core.test.entity.TestEntity;
-import org.apache.brooklyn.policy.autoscaling.AutoScalerPolicy;
-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.testing.BrooklynRestResourceTest;
-import org.apache.brooklyn.test.support.TestResourceUnavailableException;
-import org.apache.brooklyn.util.javalang.Reflections;
-import org.apache.http.HttpHeaders;
-import org.apache.http.entity.ContentType;
-import org.eclipse.jetty.http.HttpStatus;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.Assert;
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.Test;
-import org.testng.reporters.Files;
-
-import com.google.common.base.Joiner;
-import com.google.common.collect.Iterables;
-import com.sun.jersey.api.client.ClientResponse;
-import com.sun.jersey.api.client.GenericType;
-
-public class CatalogResourceTest extends BrooklynRestResourceTest {
-
-    private static final Logger log = LoggerFactory.getLogger(CatalogResourceTest.class);
-    
-    private static String TEST_VERSION = "0.1.2";
-
-    @BeforeClass(alwaysRun=true)
-    @Override
-    public void setUp() throws Exception {
-        useLocalScannedCatalog();
-        super.setUp();
-    }
-    
-    @Override
-    protected void addBrooklynResources() {
-        addResource(new CatalogResource());
-    }
-
-    @Test
-    /** based on CampYamlLiteTest */
-    public void testRegisterCustomEntityTopLevelSyntaxWithBundleWhereEntityIsFromCoreAndIconFromBundle() {
-        TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH);
-
-        String symbolicName = "my.catalog.entity.id";
-        String bundleUrl = OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL;
-        String yaml =
-                "brooklyn.catalog:\n"+
-                "  id: " + symbolicName + "\n"+
-                "  name: My Catalog App\n"+
-                "  description: My description\n"+
-                "  icon_url: classpath:/org/apache/brooklyn/test/osgi/entities/icon.gif\n"+
-                "  version: " + TEST_VERSION + "\n"+
-                "  libraries:\n"+
-                "  - url: " + bundleUrl + "\n"+
-                "\n"+
-                "services:\n"+
-                "- type: org.apache.brooklyn.core.test.entity.TestEntity\n";
-
-        ClientResponse response = client().resource("/v1/catalog")
-                .post(ClientResponse.class, yaml);
-
-        assertEquals(response.getStatus(), Response.Status.CREATED.getStatusCode());
-
-        CatalogEntitySummary entityItem = client().resource("/v1/catalog/entities/"+symbolicName + "/" + TEST_VERSION)
-                .get(CatalogEntitySummary.class);
-
-        Assert.assertNotNull(entityItem.getPlanYaml());
-        Assert.assertTrue(entityItem.getPlanYaml().contains("org.apache.brooklyn.core.test.entity.TestEntity"));
-
-        assertEquals(entityItem.getId(), ver(symbolicName));
-        assertEquals(entityItem.getSymbolicName(), symbolicName);
-        assertEquals(entityItem.getVersion(), TEST_VERSION);
-
-        // and internally let's check we have libraries
-        RegisteredType item = getManagementContext().getTypeRegistry().get(symbolicName, TEST_VERSION);
-        Assert.assertNotNull(item);
-        Collection<OsgiBundleWithUrl> libs = item.getLibraries();
-        assertEquals(libs.size(), 1);
-        assertEquals(Iterables.getOnlyElement(libs).getUrl(), bundleUrl);
-
-        // now let's check other things on the item
-        assertEquals(entityItem.getName(), "My Catalog App");
-        assertEquals(entityItem.getDescription(), "My description");
-        assertEquals(entityItem.getIconUrl(), "/v1/catalog/icon/" + symbolicName + "/" + entityItem.getVersion());
-        assertEquals(item.getIconUrl(), "classpath:/org/apache/brooklyn/test/osgi/entities/icon.gif");
-
-        // an InterfacesTag should be created for every catalog item
-        assertEquals(entityItem.getTags().size(), 1);
-        Object tag = entityItem.getTags().iterator().next();
-        List<String> actualInterfaces = ((Map<String, List<String>>) tag).get("traits");
-        List<Class<?>> expectedInterfaces = Reflections.getAllInterfaces(TestEntity.class);
-        assertEquals(actualInterfaces.size(), expectedInterfaces.size());
-        for (Class<?> expectedInterface : expectedInterfaces) {
-            assertTrue(actualInterfaces.contains(expectedInterface.getName()));
-        }
-
-        byte[] iconData = client().resource("/v1/catalog/icon/" + symbolicName + "/" + TEST_VERSION).get(byte[].class);
-        assertEquals(iconData.length, 43);
-    }
-
-    @Test
-    public void testRegisterOsgiPolicyTopLevelSyntax() {
-        TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH);
-
-        String symbolicName = "my.catalog.policy.id";
-        String policyType = "org.apache.brooklyn.test.osgi.entities.SimplePolicy";
-        String bundleUrl = OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL;
-
-        String yaml =
-                "brooklyn.catalog:\n"+
-                "  id: " + symbolicName + "\n"+
-                "  name: My Catalog App\n"+
-                "  description: My description\n"+
-                "  version: " + TEST_VERSION + "\n" +
-                "  libraries:\n"+
-                "  - url: " + bundleUrl + "\n"+
-                "\n"+
-                "brooklyn.policies:\n"+
-                "- type: " + policyType;
-
-        CatalogPolicySummary entityItem = Iterables.getOnlyElement( client().resource("/v1/catalog")
-                .post(new GenericType<Map<String,CatalogPolicySummary>>() {}, yaml).values() );
-
-        Assert.assertNotNull(entityItem.getPlanYaml());
-        Assert.assertTrue(entityItem.getPlanYaml().contains(policyType));
-        assertEquals(entityItem.getId(), ver(symbolicName));
-        assertEquals(entityItem.getSymbolicName(), symbolicName);
-        assertEquals(entityItem.getVersion(), TEST_VERSION);
-    }
-
-    @Test
-    public void testListAllEntities() {
-        List<CatalogEntitySummary> entities = client().resource("/v1/catalog/entities")
-                .get(new GenericType<List<CatalogEntitySummary>>() {});
-        assertTrue(entities.size() > 0);
-    }
-
-    @Test
-    public void testListAllEntitiesAsItem() {
-        // ensure things are happily downcasted and unknown properties ignored (e.g. sensors, effectors)
-        List<CatalogItemSummary> entities = client().resource("/v1/catalog/entities")
-                .get(new GenericType<List<CatalogItemSummary>>() {});
-        assertTrue(entities.size() > 0);
-    }
-
-    @Test
-    public void testFilterListOfEntitiesByName() {
-        List<CatalogEntitySummary> entities = client().resource("/v1/catalog/entities")
-                .queryParam("fragment", "brOOkLynENTITYmiRrOr").get(new GenericType<List<CatalogEntitySummary>>() {});
-        assertEquals(entities.size(), 1);
-
-        log.info("BrooklynEntityMirror-like entities are: " + entities);
-
-        List<CatalogEntitySummary> entities2 = client().resource("/v1/catalog/entities")
-                .queryParam("regex", "[Bb]ro+klynEntityMi[ro]+").get(new GenericType<List<CatalogEntitySummary>>() {});
-        assertEquals(entities2.size(), 1);
-
-        assertEquals(entities, entities2);
-    
-        List<CatalogEntitySummary> entities3 = client().resource("/v1/catalog/entities")
-                .queryParam("fragment", "bweqQzZ").get(new GenericType<List<CatalogEntitySummary>>() {});
-        assertEquals(entities3.size(), 0);
-
-        List<CatalogEntitySummary> entities4 = client().resource("/v1/catalog/entities")
-                .queryParam("regex", "bweq+z+").get(new GenericType<List<CatalogEntitySummary>>() {});
-        assertEquals(entities4.size(), 0);
-    }
-
-    @Test
-    @Deprecated
-    // If we move to using a yaml catalog item, the details will be of the wrapping app,
-    // not of the entity itself, so the test won't make sense any more.
-    public void testGetCatalogEntityDetails() {
-        CatalogEntitySummary details = client()
-                .resource(URI.create("/v1/catalog/entities/org.apache.brooklyn.entity.brooklynnode.BrooklynNode"))
-                .get(CatalogEntitySummary.class);
-        assertTrue(details.toString().contains("download.url"), "expected more config, only got: "+details);
-    }
-
-    @Test
-    @Deprecated
-    // If we move to using a yaml catalog item, the details will be of the wrapping app,
-    // not of the entity itself, so the test won't make sense any more.
-    public void testGetCatalogEntityPlusVersionDetails() {
-        CatalogEntitySummary details = client()
-                .resource(URI.create("/v1/catalog/entities/org.apache.brooklyn.entity.brooklynnode.BrooklynNode:0.0.0.SNAPSHOT"))
-                .get(CatalogEntitySummary.class);
-        assertTrue(details.toString().contains("download.url"), "expected more config, only got: "+details);
-    }
-
-    @Test
-    public void testGetCatalogEntityIconDetails() throws IOException {
-        String catalogItemId = "testGetCatalogEntityIconDetails";
-        addTestCatalogItemBrooklynNodeAsEntity(catalogItemId);
-        ClientResponse response = client().resource(URI.create("/v1/catalog/icon/" + catalogItemId + "/" + TEST_VERSION))
-                .get(ClientResponse.class);
-        response.bufferEntity();
-        Assert.assertEquals(response.getStatus(), 200);
-        Assert.assertEquals(response.getType(), MediaType.valueOf("image/jpeg"));
-        Image image = Toolkit.getDefaultToolkit().createImage(Files.readFile(response.getEntityInputStream()));
-        Assert.assertNotNull(image);
-    }
-
-    private void addTestCatalogItemBrooklynNodeAsEntity(String catalogItemId) {
-        addTestCatalogItem(catalogItemId, null, TEST_VERSION, "org.apache.brooklyn.entity.brooklynnode.BrooklynNode");
-    }
-
-    private void addTestCatalogItem(String catalogItemId, String itemType, String version, String service) {
-        String yaml =
-                "brooklyn.catalog:\n"+
-                "  id: " + catalogItemId + "\n"+
-                "  name: My Catalog App\n"+
-                (itemType!=null ? "  item_type: "+itemType+"\n" : "")+
-                "  description: My description\n"+
-                "  icon_url: classpath:///brooklyn-test-logo.jpg\n"+
-                "  version: " + version + "\n"+
-                "\n"+
-                "services:\n"+
-                "- type: " + service + "\n";
-
-        client().resource("/v1/catalog").post(yaml);
-    }
-
-    private enum DeprecateStyle {
-        NEW_STYLE,
-        LEGACY_STYLE
-    }
-    private void deprecateCatalogItem(DeprecateStyle style, String symbolicName, String version, boolean deprecated) {
-        String id = String.format("%s:%s", symbolicName, version);
-        ClientResponse response;
-        if (style == DeprecateStyle.NEW_STYLE) {
-            response = client().resource(String.format("/v1/catalog/entities/%s/deprecated", id))
-                    .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
-                    .post(ClientResponse.class, deprecated);
-        } else {
-            response = client().resource(String.format("/v1/catalog/entities/%s/deprecated/%s", id, deprecated))
-                    .post(ClientResponse.class);
-        }
-        assertEquals(response.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
-    }
-
-    private void disableCatalogItem(String symbolicName, String version, boolean disabled) {
-        String id = String.format("%s:%s", symbolicName, version);
-        ClientResponse getDisableResponse = client().resource(String.format("/v1/catalog/entities/%s/disabled", id))
-                .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
-                .post(ClientResponse.class, disabled);
-        assertEquals(getDisableResponse.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
-    }
-
-    @Test
-    public void testListPolicies() {
-        Set<CatalogPolicySummary> policies = client().resource("/v1/catalog/policies")
-                .get(new GenericType<Set<CatalogPolicySummary>>() {});
-
-        assertTrue(policies.size() > 0);
-        CatalogItemSummary asp = null;
-        for (CatalogItemSummary p : policies) {
-            if (AutoScalerPolicy.class.getName().equals(p.getType()))
-                asp = p;
-        }
-        Assert.assertNotNull(asp, "didn't find AutoScalerPolicy");
-    }
-
-    @Test
-    public void testLocationAddGetAndRemove() {
-        String symbolicName = "my.catalog.location.id";
-        String locationType = "localhost";
-        String yaml = Joiner.on("\n").join(
-                "brooklyn.catalog:",
-                "  id: " + symbolicName,
-                "  name: My Catalog Location",
-                "  description: My description",
-                "  version: " + TEST_VERSION,
-                "",
-                "brooklyn.locations:",
-                "- type: " + locationType);
-
-        // Create location item
-        Map<String, CatalogLocationSummary> items = client().resource("/v1/catalog")
-                .post(new GenericType<Map<String,CatalogLocationSummary>>() {}, yaml);
-        CatalogLocationSummary locationItem = Iterables.getOnlyElement(items.values());
-
-        Assert.assertNotNull(locationItem.getPlanYaml());
-        Assert.assertTrue(locationItem.getPlanYaml().contains(locationType));
-        assertEquals(locationItem.getId(), ver(symbolicName));
-        assertEquals(locationItem.getSymbolicName(), symbolicName);
-        assertEquals(locationItem.getVersion(), TEST_VERSION);
-
-        // Retrieve location item
-        CatalogLocationSummary location = client().resource("/v1/catalog/locations/"+symbolicName+"/"+TEST_VERSION)
-                .get(CatalogLocationSummary.class);
-        assertEquals(location.getSymbolicName(), symbolicName);
-
-        // Retrieve all locations
-        Set<CatalogLocationSummary> locations = client().resource("/v1/catalog/locations")
-                .get(new GenericType<Set<CatalogLocationSummary>>() {});
-        boolean found = false;
-        for (CatalogLocationSummary contender : locations) {
-            if (contender.getSymbolicName().equals(symbolicName)) {
-                found = true;
-                break;
-            }
-        }
-        Assert.assertTrue(found, "contenders="+locations);
-        
-        // Delete
-        ClientResponse deleteResponse = client().resource("/v1/catalog/locations/"+symbolicName+"/"+TEST_VERSION)
-                .delete(ClientResponse.class);
-        assertEquals(deleteResponse.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
-
-        ClientResponse getPostDeleteResponse = client().resource("/v1/catalog/locations/"+symbolicName+"/"+TEST_VERSION)
-                .get(ClientResponse.class);
-        assertEquals(getPostDeleteResponse.getStatus(), Response.Status.NOT_FOUND.getStatusCode());
-    }
-
-    @Test
-    public void testDeleteCustomEntityFromCatalog() {
-        String symbolicName = "my.catalog.app.id.to.subsequently.delete";
-        String yaml =
-                "name: "+symbolicName+"\n"+
-                // FIXME name above should be unnecessary when brooklyn.catalog below is working
-                "brooklyn.catalog:\n"+
-                "  id: " + symbolicName + "\n"+
-                "  name: My Catalog App To Be Deleted\n"+
-                "  description: My description\n"+
-                "  version: " + TEST_VERSION + "\n"+
-                "\n"+
-                "services:\n"+
-                "- type: org.apache.brooklyn.core.test.entity.TestEntity\n";
-
-        client().resource("/v1/catalog")
-                .post(ClientResponse.class, yaml);
-
-        ClientResponse deleteResponse = client().resource("/v1/catalog/entities/"+symbolicName+"/"+TEST_VERSION)
-                .delete(ClientResponse.class);
-
-        assertEquals(deleteResponse.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
-
-        ClientResponse getPostDeleteResponse = client().resource("/v1/catalog/entities/"+symbolicName+"/"+TEST_VERSION)
-                .get(ClientResponse.class);
-        assertEquals(getPostDeleteResponse.getStatus(), Response.Status.NOT_FOUND.getStatusCode());
-    }
-
-    @Test
-    public void testSetDeprecated() {
-        runSetDeprecated(DeprecateStyle.NEW_STYLE);
-    }
-    
-    // Uses old-style "/v1/catalog/{itemId}/deprecated/true", rather than the "true" in the request body.
-    @Test
-    @Deprecated
-    public void testSetDeprecatedLegacy() {
-        runSetDeprecated(DeprecateStyle.LEGACY_STYLE);
-    }
-
-    protected void runSetDeprecated(DeprecateStyle style) {
-        String symbolicName = "my.catalog.item.id.for.deprecation";
-        String serviceType = "org.apache.brooklyn.entity.stock.BasicApplication";
-        addTestCatalogItem(symbolicName, "template", TEST_VERSION, serviceType);
-        addTestCatalogItem(symbolicName, "template", "2.0", serviceType);
-        try {
-            List<CatalogEntitySummary> applications = client().resource("/v1/catalog/applications")
-                    .queryParam("fragment", symbolicName).queryParam("allVersions", "true").get(new GenericType<List<CatalogEntitySummary>>() {});
-            assertEquals(applications.size(), 2);
-            CatalogItemSummary summary0 = applications.get(0);
-            CatalogItemSummary summary1 = applications.get(1);
-    
-            // Deprecate: that app should be excluded
-            deprecateCatalogItem(style, summary0.getSymbolicName(), summary0.getVersion(), true);
-    
-            List<CatalogEntitySummary> applicationsAfterDeprecation = client().resource("/v1/catalog/applications")
-                    .queryParam("fragment", "basicapp").queryParam("allVersions", "true").get(new GenericType<List<CatalogEntitySummary>>() {});
-    
-            assertEquals(applicationsAfterDeprecation.size(), 1);
-            assertTrue(applicationsAfterDeprecation.contains(summary1));
-    
-            // Un-deprecate: that app should be included again
-            deprecateCatalogItem(style, summary0.getSymbolicName(), summary0.getVersion(), false);
-    
-            List<CatalogEntitySummary> applicationsAfterUnDeprecation = client().resource("/v1/catalog/applications")
-                    .queryParam("fragment", "basicapp").queryParam("allVersions", "true").get(new GenericType<List<CatalogEntitySummary>>() {});
-    
-            assertEquals(applications, applicationsAfterUnDeprecation);
-        } finally {
-            client().resource("/v1/catalog/entities/"+symbolicName+"/"+TEST_VERSION)
-                    .delete(ClientResponse.class);
-            client().resource("/v1/catalog/entities/"+symbolicName+"/"+"2.0")
-                    .delete(ClientResponse.class);
-        }
-    }
-
-    @Test
-    public void testSetDisabled() {
-        String symbolicName = "my.catalog.item.id.for.disabling";
-        String serviceType = "org.apache.brooklyn.entity.stock.BasicApplication";
-        addTestCatalogItem(symbolicName, "template", TEST_VERSION, serviceType);
-        addTestCatalogItem(symbolicName, "template", "2.0", serviceType);
-        try {
-            List<CatalogEntitySummary> applications = client().resource("/v1/catalog/applications")
-                    .queryParam("fragment", symbolicName).queryParam("allVersions", "true").get(new GenericType<List<CatalogEntitySummary>>() {});
-            assertEquals(applications.size(), 2);
-            CatalogItemSummary summary0 = applications.get(0);
-            CatalogItemSummary summary1 = applications.get(1);
-    
-            // Disable: that app should be excluded
-            disableCatalogItem(summary0.getSymbolicName(), summary0.getVersion(), true);
-    
-            List<CatalogEntitySummary> applicationsAfterDisabled = client().resource("/v1/catalog/applications")
-                    .queryParam("fragment", "basicapp").queryParam("allVersions", "true").get(new GenericType<List<CatalogEntitySummary>>() {});
-    
-            assertEquals(applicationsAfterDisabled.size(), 1);
-            assertTrue(applicationsAfterDisabled.contains(summary1));
-    
-            // Un-disable: that app should be included again
-            disableCatalogItem(summary0.getSymbolicName(), summary0.getVersion(), false);
-    
-            List<CatalogEntitySummary> applicationsAfterUnDisabled = client().resource("/v1/catalog/applications")
-                    .queryParam("fragment", "basicapp").queryParam("allVersions", "true").get(new GenericType<List<CatalogEntitySummary>>() {});
-    
-            assertEquals(applications, applicationsAfterUnDisabled);
-        } finally {
-            client().resource("/v1/catalog/entities/"+symbolicName+"/"+TEST_VERSION)
-                    .delete(ClientResponse.class);
-            client().resource("/v1/catalog/entities/"+symbolicName+"/"+"2.0")
-                    .delete(ClientResponse.class);
-        }
-    }
-
-    @Test
-    public void testAddUnreachableItem() {
-        addInvalidCatalogItem("http://0.0.0.0/can-not-connect");
-    }
-
-    @Test
-    public void testAddInvalidItem() {
-        //equivalent to HTTP response 200 text/html
-        addInvalidCatalogItem("classpath://not-a-jar-file.txt");
-    }
-
-    @Test
-    public void testAddMissingItem() {
-        //equivalent to HTTP response 404 text/html
-        addInvalidCatalogItem("classpath://missing-jar-file.txt");
-    }
-
-    private void addInvalidCatalogItem(String bundleUrl) {
-        String symbolicName = "my.catalog.entity.id";
-        String yaml =
-                "brooklyn.catalog:\n"+
-                "  id: " + symbolicName + "\n"+
-                "  name: My Catalog App\n"+
-                "  description: My description\n"+
-                "  icon_url: classpath:/org/apache/brooklyn/test/osgi/entities/icon.gif\n"+
-                "  version: " + TEST_VERSION + "\n"+
-                "  libraries:\n"+
-                "  - url: " + bundleUrl + "\n"+
-                "\n"+
-                "services:\n"+
-                "- type: org.apache.brooklyn.core.test.entity.TestEntity\n";
-
-        ClientResponse response = client().resource("/v1/catalog")
-                .post(ClientResponse.class, yaml);
-
-        assertEquals(response.getStatus(), HttpStatus.INTERNAL_SERVER_ERROR_500);
-    }
-
-    private static String ver(String id) {
-        return CatalogUtils.getVersionedId(id, TEST_VERSION);
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/DelegatingPrintStream.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/DelegatingPrintStream.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/DelegatingPrintStream.java
deleted file mode 100644
index 713dccb..0000000
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/DelegatingPrintStream.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.rest.resources;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.PrintStream;
-import java.util.Locale;
-
-public abstract class DelegatingPrintStream extends PrintStream {
-    
-    public DelegatingPrintStream() {
-        super(new IllegalOutputStream());
-    }
-
-    public static class IllegalOutputStream extends OutputStream {
-        @Override public void write(int b) {
-            throw new IllegalStateException("should not write to this output stream");
-        }
-        @Override public void write(byte[] b, int off, int len) {
-            throw new IllegalStateException("should not write to this output stream");
-        }
-    }
-    
-    protected abstract PrintStream getDelegate();
-
-    public int hashCode() {
-        return getDelegate().hashCode();
-    }
-
-    public void write(byte[] b) throws IOException {
-        getDelegate().write(b);
-    }
-
-    public boolean equals(Object obj) {
-        return getDelegate().equals(obj);
-    }
-
-    public String toString() {
-        return getDelegate().toString();
-    }
-
-    public void flush() {
-        getDelegate().flush();
-    }
-
-    public void close() {
-        getDelegate().close();
-    }
-
-    public boolean checkError() {
-        return getDelegate().checkError();
-    }
-
-    public void write(int b) {
-        getDelegate().write(b);
-    }
-
-    public void write(byte[] buf, int off, int len) {
-        getDelegate().write(buf, off, len);
-    }
-
-    public void print(boolean b) {
-        getDelegate().print(b);
-    }
-
-    public void print(char c) {
-        getDelegate().print(c);
-    }
-
-    public void print(int i) {
-        getDelegate().print(i);
-    }
-
-    public void print(long l) {
-        getDelegate().print(l);
-    }
-
-    public void print(float f) {
-        getDelegate().print(f);
-    }
-
-    public void print(double d) {
-        getDelegate().print(d);
-    }
-
-    public void print(char[] s) {
-        getDelegate().print(s);
-    }
-
-    public void print(String s) {
-        getDelegate().print(s);
-    }
-
-    public void print(Object obj) {
-        getDelegate().print(obj);
-    }
-
-    public void println() {
-        getDelegate().println();
-    }
-
-    public void println(boolean x) {
-        getDelegate().println(x);
-    }
-
-    public void println(char x) {
-        getDelegate().println(x);
-    }
-
-    public void println(int x) {
-        getDelegate().println(x);
-    }
-
-    public void println(long x) {
-        getDelegate().println(x);
-    }
-
-    public void println(float x) {
-        getDelegate().println(x);
-    }
-
-    public void println(double x) {
-        getDelegate().println(x);
-    }
-
-    public void println(char[] x) {
-        getDelegate().println(x);
-    }
-
-    public void println(String x) {
-        getDelegate().println(x);
-    }
-
-    public void println(Object x) {
-        getDelegate().println(x);
-    }
-
-    public PrintStream printf(String format, Object... args) {
-        return getDelegate().printf(format, args);
-    }
-
-    public PrintStream printf(Locale l, String format, Object... args) {
-        return getDelegate().printf(l, format, args);
-    }
-
-    public PrintStream format(String format, Object... args) {
-        return getDelegate().format(format, args);
-    }
-
-    public PrintStream format(Locale l, String format, Object... args) {
-        return getDelegate().format(l, format, args);
-    }
-
-    public PrintStream append(CharSequence csq) {
-        return getDelegate().append(csq);
-    }
-
-    public PrintStream append(CharSequence csq, int start, int end) {
-        return getDelegate().append(csq, start, end);
-    }
-
-    public PrintStream append(char c) {
-        return getDelegate().append(c);
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/DescendantsTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/DescendantsTest.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/DescendantsTest.java
deleted file mode 100644
index 3f42c8d..0000000
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/DescendantsTest.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.rest.resources;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertTrue;
-
-import java.io.IOException;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.TimeoutException;
-
-import org.apache.brooklyn.api.entity.Application;
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.core.sensor.Sensors;
-import org.apache.brooklyn.rest.domain.ApplicationSpec;
-import org.apache.brooklyn.rest.domain.EntitySpec;
-import org.apache.brooklyn.rest.domain.EntitySummary;
-import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
-import org.apache.brooklyn.rest.testing.mocks.RestMockSimpleEntity;
-import org.apache.brooklyn.util.collections.MutableList;
-import org.apache.brooklyn.util.text.StringEscapes;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.annotations.Test;
-
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
-import com.sun.jersey.api.client.ClientHandlerException;
-import com.sun.jersey.api.client.ClientResponse;
-import com.sun.jersey.api.client.GenericType;
-import com.sun.jersey.api.client.UniformInterfaceException;
-
-@Test(singleThreaded = true)
-public class DescendantsTest extends BrooklynRestResourceTest {
-
-    private static final Logger log = LoggerFactory.getLogger(DescendantsTest.class);
-
-    private final ApplicationSpec simpleSpec = ApplicationSpec.builder().name("simple-app").
-        entities(ImmutableSet.of(
-            new EntitySpec("simple-ent-1", RestMockSimpleEntity.class.getName()),
-            new EntitySpec("simple-ent-2", RestMockSimpleEntity.class.getName()))).
-        locations(ImmutableSet.of("localhost")).
-        build();
-
-    @Test
-    public void testDescendantsInSimpleDeployedApplication() throws InterruptedException, TimeoutException, UniformInterfaceException, ClientHandlerException, IOException {
-        ClientResponse response = clientDeploy(simpleSpec);
-        assertTrue(response.getStatus()/100 == 2, "response is "+response);
-        Application application = Iterables.getOnlyElement( getManagementContext().getApplications() );
-        List<Entity> entities = MutableList.copyOf( application.getChildren() );
-        log.debug("Created app "+application+" with children entities "+entities);
-        assertEquals(entities.size(), 2);
-        
-        Set<EntitySummary> descs;
-        descs = client().resource("/v1/applications/"+application.getApplicationId()+"/descendants")
-            .get(new GenericType<Set<EntitySummary>>() {});
-        // includes itself
-        assertEquals(descs.size(), 3);
-        
-        descs = client().resource("/v1/applications/"+application.getApplicationId()+"/descendants"
-            + "?typeRegex="+StringEscapes.escapeUrlParam(".*\\.RestMockSimpleEntity"))
-            .get(new GenericType<Set<EntitySummary>>() {});
-        assertEquals(descs.size(), 2);
-        
-        descs = client().resource("/v1/applications/"+application.getApplicationId()+"/descendants"
-            + "?typeRegex="+StringEscapes.escapeUrlParam(".*\\.BestBockSimpleEntity"))
-            .get(new GenericType<Set<EntitySummary>>() {});
-        assertEquals(descs.size(), 0);
-
-        descs = client().resource("/v1/applications/"+application.getApplicationId()
-            + "/entities/"+entities.get(1).getId()
-            + "/descendants"
-            + "?typeRegex="+StringEscapes.escapeUrlParam(".*\\.RestMockSimpleEntity"))
-            .get(new GenericType<Set<EntitySummary>>() {});
-        assertEquals(descs.size(), 1);
-        
-        Map<String,Object> sensors = client().resource("/v1/applications/"+application.getApplicationId()+"/descendants/sensor/foo"
-            + "?typeRegex="+StringEscapes.escapeUrlParam(".*\\.RestMockSimpleEntity"))
-            .get(new GenericType<Map<String,Object>>() {});
-        assertEquals(sensors.size(), 0);
-
-        long v = 0;
-        application.sensors().set(Sensors.newLongSensor("foo"), v);
-        for (Entity e: entities)
-            e.sensors().set(Sensors.newLongSensor("foo"), v+=123);
-        
-        sensors = client().resource("/v1/applications/"+application.getApplicationId()+"/descendants/sensor/foo")
-            .get(new GenericType<Map<String,Object>>() {});
-        assertEquals(sensors.size(), 3);
-        assertEquals(sensors.get(entities.get(1).getId()), 246);
-        
-        sensors = client().resource("/v1/applications/"+application.getApplicationId()+"/descendants/sensor/foo"
-            + "?typeRegex="+StringEscapes.escapeUrlParam(".*\\.RestMockSimpleEntity"))
-            .get(new GenericType<Map<String,Object>>() {});
-        assertEquals(sensors.size(), 2);
-        
-        sensors = client().resource("/v1/applications/"+application.getApplicationId()+"/"
-            + "entities/"+entities.get(1).getId()+"/"
-            + "descendants/sensor/foo"
-            + "?typeRegex="+StringEscapes.escapeUrlParam(".*\\.RestMockSimpleEntity"))
-            .get(new GenericType<Map<String,Object>>() {});
-        assertEquals(sensors.size(), 1);
-
-        sensors = client().resource("/v1/applications/"+application.getApplicationId()+"/"
-            + "entities/"+entities.get(1).getId()+"/"
-            + "descendants/sensor/foo"
-            + "?typeRegex="+StringEscapes.escapeUrlParam(".*\\.FestPockSimpleEntity"))
-            .get(new GenericType<Map<String,Object>>() {});
-        assertEquals(sensors.size(), 0);
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/EntityConfigResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/EntityConfigResourceTest.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/EntityConfigResourceTest.java
deleted file mode 100644
index 014053f..0000000
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/resources/EntityConfigResourceTest.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.rest.resources;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.assertNull;
-import static org.testng.Assert.assertTrue;
-
-import java.net.URI;
-import java.util.List;
-import java.util.Map;
-
-import javax.annotation.Nullable;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.Test;
-import org.apache.brooklyn.core.entity.EntityInternal;
-import org.apache.brooklyn.core.entity.EntityPredicates;
-import org.apache.brooklyn.rest.domain.ApplicationSpec;
-import org.apache.brooklyn.rest.domain.EntityConfigSummary;
-import org.apache.brooklyn.rest.domain.EntitySpec;
-import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
-import org.apache.brooklyn.rest.testing.mocks.RestMockSimpleEntity;
-import org.apache.brooklyn.util.collections.MutableMap;
-
-import com.google.common.base.Optional;
-import com.google.common.base.Predicate;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
-import com.sun.jersey.api.client.ClientResponse;
-import com.sun.jersey.api.client.GenericType;
-
-@Test(singleThreaded = true)
-public class EntityConfigResourceTest extends BrooklynRestResourceTest {
-    
-    private final static Logger log = LoggerFactory.getLogger(EntityConfigResourceTest.class);
-    private URI applicationUri;
-    private EntityInternal entity;
-
-    @BeforeClass(alwaysRun = true)
-    @Override
-    public void setUp() throws Exception {
-        super.setUp(); // We require that the superclass setup is done first, as we will be calling out to Jersey
-
-        // Deploy an application that we'll use to read the configuration of
-        final ApplicationSpec simpleSpec = ApplicationSpec.builder().name("simple-app").
-                  entities(ImmutableSet.of(new EntitySpec("simple-ent", RestMockSimpleEntity.class.getName(), ImmutableMap.of("install.version", "1.0.0")))).
-                  locations(ImmutableSet.of("localhost")).
-                  build();
-        
-        ClientResponse response = clientDeploy(simpleSpec);
-        int status = response.getStatus();
-        assertTrue(status >= 200 && status <= 299, "expected HTTP Response of 2xx but got " + status);
-        applicationUri = response.getLocation();
-        log.debug("Built app: application");
-        waitForApplicationToBeRunning(applicationUri);
-        
-        entity = (EntityInternal) Iterables.find(getManagementContext().getEntityManager().getEntities(), EntityPredicates.displayNameEqualTo("simple-ent"));
-    }
-
-    @Test
-    public void testList() throws Exception {
-        List<EntityConfigSummary> entityConfigSummaries = client().resource(
-                URI.create("/v1/applications/simple-app/entities/simple-ent/config"))
-                .get(new GenericType<List<EntityConfigSummary>>() {
-                });
-        
-        // Default entities have over a dozen config entries, but it's unnecessary to test them all; just pick one
-        // representative config key
-        Optional<EntityConfigSummary> configKeyOptional = Iterables.tryFind(entityConfigSummaries, new Predicate<EntityConfigSummary>() {
-            @Override
-            public boolean apply(@Nullable EntityConfigSummary input) {
-                return input != null && "install.version".equals(input.getName());
-            }
-        });
-        assertTrue(configKeyOptional.isPresent());
-        
-        assertEquals(configKeyOptional.get().getType(), "java.lang.String");
-        assertEquals(configKeyOptional.get().getDescription(), "Suggested version");
-        assertFalse(configKeyOptional.get().isReconfigurable());
-        assertNull(configKeyOptional.get().getDefaultValue());
-        assertNull(configKeyOptional.get().getLabel());
-        assertNull(configKeyOptional.get().getPriority());
-    }
-
-    @Test
-    public void testBatchConfigRead() throws Exception {
-        Map<String, Object> currentState = client().resource(
-                URI.create("/v1/applications/simple-app/entities/simple-ent/config/current-state"))
-                .get(new GenericType<Map<String, Object>>() {
-                });
-        assertTrue(currentState.containsKey("install.version"));
-        assertEquals(currentState.get("install.version"), "1.0.0");
-    }
-
-    @Test
-    public void testGetJson() throws Exception {
-        String configValue = client().resource(
-                URI.create("/v1/applications/simple-app/entities/simple-ent/config/install.version"))
-                .accept(MediaType.APPLICATION_JSON_TYPE)
-                .get(String.class);
-        assertEquals(configValue, "\"1.0.0\"");
-    }
-
-    @Test
-    public void testGetPlain() throws Exception {
-        String configValue = client().resource(
-                URI.create("/v1/applications/simple-app/entities/simple-ent/config/install.version"))
-                .accept(MediaType.TEXT_PLAIN_TYPE)
-                .get(String.class);
-        assertEquals(configValue, "1.0.0");
-    }
-
-    @Test
-    public void testSet() throws Exception {
-        try {
-            String uri = "/v1/applications/simple-app/entities/simple-ent/config/"+
-                RestMockSimpleEntity.SAMPLE_CONFIG.getName();
-            ClientResponse response = client().resource(uri)
-                .type(MediaType.APPLICATION_JSON_TYPE)
-                .post(ClientResponse.class, "\"hello world\"");
-            assertEquals(response.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
-
-            assertEquals(entity.getConfig(RestMockSimpleEntity.SAMPLE_CONFIG), "hello world");
-            
-            String value = client().resource(uri).accept(MediaType.APPLICATION_JSON_TYPE).get(String.class);
-            assertEquals(value, "\"hello world\"");
-
-        } finally { entity.config().set(RestMockSimpleEntity.SAMPLE_CONFIG, RestMockSimpleEntity.SAMPLE_CONFIG.getDefaultValue()); }
-    }
-
-    @Test
-    public void testSetFromMap() throws Exception {
-        try {
-            String uri = "/v1/applications/simple-app/entities/simple-ent/config";
-            ClientResponse response = client().resource(uri)
-                .type(MediaType.APPLICATION_JSON_TYPE)
-                .post(ClientResponse.class, MutableMap.of(
-                    RestMockSimpleEntity.SAMPLE_CONFIG.getName(), "hello world"));
-            assertEquals(response.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
-
-            assertEquals(entity.getConfig(RestMockSimpleEntity.SAMPLE_CONFIG), "hello world");
-            
-            String value = client().resource(uri+"/"+RestMockSimpleEntity.SAMPLE_CONFIG.getName()).accept(MediaType.APPLICATION_JSON_TYPE).get(String.class);
-            assertEquals(value, "\"hello world\"");
-
-        } finally { entity.config().set(RestMockSimpleEntity.SAMPLE_CONFIG, RestMockSimpleEntity.SAMPLE_CONFIG.getDefaultValue()); }
-    }
-
-}


[28/34] brooklyn-server git commit: This closes #15

Posted by he...@apache.org.
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/79b98c67/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/WebResourceUtils.java
----------------------------------------------------------------------
diff --cc rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/WebResourceUtils.java
index 0000000,5894700..238e277
mode 000000,100644..100644
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/WebResourceUtils.java
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/WebResourceUtils.java
@@@ -1,0 -1,197 +1,231 @@@
+ /*
+  * 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 java.io.IOException;
+ import java.util.Map;
+ 
+ import javax.servlet.ServletContext;
+ import javax.servlet.http.HttpServletResponse;
+ import javax.ws.rs.WebApplicationException;
+ import javax.ws.rs.core.MediaType;
+ import javax.ws.rs.core.Response;
+ 
 -import org.slf4j.Logger;
 -import org.slf4j.LoggerFactory;
+ import org.apache.brooklyn.core.catalog.internal.CatalogUtils;
+ import org.apache.brooklyn.rest.domain.ApiError;
+ import org.apache.brooklyn.rest.util.json.BrooklynJacksonJsonProvider;
+ import org.apache.brooklyn.util.exceptions.Exceptions;
++import org.apache.brooklyn.util.exceptions.PropagatedRuntimeException;
+ import org.apache.brooklyn.util.net.Urls;
+ import org.apache.brooklyn.util.text.StringEscapes.JavaStringEscapes;
++import org.slf4j.Logger;
++import org.slf4j.LoggerFactory;
+ 
+ import com.fasterxml.jackson.databind.ObjectMapper;
+ import com.google.common.collect.ImmutableMap;
+ import javax.ws.rs.core.UriBuilder;
+ 
+ public class WebResourceUtils {
+ 
+     private static final Logger log = LoggerFactory.getLogger(WebResourceUtils.class);
+ 
+     /** @throws WebApplicationException with an ApiError as its body and the given status as its response code. */
+     public static WebApplicationException throwWebApplicationException(Response.Status status, String format, Object... args) {
 -        String msg = String.format(format, args);
++        return throwWebApplicationException(status, null, format, args);
++    }
++    
++    /** @throws WebApplicationException with an ApiError as its body and the given status as its response code. */
++    public static WebApplicationException throwWebApplicationException(Response.Status status, Throwable exception) {
++        return throwWebApplicationException(status, exception, null);
++    }
++    
++    /** @throws WebApplicationException with an ApiError as its body and the given status as its response code.
++     * Exception and/or format can be null, and will be filled in / prefixed as appropriate. */
++    public static WebApplicationException throwWebApplicationException(Response.Status status, Throwable exception, String format, Object... args) {
++        String suppliedMsg = format==null ? null : String.format(format, args);
++        String fullMsg = suppliedMsg;
++        if (exception!=null) {
++            if (fullMsg==null) fullMsg = Exceptions.collapseText(exception);
++            else fullMsg = suppliedMsg + ": "+Exceptions.collapseText(exception);
++        }
+         if (log.isDebugEnabled()) {
+             log.debug("responding {} {} ({})",
 -                    new Object[]{status.getStatusCode(), status.getReasonPhrase(), msg});
++                    new Object[]{status.getStatusCode(), status.getReasonPhrase(), fullMsg});
+         }
 -        ApiError apiError = ApiError.builder().message(msg).errorCode(status).build();
++        ApiError apiError = 
++            (exception != null ? ApiError.builderFromThrowable(exception).prefixMessage(suppliedMsg) 
++                : ApiError.builder().message(fullMsg==null ? "" : fullMsg))
++            .errorCode(status).build();
+         // including a Throwable is the only way to include a message with the WebApplicationException - ugly!
 -        throw new WebApplicationException(new Throwable(apiError.toString()), apiError.asJsonResponse());
++        throw new WebApplicationException(
++            exception==null ? new Throwable(apiError.toString()) :
++                suppliedMsg==null ? exception :
++                new PropagatedRuntimeException(suppliedMsg, exception), 
++            apiError.asJsonResponse());
+     }
+ 
+     /** @throws WebApplicationException With code 500 internal server error */
+     public static WebApplicationException serverError(String format, Object... args) {
+         return throwWebApplicationException(Response.Status.INTERNAL_SERVER_ERROR, format, args);
+     }
+ 
+     /** @throws WebApplicationException With code 400 bad request */
+     public static WebApplicationException badRequest(String format, Object... args) {
+         return throwWebApplicationException(Response.Status.BAD_REQUEST, format, args);
+     }
+ 
++    /** @throws WebApplicationException With code 400 bad request */
++    public static WebApplicationException badRequest(Throwable t) {
++        return throwWebApplicationException(Response.Status.BAD_REQUEST, t);
++    }
++
++    /** @throws WebApplicationException With code 400 bad request */
++    public static WebApplicationException badRequest(Throwable t, String prefix, Object... prefixArgs) {
++        return throwWebApplicationException(Response.Status.BAD_REQUEST, t, prefix, prefixArgs);
++    }
++
+     /** @throws WebApplicationException With code 401 unauthorized */
+     public static WebApplicationException unauthorized(String format, Object... args) {
+         return throwWebApplicationException(Response.Status.UNAUTHORIZED, format, args);
+     }
+ 
+     /** @throws WebApplicationException With code 403 forbidden */
+     public static WebApplicationException forbidden(String format, Object... args) {
+         return throwWebApplicationException(Response.Status.FORBIDDEN, format, args);
+     }
+ 
+     /** @throws WebApplicationException With code 404 not found */
+     public static WebApplicationException notFound(String format, Object... args) {
+         return throwWebApplicationException(Response.Status.NOT_FOUND, format, args);
+     }
+ 
+     /** @throws WebApplicationException With code 412 precondition failed */
+     public static WebApplicationException preconditionFailed(String format, Object... args) {
+         return throwWebApplicationException(Response.Status.PRECONDITION_FAILED, format, args);
+     }
+ 
+     public final static Map<String,com.google.common.net.MediaType> IMAGE_FORMAT_MIME_TYPES = ImmutableMap.<String, com.google.common.net.MediaType>builder()
+             .put("jpg", com.google.common.net.MediaType.JPEG)
+             .put("jpeg", com.google.common.net.MediaType.JPEG)
+             .put("png", com.google.common.net.MediaType.PNG)
+             .put("gif", com.google.common.net.MediaType.GIF)
+             .put("svg", com.google.common.net.MediaType.SVG_UTF_8)
+             .build();
+     
+     public static MediaType getImageMediaTypeFromExtension(String extension) {
+         com.google.common.net.MediaType mime = IMAGE_FORMAT_MIME_TYPES.get(extension.toLowerCase());
+         if (mime==null) return null;
+         try {
+             return MediaType.valueOf(mime.toString());
+         } catch (Exception e) {
+             log.warn("Unparseable MIME type "+mime+"; ignoring ("+e+")");
+             Exceptions.propagateIfFatal(e);
+             return null;
+         }
+     }
+ 
+     /** as {@link #getValueForDisplay(ObjectMapper, Object, boolean, boolean)} with no mapper
+      * (so will only handle a subset of types) */
+     public static Object getValueForDisplay(Object value, boolean preferJson, boolean isJerseyReturnValue) {
+         return getValueForDisplay(null, value, preferJson, isJerseyReturnValue);
+     }
+     
+     /** returns an object which jersey will handle nicely, converting to json,
+      * sometimes wrapping in quotes if needed (for outermost json return types);
+      * if json is not preferred, this simply applies a toString-style rendering */ 
+     public static Object getValueForDisplay(ObjectMapper mapper, Object value, boolean preferJson, boolean isJerseyReturnValue) {
+         if (preferJson) {
+             if (value==null) return null;
+             Object result = value;
+             // no serialization checks required, with new smart-mapper which does toString
+             // (note there is more sophisticated logic in git history however)
+             result = value;
+             
+             if (isJerseyReturnValue) {
+                 if (result instanceof String) {
+                     // Jersey does not do json encoding if the return type is a string,
+                     // expecting the returner to do the json encoding himself
+                     // cf discussion at https://github.com/dropwizard/dropwizard/issues/231
+                     result = JavaStringEscapes.wrapJavaString((String)result);
+                 }
+             }
+             
+             return result;
+         } else {
+             if (value==null) return "";
+             return value.toString();            
+         }
+     }
+ 
+     public static String getPathFromVersionedId(String versionedId) {
+         if (CatalogUtils.looksLikeVersionedId(versionedId)) {
+             String symbolicName = CatalogUtils.getSymbolicNameFromVersionedId(versionedId);
+             String version = CatalogUtils.getVersionFromVersionedId(versionedId);
+             return Urls.encode(symbolicName) + "/" + Urls.encode(version);
+         } else {
+             return Urls.encode(versionedId);
+         }
+     }
+ 
+     /** Sets the {@link HttpServletResponse} target (last argument) from the given source {@link Response};
+      * useful in filters where we might have a {@link Response} and need to set up an {@link HttpServletResponse}.
+      */
+     public static void applyJsonResponse(ServletContext servletContext, Response source, HttpServletResponse target) throws IOException {
+         target.setStatus(source.getStatus());
+         target.setContentType(MediaType.APPLICATION_JSON);
+         target.setCharacterEncoding("UTF-8");
+         target.getWriter().write(BrooklynJacksonJsonProvider.findAnyObjectMapper(servletContext, null).writeValueAsString(source.getEntity()));
+     }
+ 
+     /**
+      * Provides a builder with the REST URI of a resource.
+      * @param baseUriBuilder An {@link UriBuilder} pointing at the base of the REST API.
+      * @param resourceClass The target resource class.
+      * @return A new {@link UriBuilder} that targets the specified REST resource.
+      */
+     public static UriBuilder resourceUriBuilder(UriBuilder baseUriBuilder, Class<?> resourceClass) {
+         return UriBuilder.fromPath(baseUriBuilder.build().getPath())
+                 .path(resourceClass);
+     }
+ 
+     /**
+      * Provides a builder with the REST URI of a service provided by a resource.
+      * @param baseUriBuilder An {@link UriBuilder} pointing at the base of the REST API.
+      * @param resourceClass The target resource class.
+      * @param method The target service (e.g. class method).
+      * @return A new {@link UriBuilder} that targets the specified service of the REST resource.
+      */
+     public static UriBuilder serviceUriBuilder(UriBuilder baseUriBuilder, Class<?> resourceClass, String method) {
+         return resourceUriBuilder(baseUriBuilder, resourceClass).path(resourceClass, method);
+     }
+ 
+     /**
+      * Provides a builder with the absolute REST URI of a service provided by a resource.
+      * @param baseUriBuilder An {@link UriBuilder} pointing at the base of the REST API.
+      * @param resourceClass The target resource class.
+      * @param method The target service (e.g. class method).
+      * @return A new {@link UriBuilder} that targets the specified service of the REST resource.
+      */
+     public static UriBuilder serviceAbsoluteUriBuilder(UriBuilder baseUriBuilder, Class<?> resourceClass, String method) {
+         return  baseUriBuilder
+                     .path(resourceClass)
+                     .path(resourceClass, method);
+     }
+ 
+ }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/79b98c67/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ApidocResourceTest.java
----------------------------------------------------------------------
diff --cc rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ApidocResourceTest.java
index 0000000,739d63f..2064508
mode 000000,100644..100644
--- a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ApidocResourceTest.java
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ApidocResourceTest.java
@@@ -1,0 -1,177 +1,173 @@@
+ /*
+  * Licensed to the Apache Software Foundation (ASF) under one
+  * or more contributor license agreements.  See the NOTICE file
+  * distributed with this work for additional information
+  * regarding copyright ownership.  The ASF licenses this file
+  * to you under the Apache License, Version 2.0 (the
+  * "License"); you may not use this file except in compliance
+  * with the License.  You may obtain a copy of the License at
+  *
+  *     http://www.apache.org/licenses/LICENSE-2.0
+  *
+  * Unless required by applicable law or agreed to in writing,
+  * software distributed under the License is distributed on an
+  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  * KIND, either express or implied.  See the License for the
+  * specific language governing permissions and limitations
+  * under the License.
+  */
+ package org.apache.brooklyn.rest.resources;
+ 
++import static org.testng.Assert.assertEquals;
++import static org.testng.Assert.assertFalse;
++import io.swagger.annotations.Api;
++import io.swagger.models.Operation;
++import io.swagger.models.Path;
++import io.swagger.models.Swagger;
++
++import java.util.Collection;
++
++import org.apache.brooklyn.rest.BrooklynRestApi;
++import org.apache.brooklyn.rest.api.CatalogApi;
++import org.apache.brooklyn.rest.api.EffectorApi;
++import org.apache.brooklyn.rest.api.EntityApi;
++import org.apache.brooklyn.rest.filter.SwaggerFilter;
++import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
++import org.slf4j.Logger;
++import org.slf4j.LoggerFactory;
++import org.testng.annotations.Test;
+ 
+ import com.google.common.base.Function;
+ import com.google.common.base.Joiner;
+ import com.google.common.base.Predicate;
+ import com.google.common.collect.Collections2;
+ import com.google.common.collect.ImmutableList;
+ import com.google.common.collect.ImmutableMap;
+ import com.google.common.collect.Iterables;
+ import com.sun.jersey.api.core.ClassNamesResourceConfig;
+ import com.sun.jersey.spi.container.servlet.ServletContainer;
 -
 -import org.slf4j.Logger;
 -import org.slf4j.LoggerFactory;
 -import org.testng.Assert;
 -import org.testng.annotations.Test;
 -
 -import org.apache.brooklyn.rest.BrooklynRestApi;
 -import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
 -
+ import com.sun.jersey.test.framework.AppDescriptor;
+ import com.sun.jersey.test.framework.JerseyTest;
+ import com.sun.jersey.test.framework.WebAppDescriptor;
+ import com.sun.jersey.test.framework.spi.container.TestContainerException;
+ import com.sun.jersey.test.framework.spi.container.TestContainerFactory;
+ import com.sun.jersey.test.framework.spi.container.grizzly2.web.GrizzlyWebTestContainerFactory;
 -import io.swagger.annotations.Api;
 -import io.swagger.models.Info;
 -import io.swagger.models.Operation;
 -import io.swagger.models.Path;
 -import io.swagger.models.Swagger;
 -import java.util.Collection;
 -import org.apache.brooklyn.rest.api.CatalogApi;
 -import org.apache.brooklyn.rest.api.EffectorApi;
 -import org.apache.brooklyn.rest.api.EntityApi;
 -import org.apache.brooklyn.rest.filter.SwaggerFilter;
 -import org.apache.brooklyn.rest.util.ShutdownHandlerProvider;
 -import static org.testng.Assert.assertEquals;
 -import static org.testng.Assert.assertFalse;
+ 
+ /**
+  * @author Adam Lowe
+  */
+ @Test(singleThreaded = true)
+ public class ApidocResourceTest extends BrooklynRestResourceTest {
+ 
+     private static final Logger log = LoggerFactory.getLogger(ApidocResourceTest.class);
+ 
+     @Override
+     protected JerseyTest createJerseyTest() {
+         return new JerseyTest() {
+             @Override
+             protected AppDescriptor configure() {
+                 return new WebAppDescriptor.Builder(
+                         ImmutableMap.of(
+                                 ServletContainer.RESOURCE_CONFIG_CLASS, ClassNamesResourceConfig.class.getName(),
+                                 ClassNamesResourceConfig.PROPERTY_CLASSNAMES, getResourceClassnames()))
+                         .addFilter(SwaggerFilter.class, "SwaggerFilter").build();
+             }
+ 
+             @Override
+             protected TestContainerFactory getTestContainerFactory() throws TestContainerException {
+                 return new GrizzlyWebTestContainerFactory();
+             }
+ 
+             private String getResourceClassnames() {
 -                Iterable<String> classnames = Collections2.transform(config.getClasses(), new Function<Class, String>() {
++                Iterable<String> classnames = Collections2.transform(config.getClasses(), new Function<Class<?>, String>() {
+                     @Override
 -                    public String apply(Class clazz) {
++                    public String apply(Class<?> clazz) {
+                         return clazz.getName();
+                     }
+                 });
+                 classnames = Iterables.concat(classnames, Collections2.transform(config.getSingletons(), new Function<Object, String>() {
+                     @Override
+                     public String apply(Object singleton) {
+                         return singleton.getClass().getName();
+                     }
+                 }));
+                 return Joiner.on(';').join(classnames);
+             }
+         };
+     }
+ 
+     @Override
+     protected void addBrooklynResources() {
+         for (Object o : BrooklynRestApi.getApidocResources()) {
+             addResource(o);
+         }
+         super.addBrooklynResources();
+     }
+     
+     @Test(enabled = false)
+     public void testRootSerializesSensibly() throws Exception {
+         String data = resource("/v1/apidoc/swagger.json").get(String.class);
+         log.info("apidoc gives: "+data);
+         // make sure no scala gets in
+         assertFalse(data.contains("$"));
+         assertFalse(data.contains("scala"));
+         // make sure it's an appropriate swagger 2.0 json
+         Swagger swagger = resource("/v1/apidoc/swagger.json").get(Swagger.class);
+         assertEquals(swagger.getSwagger(), "2.0");
+     }
+     
+     @Test(enabled = false)
+     public void testCountRestResources() throws Exception {
+         Swagger swagger = resource("/v1/apidoc/swagger.json").get(Swagger.class);
+         assertEquals(swagger.getTags().size(), 1 + Iterables.size(BrooklynRestApi.getBrooklynRestResources()));
+     }
+ 
+     @Test(enabled = false)
+     public void testApiDocDetails() throws Exception {
+         Swagger swagger = resource("/v1/apidoc/swagger.json").get(Swagger.class);
+         Collection<Operation> operations = getTaggedOperations(swagger, ApidocResource.class.getAnnotation(Api.class).value());
+         assertEquals(operations.size(), 2, "ops="+operations);
+     }
+ 
+     @Test(enabled = false)
+     public void testEffectorDetails() throws Exception {
+         Swagger swagger = resource("/v1/apidoc/swagger.json").get(Swagger.class);
+         Collection<Operation> operations = getTaggedOperations(swagger, EffectorApi.class.getAnnotation(Api.class).value());
+         assertEquals(operations.size(), 2, "ops="+operations);
+     }
+ 
+     @Test(enabled = false)
+     public void testEntityDetails() throws Exception {
+         Swagger swagger = resource("/v1/apidoc/swagger.json").get(Swagger.class);
+         Collection<Operation> operations = getTaggedOperations(swagger, EntityApi.class.getAnnotation(Api.class).value());
+         assertEquals(operations.size(), 14, "ops="+operations);
+     }
+ 
+     @Test(enabled = false)
+     public void testCatalogDetails() throws Exception {
+         Swagger swagger = resource("/v1/apidoc/swagger.json").get(Swagger.class);
+         Collection<Operation> operations = getTaggedOperations(swagger, CatalogApi.class.getAnnotation(Api.class).value());
+         assertEquals(operations.size(), 22, "ops="+operations);
+     }
+ 
+     /**
+      * Retrieves all operations tagged the given tag from the given swagger spec.
+      */
+     private Collection<Operation> getTaggedOperations(Swagger swagger, final String requiredTag) {
+         Iterable<Operation> allOperations = Iterables.concat(Collections2.transform(swagger.getPaths().values(),
+                 new Function<Path, Collection<Operation>>() {
+                     @Override
+                     public Collection<Operation> apply(Path path) {
+                         return path.getOperations();
+                     }
+                 }));
+ 
+         return Collections2.filter(ImmutableList.copyOf(allOperations), new Predicate<Operation>() {
+             @Override
+             public boolean apply(Operation operation) {
+                 return operation.getTags().contains(requiredTag);
+             }
+         });
+     }
+ }
+ 

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/79b98c67/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java
----------------------------------------------------------------------
diff --cc rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java
index 0000000,8b49763..ada548b
mode 000000,100644..100644
--- a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java
@@@ -1,0 -1,701 +1,701 @@@
+ /*
+  * Licensed to the Apache Software Foundation (ASF) under one
+  * or more contributor license agreements.  See the NOTICE file
+  * distributed with this work for additional information
+  * regarding copyright ownership.  The ASF licenses this file
+  * to you under the Apache License, Version 2.0 (the
+  * "License"); you may not use this file except in compliance
+  * with the License.  You may obtain a copy of the License at
+  *
+  *     http://www.apache.org/licenses/LICENSE-2.0
+  *
+  * Unless required by applicable law or agreed to in writing,
+  * software distributed under the License is distributed on an
+  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  * KIND, either express or implied.  See the License for the
+  * specific language governing permissions and limitations
+  * under the License.
+  */
+ package org.apache.brooklyn.rest.resources;
+ 
+ import static com.google.common.collect.Iterables.find;
+ import static org.testng.Assert.assertEquals;
+ import static org.testng.Assert.assertNotNull;
+ import static org.testng.Assert.assertTrue;
+ 
+ import java.io.IOException;
+ import java.net.URI;
+ import java.util.Collection;
+ import java.util.List;
+ import java.util.Map;
+ import java.util.Set;
+ import java.util.concurrent.TimeoutException;
+ 
+ import javax.ws.rs.core.MediaType;
+ import javax.ws.rs.core.MultivaluedMap;
+ import javax.ws.rs.core.Response;
+ 
+ import org.apache.brooklyn.api.entity.Application;
+ import org.apache.brooklyn.core.entity.Attributes;
+ import org.apache.brooklyn.core.entity.Entities;
+ import org.apache.brooklyn.core.entity.EntityFunctions;
+ import org.apache.brooklyn.core.entity.EntityPredicates;
+ import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
+ import org.apache.brooklyn.core.location.AbstractLocation;
+ import org.apache.brooklyn.core.location.LocationConfigKeys;
+ import org.apache.brooklyn.core.location.geo.HostGeoInfo;
+ import org.apache.brooklyn.core.location.internal.LocationInternal;
+ import org.apache.brooklyn.entity.stock.BasicApplication;
+ import org.apache.brooklyn.entity.stock.BasicEntity;
+ import org.apache.brooklyn.rest.domain.ApiError;
+ import org.apache.brooklyn.rest.domain.ApplicationSpec;
+ import org.apache.brooklyn.rest.domain.ApplicationSummary;
+ import org.apache.brooklyn.rest.domain.CatalogEntitySummary;
+ import org.apache.brooklyn.rest.domain.CatalogItemSummary;
+ import org.apache.brooklyn.rest.domain.EffectorSummary;
+ import org.apache.brooklyn.rest.domain.EntityConfigSummary;
+ import org.apache.brooklyn.rest.domain.EntitySpec;
+ import org.apache.brooklyn.rest.domain.EntitySummary;
+ import org.apache.brooklyn.rest.domain.PolicySummary;
+ import org.apache.brooklyn.rest.domain.SensorSummary;
+ import org.apache.brooklyn.rest.domain.Status;
+ import org.apache.brooklyn.rest.domain.TaskSummary;
+ import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+ import org.apache.brooklyn.rest.testing.mocks.CapitalizePolicy;
+ import org.apache.brooklyn.rest.testing.mocks.NameMatcherGroup;
+ import org.apache.brooklyn.rest.testing.mocks.RestMockApp;
+ import org.apache.brooklyn.rest.testing.mocks.RestMockAppBuilder;
+ import org.apache.brooklyn.rest.testing.mocks.RestMockSimpleEntity;
+ import org.apache.brooklyn.test.Asserts;
+ import org.apache.brooklyn.util.collections.CollectionFunctionals;
+ import org.apache.brooklyn.util.exceptions.Exceptions;
+ import org.apache.brooklyn.util.http.HttpAsserts;
+ import org.apache.brooklyn.util.text.Strings;
+ import org.apache.brooklyn.util.time.Duration;
+ import org.apache.http.HttpHeaders;
+ import org.apache.http.entity.ContentType;
+ import org.slf4j.Logger;
+ import org.slf4j.LoggerFactory;
+ import org.testng.Assert;
+ import org.testng.annotations.Test;
+ 
+ import com.google.common.base.Predicate;
+ import com.google.common.base.Predicates;
+ import com.google.common.collect.ImmutableMap;
+ import com.google.common.collect.ImmutableSet;
+ import com.google.common.collect.Iterables;
+ import com.google.common.collect.Maps;
+ import com.sun.jersey.api.client.ClientResponse;
+ import com.sun.jersey.api.client.GenericType;
+ import com.sun.jersey.api.client.UniformInterfaceException;
+ import com.sun.jersey.api.client.WebResource;
+ import com.sun.jersey.core.util.MultivaluedMapImpl;
+ 
+ @Test(singleThreaded = true)
+ public class ApplicationResourceTest extends BrooklynRestResourceTest {
+ 
+     /*
+      * In simpleSpec, not using EverythingGroup because caused problems! The group is a child of the
+      * app, and the app is a member of the group. It failed in jenkins with:
+      *   BasicApplicationImpl{id=GSPjBCe4} GSPjBCe4
+      *     service.isUp: true
+      *     service.problems: {service-lifecycle-indicators-from-children-and-members=Required entity not healthy: EverythingGroupImpl{id=KQ4mSEOJ}}
+      *     service.state: on-fire
+      *     service.state.expected: running @ 1412003485617 / Mon Sep 29 15:11:25 UTC 2014
+      *   EverythingGroupImpl{id=KQ4mSEOJ} KQ4mSEOJ
+      *     service.isUp: true
+      *     service.problems: {service-lifecycle-indicators-from-children-and-members=Required entities not healthy: BasicApplicationImpl{id=GSPjBCe4}, EverythingGroupImpl{id=KQ4mSEOJ}}
+      *     service.state: on-fire
+      * I'm guessing there's a race: the app was not yet healthy because EverythingGroup hadn't set itself to running; 
+      * but then the EverythingGroup would never transition to healthy because one of its members was not healthy.
+      */
+ 
+     private static final Logger log = LoggerFactory.getLogger(ApplicationResourceTest.class);
+     
+     private final ApplicationSpec simpleSpec = ApplicationSpec.builder().name("simple-app")
+           .entities(ImmutableSet.of(
+                   new EntitySpec("simple-ent", RestMockSimpleEntity.class.getName()),
+                   new EntitySpec("simple-group", NameMatcherGroup.class.getName(), ImmutableMap.of("namematchergroup.regex", "simple-ent"))
+           ))
+           .locations(ImmutableSet.of("localhost"))
+           .build();
+ 
+     // Convenience for finding an EntitySummary within a collection, based on its name
+     private static Predicate<EntitySummary> withName(final String name) {
+         return new Predicate<EntitySummary>() {
+             public boolean apply(EntitySummary input) {
+                 return name.equals(input.getName());
+             }
+         };
+     }
+ 
+     // Convenience for finding a Map within a collection, based on the value of one of its keys
+     private static Predicate<? super Map<?,?>> withValueForKey(final Object key, final Object value) {
+         return new Predicate<Object>() {
+             public boolean apply(Object input) {
+                 if (!(input instanceof Map)) return false;
+                 return value.equals(((Map<?, ?>) input).get(key));
+             }
+         };
+     }
+ 
+     @Test
+     public void testGetUndefinedApplication() {
+         try {
+             client().resource("/v1/applications/dummy-not-found").get(ApplicationSummary.class);
+         } catch (UniformInterfaceException e) {
+             assertEquals(e.getResponse().getStatus(), 404);
+         }
+     }
+ 
+     private static void assertRegexMatches(String actual, String patternExpected) {
+         if (actual==null) Assert.fail("Actual value is null; expected "+patternExpected);
+         if (!actual.matches(patternExpected)) {
+             Assert.fail("Text '"+actual+"' does not match expected pattern "+patternExpected);
+         }
+     }
+ 
+     @Test
+     public void testDeployApplication() throws Exception {
+         ClientResponse response = clientDeploy(simpleSpec);
+ 
+         HttpAsserts.assertHealthyStatusCode(response.getStatus());
+         assertEquals(getManagementContext().getApplications().size(), 1);
+         assertRegexMatches(response.getLocation().getPath(), "/v1/applications/.*");
+         // Object taskO = response.getEntity(Object.class);
+         TaskSummary task = response.getEntity(TaskSummary.class);
+         log.info("deployed, got " + task);
+         assertEquals(task.getEntityId(), getManagementContext().getApplications().iterator().next().getApplicationId());
+ 
+         waitForApplicationToBeRunning(response.getLocation());
+     }
+ 
+     @Test(dependsOnMethods = { "testDeleteApplication" })
+     // this must happen after we've deleted the main application, as testLocatedLocations assumes a single location
+     public void testDeployApplicationImpl() throws Exception {
+     ApplicationSpec spec = ApplicationSpec.builder()
+             .type(RestMockApp.class.getCanonicalName())
+             .name("simple-app-impl")
+             .locations(ImmutableSet.of("localhost"))
+             .build();
+ 
+         ClientResponse response = clientDeploy(spec);
+         assertTrue(response.getStatus() / 100 == 2, "response is " + response);
+ 
+         // Expect app to be running
+         URI appUri = response.getLocation();
+         waitForApplicationToBeRunning(response.getLocation());
+         assertEquals(client().resource(appUri).get(ApplicationSummary.class).getSpec().getName(), "simple-app-impl");
+     }
+ 
+     @Test(dependsOnMethods = { "testDeployApplication", "testLocatedLocation" })
+     public void testDeployApplicationFromInterface() throws Exception {
+         ApplicationSpec spec = ApplicationSpec.builder()
+                 .type(BasicApplication.class.getCanonicalName())
+                 .name("simple-app-interface")
+                 .locations(ImmutableSet.of("localhost"))
+                 .build();
+ 
+         ClientResponse response = clientDeploy(spec);
+         assertTrue(response.getStatus() / 100 == 2, "response is " + response);
+ 
+         // Expect app to be running
+         URI appUri = response.getLocation();
+         waitForApplicationToBeRunning(response.getLocation());
+         assertEquals(client().resource(appUri).get(ApplicationSummary.class).getSpec().getName(), "simple-app-interface");
+     }
+ 
+     @Test(dependsOnMethods = { "testDeployApplication", "testLocatedLocation" })
+     public void testDeployApplicationFromBuilder() throws Exception {
+         ApplicationSpec spec = ApplicationSpec.builder()
+                 .type(RestMockAppBuilder.class.getCanonicalName())
+                 .name("simple-app-builder")
+                 .locations(ImmutableSet.of("localhost"))
+                 .build();
+ 
+         ClientResponse response = clientDeploy(spec);
+         assertTrue(response.getStatus() / 100 == 2, "response is " + response);
+ 
+         // Expect app to be running
+         URI appUri = response.getLocation();
+         waitForApplicationToBeRunning(response.getLocation(), Duration.TEN_SECONDS);
+         assertEquals(client().resource(appUri).get(ApplicationSummary.class).getSpec().getName(), "simple-app-builder");
+ 
+         // Expect app to have the child-entity
+         Set<EntitySummary> entities = client().resource(appUri.toString() + "/entities")
+                 .get(new GenericType<Set<EntitySummary>>() {});
+         assertEquals(entities.size(), 1);
+         assertEquals(Iterables.getOnlyElement(entities).getName(), "child1");
+         assertEquals(Iterables.getOnlyElement(entities).getType(), RestMockSimpleEntity.class.getCanonicalName());
+     }
+ 
+     @Test(dependsOnMethods = { "testDeployApplication", "testLocatedLocation" })
+     public void testDeployApplicationYaml() throws Exception {
+         String yaml = "{ name: simple-app-yaml, location: localhost, services: [ { serviceType: "+BasicApplication.class.getCanonicalName()+" } ] }";
+ 
+         ClientResponse response = client().resource("/v1/applications")
+                 .entity(yaml, "application/x-yaml")
+                 .post(ClientResponse.class);
+         assertTrue(response.getStatus()/100 == 2, "response is "+response);
+ 
+         // Expect app to be running
+         URI appUri = response.getLocation();
+         waitForApplicationToBeRunning(response.getLocation());
+         assertEquals(client().resource(appUri).get(ApplicationSummary.class).getSpec().getName(), "simple-app-yaml");
+     }
+ 
+     @Test
+     public void testReferenceCatalogEntity() throws Exception {
+         getManagementContext().getCatalog().addItems("{ name: "+BasicEntity.class.getName()+", "
+             + "services: [ { type: "+BasicEntity.class.getName()+" } ] }");
+ 
+         String yaml = "{ name: simple-app-yaml, location: localhost, services: [ { type: " + BasicEntity.class.getName() + " } ] }";
+ 
+         ClientResponse response = client().resource("/v1/applications")
+                 .entity(yaml, "application/x-yaml")
+                 .post(ClientResponse.class);
+         assertTrue(response.getStatus()/100 == 2, "response is "+response);
+ 
+         // Expect app to be running
+         URI appUri = response.getLocation();
+         waitForApplicationToBeRunning(response.getLocation());
+         assertEquals(client().resource(appUri).get(ApplicationSummary.class).getSpec().getName(), "simple-app-yaml");
+ 
+         ClientResponse response2 = client().resource(appUri.getPath())
+                 .delete(ClientResponse.class);
+         assertEquals(response2.getStatus(), Response.Status.ACCEPTED.getStatusCode());
+     }
+ 
+     @Test
+     public void testDeployWithInvalidEntityType() {
+         try {
+             clientDeploy(ApplicationSpec.builder()
+                     .name("invalid-app")
+                     .entities(ImmutableSet.of(new EntitySpec("invalid-ent", "not.existing.entity")))
+                     .locations(ImmutableSet.of("localhost"))
+                     .build());
+ 
+         } catch (UniformInterfaceException e) {
+             ApiError error = e.getResponse().getEntity(ApiError.class);
+             assertEquals(error.getMessage(), "Undefined type 'not.existing.entity'");
+         }
+     }
+ 
+     @Test
+     public void testDeployWithInvalidLocation() {
+         try {
+             clientDeploy(ApplicationSpec.builder()
+                     .name("invalid-app")
+                     .entities(ImmutableSet.of(new EntitySpec("simple-ent", RestMockSimpleEntity.class.getName())))
+                     .locations(ImmutableSet.of("3423"))
+                     .build());
+ 
+         } catch (UniformInterfaceException e) {
+             ApiError error = e.getResponse().getEntity(ApiError.class);
+             assertEquals(error.getMessage(), "Undefined location '3423'");
+         }
+     }
+ 
+     @Test(dependsOnMethods = "testDeployApplication")
+     public void testListEntities() {
+         Set<EntitySummary> entities = client().resource("/v1/applications/simple-app/entities")
+                 .get(new GenericType<Set<EntitySummary>>() {});
+ 
+         assertEquals(entities.size(), 2);
+ 
+         EntitySummary entity = Iterables.find(entities, withName("simple-ent"), null);
+         EntitySummary group = Iterables.find(entities, withName("simple-group"), null);
+         Assert.assertNotNull(entity);
+         Assert.assertNotNull(group);
+ 
+         client().resource(entity.getLinks().get("self")).get(ClientResponse.class);
+ 
+         Set<EntitySummary> children = client().resource(entity.getLinks().get("children"))
+                 .get(new GenericType<Set<EntitySummary>>() {});
+         assertEquals(children.size(), 0);
+     }
+ 
+     @Test(dependsOnMethods = "testDeployApplication")
+     public void testListApplications() {
+         Set<ApplicationSummary> applications = client().resource("/v1/applications")
+                 .get(new GenericType<Set<ApplicationSummary>>() { });
+         log.info("Applications listed are: " + applications);
+         for (ApplicationSummary app : applications) {
+             if (simpleSpec.getName().equals(app.getSpec().getName())) return;
+         }
+         Assert.fail("simple-app not found in list of applications: "+applications);
+     }
+ 
+     @Test(dependsOnMethods = "testDeployApplication")
+     public void testGetApplicationOnFire() {
+         Application app = Iterables.find(manager.getApplications(), EntityPredicates.displayNameEqualTo(simpleSpec.getName()));
+         Lifecycle origState = app.getAttribute(Attributes.SERVICE_STATE_ACTUAL);
+         
+         ApplicationSummary summary = client().resource("/v1/applications/"+app.getId())
+                 .get(ApplicationSummary.class);
+         assertEquals(summary.getStatus(), Status.RUNNING);
+ 
+         app.sensors().set(Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
+         try {
+             ApplicationSummary summary2 = client().resource("/v1/applications/"+app.getId())
+                     .get(ApplicationSummary.class);
+             log.info("Application: " + summary2);
+             assertEquals(summary2.getStatus(), Status.ERROR);
+             
+         } finally {
+             app.sensors().set(Attributes.SERVICE_STATE_ACTUAL, origState);
+         }
+     }
+ 
+     @SuppressWarnings({ "rawtypes", "unchecked" })
+     @Test(dependsOnMethods = "testDeployApplication")
+     public void testFetchApplicationsAndEntity() {
+         Collection apps = client().resource("/v1/applications/fetch").get(Collection.class);
+         log.info("Applications fetched are: " + apps);
+ 
+         Map app = null;
+         for (Object appI : apps) {
+             Object name = ((Map) appI).get("name");
+             if ("simple-app".equals(name)) {
+                 app = (Map) appI;
+             }
+             if (ImmutableSet.of("simple-ent", "simple-group").contains(name))
+                 Assert.fail(name + " should not be listed at high level: " + apps);
+         }
+ 
+         Assert.assertNotNull(app);
+         Assert.assertFalse(Strings.isBlank((String) app.get("applicationId")),
+                 "expected value for applicationId, was: " + app.get("applicationId"));
+         Assert.assertFalse(Strings.isBlank((String) app.get("serviceState")),
+                 "expected value for serviceState, was: " + app.get("serviceState"));
+         Assert.assertNotNull(app.get("serviceUp"), "expected non-null value for serviceUp");
+ 
+         Collection children = (Collection) app.get("children");
+         Assert.assertEquals(children.size(), 2);
+ 
+         Map entitySummary = (Map) Iterables.find(children, withValueForKey("name", "simple-ent"), null);
+         Map groupSummary = (Map) Iterables.find(children, withValueForKey("name", "simple-group"), null);
+         Assert.assertNotNull(entitySummary);
+         Assert.assertNotNull(groupSummary);
+ 
+         String itemIds = app.get("id") + "," + entitySummary.get("id") + "," + groupSummary.get("id");
+         Collection entities = client().resource("/v1/applications/fetch?items="+itemIds)
+                 .get(Collection.class);
+         log.info("Applications+Entities fetched are: " + entities);
+ 
+         Assert.assertEquals(entities.size(), apps.size() + 2);
+         Map entityDetails = (Map) Iterables.find(entities, withValueForKey("name", "simple-ent"), null);
+         Map groupDetails = (Map) Iterables.find(entities, withValueForKey("name", "simple-group"), null);
+         Assert.assertNotNull(entityDetails);
+         Assert.assertNotNull(groupDetails);
+ 
+         Assert.assertEquals(entityDetails.get("parentId"), app.get("id"));
+         Assert.assertNull(entityDetails.get("children"));
+         Assert.assertEquals(groupDetails.get("parentId"), app.get("id"));
+         Assert.assertNull(groupDetails.get("children"));
+ 
+         Collection entityGroupIds = (Collection) entityDetails.get("groupIds");
+         Assert.assertNotNull(entityGroupIds);
+         Assert.assertEquals(entityGroupIds.size(), 1);
+         Assert.assertEquals(entityGroupIds.iterator().next(), groupDetails.get("id"));
+ 
+         Collection groupMembers = (Collection) groupDetails.get("members");
+         Assert.assertNotNull(groupMembers);
+ 
+         for (Application appi : getManagementContext().getApplications()) {
+             Entities.dumpInfo(appi);
+         }
+         log.info("MEMBERS: " + groupMembers);
+ 
+         Assert.assertEquals(groupMembers.size(), 1);
+         Map entityMemberDetails = (Map) Iterables.find(groupMembers, withValueForKey("name", "simple-ent"), null);
+         Assert.assertNotNull(entityMemberDetails);
+         Assert.assertEquals(entityMemberDetails.get("id"), entityDetails.get("id"));
+     }
+ 
+     @Test(dependsOnMethods = "testDeployApplication")
+     public void testListSensors() {
+         Set<SensorSummary> sensors = client().resource("/v1/applications/simple-app/entities/simple-ent/sensors")
+                 .get(new GenericType<Set<SensorSummary>>() { });
+         assertTrue(sensors.size() > 0);
+         SensorSummary sample = Iterables.find(sensors, new Predicate<SensorSummary>() {
+             @Override
+             public boolean apply(SensorSummary sensorSummary) {
+                 return sensorSummary.getName().equals(RestMockSimpleEntity.SAMPLE_SENSOR.getName());
+             }
+         });
+         assertEquals(sample.getType(), "java.lang.String");
+     }
+ 
+     @Test(dependsOnMethods = "testDeployApplication")
+     public void testListConfig() {
+         Set<EntityConfigSummary> config = client().resource("/v1/applications/simple-app/entities/simple-ent/config")
+                 .get(new GenericType<Set<EntityConfigSummary>>() { });
+         assertTrue(config.size() > 0);
+         System.out.println(("CONFIG: " + config));
+     }
+ 
+     @Test(dependsOnMethods = "testListConfig")
+     public void testListConfig2() {
+         Set<EntityConfigSummary> config = client().resource("/v1/applications/simple-app/entities/simple-ent/config")
+                 .get(new GenericType<Set<EntityConfigSummary>>() {});
+         assertTrue(config.size() > 0);
+         System.out.println(("CONFIG: " + config));
+     }
+ 
+     @Test(dependsOnMethods = "testDeployApplication")
+     public void testListEffectors() {
+         Set<EffectorSummary> effectors = client().resource("/v1/applications/simple-app/entities/simple-ent/effectors")
+                 .get(new GenericType<Set<EffectorSummary>>() {});
+ 
+         assertTrue(effectors.size() > 0);
+ 
+         EffectorSummary sampleEffector = find(effectors, new Predicate<EffectorSummary>() {
+             @Override
+             public boolean apply(EffectorSummary input) {
+                 return input.getName().equals("sampleEffector");
+             }
+         });
+         assertEquals(sampleEffector.getReturnType(), "java.lang.String");
+     }
+ 
+     @Test(dependsOnMethods = "testListSensors")
+     public void testTriggerSampleEffector() throws InterruptedException, IOException {
+         ClientResponse response = client()
+                 .resource("/v1/applications/simple-app/entities/simple-ent/effectors/"+RestMockSimpleEntity.SAMPLE_EFFECTOR.getName())
+                 .type(MediaType.APPLICATION_JSON_TYPE)
+                 .post(ClientResponse.class, ImmutableMap.of("param1", "foo", "param2", 4));
+ 
+         assertEquals(response.getStatus(), Response.Status.ACCEPTED.getStatusCode());
+ 
+         String result = response.getEntity(String.class);
+         assertEquals(result, "foo4");
+     }
+ 
+     @Test(dependsOnMethods = "testListSensors")
+     public void testTriggerSampleEffectorWithFormData() throws InterruptedException, IOException {
+         MultivaluedMap<String, String> data = new MultivaluedMapImpl();
+         data.add("param1", "foo");
+         data.add("param2", "4");
+         ClientResponse response = client()
+                 .resource("/v1/applications/simple-app/entities/simple-ent/effectors/"+RestMockSimpleEntity.SAMPLE_EFFECTOR.getName())
+                 .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE)
+                 .post(ClientResponse.class, data);
+ 
+         assertEquals(response.getStatus(), Response.Status.ACCEPTED.getStatusCode());
+ 
+         String result = response.getEntity(String.class);
+         assertEquals(result, "foo4");
+     }
+ 
+     @Test(dependsOnMethods = "testTriggerSampleEffector")
+     public void testBatchSensorValues() {
+         WebResource resource = client().resource("/v1/applications/simple-app/entities/simple-ent/sensors/current-state");
+         Map<String, Object> sensors = resource.get(new GenericType<Map<String, Object>>() {});
+         assertTrue(sensors.size() > 0);
+         assertEquals(sensors.get(RestMockSimpleEntity.SAMPLE_SENSOR.getName()), "foo4");
+     }
+ 
+     @Test(dependsOnMethods = "testBatchSensorValues")
+     public void testReadEachSensor() {
+     Set<SensorSummary> sensors = client().resource("/v1/applications/simple-app/entities/simple-ent/sensors")
+             .get(new GenericType<Set<SensorSummary>>() {});
+ 
+         Map<String, String> readings = Maps.newHashMap();
+         for (SensorSummary sensor : sensors) {
+             try {
+                 readings.put(sensor.getName(), client().resource(sensor.getLinks().get("self")).accept(MediaType.TEXT_PLAIN).get(String.class));
+             } catch (UniformInterfaceException uie) {
+                 if (uie.getResponse().getStatus() == 204) { // no content
+                     readings.put(sensor.getName(), null);
+                 } else {
+                     Exceptions.propagate(uie);
+                 }
+             }
+         }
+ 
+         assertEquals(readings.get(RestMockSimpleEntity.SAMPLE_SENSOR.getName()), "foo4");
+     }
+ 
+     @Test(dependsOnMethods = "testTriggerSampleEffector")
+     public void testPolicyWhichCapitalizes() {
+         String policiesEndpoint = "/v1/applications/simple-app/entities/simple-ent/policies";
+         Set<PolicySummary> policies = client().resource(policiesEndpoint).get(new GenericType<Set<PolicySummary>>(){});
+         assertEquals(policies.size(), 0);
+ 
+         ClientResponse response = client().resource(policiesEndpoint)
+                 .queryParam("type", CapitalizePolicy.class.getCanonicalName())
+                 .type(MediaType.APPLICATION_JSON_TYPE)
+                 .post(ClientResponse.class, Maps.newHashMap());
+         assertEquals(response.getStatus(), 200);
+         PolicySummary policy = response.getEntity(PolicySummary.class);
+         assertNotNull(policy.getId());
+         String newPolicyId = policy.getId();
+         log.info("POLICY CREATED: " + newPolicyId);
+         policies = client().resource(policiesEndpoint).get(new GenericType<Set<PolicySummary>>() {});
+         assertEquals(policies.size(), 1);
+ 
+         Lifecycle status = client().resource(policiesEndpoint + "/" + newPolicyId).get(Lifecycle.class);
+         log.info("POLICY STATUS: " + status);
+ 
+         response = client().resource(policiesEndpoint+"/"+newPolicyId+"/start")
+                 .post(ClientResponse.class);
+         assertEquals(response.getStatus(), 204);
+         status = client().resource(policiesEndpoint + "/" + newPolicyId).get(Lifecycle.class);
+         assertEquals(status, Lifecycle.RUNNING);
+ 
+         response = client().resource(policiesEndpoint+"/"+newPolicyId+"/stop")
+                 .post(ClientResponse.class);
+         assertEquals(response.getStatus(), 204);
+         status = client().resource(policiesEndpoint + "/" + newPolicyId).get(Lifecycle.class);
+         assertEquals(status, Lifecycle.STOPPED);
+ 
+         response = client().resource(policiesEndpoint+"/"+newPolicyId+"/destroy")
+                 .post(ClientResponse.class);
+         assertEquals(response.getStatus(), 204);
+ 
+         response = client().resource(policiesEndpoint+"/"+newPolicyId).get(ClientResponse.class);
+         log.info("POLICY STATUS RESPONSE AFTER DESTROY: " + response.getStatus());
+         assertEquals(response.getStatus(), 404);
+ 
+         policies = client().resource(policiesEndpoint).get(new GenericType<Set<PolicySummary>>() {});
+         assertEquals(0, policies.size());
+     }
+ 
+     @SuppressWarnings({ "rawtypes" })
+     @Test(dependsOnMethods = "testDeployApplication")
+     public void testLocatedLocation() {
+         log.info("starting testLocatedLocations");
+         testListApplications();
+ 
+         LocationInternal l = (LocationInternal) getManagementContext().getApplications().iterator().next().getLocations().iterator().next();
+         if (l.config().getLocalRaw(LocationConfigKeys.LATITUDE).isAbsent()) {
+             log.info("Supplying fake locations for localhost because could not be autodetected");
+             ((AbstractLocation) l).setHostGeoInfo(new HostGeoInfo("localhost", "localhost", 50, 0));
+         }
+         Map result = client().resource("/v1/locations/usage/LocatedLocations")
+                 .get(Map.class);
+         log.info("LOCATIONS: " + result);
+         Assert.assertEquals(result.size(), 1);
+         Map details = (Map) result.values().iterator().next();
+         assertEquals(details.get("leafEntityCount"), 2);
+     }
+ 
+     @Test(dependsOnMethods = {"testListEffectors", "testFetchApplicationsAndEntity", "testTriggerSampleEffector", "testListApplications","testReadEachSensor","testPolicyWhichCapitalizes","testLocatedLocation"})
+     public void testDeleteApplication() throws TimeoutException, InterruptedException {
+         waitForPageFoundResponse("/v1/applications/simple-app", ApplicationSummary.class);
+         Collection<Application> apps = getManagementContext().getApplications();
+         log.info("Deleting simple-app from " + apps);
+         int size = apps.size();
+ 
+         ClientResponse response = client().resource("/v1/applications/simple-app")
+                 .delete(ClientResponse.class);
+ 
+         assertEquals(response.getStatus(), Response.Status.ACCEPTED.getStatusCode());
+         TaskSummary task = response.getEntity(TaskSummary.class);
+         assertTrue(task.getDescription().toLowerCase().contains("destroy"), task.getDescription());
+         assertTrue(task.getDescription().toLowerCase().contains("simple-app"), task.getDescription());
+ 
+         waitForPageNotFoundResponse("/v1/applications/simple-app", ApplicationSummary.class);
+ 
+         log.info("App appears gone, apps are: " + getManagementContext().getApplications());
+         // more logging above, for failure in the check below
+ 
+         Asserts.eventually(
+                 EntityFunctions.applications(getManagementContext()),
+                 Predicates.compose(Predicates.equalTo(size-1), CollectionFunctionals.sizeFunction()) );
+     }
+ 
+     @Test
+     public void testDisabledApplicationCatalog() throws TimeoutException, InterruptedException {
+         String itemSymbolicName = "my.catalog.item.id.for.disabling";
+         String itemVersion = "1.0";
+         String serviceType = "org.apache.brooklyn.entity.stock.BasicApplication";
+         
+         // Deploy the catalog item
+         addTestCatalogItem(itemSymbolicName, "template", itemVersion, serviceType);
+         List<CatalogEntitySummary> itemSummaries = client().resource("/v1/catalog/applications")
+                 .queryParam("fragment", itemSymbolicName).queryParam("allVersions", "true").get(new GenericType<List<CatalogEntitySummary>>() {});
+         CatalogItemSummary itemSummary = Iterables.getOnlyElement(itemSummaries);
+         String itemVersionedId = String.format("%s:%s", itemSummary.getSymbolicName(), itemSummary.getVersion());
+         assertEquals(itemSummary.getId(), itemVersionedId);
+ 
+         try {
+             // Create an app before disabling: this should work
+             String yaml = "{ name: my-app, location: localhost, services: [ { type: \""+itemVersionedId+"\" } ] }";
+             ClientResponse response = client().resource("/v1/applications")
+                     .entity(yaml, "application/x-yaml")
+                     .post(ClientResponse.class);
+             HttpAsserts.assertHealthyStatusCode(response.getStatus());
+             waitForPageFoundResponse("/v1/applications/my-app", ApplicationSummary.class);
+     
+             // Deprecate
+             deprecateCatalogItem(itemSymbolicName, itemVersion, true);
+ 
+             // Create an app when deprecated: this should work
+             String yaml2 = "{ name: my-app2, location: localhost, services: [ { type: \""+itemVersionedId+"\" } ] }";
+             ClientResponse response2 = client().resource("/v1/applications")
+                     .entity(yaml2, "application/x-yaml")
+                     .post(ClientResponse.class);
+             HttpAsserts.assertHealthyStatusCode(response2.getStatus());
+             waitForPageFoundResponse("/v1/applications/my-app2", ApplicationSummary.class);
+     
+             // Disable
+             disableCatalogItem(itemSymbolicName, itemVersion, true);
+ 
+             // Now try creating an app; this should fail because app is disabled
+             String yaml3 = "{ name: my-app3, location: localhost, services: [ { type: \""+itemVersionedId+"\" } ] }";
+             ClientResponse response3 = client().resource("/v1/applications")
+                     .entity(yaml3, "application/x-yaml")
+                     .post(ClientResponse.class);
+             HttpAsserts.assertClientErrorStatusCode(response3.getStatus());
 -            assertTrue(response3.getEntity(String.class).contains("cannot be matched"));
++            assertTrue(response3.getEntity(String.class).toLowerCase().contains("unable to match"));
+             waitForPageNotFoundResponse("/v1/applications/my-app3", ApplicationSummary.class);
+             
+         } finally {
+             client().resource("/v1/applications/my-app")
+                     .delete(ClientResponse.class);
+ 
+             client().resource("/v1/applications/my-app2")
+                     .delete(ClientResponse.class);
+ 
+             client().resource("/v1/applications/my-app3")
+                     .delete(ClientResponse.class);
+ 
+             client().resource("/v1/catalog/entities/"+itemVersionedId+"/"+itemVersion)
+                     .delete(ClientResponse.class);
+         }
+     }
+ 
+     private void deprecateCatalogItem(String symbolicName, String version, boolean deprecated) {
+         String id = String.format("%s:%s", symbolicName, version);
+         ClientResponse response = client().resource(String.format("/v1/catalog/entities/%s/deprecated", id))
+                     .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+                     .post(ClientResponse.class, deprecated);
+         assertEquals(response.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
+     }
+     
+     private void disableCatalogItem(String symbolicName, String version, boolean disabled) {
+         String id = String.format("%s:%s", symbolicName, version);
+         ClientResponse response = client().resource(String.format("/v1/catalog/entities/%s/disabled", id))
+                 .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+                 .post(ClientResponse.class, disabled);
+         assertEquals(response.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
+     }
+ 
+     private void addTestCatalogItem(String catalogItemId, String itemType, String version, String service) {
+         String yaml =
+                 "brooklyn.catalog:\n"+
+                 "  id: " + catalogItemId + "\n"+
+                 "  name: My Catalog App\n"+
+                 (itemType!=null ? "  item_type: "+itemType+"\n" : "")+
+                 "  description: My description\n"+
+                 "  icon_url: classpath:///redis-logo.png\n"+
+                 "  version: " + version + "\n"+
+                 "\n"+
+                 "services:\n"+
+                 "- type: " + service + "\n";
+ 
+         client().resource("/v1/catalog").post(yaml);
+     }
+ }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/79b98c67/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/CatalogResourceTest.java
----------------------------------------------------------------------
diff --cc rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/CatalogResourceTest.java
index 0000000,4e4d79e..a810e6d
mode 000000,100644..100644
--- a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/CatalogResourceTest.java
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/CatalogResourceTest.java
@@@ -1,0 -1,513 +1,515 @@@
+ /*
+  * Licensed to the Apache Software Foundation (ASF) under one
+  * or more contributor license agreements.  See the NOTICE file
+  * distributed with this work for additional information
+  * regarding copyright ownership.  The ASF licenses this file
+  * to you under the Apache License, Version 2.0 (the
+  * "License"); you may not use this file except in compliance
+  * with the License.  You may obtain a copy of the License at
+  *
+  *     http://www.apache.org/licenses/LICENSE-2.0
+  *
+  * Unless required by applicable law or agreed to in writing,
+  * software distributed under the License is distributed on an
+  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  * KIND, either express or implied.  See the License for the
+  * specific language governing permissions and limitations
+  * under the License.
+  */
+ package org.apache.brooklyn.rest.resources;
+ 
+ import static org.testng.Assert.assertEquals;
+ import static org.testng.Assert.assertTrue;
+ 
+ import java.awt.*;
+ import java.io.IOException;
+ import java.net.URI;
+ import java.net.URL;
+ import java.util.Collection;
+ import java.util.List;
+ import java.util.Map;
+ import java.util.Set;
+ 
+ import javax.ws.rs.core.MediaType;
+ import javax.ws.rs.core.Response;
+ 
+ import org.apache.brooklyn.api.typereg.OsgiBundleWithUrl;
+ import org.apache.brooklyn.api.typereg.RegisteredType;
+ import org.apache.brooklyn.core.catalog.internal.CatalogUtils;
+ import org.apache.brooklyn.core.mgmt.osgi.OsgiStandaloneTest;
+ import org.apache.brooklyn.core.test.entity.TestEntity;
+ import org.apache.brooklyn.policy.autoscaling.AutoScalerPolicy;
+ 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.testing.BrooklynRestResourceTest;
+ import org.apache.brooklyn.test.support.TestResourceUnavailableException;
+ import org.apache.brooklyn.util.javalang.Reflections;
+ import org.apache.http.HttpHeaders;
+ import org.apache.http.entity.ContentType;
+ import org.eclipse.jetty.http.HttpStatus;
+ import org.slf4j.Logger;
+ import org.slf4j.LoggerFactory;
+ import org.testng.Assert;
+ import org.testng.annotations.BeforeClass;
+ import org.testng.annotations.Test;
+ import org.testng.reporters.Files;
+ 
+ import com.google.common.base.Joiner;
+ import com.google.common.collect.Iterables;
+ import com.sun.jersey.api.client.ClientResponse;
+ import com.sun.jersey.api.client.GenericType;
+ 
+ public class CatalogResourceTest extends BrooklynRestResourceTest {
+ 
+     private static final Logger log = LoggerFactory.getLogger(CatalogResourceTest.class);
+     
+     private static String TEST_VERSION = "0.1.2";
+ 
+     @BeforeClass(alwaysRun=true)
+     @Override
+     public void setUp() throws Exception {
+         useLocalScannedCatalog();
+         super.setUp();
+     }
+     
+     @Override
+     protected void addBrooklynResources() {
+         addResource(new CatalogResource());
+     }
+ 
+     @Test
+     /** based on CampYamlLiteTest */
+     public void testRegisterCustomEntityTopLevelSyntaxWithBundleWhereEntityIsFromCoreAndIconFromBundle() {
+         TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH);
+ 
+         String symbolicName = "my.catalog.entity.id";
+         String bundleUrl = OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL;
+         String yaml =
+                 "brooklyn.catalog:\n"+
+                 "  id: " + symbolicName + "\n"+
+                 "  name: My Catalog App\n"+
+                 "  description: My description\n"+
+                 "  icon_url: classpath:/org/apache/brooklyn/test/osgi/entities/icon.gif\n"+
+                 "  version: " + TEST_VERSION + "\n"+
+                 "  libraries:\n"+
+                 "  - url: " + bundleUrl + "\n"+
+                 "\n"+
+                 "services:\n"+
+                 "- type: org.apache.brooklyn.core.test.entity.TestEntity\n";
+ 
+         ClientResponse response = client().resource("/v1/catalog")
+                 .post(ClientResponse.class, yaml);
+ 
+         assertEquals(response.getStatus(), Response.Status.CREATED.getStatusCode());
+ 
+         CatalogEntitySummary entityItem = client().resource("/v1/catalog/entities/"+symbolicName + "/" + TEST_VERSION)
+                 .get(CatalogEntitySummary.class);
+ 
+         Assert.assertNotNull(entityItem.getPlanYaml());
+         Assert.assertTrue(entityItem.getPlanYaml().contains("org.apache.brooklyn.core.test.entity.TestEntity"));
+ 
+         assertEquals(entityItem.getId(), ver(symbolicName));
+         assertEquals(entityItem.getSymbolicName(), symbolicName);
+         assertEquals(entityItem.getVersion(), TEST_VERSION);
+ 
+         // and internally let's check we have libraries
+         RegisteredType item = getManagementContext().getTypeRegistry().get(symbolicName, TEST_VERSION);
+         Assert.assertNotNull(item);
+         Collection<OsgiBundleWithUrl> libs = item.getLibraries();
+         assertEquals(libs.size(), 1);
+         assertEquals(Iterables.getOnlyElement(libs).getUrl(), bundleUrl);
+ 
+         // now let's check other things on the item
+         assertEquals(entityItem.getName(), "My Catalog App");
+         assertEquals(entityItem.getDescription(), "My description");
+         assertEquals(URI.create(entityItem.getIconUrl()).getPath(), "/v1/catalog/icon/" + symbolicName + "/" + entityItem.getVersion());
+         assertEquals(item.getIconUrl(), "classpath:/org/apache/brooklyn/test/osgi/entities/icon.gif");
+ 
+         // an InterfacesTag should be created for every catalog item
+         assertEquals(entityItem.getTags().size(), 1);
+         Object tag = entityItem.getTags().iterator().next();
++        @SuppressWarnings("unchecked")
+         List<String> actualInterfaces = ((Map<String, List<String>>) tag).get("traits");
+         List<Class<?>> expectedInterfaces = Reflections.getAllInterfaces(TestEntity.class);
+         assertEquals(actualInterfaces.size(), expectedInterfaces.size());
+         for (Class<?> expectedInterface : expectedInterfaces) {
+             assertTrue(actualInterfaces.contains(expectedInterface.getName()));
+         }
+ 
+         byte[] iconData = client().resource("/v1/catalog/icon/" + symbolicName + "/" + TEST_VERSION).get(byte[].class);
+         assertEquals(iconData.length, 43);
+     }
+ 
+     @Test
++    // osgi fails in IDE, should work on CLI though
+     public void testRegisterOsgiPolicyTopLevelSyntax() {
+         TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH);
+ 
+         String symbolicName = "my.catalog.policy.id";
+         String policyType = "org.apache.brooklyn.test.osgi.entities.SimplePolicy";
+         String bundleUrl = OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL;
+ 
+         String yaml =
+                 "brooklyn.catalog:\n"+
+                 "  id: " + symbolicName + "\n"+
+                 "  name: My Catalog App\n"+
+                 "  description: My description\n"+
+                 "  version: " + TEST_VERSION + "\n" +
+                 "  libraries:\n"+
+                 "  - url: " + bundleUrl + "\n"+
+                 "\n"+
+                 "brooklyn.policies:\n"+
+                 "- type: " + policyType;
+ 
+         CatalogPolicySummary entityItem = Iterables.getOnlyElement( client().resource("/v1/catalog")
+                 .post(new GenericType<Map<String,CatalogPolicySummary>>() {}, yaml).values() );
+ 
+         Assert.assertNotNull(entityItem.getPlanYaml());
+         Assert.assertTrue(entityItem.getPlanYaml().contains(policyType));
+         assertEquals(entityItem.getId(), ver(symbolicName));
+         assertEquals(entityItem.getSymbolicName(), symbolicName);
+         assertEquals(entityItem.getVersion(), TEST_VERSION);
+     }
+ 
+     @Test
+     public void testListAllEntities() {
+         List<CatalogEntitySummary> entities = client().resource("/v1/catalog/entities")
+                 .get(new GenericType<List<CatalogEntitySummary>>() {});
+         assertTrue(entities.size() > 0);
+     }
+ 
+     @Test
+     public void testListAllEntitiesAsItem() {
+         // ensure things are happily downcasted and unknown properties ignored (e.g. sensors, effectors)
+         List<CatalogItemSummary> entities = client().resource("/v1/catalog/entities")
+                 .get(new GenericType<List<CatalogItemSummary>>() {});
+         assertTrue(entities.size() > 0);
+     }
+ 
+     @Test
+     public void testFilterListOfEntitiesByName() {
+         List<CatalogEntitySummary> entities = client().resource("/v1/catalog/entities")
+                 .queryParam("fragment", "brOOkLynENTITYmiRrOr").get(new GenericType<List<CatalogEntitySummary>>() {});
+         assertEquals(entities.size(), 1);
+ 
+         log.info("BrooklynEntityMirror-like entities are: " + entities);
+ 
+         List<CatalogEntitySummary> entities2 = client().resource("/v1/catalog/entities")
+                 .queryParam("regex", "[Bb]ro+klynEntityMi[ro]+").get(new GenericType<List<CatalogEntitySummary>>() {});
+         assertEquals(entities2.size(), 1);
+ 
+         assertEquals(entities, entities2);
+     
+         List<CatalogEntitySummary> entities3 = client().resource("/v1/catalog/entities")
+                 .queryParam("fragment", "bweqQzZ").get(new GenericType<List<CatalogEntitySummary>>() {});
+         assertEquals(entities3.size(), 0);
+ 
+         List<CatalogEntitySummary> entities4 = client().resource("/v1/catalog/entities")
+                 .queryParam("regex", "bweq+z+").get(new GenericType<List<CatalogEntitySummary>>() {});
+         assertEquals(entities4.size(), 0);
+     }
+ 
+     @Test
+     @Deprecated
+     // If we move to using a yaml catalog item, the details will be of the wrapping app,
+     // not of the entity itself, so the test won't make sense any more.
+     public void testGetCatalogEntityDetails() {
+         CatalogEntitySummary details = client()
+                 .resource(URI.create("/v1/catalog/entities/org.apache.brooklyn.entity.brooklynnode.BrooklynNode"))
+                 .get(CatalogEntitySummary.class);
+         assertTrue(details.toString().contains("download.url"), "expected more config, only got: "+details);
+     }
+ 
+     @Test
+     @Deprecated
+     // If we move to using a yaml catalog item, the details will be of the wrapping app,
+     // not of the entity itself, so the test won't make sense any more.
+     public void testGetCatalogEntityPlusVersionDetails() {
+         CatalogEntitySummary details = client()
+                 .resource(URI.create("/v1/catalog/entities/org.apache.brooklyn.entity.brooklynnode.BrooklynNode:0.0.0.SNAPSHOT"))
+                 .get(CatalogEntitySummary.class);
+         assertTrue(details.toString().contains("download.url"), "expected more config, only got: "+details);
+     }
+ 
+     @Test
+     public void testGetCatalogEntityIconDetails() throws IOException {
+         String catalogItemId = "testGetCatalogEntityIconDetails";
+         addTestCatalogItemBrooklynNodeAsEntity(catalogItemId);
+         ClientResponse response = client().resource(URI.create("/v1/catalog/icon/" + catalogItemId + "/" + TEST_VERSION))
+                 .get(ClientResponse.class);
+         response.bufferEntity();
+         Assert.assertEquals(response.getStatus(), 200);
+         Assert.assertEquals(response.getType(), MediaType.valueOf("image/jpeg"));
+         Image image = Toolkit.getDefaultToolkit().createImage(Files.readFile(response.getEntityInputStream()));
+         Assert.assertNotNull(image);
+     }
+ 
+     private void addTestCatalogItemBrooklynNodeAsEntity(String catalogItemId) {
+         addTestCatalogItem(catalogItemId, null, TEST_VERSION, "org.apache.brooklyn.entity.brooklynnode.BrooklynNode");
+     }
+ 
+     private void addTestCatalogItem(String catalogItemId, String itemType, String version, String service) {
+         String yaml =
+                 "brooklyn.catalog:\n"+
+                 "  id: " + catalogItemId + "\n"+
+                 "  name: My Catalog App\n"+
+                 (itemType!=null ? "  item_type: "+itemType+"\n" : "")+
+                 "  description: My description\n"+
+                 "  icon_url: classpath:///brooklyn-test-logo.jpg\n"+
+                 "  version: " + version + "\n"+
+                 "\n"+
+                 "services:\n"+
+                 "- type: " + service + "\n";
+ 
+         client().resource("/v1/catalog").post(yaml);
+     }
+ 
+     private enum DeprecateStyle {
+         NEW_STYLE,
+         LEGACY_STYLE
+     }
+     private void deprecateCatalogItem(DeprecateStyle style, String symbolicName, String version, boolean deprecated) {
+         String id = String.format("%s:%s", symbolicName, version);
+         ClientResponse response;
+         if (style == DeprecateStyle.NEW_STYLE) {
+             response = client().resource(String.format("/v1/catalog/entities/%s/deprecated", id))
+                     .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+                     .post(ClientResponse.class, deprecated);
+         } else {
+             response = client().resource(String.format("/v1/catalog/entities/%s/deprecated/%s", id, deprecated))
+                     .post(ClientResponse.class);
+         }
+         assertEquals(response.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
+     }
+ 
+     private void disableCatalogItem(String symbolicName, String version, boolean disabled) {
+         String id = String.format("%s:%s", symbolicName, version);
+         ClientResponse getDisableResponse = client().resource(String.format("/v1/catalog/entities/%s/disabled", id))
+                 .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+                 .post(ClientResponse.class, disabled);
+         assertEquals(getDisableResponse.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
+     }
+ 
+     @Test
+     public void testListPolicies() {
+         Set<CatalogPolicySummary> policies = client().resource("/v1/catalog/policies")
+                 .get(new GenericType<Set<CatalogPolicySummary>>() {});
+ 
+         assertTrue(policies.size() > 0);
+         CatalogItemSummary asp = null;
+         for (CatalogItemSummary p : policies) {
+             if (AutoScalerPolicy.class.getName().equals(p.getType()))
+                 asp = p;
+         }
+         Assert.assertNotNull(asp, "didn't find AutoScalerPolicy");
+     }
+ 
+     @Test
+     public void testLocationAddGetAndRemove() {
+         String symbolicName = "my.catalog.location.id";
+         String locationType = "localhost";
+         String yaml = Joiner.on("\n").join(
+                 "brooklyn.catalog:",
+                 "  id: " + symbolicName,
+                 "  name: My Catalog Location",
+                 "  description: My description",
+                 "  version: " + TEST_VERSION,
+                 "",
+                 "brooklyn.locations:",
+                 "- type: " + locationType);
+ 
+         // Create location item
+         Map<String, CatalogLocationSummary> items = client().resource("/v1/catalog")
+                 .post(new GenericType<Map<String,CatalogLocationSummary>>() {}, yaml);
+         CatalogLocationSummary locationItem = Iterables.getOnlyElement(items.values());
+ 
+         Assert.assertNotNull(locationItem.getPlanYaml());
+         Assert.assertTrue(locationItem.getPlanYaml().contains(locationType));
+         assertEquals(locationItem.getId(), ver(symbolicName));
+         assertEquals(locationItem.getSymbolicName(), symbolicName);
+         assertEquals(locationItem.getVersion(), TEST_VERSION);
+ 
+         // Retrieve location item
+         CatalogLocationSummary location = client().resource("/v1/catalog/locations/"+symbolicName+"/"+TEST_VERSION)
+                 .get(CatalogLocationSummary.class);
+         assertEquals(location.getSymbolicName(), symbolicName);
+ 
+         // Retrieve all locations
+         Set<CatalogLocationSummary> locations = client().resource("/v1/catalog/locations")
+                 .get(new GenericType<Set<CatalogLocationSummary>>() {});
+         boolean found = false;
+         for (CatalogLocationSummary contender : locations) {
+             if (contender.getSymbolicName().equals(symbolicName)) {
+                 found = true;
+                 break;
+             }
+         }
+         Assert.assertTrue(found, "contenders="+locations);
+         
+         // Delete
+         ClientResponse deleteResponse = client().resource("/v1/catalog/locations/"+symbolicName+"/"+TEST_VERSION)
+                 .delete(ClientResponse.class);
+         assertEquals(deleteResponse.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
+ 
+         ClientResponse getPostDeleteResponse = client().resource("/v1/catalog/locations/"+symbolicName+"/"+TEST_VERSION)
+                 .get(ClientResponse.class);
+         assertEquals(getPostDeleteResponse.getStatus(), Response.Status.NOT_FOUND.getStatusCode());
+     }
+ 
+     @Test
+     public void testDeleteCustomEntityFromCatalog() {
+         String symbolicName = "my.catalog.app.id.to.subsequently.delete";
+         String yaml =
+                 "name: "+symbolicName+"\n"+
+                 // FIXME name above should be unnecessary when brooklyn.catalog below is working
+                 "brooklyn.catalog:\n"+
+                 "  id: " + symbolicName + "\n"+
+                 "  name: My Catalog App To Be Deleted\n"+
+                 "  description: My description\n"+
+                 "  version: " + TEST_VERSION + "\n"+
+                 "\n"+
+                 "services:\n"+
+                 "- type: org.apache.brooklyn.core.test.entity.TestEntity\n";
+ 
+         client().resource("/v1/catalog")
+                 .post(ClientResponse.class, yaml);
+ 
+         ClientResponse deleteResponse = client().resource("/v1/catalog/entities/"+symbolicName+"/"+TEST_VERSION)
+                 .delete(ClientResponse.class);
+ 
+         assertEquals(deleteResponse.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
+ 
+         ClientResponse getPostDeleteResponse = client().resource("/v1/catalog/entities/"+symbolicName+"/"+TEST_VERSION)
+                 .get(ClientResponse.class);
+         assertEquals(getPostDeleteResponse.getStatus(), Response.Status.NOT_FOUND.getStatusCode());
+     }
+ 
+     @Test
+     public void testSetDeprecated() {
+         runSetDeprecated(DeprecateStyle.NEW_STYLE);
+     }
+     
+     // Uses old-style "/v1/catalog/{itemId}/deprecated/true", rather than the "true" in the request body.
+     @Test
+     @Deprecated
+     public void testSetDeprecatedLegacy() {
+         runSetDeprecated(DeprecateStyle.LEGACY_STYLE);
+     }
+ 
+     protected void runSetDeprecated(DeprecateStyle style) {
+         String symbolicName = "my.catalog.item.id.for.deprecation";
+         String serviceType = "org.apache.brooklyn.entity.stock.BasicApplication";
+         addTestCatalogItem(symbolicName, "template", TEST_VERSION, serviceType);
+         addTestCatalogItem(symbolicName, "template", "2.0", serviceType);
+         try {
+             List<CatalogEntitySummary> applications = client().resource("/v1/catalog/applications")
+                     .queryParam("fragment", symbolicName).queryParam("allVersions", "true").get(new GenericType<List<CatalogEntitySummary>>() {});
+             assertEquals(applications.size(), 2);
+             CatalogItemSummary summary0 = applications.get(0);
+             CatalogItemSummary summary1 = applications.get(1);
+     
+             // Deprecate: that app should be excluded
+             deprecateCatalogItem(style, summary0.getSymbolicName(), summary0.getVersion(), true);
+     
+             List<CatalogEntitySummary> applicationsAfterDeprecation = client().resource("/v1/catalog/applications")
+                     .queryParam("fragment", "basicapp").queryParam("allVersions", "true").get(new GenericType<List<CatalogEntitySummary>>() {});
+     
+             assertEquals(applicationsAfterDeprecation.size(), 1);
+             assertTrue(applicationsAfterDeprecation.contains(summary1));
+     
+             // Un-deprecate: that app should be included again
+             deprecateCatalogItem(style, summary0.getSymbolicName(), summary0.getVersion(), false);
+     
+             List<CatalogEntitySummary> applicationsAfterUnDeprecation = client().resource("/v1/catalog/applications")
+                     .queryParam("fragment", "basicapp").queryParam("allVersions", "true").get(new GenericType<List<CatalogEntitySummary>>() {});
+     
+             assertEquals(applications, applicationsAfterUnDeprecation);
+         } finally {
+             client().resource("/v1/catalog/entities/"+symbolicName+"/"+TEST_VERSION)
+                     .delete(ClientResponse.class);
+             client().resource("/v1/catalog/entities/"+symbolicName+"/"+"2.0")
+                     .delete(ClientResponse.class);
+         }
+     }
+ 
+     @Test
+     public void testSetDisabled() {
+         String symbolicName = "my.catalog.item.id.for.disabling";
+         String serviceType = "org.apache.brooklyn.entity.stock.BasicApplication";
+         addTestCatalogItem(symbolicName, "template", TEST_VERSION, serviceType);
+         addTestCatalogItem(symbolicName, "template", "2.0", serviceType);
+         try {
+             List<CatalogEntitySummary> applications = client().resource("/v1/catalog/applications")
+                     .queryParam("fragment", symbolicName).queryParam("allVersions", "true").get(new GenericType<List<CatalogEntitySummary>>() {});
+             assertEquals(applications.size(), 2);
+             CatalogItemSummary summary0 = applications.get(0);
+             CatalogItemSummary summary1 = applications.get(1);
+     
+             // Disable: that app should be excluded
+             disableCatalogItem(summary0.getSymbolicName(), summary0.getVersion(), true);
+     
+             List<CatalogEntitySummary> applicationsAfterDisabled = client().resource("/v1/catalog/applications")
+                     .queryParam("fragment", "basicapp").queryParam("allVersions", "true").get(new GenericType<List<CatalogEntitySummary>>() {});
+     
+             assertEquals(applicationsAfterDisabled.size(), 1);
+             assertTrue(applicationsAfterDisabled.contains(summary1));
+     
+             // Un-disable: that app should be included again
+             disableCatalogItem(summary0.getSymbolicName(), summary0.getVersion(), false);
+     
+             List<CatalogEntitySummary> applicationsAfterUnDisabled = client().resource("/v1/catalog/applications")
+                     .queryParam("fragment", "basicapp").queryParam("allVersions", "true").get(new GenericType<List<CatalogEntitySummary>>() {});
+     
+             assertEquals(applications, applicationsAfterUnDisabled);
+         } finally {
+             client().resource("/v1/catalog/entities/"+symbolicName+"/"+TEST_VERSION)
+                     .delete(ClientResponse.class);
+             client().resource("/v1/catalog/entities/"+symbolicName+"/"+"2.0")
+                     .delete(ClientResponse.class);
+         }
+     }
+ 
+     @Test
+     public void testAddUnreachableItem() {
+         addInvalidCatalogItem("http://0.0.0.0/can-not-connect");
+     }
+ 
+     @Test
+     public void testAddInvalidItem() {
+         //equivalent to HTTP response 200 text/html
+         addInvalidCatalogItem("classpath://not-a-jar-file.txt");
+     }
+ 
+     @Test
+     public void testAddMissingItem() {
+         //equivalent to HTTP response 404 text/html
+         addInvalidCatalogItem("classpath://missing-jar-file.txt");
+     }
+ 
+     private void addInvalidCatalogItem(String bundleUrl) {
+         String symbolicName = "my.catalog.entity.id";
+         String yaml =
+                 "brooklyn.catalog:\n"+
+                 "  id: " + symbolicName + "\n"+
+                 "  name: My Catalog App\n"+
+                 "  description: My description\n"+
+                 "  icon_url: classpath:/org/apache/brooklyn/test/osgi/entities/icon.gif\n"+
+                 "  version: " + TEST_VERSION + "\n"+
+                 "  libraries:\n"+
+                 "  - url: " + bundleUrl + "\n"+
+                 "\n"+
+                 "services:\n"+
+                 "- type: org.apache.brooklyn.core.test.entity.TestEntity\n";
+ 
+         ClientResponse response = client().resource("/v1/catalog")
+                 .post(ClientResponse.class, yaml);
+ 
 -        assertEquals(response.getStatus(), HttpStatus.INTERNAL_SERVER_ERROR_500);
++        assertEquals(response.getStatus(), HttpStatus.BAD_REQUEST_400);
+     }
+ 
+     private static String ver(String id) {
+         return CatalogUtils.getVersionedId(id, TEST_VERSION);
+     }
+ }


[14/34] brooklyn-server git commit: [BROOKLYN-183] REST API using CXF JAX-RS 2.0 implementation

Posted by he...@apache.org.
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/ErrorAndToStringUnknownTypeSerializer.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/ErrorAndToStringUnknownTypeSerializer.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/ErrorAndToStringUnknownTypeSerializer.java
new file mode 100644
index 0000000..e529ec9
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/ErrorAndToStringUnknownTypeSerializer.java
@@ -0,0 +1,123 @@
+/*
+ * 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.json;
+
+import java.io.IOException;
+import java.io.NotSerializableException;
+import java.util.Collections;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+
+import org.apache.brooklyn.util.collections.MutableSet;
+import org.apache.brooklyn.util.javalang.Reflections;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonStreamContext;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.impl.UnknownSerializer;
+
+/**
+ * for non-json-serializable classes (quite a lot of them!) simply provide a sensible error message and a toString.
+ * TODO maybe we want to attempt to serialize fields instead?  (but being careful not to be self-referential!)
+ */
+public class ErrorAndToStringUnknownTypeSerializer extends UnknownSerializer {
+
+    private static final Logger log = LoggerFactory.getLogger(ErrorAndToStringUnknownTypeSerializer.class);
+    private static Set<String> WARNED_CLASSES = Collections.synchronizedSet(MutableSet.<String>of());
+
+    @Override
+    public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
+        if (BidiSerialization.isStrictSerialization())
+            throw new JsonMappingException("Cannot serialize object containing "+value.getClass().getName()+" when strict serialization requested");
+
+        serializeFromError(jgen.getOutputContext(), null, value, jgen, provider);
+    }
+
+    public void serializeFromError(JsonStreamContext ctxt, @Nullable Exception error, Object value, JsonGenerator jgen, SerializerProvider configurableSerializerProvider) throws IOException {
+        if (log.isDebugEnabled())
+            log.debug("Recovering from json serialization error, serializing "+value+": "+error);
+
+        if (BidiSerialization.isStrictSerialization())
+            throw new JsonMappingException("Cannot serialize "
+                + (ctxt!=null && !ctxt.inRoot() ? "object containing " : "")
+                + value.getClass().getName()+" when strict serialization requested");
+
+        if (WARNED_CLASSES.add(value.getClass().getCanonicalName())) {
+            log.warn("Standard serialization not possible for "+value.getClass()+" ("+value+")", error);
+        }
+        JsonStreamContext newCtxt = jgen.getOutputContext();
+
+        // very odd, but flush seems necessary when working with large objects; presumably a buffer which is allowed to clear itself?
+        // without this, when serializing the large (1.5M) Server json object from BrooklynJacksonSerializerTest creates invalid json,
+        // containing:  "foo":false,"{"error":true,...
+        jgen.flush();
+
+        boolean createObject = !newCtxt.inObject() || newCtxt.getCurrentName()!=null;
+        if (createObject) {
+            jgen.writeStartObject();
+        }
+
+        if (allowEmpty(value.getClass())) {
+            // write nothing
+        } else {
+
+            jgen.writeFieldName("error");
+            jgen.writeBoolean(true);
+
+            jgen.writeFieldName("errorType");
+            jgen.writeString(NotSerializableException.class.getCanonicalName());
+
+            jgen.writeFieldName("type");
+            jgen.writeString(value.getClass().getCanonicalName());
+
+            jgen.writeFieldName("toString");
+            jgen.writeString(value.toString());
+
+            if (error!=null) {
+                jgen.writeFieldName("causedByError");
+                jgen.writeString(error.toString());
+            }
+
+        }
+
+        if (createObject) {
+            jgen.writeEndObject();
+        }
+
+        while (newCtxt!=null && !newCtxt.equals(ctxt)) {
+            if (jgen.getOutputContext().inArray()) { jgen.writeEndArray(); continue; }
+            if (jgen.getOutputContext().inObject()) { jgen.writeEndObject(); continue; }
+            break;
+        }
+
+    }
+
+    protected boolean allowEmpty(Class<? extends Object> clazz) {
+        if (clazz.getAnnotation(JsonSerialize.class)!=null && Reflections.hasNoNonObjectFields(clazz)) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/MultimapSerializer.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/MultimapSerializer.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/MultimapSerializer.java
new file mode 100644
index 0000000..f750e86
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/MultimapSerializer.java
@@ -0,0 +1,64 @@
+/*
+ * 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.json;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Map;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.std.StdSerializer;
+
+import com.google.common.annotations.Beta;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
+
+/**
+ * Provides a serializer for {@link Multimap} instances.
+ * <p>
+ * When Brooklyn's Jackson dependency is updated from org.codehaus.jackson:1.9.13 to
+ * com.fasterxml.jackson:2.3+ then this class should be replaced with a dependency on
+ * jackson-datatype-guava and a GuavaModule registered with Brooklyn's ObjectMapper.
+ * Check the guava version when doing the switch as it could be incompatible with the
+ * version used by Brooklyn.
+ */
+@Beta
+public class MultimapSerializer extends StdSerializer<Multimap<?, ?>> {
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    protected MultimapSerializer() {
+        super((Class<Multimap<?, ?>>) (Class) Multimap.class);
+    }
+
+    @Override
+    public void serialize(Multimap<?, ?> value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
+        jgen.writeStartObject();
+        writeEntries(value, jgen, provider);
+        jgen.writeEndObject();
+    }
+
+    private void writeEntries(Multimap<?, ?> value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
+        for (Map.Entry<?, ? extends Collection<?>> entry : value.asMap().entrySet()) {
+            provider.findKeySerializer(provider.constructType(String.class), null)
+                    .serialize(entry.getKey(), jgen, provider);
+            provider.defaultSerializeValue(Lists.newArrayList(entry.getValue()), jgen);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/PossiblyStrictPreferringFieldsVisibilityChecker.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/PossiblyStrictPreferringFieldsVisibilityChecker.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/PossiblyStrictPreferringFieldsVisibilityChecker.java
new file mode 100644
index 0000000..e474467
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/PossiblyStrictPreferringFieldsVisibilityChecker.java
@@ -0,0 +1,108 @@
+/*
+ * 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.json;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.PropertyAccessor;
+import com.fasterxml.jackson.databind.introspect.AnnotatedField;
+import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
+import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
+import com.fasterxml.jackson.databind.introspect.VisibilityChecker;
+
+import static com.fasterxml.jackson.annotation.JsonAutoDetect.*;
+
+/** a visibility checker which disables getters, but allows private access,
+ * unless {@link BidiSerialization#isStrictSerialization()} is enabled in which case public fields or annotations must be used.
+ * <p>
+ * the reason for this change to visibility
+ * is that getters might generate a copy, resulting in infinite loops, whereas field access should never do so.
+ * (see e.g. test in {@link BrooklynJacksonSerializerTest} which uses a sensor+config object whose getTypeToken
+ * causes infinite recursion)
+ **/
+public class PossiblyStrictPreferringFieldsVisibilityChecker implements VisibilityChecker<PossiblyStrictPreferringFieldsVisibilityChecker> {
+    VisibilityChecker<?>
+        vizDefault = new VisibilityChecker.Std(Visibility.NONE, Visibility.NONE, Visibility.NONE, Visibility.ANY, Visibility.ANY),
+        vizStrict = new VisibilityChecker.Std(Visibility.NONE, Visibility.NONE, Visibility.NONE, Visibility.PUBLIC_ONLY, Visibility.PUBLIC_ONLY);
+    
+    @Override public PossiblyStrictPreferringFieldsVisibilityChecker with(JsonAutoDetect ann) { throw new UnsupportedOperationException(); }
+    @Override public PossiblyStrictPreferringFieldsVisibilityChecker with(Visibility v) { throw new UnsupportedOperationException(); }
+    @Override public PossiblyStrictPreferringFieldsVisibilityChecker withVisibility(PropertyAccessor method, Visibility v) { throw new UnsupportedOperationException(); }
+    @Override public PossiblyStrictPreferringFieldsVisibilityChecker withGetterVisibility(Visibility v) { throw new UnsupportedOperationException(); }
+    @Override public PossiblyStrictPreferringFieldsVisibilityChecker withIsGetterVisibility(Visibility v) { throw new UnsupportedOperationException(); }
+    @Override public PossiblyStrictPreferringFieldsVisibilityChecker withSetterVisibility(Visibility v) { throw new UnsupportedOperationException(); }
+    @Override public PossiblyStrictPreferringFieldsVisibilityChecker withCreatorVisibility(Visibility v) { throw new UnsupportedOperationException(); }
+    @Override public PossiblyStrictPreferringFieldsVisibilityChecker withFieldVisibility(Visibility v) { throw new UnsupportedOperationException(); }
+    
+    protected VisibilityChecker<?> viz() {
+        return BidiSerialization.isStrictSerialization() ? vizStrict : vizDefault;
+    }
+    
+    @Override public boolean isGetterVisible(Method m) { 
+        return viz().isGetterVisible(m);
+    }
+
+    @Override
+    public boolean isGetterVisible(AnnotatedMethod m) {
+        return isGetterVisible(m.getAnnotated());
+    }
+
+    @Override
+    public boolean isIsGetterVisible(Method m) {
+        return viz().isIsGetterVisible(m);
+    }
+
+    @Override
+    public boolean isIsGetterVisible(AnnotatedMethod m) {
+        return isIsGetterVisible(m.getAnnotated());
+    }
+
+    @Override
+    public boolean isSetterVisible(Method m) {
+        return viz().isSetterVisible(m);
+    }
+
+    @Override
+    public boolean isSetterVisible(AnnotatedMethod m) {
+        return isSetterVisible(m.getAnnotated());
+    }
+
+    @Override
+    public boolean isCreatorVisible(Member m) {
+        return viz().isCreatorVisible(m);
+    }
+
+    @Override
+    public boolean isCreatorVisible(AnnotatedMember m) {
+        return isCreatorVisible(m.getMember());
+    }
+
+    @Override
+    public boolean isFieldVisible(Field f) {
+        return viz().isFieldVisible(f);
+    }
+
+    @Override
+    public boolean isFieldVisible(AnnotatedField f) {
+        return isFieldVisible(f.getAnnotated());
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/resources/OSGI-INF/blueprint/service.xml
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/resources/OSGI-INF/blueprint/service.xml b/rest/rest-resources/src/main/resources/OSGI-INF/blueprint/service.xml
new file mode 100644
index 0000000..1639fef
--- /dev/null
+++ b/rest/rest-resources/src/main/resources/OSGI-INF/blueprint/service.xml
@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright 2015 The Apache Software Foundation.
+
+Licensed 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.
+-->
+<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
+           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+           xmlns:cm="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.0.0"
+           xmlns:jaxws="http://cxf.apache.org/blueprint/jaxws"
+           xmlns:jaxrs="http://cxf.apache.org/blueprint/jaxrs"
+           xmlns:cxf="http://cxf.apache.org/blueprint/core"
+           xsi:schemaLocation="
+             http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd
+             http://cxf.apache.org/blueprint/jaxws http://cxf.apache.org/schemas/blueprint/jaxws.xsd
+             http://cxf.apache.org/blueprint/jaxrs http://cxf.apache.org/schemas/blueprint/jaxrs.xsd
+             http://cxf.apache.org/blueprint/core http://cxf.apache.org/schemas/blueprint/core.xsd
+             ">
+
+    <cxf:bus>
+        <cxf:features>
+            <cxf:logging/>
+        </cxf:features>
+    </cxf:bus>
+
+    <reference id="localManagementContext"
+               interface="org.apache.brooklyn.api.mgmt.ManagementContext" />
+
+    <bean id="accessResourceBean" class="org.apache.brooklyn.rest.resources.AccessResource">
+        <property name="managementContext" ref="localManagementContext" />
+    </bean>
+    <bean id="activityResourceBean" class="org.apache.brooklyn.rest.resources.ActivityResource">
+        <property name="managementContext" ref="localManagementContext" />
+    </bean>
+    <!--<bean id="apidocResourceBean" class="org.apache.brooklyn.rest.resources.ApidocResource" />-->
+    <bean id="applicationResourceBean" class="org.apache.brooklyn.rest.resources.ApplicationResource">
+        <property name="managementContext" ref="localManagementContext" />
+    </bean>
+    <bean id="catalogResourceBean" class="org.apache.brooklyn.rest.resources.CatalogResource">
+        <property name="managementContext" ref="localManagementContext" />
+    </bean>
+    <bean id="effectorResourceBean" class="org.apache.brooklyn.rest.resources.EffectorResource">
+        <property name="managementContext" ref="localManagementContext" />
+    </bean>
+    <bean id="entityConfigResourceBean" class="org.apache.brooklyn.rest.resources.EntityConfigResource">
+        <property name="managementContext" ref="localManagementContext" />
+    </bean>
+    <bean id="entityResourceBean" class="org.apache.brooklyn.rest.resources.EntityResource">
+        <property name="managementContext" ref="localManagementContext" />
+    </bean>
+    <bean id="locationResourceBean" class="org.apache.brooklyn.rest.resources.LocationResource">
+        <property name="managementContext" ref="localManagementContext" />
+    </bean>
+    <bean id="policyConfigResourceBean" class="org.apache.brooklyn.rest.resources.PolicyConfigResource">
+        <property name="managementContext" ref="localManagementContext" />
+    </bean>
+    <bean id="policyResourceBean" class="org.apache.brooklyn.rest.resources.PolicyResource">
+        <property name="managementContext" ref="localManagementContext" />
+    </bean>
+    <bean id="scriptResourceBean" class="org.apache.brooklyn.rest.resources.ScriptResource">
+        <property name="managementContext" ref="localManagementContext" />
+    </bean>
+    <bean id="sensorResourceBean" class="org.apache.brooklyn.rest.resources.SensorResource">
+        <property name="managementContext" ref="localManagementContext" />
+    </bean>
+    <bean id="serverResourceBean" class="org.apache.brooklyn.rest.resources.ServerResource">
+        <property name="managementContext" ref="localManagementContext" />
+    </bean>
+    <bean id="usageResourceBean" class="org.apache.brooklyn.rest.resources.UsageResource">
+        <property name="managementContext" ref="localManagementContext" />
+    </bean>
+    <bean id="versionResourceBean" class="org.apache.brooklyn.rest.resources.VersionResource">
+        <property name="managementContext" ref="localManagementContext" />
+    </bean>
+
+    <jaxrs:server id="brooklynRestApiV1" address="/">
+        <jaxrs:serviceBeans>
+            <ref component-id="accessResourceBean" />
+            <ref component-id="activityResourceBean" />
+            <!--<ref component-id="apidocResourceBean" />-->
+            <ref component-id="applicationResourceBean" />
+            <ref component-id="catalogResourceBean" />
+            <ref component-id="effectorResourceBean" />
+            <ref component-id="entityConfigResourceBean" />
+            <ref component-id="entityResourceBean" />
+            <ref component-id="locationResourceBean" />
+            <ref component-id="policyConfigResourceBean" />
+            <ref component-id="policyResourceBean" />
+            <ref component-id="scriptResourceBean" />
+            <ref component-id="sensorResourceBean" />
+            <ref component-id="serverResourceBean" />
+            <ref component-id="usageResourceBean" />
+            <ref component-id="versionResourceBean" />
+        </jaxrs:serviceBeans>
+
+        <jaxrs:providers>
+            <bean class="org.apache.brooklyn.rest.util.DefaultExceptionMapper"/>
+            <bean class="com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider"/>
+            <bean class="org.apache.brooklyn.rest.util.FormMapProvider"/>
+        </jaxrs:providers>
+    </jaxrs:server>
+</blueprint>

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/resources/build-metadata.properties
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/resources/build-metadata.properties b/rest/rest-resources/src/main/resources/build-metadata.properties
new file mode 100644
index 0000000..eab85ef
--- /dev/null
+++ b/rest/rest-resources/src/main/resources/build-metadata.properties
@@ -0,0 +1,18 @@
+# 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.
+git-sha-1 = ${buildNumber}
+git-branch-name = ${scmBranch}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/resources/not-a-jar-file.txt
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/resources/not-a-jar-file.txt b/rest/rest-resources/src/main/resources/not-a-jar-file.txt
new file mode 100644
index 0000000..fbc22fe
--- /dev/null
+++ b/rest/rest-resources/src/main/resources/not-a-jar-file.txt
@@ -0,0 +1,18 @@
+# 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.
+
+Test loading of malformed jar file
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/resources/reset-catalog.xml
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/resources/reset-catalog.xml b/rest/rest-resources/src/main/resources/reset-catalog.xml
new file mode 100644
index 0000000..adef40a
--- /dev/null
+++ b/rest/rest-resources/src/main/resources/reset-catalog.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<catalog>
+    <name>Brooklyn Demos</name>
+
+    <template type="org.apache.brooklyn.entity.stock.BasicApplication" name="Basic application" />
+    <template type="org.apache.brooklyn.test.osgi.entities.SimpleApplication" name="Simple OSGi application">
+        <libraries>
+            <bundle>${bundle-location}</bundle>
+        </libraries>
+    </template>
+    <catalog>
+        <name>Nested catalog</name>
+        <template type="org.apache.brooklyn.test.osgi.entities.SimpleApplication" name="Simple OSGi application">
+            <libraries>
+                <bundle>${bundle-location}</bundle>
+            </libraries>
+        </template>
+    </catalog>
+</catalog>

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/domain/ApplicationTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/domain/ApplicationTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/domain/ApplicationTest.java
new file mode 100644
index 0000000..f715178
--- /dev/null
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/domain/ApplicationTest.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.domain;
+
+import static org.apache.brooklyn.rest.util.RestApiTestUtils.asJson;
+import static org.apache.brooklyn.rest.util.RestApiTestUtils.fromJson;
+import static org.apache.brooklyn.rest.util.RestApiTestUtils.jsonFixture;
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.Map;
+
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
+import org.apache.brooklyn.core.test.entity.TestApplication;
+import org.apache.brooklyn.test.Asserts;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+public class ApplicationTest {
+
+    final EntitySpec entitySpec = new EntitySpec("Vanilla Java App", "org.apache.brooklyn.entity.java.VanillaJavaApp",
+            ImmutableMap.of(
+                    "initialSize", "1",
+                    "creationScriptUrl", "http://my.brooklyn.io/storage/foo.sql"));
+
+    final ApplicationSpec applicationSpec = ApplicationSpec.builder().name("myapp")
+            .entities(ImmutableSet.of(entitySpec))
+            .locations(ImmutableSet.of("/locations/1"))
+            .build();
+
+    final Map<String, URI> links = ImmutableMap.of(
+            "self", URI.create("/applications/" + applicationSpec.getName()),
+            "entities", URI.create("fixtures/entity-summary-list.json"));
+    final ApplicationSummary application = new ApplicationSummary("myapp_id", applicationSpec, Status.STARTING, links);
+
+    @Test
+    public void testSerializeToJSON() throws IOException {
+        assertEquals(asJson(application), jsonFixture("fixtures/application.json"));
+    }
+
+    @Test
+    public void testDeserializeFromJSON() throws IOException {
+        assertEquals(fromJson(jsonFixture("fixtures/application.json"),
+                ApplicationSummary.class), application);
+    }
+
+    @Test
+    public void testTransitionToRunning() {
+        ApplicationSummary running = application.transitionTo(Status.RUNNING);
+        assertEquals(running.getStatus(), Status.RUNNING);
+    }
+
+    @Test
+    public void testAppInAppTest() throws IOException {
+        ManagementContext mgmt = LocalManagementContextForTests.newInstance();
+        try {
+            TestApplication app = mgmt.getEntityManager().createEntity(org.apache.brooklyn.api.entity.EntitySpec.create(TestApplication.class));
+            app.addChild(org.apache.brooklyn.api.entity.EntitySpec.create(TestApplication.class));
+            Asserts.assertEqualsIgnoringOrder(mgmt.getApplications(), ImmutableList.of(app));
+        } finally {
+            Entities.destroyAll(mgmt);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/domain/SensorSummaryTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/domain/SensorSummaryTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/domain/SensorSummaryTest.java
new file mode 100644
index 0000000..1900c56
--- /dev/null
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/domain/SensorSummaryTest.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.domain;
+
+import static org.apache.brooklyn.rest.util.RestApiTestUtils.asJson;
+import static org.apache.brooklyn.rest.util.RestApiTestUtils.fromJson;
+import static org.apache.brooklyn.rest.util.RestApiTestUtils.jsonFixture;
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+import java.net.URI;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+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.core.entity.Entities;
+import org.apache.brooklyn.core.sensor.Sensors;
+import org.apache.brooklyn.core.test.entity.TestApplication;
+import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.apache.brooklyn.rest.transform.SensorTransformer;
+
+import com.google.common.collect.ImmutableMap;
+import javax.ws.rs.core.UriBuilder;
+
+public class SensorSummaryTest {
+
+    private SensorSummary sensorSummary = new SensorSummary("redis.uptime", "Integer",
+            "Description", ImmutableMap.of(
+            "self", URI.create("/applications/redis-app/entities/redis-ent/sensors/redis.uptime")));
+
+    private TestApplication app;
+    private TestEntity entity;
+    private ManagementContext mgmt;
+
+    @BeforeMethod(alwaysRun = true)
+    public void setUp() throws Exception {
+        app = TestApplication.Factory.newManagedInstanceForTests();
+        mgmt = app.getManagementContext();
+        entity = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+    }
+
+    @AfterMethod(alwaysRun = true)
+    public void tearDown() throws Exception {
+        if (mgmt != null) Entities.destroyAll(mgmt);
+    }
+
+    @Test
+    public void testSerializeToJSON() throws IOException {
+        assertEquals(asJson(sensorSummary), jsonFixture("fixtures/sensor-summary.json"));
+    }
+
+    @Test
+    public void testDeserializeFromJSON() throws IOException {
+        assertEquals(fromJson(jsonFixture("fixtures/sensor-summary.json"), SensorSummary.class), sensorSummary);
+    }
+
+    @Test
+    public void testEscapesUriForSensorName() throws IOException {
+        Sensor<String> sensor = Sensors.newStringSensor("name with space");
+        SensorSummary summary = SensorTransformer.sensorSummary(entity, sensor, UriBuilder.fromPath("/"));
+        URI selfUri = summary.getLinks().get("self");
+
+        String expectedUri = "/applications/" + entity.getApplicationId() + "/entities/" + entity.getId() + "/sensors/" + "name%20with%20space";
+
+        assertEquals(selfUri, URI.create(expectedUri));
+    }
+
+    // Previously failed because immutable-map builder threw exception if put same key multiple times,
+    // and the NamedActionWithUrl did not have equals/hashCode
+    @Test
+    public void testSensorWithMultipleOpenUrlActionsRegistered() throws IOException {
+        AttributeSensor<String> sensor = Sensors.newStringSensor("sensor1");
+        entity.sensors().set(sensor, "http://myval");
+        RendererHints.register(sensor, RendererHints.namedActionWithUrl());
+        RendererHints.register(sensor, RendererHints.namedActionWithUrl());
+
+        SensorSummary summary = SensorTransformer.sensorSummary(entity, sensor, UriBuilder.fromPath("/"));
+
+        assertEquals(summary.getLinks().get("action:open"), URI.create("http://myval"));
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/filter/HaHotCheckTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/filter/HaHotCheckTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/filter/HaHotCheckTest.java
new file mode 100644
index 0000000..4b1faca
--- /dev/null
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/filter/HaHotCheckTest.java
@@ -0,0 +1,120 @@
+/*
+ * 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.filter;
+
+import static org.testng.Assert.assertEquals;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.apache.brooklyn.api.mgmt.ha.HighAvailabilityManager;
+import org.apache.brooklyn.api.mgmt.ha.HighAvailabilityMode;
+import org.apache.brooklyn.api.mgmt.ha.ManagementNodeState;
+import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
+import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
+import org.apache.brooklyn.rest.filter.HaHotCheckResourceFilter;
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+import org.apache.brooklyn.rest.util.HaHotStateCheckClassResource;
+import org.apache.brooklyn.rest.util.HaHotStateCheckResource;
+import org.apache.cxf.jaxrs.client.WebClient;
+import org.testng.annotations.Test;
+
+public class HaHotCheckTest extends BrooklynRestResourceTest {
+
+    @Override
+    protected void addBrooklynResources() {
+        addResource(new HaHotCheckResourceFilter());
+        addResource(new HaHotStateCheckResource());
+        addResource(new HaHotStateCheckClassResource());
+        
+        ((LocalManagementContext)getManagementContext()).noteStartupComplete();
+    }
+
+    @Override
+    protected boolean isMethodInit() {
+        return true;
+    }
+
+    @Test
+    public void testHaCheck() {
+        HighAvailabilityManager ha = getManagementContext().getHighAvailabilityManager();
+        assertEquals(ha.getNodeState(), ManagementNodeState.MASTER);
+        testResourceFetch("/ha/method/ok", 200);
+        testResourceFetch("/ha/method/fail", 200);
+        testResourceFetch("/ha/class/fail", 200);
+
+        getManagementContext().getHighAvailabilityManager().changeMode(HighAvailabilityMode.STANDBY);
+        assertEquals(ha.getNodeState(), ManagementNodeState.STANDBY);
+
+        testResourceFetch("/ha/method/ok", 200);
+        testResourceFetch("/ha/method/fail", 403);
+        testResourceFetch("/ha/class/fail", 403);
+
+        ((ManagementContextInternal)getManagementContext()).terminate();
+        assertEquals(ha.getNodeState(), ManagementNodeState.TERMINATED);
+
+        testResourceFetch("/ha/method/ok", 200);
+        testResourceFetch("/ha/method/fail", 403);
+        testResourceFetch("/ha/class/fail", 403);
+    }
+
+    @Test
+    public void testHaCheckForce() {
+        HighAvailabilityManager ha = getManagementContext().getHighAvailabilityManager();
+        assertEquals(ha.getNodeState(), ManagementNodeState.MASTER);
+        testResourceForcedFetch("/ha/method/ok", 200);
+        testResourceForcedFetch("/ha/method/fail", 200);
+        testResourceForcedFetch("/ha/class/fail", 200);
+
+        getManagementContext().getHighAvailabilityManager().changeMode(HighAvailabilityMode.STANDBY);
+        assertEquals(ha.getNodeState(), ManagementNodeState.STANDBY);
+
+        testResourceForcedFetch("/ha/method/ok", 200);
+        testResourceForcedFetch("/ha/method/fail", 200);
+        testResourceForcedFetch("/ha/class/fail", 200);
+
+        ((ManagementContextInternal)getManagementContext()).terminate();
+        assertEquals(ha.getNodeState(), ManagementNodeState.TERMINATED);
+
+        testResourceForcedFetch("/ha/method/ok", 200);
+        testResourceForcedFetch("/ha/method/fail", 200);
+        testResourceForcedFetch("/ha/class/fail", 200);
+    }
+
+
+    private void testResourceFetch(String resourcePath, int code) {
+        testResourceFetch(resourcePath, false, code);
+    }
+
+    private void testResourceForcedFetch(String resourcePath, int code) {
+        testResourceFetch(resourcePath, true, code);
+    }
+
+    private void testResourceFetch(String resourcePath, boolean force, int code) {
+        WebClient resource = client().path(resourcePath)
+                .accept(MediaType.APPLICATION_JSON_TYPE);
+        if (force) {
+            resource.header(HaHotCheckResourceFilter.SKIP_CHECK_HEADER, "true");
+        }
+        Response response = resource
+                .get();
+        assertEquals(response.getStatus(), code);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/AccessResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/AccessResourceTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/AccessResourceTest.java
new file mode 100644
index 0000000..f63676d
--- /dev/null
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/AccessResourceTest.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import javax.ws.rs.core.Response;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.Test;
+
+import org.apache.brooklyn.rest.domain.AccessSummary;
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+
+@Test(singleThreaded = true,
+        // by using a different suite name we disallow interleaving other tests between the methods of this test class, which wrecks the test fixtures
+        suiteName = "AccessResourceTest")
+public class AccessResourceTest extends BrooklynRestResourceTest {
+
+    @SuppressWarnings("unused")
+    private static final Logger log = LoggerFactory.getLogger(AccessResourceTest.class);
+
+    @Test
+    public void testGetAndSetAccessControl() throws Exception {
+        // Default is everything allowed
+        AccessSummary summary = client().path("/access").get(AccessSummary.class);
+        assertTrue(summary.isLocationProvisioningAllowed());
+
+        // Forbid location provisioning
+        Response response = client().path("/access/locationProvisioningAllowed")
+                .query("allowed", "false")
+                .post(null);
+        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
+
+        AccessSummary summary2 = client().path("/access").get(AccessSummary.class);
+        assertFalse(summary2.isLocationProvisioningAllowed());
+
+        // Allow location provisioning
+        Response response2 = client().path("/access/locationProvisioningAllowed")
+                .query("allowed", "true")
+                .post(null);
+        assertEquals(response2.getStatus(), Response.Status.OK.getStatusCode());
+
+        AccessSummary summary3 = client().path("/access").get(AccessSummary.class);
+        assertTrue(summary3.isLocationProvisioningAllowed());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApidocResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApidocResourceTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApidocResourceTest.java
new file mode 100644
index 0000000..af09e67
--- /dev/null
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApidocResourceTest.java
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+
+import java.util.Collection;
+
+import org.apache.brooklyn.rest.BrooklynRestApi;
+import org.apache.brooklyn.rest.api.CatalogApi;
+import org.apache.brooklyn.rest.api.EffectorApi;
+import org.apache.brooklyn.rest.api.EntityApi;
+import org.apache.brooklyn.rest.apidoc.RestApiResourceScanner;
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
+import io.swagger.annotations.Api;
+import io.swagger.config.ScannerFactory;
+import io.swagger.models.Operation;
+import io.swagger.models.Path;
+import io.swagger.models.Swagger;
+
+/**
+ * @author Adam Lowe
+ */
+@Test(singleThreaded = true)
+public class ApidocResourceTest extends BrooklynRestResourceTest {
+
+    private static final Logger log = LoggerFactory.getLogger(ApidocResourceTest.class);
+
+    @Override
+    protected void addBrooklynResources() {
+        ScannerFactory.setScanner(new RestApiResourceScanner());
+
+        for (Object o : BrooklynRestApi.getApidocResources()) {
+            addResource(o);
+        }
+        super.addBrooklynResources();
+    }
+
+    
+    @Test(enabled = false)
+    public void testRootSerializesSensibly() throws Exception {
+        String data = client().path("/apidoc/swagger.json").get(String.class);
+        log.info("apidoc gives: "+data);
+        // make sure no scala gets in
+        assertFalse(data.contains("$"));
+        assertFalse(data.contains("scala"));
+        // make sure it's an appropriate swagger 2.0 json
+        Swagger swagger = client().path("/apidoc/swagger.json").get(Swagger.class);
+        assertEquals(swagger.getSwagger(), "2.0");
+    }
+    
+    @Test(enabled = false)
+    public void testCountRestResources() throws Exception {
+        Swagger swagger = client().path("/apidoc/swagger.json").get(Swagger.class);
+        assertEquals(swagger.getTags().size(), 1 + Iterables.size(BrooklynRestApi.getBrooklynRestResources()));
+    }
+
+    @Test(enabled = false)
+    public void testApiDocDetails() throws Exception {
+        Swagger swagger = client().path("/apidoc/swagger.json").get(Swagger.class);
+        Collection<Operation> operations = getTaggedOperations(swagger, ApidocResource.class.getAnnotation(Api.class).value());
+        assertEquals(operations.size(), 2, "ops="+operations);
+    }
+
+    @Test(enabled = false)
+    public void testEffectorDetails() throws Exception {
+        Swagger swagger = client().path("/apidoc/swagger.json").get(Swagger.class);
+        Collection<Operation> operations = getTaggedOperations(swagger, EffectorApi.class.getAnnotation(Api.class).value());
+        assertEquals(operations.size(), 2, "ops="+operations);
+    }
+
+    @Test(enabled = false)
+    public void testEntityDetails() throws Exception {
+        Swagger swagger = client().path("/apidoc/swagger.json").get(Swagger.class);
+        Collection<Operation> operations = getTaggedOperations(swagger, EntityApi.class.getAnnotation(Api.class).value());
+        assertEquals(operations.size(), 14, "ops="+operations);
+    }
+
+    @Test(enabled = false)
+    public void testCatalogDetails() throws Exception {
+        Swagger swagger = client().path("/apidoc/swagger.json").get(Swagger.class);
+        Collection<Operation> operations = getTaggedOperations(swagger, CatalogApi.class.getAnnotation(Api.class).value());
+        assertEquals(operations.size(), 22, "ops="+operations);
+    }
+
+    /**
+     * Retrieves all operations tagged the given tag from the given swagger spec.
+     */
+    private Collection<Operation> getTaggedOperations(Swagger swagger, final String requiredTag) {
+        Iterable<Operation> allOperations = Iterables.concat(Collections2.transform(swagger.getPaths().values(),
+                new Function<Path, Collection<Operation>>() {
+                    @Override
+                    public Collection<Operation> apply(Path path) {
+                        return path.getOperations();
+                    }
+                }));
+
+        return Collections2.filter(ImmutableList.copyOf(allOperations), new Predicate<Operation>() {
+            @Override
+            public boolean apply(Operation operation) {
+                return operation.getTags().contains(requiredTag);
+            }
+        });
+    }
+
+}
+

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceIntegrationTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceIntegrationTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceIntegrationTest.java
new file mode 100644
index 0000000..743eee4
--- /dev/null
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceIntegrationTest.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.resources;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.net.URI;
+import java.util.Set;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.Test;
+import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
+import org.apache.brooklyn.rest.domain.ApplicationSpec;
+import org.apache.brooklyn.rest.domain.ApplicationSummary;
+import org.apache.brooklyn.rest.domain.EntitySpec;
+import org.apache.brooklyn.rest.domain.EntitySummary;
+import org.apache.brooklyn.rest.domain.SensorSummary;
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+import org.apache.brooklyn.test.Asserts;
+import org.apache.brooklyn.util.collections.MutableMap;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import javax.ws.rs.core.GenericType;
+
+@Test(singleThreaded = true)
+public class ApplicationResourceIntegrationTest extends BrooklynRestResourceTest {
+
+    @SuppressWarnings("unused")
+    private static final Logger log = LoggerFactory.getLogger(ApplicationResourceIntegrationTest.class);
+
+    private final ApplicationSpec redisSpec = ApplicationSpec.builder().name("redis-app")
+            .entities(ImmutableSet.of(new EntitySpec("redis-ent", "org.apache.brooklyn.entity.nosql.redis.RedisStore")))
+            .locations(ImmutableSet.of("localhost"))
+            .build();
+
+    @Test(groups="Integration")
+    public void testDeployRedisApplication() throws Exception {
+        Response response = clientDeploy(redisSpec);
+
+        assertEquals(response.getStatus(), 201);
+        assertEquals(getManagementContext().getApplications().size(), 1);
+        assertTrue(response.getLocation().getPath().startsWith("/applications/"), "path="+response.getLocation().getPath()); // path uses id, rather than app name
+
+        waitForApplicationToBeRunning(response.getLocation());
+    }
+
+    @Test(groups="Integration", dependsOnMethods = "testDeployRedisApplication")
+    public void testListEntities() {
+        Set<EntitySummary> entities = client().path("/applications/redis-app/entities")
+                .get(new GenericType<Set<EntitySummary>>() {});
+
+        for (EntitySummary entity : entities) {
+            client().path(entity.getLinks().get("self")).get();
+            // TODO assertions on the above call?
+
+            Set<EntitySummary> children = client().path(entity.getLinks().get("children"))
+                    .get(new GenericType<Set<EntitySummary>>() {});
+            assertEquals(children.size(), 0);
+        }
+    }
+
+    @Test(groups="Integration", dependsOnMethods = "testDeployRedisApplication")
+    public void testListSensorsRedis() {
+        Set<SensorSummary> sensors = client().path("/applications/redis-app/entities/redis-ent/sensors")
+                .get(new GenericType<Set<SensorSummary>>() {});
+        assertTrue(sensors.size() > 0);
+        SensorSummary uptime = Iterables.find(sensors, new Predicate<SensorSummary>() {
+            @Override
+            public boolean apply(SensorSummary sensorSummary) {
+                return sensorSummary.getName().equals("redis.uptime");
+            }
+        });
+        assertEquals(uptime.getType(), "java.lang.Integer");
+    }
+
+    @Test(groups="Integration", dependsOnMethods = { "testListSensorsRedis", "testListEntities" })
+    public void testTriggerRedisStopEffector() throws Exception {
+        Response response = client().path("/applications/redis-app/entities/redis-ent/effectors/stop")
+                .type(MediaType.APPLICATION_JSON_TYPE)
+                .post(toJsonEntity(ImmutableMap.of()));
+        assertEquals(response.getStatus(), Response.Status.ACCEPTED.getStatusCode());
+
+        final URI stateSensor = URI.create("/applications/redis-app/entities/redis-ent/sensors/service.state");
+        final String expectedStatus = Lifecycle.STOPPED.toString();
+        Asserts.succeedsEventually(MutableMap.of("timeout", 60 * 1000), new Runnable() {
+            public void run() {
+                // Accept with and without quotes; if don't specify "Accepts" header, then
+                // might get back json or plain text (depending on compiler / java runtime 
+                // used for SensorApi!)
+                String val = client().path(stateSensor).get(String.class);
+                assertTrue(expectedStatus.equalsIgnoreCase(val) || ("\""+expectedStatus+"\"").equalsIgnoreCase(val), "state="+val);
+            }
+        });
+    }
+
+    @Test(groups="Integration", dependsOnMethods = "testTriggerRedisStopEffector" )
+    public void testDeleteRedisApplication() throws Exception {
+        int size = getManagementContext().getApplications().size();
+        Response response = client().path("/applications/redis-app")
+                .delete();
+
+        waitForPageNotFoundResponse("/applications/redis-app", ApplicationSummary.class);
+
+        assertEquals(response.getStatus(), Response.Status.ACCEPTED.getStatusCode());
+        assertEquals(getManagementContext().getApplications().size(), size-1);
+    }
+
+}


[15/34] brooklyn-server git commit: [BROOKLYN-183] REST API using CXF JAX-RS 2.0 implementation

Posted by he...@apache.org.
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/BrooklynRestResourceUtils.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/BrooklynRestResourceUtils.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/BrooklynRestResourceUtils.java
new file mode 100644
index 0000000..3f837a1
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/BrooklynRestResourceUtils.java
@@ -0,0 +1,609 @@
+/*
+ * 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 com.google.common.collect.Iterables.transform;
+import static org.apache.brooklyn.rest.util.WebResourceUtils.notFound;
+
+import java.lang.reflect.Constructor;
+import java.util.ArrayList;
+import java.util.Collection;
+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 org.apache.brooklyn.api.catalog.BrooklynCatalog;
+import org.apache.brooklyn.api.catalog.CatalogItem;
+import org.apache.brooklyn.api.entity.Application;
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.location.LocationRegistry;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.api.mgmt.Task;
+import org.apache.brooklyn.api.policy.Policy;
+import org.apache.brooklyn.api.typereg.RegisteredType;
+import org.apache.brooklyn.camp.brooklyn.BrooklynCampConstants;
+import org.apache.brooklyn.camp.brooklyn.spi.dsl.methods.DslComponent;
+import org.apache.brooklyn.camp.brooklyn.spi.dsl.methods.DslComponent.Scope;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.catalog.CatalogPredicates;
+import org.apache.brooklyn.core.catalog.internal.CatalogItemComparator;
+import org.apache.brooklyn.core.catalog.internal.CatalogUtils;
+import org.apache.brooklyn.core.entity.Attributes;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.entity.EntityInternal;
+import org.apache.brooklyn.core.entity.trait.Startable;
+import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
+import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
+import org.apache.brooklyn.core.mgmt.entitlement.Entitlements.StringAndArgument;
+import org.apache.brooklyn.core.objs.BrooklynTypes;
+import org.apache.brooklyn.core.typereg.RegisteredTypes;
+import org.apache.brooklyn.enricher.stock.Enrichers;
+import org.apache.brooklyn.entity.stock.BasicApplication;
+import org.apache.brooklyn.rest.domain.ApplicationSpec;
+import org.apache.brooklyn.rest.domain.EntitySpec;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.collections.MutableSet;
+import org.apache.brooklyn.util.core.flags.TypeCoercions;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.javalang.Reflections;
+import org.apache.brooklyn.util.net.Urls;
+import org.apache.brooklyn.util.text.Strings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSortedSet;
+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.policies()) {
+            if (policy.equals(p.getId())) return p;
+        }
+        for (Policy p: entity.policies()) {
+            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 Entity getEntity(String application, String entity) {
+        if (entity==null) return null;
+        Application app = application!=null ? getApplication(application) : null;
+        Entity e = 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 Entity searchForEntityNamed(Entity root, String entity) {
+        if (root.getId().equals(entity) || entity.equals(root.getDisplayName())) return root;
+        for (Entity child: root.getChildren()) {
+            Entity result = searchForEntityNamed(child, entity);
+            if (result!=null) return result;
+        }
+        return null;
+    }
+
+    private class FindItemAndClass {
+        String catalogItemId;
+        Class<? extends Entity> clazz;
+        
+        @SuppressWarnings({ "unchecked" })
+        private FindItemAndClass inferFrom(String type) {
+            RegisteredType item = mgmt.getTypeRegistry().get(type);
+            if (item==null) {
+                // deprecated attempt to load an item not in the type registry
+                
+                // although the method called was deprecated in 0.7.0, its use here was not warned until 0.9.0;
+                // therefore this behaviour should not be changed until after 0.9.0;
+                // at which point it should try a pojo load (see below)
+                item = getCatalogItemForType(type);
+                if (item!=null) {
+                    log.warn("Creating application for requested type `"+type+" using item "+item+"; "
+                        + "the registered type name ("+item.getSymbolicName()+") should be used from the spec instead, "
+                        + "or the type registered under its own name. "
+                        + "Future versions will likely change semantics to attempt a POJO load of the type instead.");
+                }
+            }
+            
+            if (item != null) {
+                return setAs(
+                    mgmt.getTypeRegistry().createSpec(item, null, org.apache.brooklyn.api.entity.EntitySpec.class).getType(),
+                    item.getId());
+            } else {
+                try {
+                    setAs(
+                        (Class<? extends Entity>) getCatalog().getRootClassLoader().loadClass(type),
+                        null);
+                    log.info("Catalog does not contain item for type {}; loaded class directly instead", type);
+                    return this;
+                } 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);
+                }
+            }
+        }
+
+        private FindItemAndClass setAs(Class<? extends Entity> clazz, String catalogItemId) {
+            this.clazz = clazz;
+            this.catalogItemId = catalogItemId;
+            return this;
+        }
+        
+        @Deprecated // see caller
+        private RegisteredType getCatalogItemForType(String typeName) {
+            final RegisteredType resultI;
+            if (CatalogUtils.looksLikeVersionedId(typeName)) {
+                //All catalog identifiers of the form xxxx:yyyy are composed of symbolicName+version.
+                //No javaType is allowed as part of the identifier.
+                resultI = mgmt.getTypeRegistry().get(typeName);
+            } else {
+                //Usually for catalog items with javaType (that is items from catalog.xml)
+                //the symbolicName and javaType match because symbolicName (was ID)
+                //is not specified explicitly. But could be the case that there is an item
+                //whose symbolicName is explicitly set to be different from the javaType.
+                //Note that in the XML the attribute is called registeredTypeName.
+                Iterable<CatalogItem<Object,Object>> resultL = mgmt.getCatalog().getCatalogItems(CatalogPredicates.javaType(Predicates.equalTo(typeName)));
+                if (!Iterables.isEmpty(resultL)) {
+                    //Push newer versions in front of the list (not that there should
+                    //be more than one considering the items are coming from catalog.xml).
+                    resultI = RegisteredTypes.of(sortVersionsDesc(resultL).iterator().next());
+                    if (log.isDebugEnabled() && Iterables.size(resultL)>1) {
+                        log.debug("Found "+Iterables.size(resultL)+" matches in catalog for type "+typeName+"; returning the result with preferred version, "+resultI);
+                    }
+                } else {
+                    //As a last resort try searching for items with the same symbolicName supposedly
+                    //different from the javaType.
+                    resultI = mgmt.getTypeRegistry().get(typeName, BrooklynCatalog.DEFAULT_VERSION);
+                    if (resultI != null) {
+                        if (resultI.getSuperTypes().isEmpty()) {
+                            //Catalog items scanned from the classpath (using reflection and annotations) now
+                            //get yaml spec rather than a java type. Can't use those when creating apps from
+                            //the legacy app spec format.
+                            log.warn("Unable to find catalog item for type "+typeName +
+                                    ". There is an existing catalog item with ID " + resultI.getId() +
+                                    " but it doesn't define a class type.");
+                            return null;
+                        }
+                    }
+                }
+            }
+            return resultI;
+        }
+        private <T,SpecT> Collection<CatalogItem<T,SpecT>> sortVersionsDesc(Iterable<CatalogItem<T,SpecT>> versions) {
+            return ImmutableSortedSet.orderedBy(CatalogItemComparator.<T,SpecT>getInstance()).addAll(versions).build();
+        }
+    }
+    
+    @SuppressWarnings({ "deprecation" })
+    public Application create(ApplicationSpec spec) {
+        log.warn("Using deprecated functionality (as of 0.9.0), ApplicationSpec style (pre CAMP plans). " +
+                    "Transition to actively supported spec plans.");
+        log.debug("REST creating application instance for {}", spec);
+        
+        if (!Entitlements.isEntitled(mgmt.getEntitlementManager(), Entitlements.DEPLOY_APPLICATION, spec)) {
+            throw WebResourceUtils.forbidden("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
+        FindItemAndClass itemAndClass;
+        if (Strings.isEmpty(type)) {
+            itemAndClass = new FindItemAndClass().setAs(BasicApplication.class, null);
+        } else {
+            itemAndClass = new FindItemAndClass().inferFrom(type);
+        }
+        
+        if (!Entitlements.isEntitled(mgmt.getEntitlementManager(), Entitlements.INVOKE_EFFECTOR, null)) {
+            throw WebResourceUtils.forbidden("User '%s' is not authorized to create application from applicationSpec %s",
+                Entitlements.getEntitlementContext().user(), spec);
+        }
+
+        try {
+            if (org.apache.brooklyn.core.entity.factory.ApplicationBuilder.class.isAssignableFrom(itemAndClass.clazz)) {
+                // warning only added in 0.9.0
+                log.warn("Using deprecated ApplicationBuilder "+itemAndClass.clazz+"; callers must migrate to use of Application");
+                Constructor<?> constructor = itemAndClass.clazz.getConstructor();
+                org.apache.brooklyn.core.entity.factory.ApplicationBuilder appBuilder = (org.apache.brooklyn.core.entity.factory.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(itemAndClass.clazz)) {
+                org.apache.brooklyn.api.entity.EntitySpec<?> coreSpec = toCoreEntitySpec(itemAndClass.clazz, name, configO, itemAndClass.catalogItemId);
+                configureRenderingMetadata(spec, coreSpec);
+                for (EntitySpec entitySpec : entities) {
+                    log.info("REST creating instance for entity {}", entitySpec.getType());
+                    coreSpec.child(toCoreEntitySpec(entitySpec));
+                }
+
+                log.info("REST placing '{}' under management", spec.getName() != null ? spec.getName() : spec);
+                instance = (Application) mgmt.getEntityManager().createEntity(coreSpec);
+
+            } else if (Entity.class.isAssignableFrom(itemAndClass.clazz)) {
+                if (entities.size() > 0)
+                    log.warn("Cannot supply additional entities when using a non-application entity; ignoring in spec {}", spec);
+
+                org.apache.brooklyn.api.entity.EntitySpec<?> coreSpec = toCoreEntitySpec(BasicApplication.class, name, configO, itemAndClass.catalogItemId);
+                configureRenderingMetadata(spec, coreSpec);
+
+                coreSpec.child(toCoreEntitySpec(itemAndClass.clazz, name, configO, itemAndClass.catalogItemId)
+                    .configure(BrooklynCampConstants.PLAN_ID, "soleChildId"));
+                coreSpec.enricher(Enrichers.builder()
+                    .propagatingAllBut(Attributes.SERVICE_UP, Attributes.SERVICE_NOT_UP_INDICATORS, 
+                        Attributes.SERVICE_STATE_ACTUAL, Attributes.SERVICE_STATE_EXPECTED, 
+                        Attributes.SERVICE_PROBLEMS)
+                        .from(new DslComponent(Scope.CHILD, "soleChildId").newTask())
+                        .build());
+
+                log.info("REST placing '{}' under management", spec.getName());
+                instance = (Application) mgmt.getEntityManager().createEntity(coreSpec);
+
+            } else {
+                throw new IllegalArgumentException("Class " + itemAndClass.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);
+        }
+    }
+    
+    public Task<?> start(Application app, ApplicationSpec spec) {
+        return start(app, getLocations(spec));
+    }
+
+    public Task<?> start(Application app, List<? extends Location> locations) {
+        return Entities.invokeEffector(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;
+    }
+
+    private org.apache.brooklyn.api.entity.EntitySpec<? extends Entity> toCoreEntitySpec(org.apache.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());
+
+        FindItemAndClass itemAndClass = new FindItemAndClass().inferFrom(type);
+        
+        final Class<? extends Entity> clazz = itemAndClass.clazz;
+        org.apache.brooklyn.api.entity.EntitySpec<? extends Entity> result;
+        if (clazz.isInterface()) {
+            result = org.apache.brooklyn.api.entity.EntitySpec.create(clazz);
+        } else {
+            result = org.apache.brooklyn.api.entity.EntitySpec.create(Entity.class).impl(clazz).additionalInterfaces(Reflections.getAllInterfaces(clazz));
+        }
+        result.catalogItemId(itemAndClass.catalogItemId);
+        if (!Strings.isEmpty(name)) result.displayName(name);
+        result.configure( convertFlagsToKeys(result.getType(), config) );
+        configureRenderingMetadata(spec, result);
+        return result;
+    }
+    
+    @SuppressWarnings("deprecation")
+    protected void configureRenderingMetadata(ApplicationSpec spec, org.apache.brooklyn.core.entity.factory.ApplicationBuilder appBuilder) {
+        appBuilder.configure(getRenderingConfigurationFor(spec.getType()));
+    }
+
+    protected void configureRenderingMetadata(ApplicationSpec input, org.apache.brooklyn.api.entity.EntitySpec<?> entity) {
+        entity.configure(getRenderingConfigurationFor(input.getType()));
+    }
+
+    protected void configureRenderingMetadata(EntitySpec input, org.apache.brooklyn.api.entity.EntitySpec<?> entity) {
+        entity.configure(getRenderingConfigurationFor(input.getType()));
+    }
+
+    protected Map<?, ?> getRenderingConfigurationFor(String catalogId) {
+        MutableMap<Object, Object> result = MutableMap.of();
+        RegisteredType item = mgmt.getTypeRegistry().get(catalogId);
+        if (item==null) return result;
+        
+        result.addIfNotNull("iconUrl", item.getIconUrl());
+        return result;
+    }
+    
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    private <T extends Entity> org.apache.brooklyn.api.entity.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);
+        
+        org.apache.brooklyn.api.entity.EntitySpec<? extends Entity> result;
+        if (clazz.isInterface()) {
+            result = org.apache.brooklyn.api.entity.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 = org.apache.brooklyn.api.entity.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))))) {
+            Map<String, Object> flags = MutableMap.<String, Object>of("displayName", "expunging " + entity, "description", "REST call to expunge entity "
+                    + entity.getDisplayName() + " (" + entity + ")");
+            if (Entitlements.getEntitlementContext() != null) {
+                flags.put("tags", MutableSet.of(BrooklynTaskTags.tagForEntitlement(Entitlements.getEntitlementContext())));
+            }
+            return mgmt.getExecutionManager().submit(
+                    flags, new Runnable() {
+                        @Override
+                        public void run() {
+                            if (release)
+                                Entities.destroyCatching(entity);
+                            else
+                                mgmt.getEntityManager().unmanage(entity);
+                        }
+                    });
+        }
+        throw WebResourceUtils.forbidden("User '%s' is not authorized to expunge entity %s",
+                    Entitlements.getEntitlementContext().user(), entity);
+    }
+
+    @Deprecated
+    public static String fixLocation(String locationId) {
+        if (locationId.startsWith("/locations/") || locationId.startsWith("/v1/locations/")) {
+            log.warn("REST API using legacy URI syntax for location: "+locationId);
+            locationId = Strings.removeFromStart(locationId, "/v1/locations/");
+            locationId = Strings.removeFromStart(locationId, "/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();
+        Entity 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();
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/DefaultExceptionMapper.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/DefaultExceptionMapper.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/DefaultExceptionMapper.java
new file mode 100644
index 0000000..1926d5e
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/DefaultExceptionMapper.java
@@ -0,0 +1,111 @@
+/*
+ * 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 java.util.Set;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
+import org.apache.brooklyn.rest.domain.ApiError;
+import org.apache.brooklyn.rest.domain.ApiError.Builder;
+import org.apache.brooklyn.util.collections.MutableSet;
+import org.apache.brooklyn.util.core.flags.ClassCoercionException;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.exceptions.UserFacingException;
+import org.apache.brooklyn.util.text.Strings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.yaml.snakeyaml.error.YAMLException;
+
+@Provider
+public class DefaultExceptionMapper implements ExceptionMapper<Throwable> {
+
+    private static final Logger LOG = LoggerFactory.getLogger(DefaultExceptionMapper.class);
+
+    static Set<Class<?>> warnedUnknownExceptions = MutableSet.of();
+    
+    /**
+     * Maps a throwable to a response.
+     * <p/>
+     * Returns {@link WebApplicationException#getResponse} if the exception is an instance of
+     * {@link WebApplicationException}. Otherwise maps known exceptions to responses. If no
+     * mapping is found a {@link Status#INTERNAL_SERVER_ERROR} is assumed.
+     */
+    @Override
+    public Response toResponse(Throwable throwable1) {
+        // EofException is thrown when the connection is reset,
+        // for example when refreshing the browser window.
+        // Don't depend on jetty, could be running in other environments as well.
+        if (throwable1.getClass().getName().equals("org.eclipse.jetty.io.EofException")) {
+            if (LOG.isTraceEnabled()) {
+                LOG.trace("REST request running as {} threw: {}", Entitlements.getEntitlementContext(), 
+                        Exceptions.collapse(throwable1));
+            }
+            return null;
+        }
+
+        LOG.debug("REST request running as {} threw: {}", Entitlements.getEntitlementContext(), 
+            Exceptions.collapse(throwable1));
+        if (LOG.isTraceEnabled()) {
+            LOG.trace("Full details of "+Entitlements.getEntitlementContext()+" "+throwable1, throwable1);
+        }
+
+        Throwable throwable2 = Exceptions.getFirstInteresting(throwable1);
+        // Some methods will throw this, which gets converted automatically
+        if (throwable2 instanceof WebApplicationException) {
+            WebApplicationException wae = (WebApplicationException) throwable2;
+            return wae.getResponse();
+        }
+
+        // The nicest way for methods to provide errors, wrap as this, and the stack trace will be suppressed
+        if (throwable2 instanceof UserFacingException) {
+            return ApiError.of(throwable2.getMessage()).asBadRequestResponseJson();
+        }
+
+        // For everything else, a trace is supplied
+        
+        // Assume ClassCoercionExceptions are caused by TypeCoercions from input parameters gone wrong
+        // And IllegalArgumentException for malformed input parameters.
+        if (throwable2 instanceof ClassCoercionException || throwable2 instanceof IllegalArgumentException) {
+            return ApiError.of(throwable2).asBadRequestResponseJson();
+        }
+
+        // YAML exception 
+        if (throwable2 instanceof YAMLException) {
+            return ApiError.builder().message(throwable2.getMessage()).prefixMessage("Invalid YAML").build().asBadRequestResponseJson();
+        }
+
+        if (!Exceptions.isPrefixBoring(throwable2)) {
+            if ( warnedUnknownExceptions.add( throwable2.getClass() )) {
+                LOG.warn("REST call generated exception type "+throwable2.getClass()+" unrecognized in "+getClass()+" (subsequent occurrences will be logged debug only): " + throwable2, throwable2);
+            }
+        }
+        
+        Builder rb = ApiError.builderFromThrowable(Exceptions.collapse(throwable2));
+        if (Strings.isBlank(rb.getMessage()))
+            rb.message("Internal error. Contact server administrator to consult logs for more details.");
+        return rb.build().asResponse(Status.INTERNAL_SERVER_ERROR, MediaType.APPLICATION_JSON_TYPE);
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/EntityLocationUtils.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/EntityLocationUtils.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/EntityLocationUtils.java
new file mode 100644
index 0000000..32bb66d
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/EntityLocationUtils.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.util;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.core.location.LocationConfigKeys;
+
+public class EntityLocationUtils {
+
+    protected final ManagementContext context;
+
+    public EntityLocationUtils(ManagementContext ctx) {
+        this.context = ctx;
+    }
+    
+    /* Returns the number of entites at each location for which the geographic coordinates are known. */
+    public Map<Location, Integer> countLeafEntitiesByLocatedLocations() {
+        Map<Location, Integer> result = new LinkedHashMap<Location, Integer>();
+        for (Entity e: context.getApplications()) {
+            countLeafEntitiesByLocatedLocations(e, null, result);
+        }
+        return result;
+    }
+
+    protected void countLeafEntitiesByLocatedLocations(Entity target, Entity locatedParent, Map<Location, Integer> result) {
+        if (isLocatedLocation(target))
+            locatedParent = target;
+        if (!target.getChildren().isEmpty()) {
+            // non-leaf - inspect children
+            for (Entity child: target.getChildren()) 
+                countLeafEntitiesByLocatedLocations(child, locatedParent, result);
+        } else {
+            // leaf node - increment location count
+            if (locatedParent!=null) {
+                for (Location l: locatedParent.getLocations()) {
+                    Location ll = getMostGeneralLocatedLocation(l);
+                    if (ll!=null) {
+                        Integer count = result.get(ll);
+                        if (count==null) count = 1;
+                        else count++;
+                        result.put(ll, count);
+                    }
+                }
+            }
+        }
+    }
+
+    protected Location getMostGeneralLocatedLocation(Location l) {
+        if (l==null) return null;
+        if (!isLocatedLocation(l)) return null;
+        Location ll = getMostGeneralLocatedLocation(l.getParent());
+        if (ll!=null) return ll;
+        return l;
+    }
+
+    protected boolean isLocatedLocation(Entity target) {
+        for (Location l: target.getLocations())
+            if (isLocatedLocation(l)) return true;
+        return false;
+    }
+    protected boolean isLocatedLocation(Location l) {
+        return l.getConfig(LocationConfigKeys.LATITUDE)!=null && l.getConfig(LocationConfigKeys.LONGITUDE)!=null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/FormMapProvider.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/FormMapProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/FormMapProvider.java
new file mode 100644
index 0000000..106b73a
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/FormMapProvider.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.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.List;
+import java.util.Map;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyReader;
+import javax.ws.rs.ext.Provider;
+
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import javax.ws.rs.core.Context;
+import org.apache.cxf.jaxrs.ext.MessageContext;
+import org.apache.cxf.jaxrs.provider.FormEncodingProvider;
+
+/**
+ * A MessageBodyReader producing a <code>Map&lt;String, Object&gt;</code>, where Object
+ * is either a <code>String</code>, a <code>List&lt;String&gt;</code> or null.
+ */
+@Provider
+@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+public class FormMapProvider implements MessageBodyReader<Map<String, Object>> {
+
+    @Context
+    private MessageContext mc;
+
+    @Override
+    public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
+        if (!Map.class.equals(type) || !(genericType instanceof ParameterizedType)) {
+            return false;
+        }
+        ParameterizedType parameterized = (ParameterizedType) genericType;
+        return parameterized.getActualTypeArguments().length == 2 &&
+                parameterized.getActualTypeArguments()[0] == String.class &&
+                parameterized.getActualTypeArguments()[1] == Object.class;
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Override
+    public Map<String, Object> readFrom(Class<Map<String, Object>> type, Type genericType, Annotation[] annotations,
+            MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream)
+            throws IOException, WebApplicationException {
+        FormEncodingProvider delegate = new FormEncodingProvider();
+        MultivaluedMap<String, String> multi = (MultivaluedMap<String, String>) delegate.readFrom(MultivaluedMap.class, null, null,
+                        mediaType, httpHeaders, entityStream);
+
+        Map<String, Object> map = Maps.newHashMapWithExpectedSize(multi.keySet().size());
+        for (String key : multi.keySet()) {
+            List<String> value = multi.get(key);
+            if (value.size() > 1) {
+                map.put(key, Lists.newArrayList(value));
+            } else if (value.size() == 1) {
+                map.put(key, Iterables.getOnlyElement(value));
+            } else {
+                map.put(key, null);
+            }
+        }
+        return map;
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/ManagementContextProvider.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/ManagementContextProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/ManagementContextProvider.java
new file mode 100644
index 0000000..ae90d0e
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/ManagementContextProvider.java
@@ -0,0 +1,41 @@
+/*
+ * 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 javax.ws.rs.ext.ContextResolver;
+import javax.ws.rs.ext.Provider;
+
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+
+@Provider
+// Needed by tests in rest-resources module and by main code in rest-server
+public class ManagementContextProvider implements ContextResolver<ManagementContext> {
+
+    private ManagementContext mgmt;
+
+    public ManagementContextProvider(ManagementContext mgmt) {
+        this.mgmt = mgmt;
+    }
+
+    @Override
+    public ManagementContext getContext(Class<?> type) {
+        return mgmt;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/OsgiCompat.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/OsgiCompat.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/OsgiCompat.java
new file mode 100644
index 0000000..6669f95
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/OsgiCompat.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2015 The Apache Software Foundation.
+ *
+ * Licensed 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 javax.servlet.ServletContext;
+
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.core.server.BrooklynServiceAttributes;
+import org.apache.brooklyn.util.core.osgi.Compat;
+
+/**
+ * Compatibility methods between karaf launcher and monolithic launcher.
+ *
+ * @todo Remove after transition to karaf launcher.
+ */
+public class OsgiCompat {
+
+    public static ManagementContext getManagementContext(ServletContext servletContext) {
+        ManagementContext managementContext = Compat.getInstance().getManagementContext();
+        if (managementContext == null && servletContext != null) {
+            managementContext = (ManagementContext) servletContext.getAttribute(BrooklynServiceAttributes.BROOKLYN_MANAGEMENT_CONTEXT);
+        }
+        return managementContext;
+    }
+
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/ShutdownHandler.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/ShutdownHandler.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/ShutdownHandler.java
new file mode 100644
index 0000000..e573bf6
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/ShutdownHandler.java
@@ -0,0 +1,23 @@
+/*
+ * 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;
+
+public interface ShutdownHandler {
+    void onShutdownRequest();
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/ShutdownHandlerProvider.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/ShutdownHandlerProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/ShutdownHandlerProvider.java
new file mode 100644
index 0000000..bae2922
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/ShutdownHandlerProvider.java
@@ -0,0 +1,41 @@
+/*
+ * 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 javax.annotation.Nullable;
+import javax.ws.rs.ext.ContextResolver;
+import javax.ws.rs.ext.Provider;
+
+
+@Provider
+public class ShutdownHandlerProvider implements ContextResolver<ShutdownHandler> {
+
+    private ShutdownHandler shutdownHandler;
+
+    public ShutdownHandlerProvider(@Nullable ShutdownHandler instance) {
+        this.shutdownHandler = instance;
+    }
+
+    @Override
+    public ShutdownHandler getContext(Class<?> type) {
+        return shutdownHandler;
+    }
+
+}
+

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/URLParamEncoder.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/URLParamEncoder.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/URLParamEncoder.java
new file mode 100644
index 0000000..8c25fda
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/URLParamEncoder.java
@@ -0,0 +1,27 @@
+/*
+ * 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;
+
+
+/**
+ * @deprecated since 0.7.0 use {@link org.apache.brooklyn.util.net.URLParamEncoder}
+ */
+public class URLParamEncoder extends org.apache.brooklyn.util.net.URLParamEncoder {
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/WebResourceUtils.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/WebResourceUtils.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/WebResourceUtils.java
new file mode 100644
index 0000000..5894700
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/WebResourceUtils.java
@@ -0,0 +1,197 @@
+/*
+ * 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 java.io.IOException;
+import java.util.Map;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.brooklyn.core.catalog.internal.CatalogUtils;
+import org.apache.brooklyn.rest.domain.ApiError;
+import org.apache.brooklyn.rest.util.json.BrooklynJacksonJsonProvider;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.net.Urls;
+import org.apache.brooklyn.util.text.StringEscapes.JavaStringEscapes;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.collect.ImmutableMap;
+import javax.ws.rs.core.UriBuilder;
+
+public class WebResourceUtils {
+
+    private static final Logger log = LoggerFactory.getLogger(WebResourceUtils.class);
+
+    /** @throws WebApplicationException with an ApiError as its body and the given status as its response code. */
+    public static WebApplicationException throwWebApplicationException(Response.Status status, String format, Object... args) {
+        String msg = String.format(format, args);
+        if (log.isDebugEnabled()) {
+            log.debug("responding {} {} ({})",
+                    new Object[]{status.getStatusCode(), status.getReasonPhrase(), msg});
+        }
+        ApiError apiError = ApiError.builder().message(msg).errorCode(status).build();
+        // including a Throwable is the only way to include a message with the WebApplicationException - ugly!
+        throw new WebApplicationException(new Throwable(apiError.toString()), apiError.asJsonResponse());
+    }
+
+    /** @throws WebApplicationException With code 500 internal server error */
+    public static WebApplicationException serverError(String format, Object... args) {
+        return throwWebApplicationException(Response.Status.INTERNAL_SERVER_ERROR, format, args);
+    }
+
+    /** @throws WebApplicationException With code 400 bad request */
+    public static WebApplicationException badRequest(String format, Object... args) {
+        return throwWebApplicationException(Response.Status.BAD_REQUEST, format, args);
+    }
+
+    /** @throws WebApplicationException With code 401 unauthorized */
+    public static WebApplicationException unauthorized(String format, Object... args) {
+        return throwWebApplicationException(Response.Status.UNAUTHORIZED, format, args);
+    }
+
+    /** @throws WebApplicationException With code 403 forbidden */
+    public static WebApplicationException forbidden(String format, Object... args) {
+        return throwWebApplicationException(Response.Status.FORBIDDEN, format, args);
+    }
+
+    /** @throws WebApplicationException With code 404 not found */
+    public static WebApplicationException notFound(String format, Object... args) {
+        return throwWebApplicationException(Response.Status.NOT_FOUND, format, args);
+    }
+
+    /** @throws WebApplicationException With code 412 precondition failed */
+    public static WebApplicationException preconditionFailed(String format, Object... args) {
+        return throwWebApplicationException(Response.Status.PRECONDITION_FAILED, format, args);
+    }
+
+    public final static Map<String,com.google.common.net.MediaType> IMAGE_FORMAT_MIME_TYPES = ImmutableMap.<String, com.google.common.net.MediaType>builder()
+            .put("jpg", com.google.common.net.MediaType.JPEG)
+            .put("jpeg", com.google.common.net.MediaType.JPEG)
+            .put("png", com.google.common.net.MediaType.PNG)
+            .put("gif", com.google.common.net.MediaType.GIF)
+            .put("svg", com.google.common.net.MediaType.SVG_UTF_8)
+            .build();
+    
+    public static MediaType getImageMediaTypeFromExtension(String extension) {
+        com.google.common.net.MediaType mime = IMAGE_FORMAT_MIME_TYPES.get(extension.toLowerCase());
+        if (mime==null) return null;
+        try {
+            return MediaType.valueOf(mime.toString());
+        } catch (Exception e) {
+            log.warn("Unparseable MIME type "+mime+"; ignoring ("+e+")");
+            Exceptions.propagateIfFatal(e);
+            return null;
+        }
+    }
+
+    /** as {@link #getValueForDisplay(ObjectMapper, Object, boolean, boolean)} with no mapper
+     * (so will only handle a subset of types) */
+    public static Object getValueForDisplay(Object value, boolean preferJson, boolean isJerseyReturnValue) {
+        return getValueForDisplay(null, value, preferJson, isJerseyReturnValue);
+    }
+    
+    /** returns an object which jersey will handle nicely, converting to json,
+     * sometimes wrapping in quotes if needed (for outermost json return types);
+     * if json is not preferred, this simply applies a toString-style rendering */ 
+    public static Object getValueForDisplay(ObjectMapper mapper, Object value, boolean preferJson, boolean isJerseyReturnValue) {
+        if (preferJson) {
+            if (value==null) return null;
+            Object result = value;
+            // no serialization checks required, with new smart-mapper which does toString
+            // (note there is more sophisticated logic in git history however)
+            result = value;
+            
+            if (isJerseyReturnValue) {
+                if (result instanceof String) {
+                    // Jersey does not do json encoding if the return type is a string,
+                    // expecting the returner to do the json encoding himself
+                    // cf discussion at https://github.com/dropwizard/dropwizard/issues/231
+                    result = JavaStringEscapes.wrapJavaString((String)result);
+                }
+            }
+            
+            return result;
+        } else {
+            if (value==null) return "";
+            return value.toString();            
+        }
+    }
+
+    public static String getPathFromVersionedId(String versionedId) {
+        if (CatalogUtils.looksLikeVersionedId(versionedId)) {
+            String symbolicName = CatalogUtils.getSymbolicNameFromVersionedId(versionedId);
+            String version = CatalogUtils.getVersionFromVersionedId(versionedId);
+            return Urls.encode(symbolicName) + "/" + Urls.encode(version);
+        } else {
+            return Urls.encode(versionedId);
+        }
+    }
+
+    /** Sets the {@link HttpServletResponse} target (last argument) from the given source {@link Response};
+     * useful in filters where we might have a {@link Response} and need to set up an {@link HttpServletResponse}.
+     */
+    public static void applyJsonResponse(ServletContext servletContext, Response source, HttpServletResponse target) throws IOException {
+        target.setStatus(source.getStatus());
+        target.setContentType(MediaType.APPLICATION_JSON);
+        target.setCharacterEncoding("UTF-8");
+        target.getWriter().write(BrooklynJacksonJsonProvider.findAnyObjectMapper(servletContext, null).writeValueAsString(source.getEntity()));
+    }
+
+    /**
+     * Provides a builder with the REST URI of a resource.
+     * @param baseUriBuilder An {@link UriBuilder} pointing at the base of the REST API.
+     * @param resourceClass The target resource class.
+     * @return A new {@link UriBuilder} that targets the specified REST resource.
+     */
+    public static UriBuilder resourceUriBuilder(UriBuilder baseUriBuilder, Class<?> resourceClass) {
+        return UriBuilder.fromPath(baseUriBuilder.build().getPath())
+                .path(resourceClass);
+    }
+
+    /**
+     * Provides a builder with the REST URI of a service provided by a resource.
+     * @param baseUriBuilder An {@link UriBuilder} pointing at the base of the REST API.
+     * @param resourceClass The target resource class.
+     * @param method The target service (e.g. class method).
+     * @return A new {@link UriBuilder} that targets the specified service of the REST resource.
+     */
+    public static UriBuilder serviceUriBuilder(UriBuilder baseUriBuilder, Class<?> resourceClass, String method) {
+        return resourceUriBuilder(baseUriBuilder, resourceClass).path(resourceClass, method);
+    }
+
+    /**
+     * Provides a builder with the absolute REST URI of a service provided by a resource.
+     * @param baseUriBuilder An {@link UriBuilder} pointing at the base of the REST API.
+     * @param resourceClass The target resource class.
+     * @param method The target service (e.g. class method).
+     * @return A new {@link UriBuilder} that targets the specified service of the REST resource.
+     */
+    public static UriBuilder serviceAbsoluteUriBuilder(UriBuilder baseUriBuilder, Class<?> resourceClass, String method) {
+        return  baseUriBuilder
+                    .path(resourceClass)
+                    .path(resourceClass, method);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/BidiSerialization.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/BidiSerialization.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/BidiSerialization.java
new file mode 100644
index 0000000..93cae3f
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/BidiSerialization.java
@@ -0,0 +1,173 @@
+/*
+ * 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.json;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.api.objs.BrooklynObject;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+
+public class BidiSerialization {
+
+    protected final static ThreadLocal<Boolean> STRICT_SERIALIZATION = new ThreadLocal<Boolean>(); 
+
+    /**
+     * Sets strict serialization on, or off (the default), for the current thread.
+     * Recommended to be used in a <code>try { ... } finally { ... }</code> block
+     * with {@link #clearStrictSerialization()} at the end.
+     * <p>
+     * With strict serialization, classes must have public fields or annotated fields, else they will not be serialized.
+     */
+    public static void setStrictSerialization(Boolean value) {
+        STRICT_SERIALIZATION.set(value);
+    }
+
+    public static void clearStrictSerialization() {
+        STRICT_SERIALIZATION.remove();
+    }
+
+    public static boolean isStrictSerialization() {
+        Boolean result = STRICT_SERIALIZATION.get();
+        if (result!=null) return result;
+        return false;
+    }
+
+
+    public abstract static class AbstractWithManagementContextSerialization<T> {
+
+        protected class Serializer extends JsonSerializer<T> {
+            @Override
+            public void serialize(T value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
+                AbstractWithManagementContextSerialization.this.serialize(value, jgen, provider);
+            }
+        }
+        
+        protected class Deserializer extends JsonDeserializer<T> {
+            @Override
+            public T deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
+                return AbstractWithManagementContextSerialization.this.deserialize(jp, ctxt);
+            }
+        }
+        
+        protected final Serializer serializer = new Serializer();
+        protected final Deserializer deserializer = new Deserializer();
+        protected final Class<T> type;
+        protected final ManagementContext mgmt;
+        
+        public AbstractWithManagementContextSerialization(Class<T> type, ManagementContext mgmt) {
+            this.type = type;
+            this.mgmt = mgmt;
+        }
+        
+        public JsonSerializer<T> getSerializer() {
+            return serializer;
+        }
+        
+        public JsonDeserializer<T> getDeserializer() {
+            return deserializer;
+        }
+
+        public void serialize(T value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
+            jgen.writeStartObject();
+            writeBody(value, jgen, provider);
+            jgen.writeEndObject();
+        }
+
+        protected void writeBody(T value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
+            jgen.writeStringField("type", value.getClass().getCanonicalName());
+            customWriteBody(value, jgen, provider);
+        }
+
+        public abstract void customWriteBody(T value, JsonGenerator jgen, SerializerProvider provider) throws IOException;
+
+        public T deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
+            @SuppressWarnings("unchecked")
+            Map<Object,Object> values = jp.readValueAs(Map.class);
+            String type = (String) values.get("type");
+            return customReadBody(type, values, jp, ctxt);
+        }
+
+        protected abstract T customReadBody(String type, Map<Object, Object> values, JsonParser jp, DeserializationContext ctxt) throws IOException;
+
+        public void install(SimpleModule module) {
+            module.addSerializer(type, serializer);
+            module.addDeserializer(type, deserializer);
+        }
+    }
+    
+    public static class ManagementContextSerialization extends AbstractWithManagementContextSerialization<ManagementContext> {
+        public ManagementContextSerialization(ManagementContext mgmt) { super(ManagementContext.class, mgmt); }
+        @Override
+        public void customWriteBody(ManagementContext value, JsonGenerator jgen, SerializerProvider provider) throws IOException {}
+        @Override
+        protected ManagementContext customReadBody(String type, Map<Object, Object> values, JsonParser jp, DeserializationContext ctxt) throws IOException {
+            return mgmt;
+        }
+    }
+    
+    public abstract static class AbstractBrooklynObjectSerialization<T extends BrooklynObject> extends AbstractWithManagementContextSerialization<T> {
+        public AbstractBrooklynObjectSerialization(Class<T> type, ManagementContext mgmt) { 
+            super(type, mgmt);
+        }
+        @Override
+        protected void writeBody(T value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
+            jgen.writeStringField("type", type.getCanonicalName());
+            customWriteBody(value, jgen, provider);
+        }
+        @Override
+        public void customWriteBody(T value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
+            jgen.writeStringField("id", value.getId());
+        }
+        @Override
+        protected T customReadBody(String type, Map<Object, Object> values, JsonParser jp, DeserializationContext ctxt) throws IOException {
+            return getInstanceFromId((String) values.get("id"));
+        }
+        protected abstract T getInstanceFromId(String id);
+    }
+
+    public static class EntitySerialization extends AbstractBrooklynObjectSerialization<Entity> {
+        public EntitySerialization(ManagementContext mgmt) { super(Entity.class, mgmt); }
+        @Override protected Entity getInstanceFromId(String id) { return mgmt.getEntityManager().getEntity(id); }
+    }
+    public static class LocationSerialization extends AbstractBrooklynObjectSerialization<Location> {
+        public LocationSerialization(ManagementContext mgmt) { super(Location.class, mgmt); }
+        @Override protected Location getInstanceFromId(String id) { return mgmt.getLocationManager().getLocation(id); }
+    }
+    // TODO how to look up policies and enrichers? (not essential...)
+//    public static class PolicySerialization extends AbstractBrooklynObjectSerialization<Policy> {
+//        public EntitySerialization(ManagementContext mgmt) { super(Policy.class, mgmt); }
+//        @Override protected Policy getKind(String id) { return mgmt.getEntityManager().getEntity(id); }
+//    }
+//    public static class EnricherSerialization extends AbstractBrooklynObjectSerialization<Enricher> {
+//        public EntitySerialization(ManagementContext mgmt) { super(Entity.class, mgmt); }
+//        @Override protected Enricher getKind(String id) { return mgmt.getEntityManager().getEntity(id); }
+//    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonJsonProvider.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonJsonProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonJsonProvider.java
new file mode 100644
index 0000000..5568208
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonJsonProvider.java
@@ -0,0 +1,177 @@
+/*
+ * 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.json;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import javax.servlet.ServletContext;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.ext.MessageBodyReader;
+import javax.ws.rs.ext.MessageBodyWriter;
+
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.internal.BrooklynProperties;
+import org.apache.brooklyn.core.server.BrooklynServiceAttributes;
+import org.apache.brooklyn.rest.util.OsgiCompat;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.core.Version;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
+
+public class BrooklynJacksonJsonProvider extends JacksonJsonProvider implements
+        //CXF only looks at the interfaces of this class to determine if the Provider is a MessageBodyWriter/Reader
+        MessageBodyWriter<Object>, MessageBodyReader<Object> {
+
+    private static final Logger log = LoggerFactory.getLogger(BrooklynJacksonJsonProvider.class);
+
+    public static final String BROOKLYN_REST_OBJECT_MAPPER = BrooklynServiceAttributes.BROOKLYN_REST_OBJECT_MAPPER;
+
+    @Context protected ServletContext servletContext;
+
+    protected ObjectMapper ourMapper;
+    protected boolean notFound = false;
+
+    private ManagementContext mgmt;
+
+    @Override
+    public ObjectMapper locateMapper(Class<?> type, MediaType mediaType) {
+        if (ourMapper != null)
+            return ourMapper;
+
+        findSharedMapper();
+
+        if (ourMapper != null)
+            return ourMapper;
+
+        if (!notFound) {
+            log.warn("Management context not available; using default ObjectMapper in "+this);
+            notFound = true;
+        }
+
+        return super.locateMapper(Object.class, MediaType.APPLICATION_JSON_TYPE);
+    }
+
+    protected synchronized ObjectMapper findSharedMapper() {
+        if (ourMapper != null || notFound)
+            return ourMapper;
+
+        ourMapper = findSharedObjectMapper(servletContext, mgmt);
+        if (ourMapper == null) return null;
+
+        if (notFound) {
+            notFound = false;
+        }
+        log.debug("Found mapper "+ourMapper+" for "+this+", creating custom Brooklyn mapper");
+
+        return ourMapper;
+    }
+
+    /**
+     * Finds a shared {@link ObjectMapper} or makes a new one, stored against the servlet context;
+     * returns null if a shared instance cannot be created.
+     */
+    public static ObjectMapper findSharedObjectMapper(ServletContext servletContext, ManagementContext mgmt) {
+        checkNotNull(mgmt, "mgmt");
+        if (servletContext != null) {
+            synchronized (servletContext) {
+                boolean isServletContextNull = false;
+                try {
+                    ObjectMapper mapper = (ObjectMapper) servletContext.getAttribute(BROOKLYN_REST_OBJECT_MAPPER);
+                    if (mapper != null) return mapper;
+                } catch (NullPointerException e) {
+                    // CXF always injects a ThreadLocalServletContext that may return null later on.
+                    // Ignore this case so this provider can be used outside the REST server, such as the CXF client during tests.
+                    isServletContextNull = true;
+                }
+
+                if (!isServletContextNull) {
+                    ObjectMapper mapper = newPrivateObjectMapper(mgmt);
+                    servletContext.setAttribute(BROOKLYN_REST_OBJECT_MAPPER, mapper);
+                    return mapper;
+                }
+            }
+        }
+        if (mgmt != null) {
+            synchronized (mgmt) {
+                ConfigKey<ObjectMapper> key = ConfigKeys.newConfigKey(ObjectMapper.class, BROOKLYN_REST_OBJECT_MAPPER);
+                ObjectMapper mapper = mgmt.getConfig().getConfig(key);
+                if (mapper != null) return mapper;
+
+                mapper = newPrivateObjectMapper(mgmt);
+                log.debug("Storing new ObjectMapper against "+mgmt+" because no ServletContext available: "+mapper);
+                ((BrooklynProperties)mgmt.getConfig()).put(key, mapper);
+                return mapper;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Like {@link #findSharedObjectMapper(ServletContext, ManagementContext)} but will create a private
+     * ObjectMapper if it can, from the servlet context and/or the management context, or else fail
+     */
+    public static ObjectMapper findAnyObjectMapper(ServletContext servletContext, ManagementContext mgmt) {
+        ObjectMapper mapper = findSharedObjectMapper(servletContext, mgmt);
+        if (mapper != null) return mapper;
+
+        if (mgmt == null && servletContext != null) {
+            mgmt = getManagementContext(servletContext);
+        }
+        return newPrivateObjectMapper(mgmt);
+    }
+
+    /**
+     * @return A new Brooklyn-specific ObjectMapper.
+     *   Normally {@link #findSharedObjectMapper(ServletContext, ManagementContext)} is preferred
+     */
+    public static ObjectMapper newPrivateObjectMapper(ManagementContext mgmt) {
+        if (mgmt == null) {
+            throw new IllegalStateException("No management context available for creating ObjectMapper");
+        }
+
+        ConfigurableSerializerProvider sp = new ConfigurableSerializerProvider();
+        sp.setUnknownTypeSerializer(new ErrorAndToStringUnknownTypeSerializer());
+
+        ObjectMapper mapper = new ObjectMapper();
+        mapper.setSerializerProvider(sp);
+        mapper.setVisibilityChecker(new PossiblyStrictPreferringFieldsVisibilityChecker());
+
+        SimpleModule mapperModule = new SimpleModule("Brooklyn", new Version(0, 0, 0, "ignored"));
+
+        new BidiSerialization.ManagementContextSerialization(mgmt).install(mapperModule);
+        new BidiSerialization.EntitySerialization(mgmt).install(mapperModule);
+        new BidiSerialization.LocationSerialization(mgmt).install(mapperModule);
+
+        mapperModule.addSerializer(new MultimapSerializer());
+        mapper.registerModule(mapperModule);
+
+        return mapper;
+    }
+
+    public static ManagementContext getManagementContext(ServletContext servletContext) {
+        return OsgiCompat.getManagementContext(servletContext);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/ConfigurableSerializerProvider.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/ConfigurableSerializerProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/ConfigurableSerializerProvider.java
new file mode 100644
index 0000000..1b87e76
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/json/ConfigurableSerializerProvider.java
@@ -0,0 +1,90 @@
+/*
+ * 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.json;
+
+import java.io.IOException;
+
+import org.apache.brooklyn.util.exceptions.Exceptions;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonStreamContext;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializationConfig;
+import com.fasterxml.jackson.databind.ser.DefaultSerializerProvider;
+import com.fasterxml.jackson.databind.ser.SerializerFactory;
+
+/** allows the serializer-of-last-resort to be customized, ie used for unknown-types */
+final class ConfigurableSerializerProvider extends DefaultSerializerProvider {
+
+    protected JsonSerializer<Object> unknownTypeSerializer;
+
+    public ConfigurableSerializerProvider() {}
+
+    @Override
+    public DefaultSerializerProvider createInstance(SerializationConfig config, SerializerFactory jsf) {
+        return new ConfigurableSerializerProvider(config, this, jsf);
+    }
+
+    public ConfigurableSerializerProvider(SerializationConfig config, ConfigurableSerializerProvider src, SerializerFactory jsf) {
+        super(src, config, jsf);
+        unknownTypeSerializer = src.unknownTypeSerializer;
+    }
+
+    @Override
+    public JsonSerializer<Object> getUnknownTypeSerializer(Class<?> unknownType) {
+        if (unknownTypeSerializer!=null) return unknownTypeSerializer;
+        return super.getUnknownTypeSerializer(unknownType);
+    }
+
+    public void setUnknownTypeSerializer(JsonSerializer<Object> unknownTypeSerializer) {
+        this.unknownTypeSerializer = unknownTypeSerializer;
+    }
+
+    @Override
+    public void serializeValue(JsonGenerator jgen, Object value) throws IOException {
+        JsonStreamContext ctxt = jgen.getOutputContext();
+        try {
+            super.serializeValue(jgen, value);
+        } catch (Exception e) {
+            onSerializationException(ctxt, jgen, value, e);
+        }
+    }
+
+    @Override
+    public void serializeValue(JsonGenerator jgen, Object value, JavaType rootType) throws IOException {
+        JsonStreamContext ctxt = jgen.getOutputContext();
+        try {
+            super.serializeValue(jgen, value, rootType);
+        } catch (Exception e) {
+            onSerializationException(ctxt, jgen, value, e);
+        }
+    }
+
+    protected void onSerializationException(JsonStreamContext ctxt, JsonGenerator jgen, Object value, Exception e) throws IOException {
+        Exceptions.propagateIfFatal(e);
+
+        JsonSerializer<Object> unknownTypeSerializer = getUnknownTypeSerializer(value.getClass());
+        if (unknownTypeSerializer instanceof ErrorAndToStringUnknownTypeSerializer) {
+            ((ErrorAndToStringUnknownTypeSerializer)unknownTypeSerializer).serializeFromError(ctxt, e, value, jgen, this);
+        } else {
+            unknownTypeSerializer.serialize(value, jgen, this);
+        }
+    }
+}


[21/34] brooklyn-server git commit: REST API optional Jersey compatibility

Posted by he...@apache.org.
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/util/EntityLocationUtilsTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/util/EntityLocationUtilsTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/util/EntityLocationUtilsTest.java
new file mode 100644
index 0000000..f0c65e4
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/util/EntityLocationUtilsTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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.testng.Assert.assertEquals;
+
+import java.util.Arrays;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.location.AbstractLocation;
+import org.apache.brooklyn.core.location.geo.HostGeoInfo;
+import org.apache.brooklyn.core.location.internal.LocationInternal;
+import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
+import org.apache.brooklyn.entity.software.base.SoftwareProcess;
+import org.apache.brooklyn.rest.testing.mocks.RestMockSimpleEntity;
+
+import com.google.common.collect.ImmutableList;
+
+public class EntityLocationUtilsTest extends BrooklynAppUnitTestSupport {
+
+    private static final Logger log = LoggerFactory.getLogger(EntityLocationUtilsTest.class);
+    
+    private Location loc;
+    
+    @BeforeMethod(alwaysRun=true)
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        loc = mgmt.getLocationRegistry().resolve("localhost");
+        ((AbstractLocation)loc).setHostGeoInfo(new HostGeoInfo("localhost", "localhost", 50, 0));
+    }
+    
+    @Test
+    public void testCount() {
+        @SuppressWarnings("unused")
+        SoftwareProcess r1 = app.createAndManageChild(EntitySpec.create(SoftwareProcess.class, RestMockSimpleEntity.class));
+        SoftwareProcess r2 = app.createAndManageChild(EntitySpec.create(SoftwareProcess.class, RestMockSimpleEntity.class));
+        Entities.start(app, Arrays.<Location>asList(loc));
+
+        Entities.dumpInfo(app);
+
+        log.info("r2loc: "+r2.getLocations());
+        log.info("props: "+((LocationInternal)r2.getLocations().iterator().next()).config().getBag().getAllConfig());
+
+        Map<Location, Integer> counts = new EntityLocationUtils(mgmt).countLeafEntitiesByLocatedLocations();
+        log.info("count: "+counts);
+        assertEquals(ImmutableList.copyOf(counts.values()), ImmutableList.of(2), "counts="+counts);
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/util/HaHotStateCheckClassResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/util/HaHotStateCheckClassResource.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/util/HaHotStateCheckClassResource.java
new file mode 100644
index 0000000..80e9c46
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/util/HaHotStateCheckClassResource.java
@@ -0,0 +1,38 @@
+/*
+ * 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 javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import org.apache.brooklyn.rest.filter.HaHotStateRequired;
+
+@Path("/ha/class")
+@Produces(MediaType.APPLICATION_JSON)
+@HaHotStateRequired
+public class HaHotStateCheckClassResource {
+
+    @GET
+    @Path("fail")
+    public String fail() {
+        return "FAIL";
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/util/HaHotStateCheckResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/util/HaHotStateCheckResource.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/util/HaHotStateCheckResource.java
new file mode 100644
index 0000000..5c9d4d1
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/util/HaHotStateCheckResource.java
@@ -0,0 +1,44 @@
+/*
+ * 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 javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import org.apache.brooklyn.rest.filter.HaHotStateRequired;
+
+@Path("/ha/method")
+@Produces(MediaType.APPLICATION_JSON)
+public class HaHotStateCheckResource {
+
+    @GET
+    @Path("ok")
+    public String ok() {
+        return "OK";
+    }
+
+    @GET
+    @Path("fail")
+    @HaHotStateRequired
+    public String fail() {
+        return "FAIL";
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/util/NoOpRecordingShutdownHandler.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/util/NoOpRecordingShutdownHandler.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/util/NoOpRecordingShutdownHandler.java
new file mode 100644
index 0000000..a99d3d9
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/util/NoOpRecordingShutdownHandler.java
@@ -0,0 +1,39 @@
+/*
+ * 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 org.apache.brooklyn.rest.util.ShutdownHandler;
+
+public class NoOpRecordingShutdownHandler implements ShutdownHandler {
+    private volatile boolean isRequested;
+
+    @Override
+    public void onShutdownRequest() {
+        isRequested = true;
+    }
+
+    public boolean isRequested() {
+        return isRequested;
+    }
+
+    public void reset() {
+        isRequested = false;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/util/NullHttpServletRequestProvider.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/util/NullHttpServletRequestProvider.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/util/NullHttpServletRequestProvider.java
new file mode 100644
index 0000000..7a24f31
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/util/NullHttpServletRequestProvider.java
@@ -0,0 +1,46 @@
+/*
+ * 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 java.lang.reflect.Type;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.ext.Provider;
+
+import com.sun.jersey.core.spi.component.ComponentContext;
+import com.sun.jersey.core.spi.component.ComponentScope;
+import com.sun.jersey.spi.inject.Injectable;
+import com.sun.jersey.spi.inject.InjectableProvider;
+
+@Provider
+public class NullHttpServletRequestProvider implements InjectableProvider<Context, Type> { 
+    public Injectable<HttpServletRequest> getInjectable(ComponentContext ic, 
+            Context a, Type c) { 
+        if (HttpServletRequest.class == c) { 
+            return new Injectable<HttpServletRequest>() {
+                public HttpServletRequest getValue() { return null; }
+            }; 
+        } else 
+            return null; 
+    } 
+    public ComponentScope getScope() { 
+        return ComponentScope.Singleton; 
+    } 
+} 

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/util/NullServletConfigProvider.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/util/NullServletConfigProvider.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/util/NullServletConfigProvider.java
new file mode 100644
index 0000000..106780d
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/util/NullServletConfigProvider.java
@@ -0,0 +1,51 @@
+/*
+ * 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 java.lang.reflect.Type;
+
+import javax.servlet.ServletContext;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.ext.Provider;
+
+import com.sun.jersey.core.spi.component.ComponentContext;
+import com.sun.jersey.core.spi.component.ComponentScope;
+import com.sun.jersey.spi.container.servlet.WebConfig;
+import com.sun.jersey.spi.inject.Injectable;
+import com.sun.jersey.spi.inject.InjectableProvider;
+
+@Provider
+public class NullServletConfigProvider implements InjectableProvider<Context, Type> { 
+    public Injectable<ServletContext> getInjectable(ComponentContext ic, 
+            Context a, Type c) { 
+        if (ServletContext.class == c) { 
+            return new Injectable<ServletContext>() {
+                public ServletContext getValue() { return null; }
+            }; 
+        } else if (WebConfig.class == c) {
+            return new Injectable<ServletContext>() {
+                public ServletContext getValue() { return null; }
+            }; 
+        } else 
+            return null; 
+    } 
+    public ComponentScope getScope() { 
+        return ComponentScope.Singleton; 
+    } 
+} 

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/util/RestApiTestUtils.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/util/RestApiTestUtils.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/util/RestApiTestUtils.java
new file mode 100644
index 0000000..36ad69c
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/util/RestApiTestUtils.java
@@ -0,0 +1,58 @@
+/*
+ * 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 java.io.InputStream;
+
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.stream.Streams;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class RestApiTestUtils {
+
+    public static <T> T fromJson(String text, Class<T> type) {
+        try {
+            return new ObjectMapper().readValue(text, type);
+        } catch (Exception e) {
+            throw Exceptions.propagate(e);
+        }
+    }
+    public static String asJson(Object x) {
+        try {
+            return new ObjectMapper().writeValueAsString(x);
+        } catch (Exception e) {
+            throw Exceptions.propagate(e);
+        }
+    }
+    public static String jsonFixture(String path) {
+        InputStream stream = RestApiTestUtils.class.getClassLoader().getResourceAsStream(path);
+        if (stream==null) throw new IllegalStateException("Cannot find resource: "+path);
+        return asJson(fromJson(Streams.readFullyString(stream), Object.class));
+    }
+
+    public static <T> T fromJson(String text, TypeReference<T> type) {
+        try {
+            return new ObjectMapper().readValue(text, type);
+        } catch (Exception e) {
+            throw Exceptions.propagate(e);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/util/ServerStoppingShutdownHandler.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/util/ServerStoppingShutdownHandler.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/util/ServerStoppingShutdownHandler.java
new file mode 100644
index 0000000..fc3bbc4
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/util/ServerStoppingShutdownHandler.java
@@ -0,0 +1,75 @@
+/*
+ * 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 org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
+import org.apache.brooklyn.util.time.Duration;
+import org.apache.brooklyn.util.time.Time;
+import org.eclipse.jetty.server.Server;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/* not the cleanest way to enforce a clean shutdown, but a simple and effective way;
+ * usage is restricted to BrooklynRestApiLauncher and subclasses, to stop it inline.
+ * the main app stops the server in a more principled way using callbacks. */
+public class ServerStoppingShutdownHandler implements ShutdownHandler {
+
+    private static final Logger log = LoggerFactory.getLogger(ServerStoppingShutdownHandler.class);
+    
+    private final ManagementContext mgmt;
+    private Server server;
+    
+    public ServerStoppingShutdownHandler(ManagementContext mgmt) {
+        this.mgmt = mgmt;
+    }
+
+    @Override
+    public void onShutdownRequest() {
+        log.info("Shutting down server (when running in rest-api dev mode, using background thread)");
+
+        // essentially same as BrooklynLauncher.terminate() but cut down ...
+        // NB: this is only used in dev mode use of BrooklynJavascriptGuiLauncher
+        new Thread(new Runnable() {
+            public void run() {
+                Time.sleep(Duration.millis(250));
+                log.debug("Shutting down server in background thread, closing "+server+" and "+mgmt);
+                if (server!=null) {
+                    try {
+                        server.stop();
+                        server.join();
+                    } catch (Exception e) {
+                        log.debug("Stopping server gave an error (not usually a concern): "+e);
+                        /* NPE may be thrown e.g. if threadpool not started */
+                    }
+                }
+
+                if (mgmt instanceof ManagementContextInternal) {
+                    ((ManagementContextInternal)mgmt).terminate();
+                }
+            }
+        }).start();
+    }
+
+    /** Expect this to be injected; typically it is not known when this is created, but we need it to trigger shutdown. */
+    public void setServer(Server server) {
+        this.server = server;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonSerializerTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonSerializerTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonSerializerTest.java
new file mode 100644
index 0000000..9542eda
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonSerializerTest.java
@@ -0,0 +1,399 @@
+/*
+ * 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.json;
+
+import java.io.NotSerializableException;
+import java.net.URI;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.core.MediaType;
+
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.utils.URIBuilder;
+import org.eclipse.jetty.server.Server;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.core.entity.Attributes;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
+import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
+import org.apache.brooklyn.core.test.entity.TestApplication;
+import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.apache.brooklyn.rest.BrooklynRestApiLauncher;
+import org.apache.brooklyn.util.collections.MutableList;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.http.HttpTool;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.stream.Streams;
+import org.apache.brooklyn.util.text.Strings;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.MultimapBuilder;
+import com.google.gson.Gson;
+import org.eclipse.jetty.server.NetworkConnector;
+
+public class BrooklynJacksonSerializerTest {
+
+    private static final Logger log = LoggerFactory.getLogger(BrooklynJacksonSerializerTest.class);
+    
+    public static class SillyClassWithManagementContext {
+        @JsonProperty
+        ManagementContext mgmt;
+        @JsonProperty
+        String id;
+        
+        public SillyClassWithManagementContext() { }
+        
+        public SillyClassWithManagementContext(String id, ManagementContext mgmt) {
+            this.id = id;
+            this.mgmt = mgmt;
+        }
+
+        @Override
+        public String toString() {
+            return super.toString()+"[id="+id+";mgmt="+mgmt+"]";
+        }
+    }
+
+    @Test
+    public void testCustomSerializerWithSerializableSillyManagementExample() throws Exception {
+        ManagementContext mgmt = LocalManagementContextForTests.newInstance();
+        try {
+
+            ObjectMapper mapper = BrooklynJacksonJsonProvider.newPrivateObjectMapper(mgmt);
+
+            SillyClassWithManagementContext silly = new SillyClassWithManagementContext("123", mgmt);
+            log.info("silly is: "+silly);
+
+            String sillyS = mapper.writeValueAsString(silly);
+
+            log.info("silly json is: "+sillyS);
+
+            SillyClassWithManagementContext silly2 = mapper.readValue(sillyS, SillyClassWithManagementContext.class);
+            log.info("silly2 is: "+silly2);
+
+            Assert.assertEquals(silly.id, silly2.id);
+            
+        } finally {
+            Entities.destroyAll(mgmt);
+        }
+    }
+    
+    public static class SelfRefNonSerializableClass {
+        @JsonProperty
+        Object bogus = this;
+    }
+
+    @Test
+    public void testSelfReferenceFailsWhenStrict() {
+        checkNonSerializableWhenStrict(new SelfRefNonSerializableClass());
+    }
+    @Test
+    public void testSelfReferenceGeneratesErrorMapObject() throws Exception {
+        checkSerializesAsMapWithErrorAndToString(new SelfRefNonSerializableClass());
+    }
+    @Test
+    public void testNonSerializableInListIsShownInList() throws Exception {
+        List<?> result = checkSerializesAs(MutableList.of(1, new SelfRefNonSerializableClass()), List.class);
+        Assert.assertEquals( result.get(0), 1 );
+        Assert.assertEquals( ((Map<?,?>)result.get(1)).get("errorType"), NotSerializableException.class.getName() );
+    }
+    @Test
+    public void testNonSerializableInMapIsShownInMap() throws Exception {
+        Map<?,?> result = checkSerializesAs(MutableMap.of("x", new SelfRefNonSerializableClass()), Map.class);
+        Assert.assertEquals( ((Map<?,?>)result.get("x")).get("errorType"), NotSerializableException.class.getName() );
+    }
+    static class TupleWithNonSerializable {
+        String good = "bon";
+        SelfRefNonSerializableClass bad = new SelfRefNonSerializableClass();
+    }
+    @Test
+    public void testNonSerializableInObjectIsShownInMap() throws Exception {
+        String resultS = checkSerializesAs(new TupleWithNonSerializable(), null);
+        log.info("nested non-serializable json is "+resultS);
+        Assert.assertTrue(resultS.startsWith("{\"good\":\"bon\",\"bad\":{"), "expected a nested map for the error field, not "+resultS);
+        
+        Map<?,?> result = checkSerializesAs(new TupleWithNonSerializable(), Map.class);
+        Assert.assertEquals( result.get("good"), "bon" );
+        Assert.assertTrue( result.containsKey("bad"), "Should have had a key for field 'bad'" );
+        Assert.assertEquals( ((Map<?,?>)result.get("bad")).get("errorType"), NotSerializableException.class.getName() );
+    }
+    
+    public static class EmptyClass {
+    }
+
+    @Test
+    public void testEmptySerializesAsEmpty() throws Exception {
+        // deliberately, a class with no fields and no annotations serializes as an error,
+        // because the alternative, {}, is useless.  however if it *is* annotated, as below, then it will serialize fine.
+        checkSerializesAsMapWithErrorAndToString(new SelfRefNonSerializableClass());
+    }
+    @Test
+    public void testEmptyNonSerializableFailsWhenStrict() {
+        checkNonSerializableWhenStrict(new EmptyClass());
+    }
+
+    @JsonSerialize
+    public static class EmptyClassWithSerialize {
+    }
+
+    @Test
+    public void testEmptyAnnotatedSerializesAsEmptyEvenWhenStrict() throws Exception {
+        try {
+            BidiSerialization.setStrictSerialization(true);
+            testEmptyAnnotatedSerializesAsEmpty();
+        } finally {
+            BidiSerialization.clearStrictSerialization();
+        }
+    }
+    
+    @Test
+    public void testEmptyAnnotatedSerializesAsEmpty() throws Exception {
+        Map<?, ?> map = checkSerializesAs( new EmptyClassWithSerialize(), Map.class );
+        Assert.assertTrue(map.isEmpty(), "Expected an empty map; instead got: "+map);
+
+        String result = checkSerializesAs( MutableList.of(new EmptyClassWithSerialize()), null );
+        result = result.replaceAll(" ", "").trim();
+        Assert.assertEquals(result, "[{}]");
+    }
+
+    @Test
+    public void testSensorFailsWhenStrict() {
+        checkNonSerializableWhenStrict(MutableList.of(Attributes.HTTP_PORT));
+    }
+    @Test
+    public void testSensorSensible() throws Exception {
+        Map<?,?> result = checkSerializesAs(Attributes.HTTP_PORT, Map.class);
+        log.info("SENSOR json is: "+result);
+        Assert.assertFalse(result.toString().contains("error"), "Shouldn't have had an error, instead got: "+result);
+    }
+
+    @Test
+    public void testLinkedListSerialization() throws Exception {
+        LinkedList<Object> ll = new LinkedList<Object>();
+        ll.add(1); ll.add("two");
+        String result = checkSerializesAs(ll, null);
+        log.info("LLIST json is: "+result);
+        Assert.assertFalse(result.contains("error"), "Shouldn't have had an error, instead got: "+result);
+        Assert.assertEquals(Strings.collapseWhitespace(result, ""), "[1,\"two\"]");
+    }
+
+    @Test
+    public void testMultiMapSerialization() throws Exception {
+        Multimap<String, Integer> m = MultimapBuilder.hashKeys().arrayListValues().build();
+        m.put("bob", 24);
+        m.put("bob", 25);
+        String result = checkSerializesAs(m, null);
+        log.info("multimap serialized as: " + result);
+        Assert.assertFalse(result.contains("error"), "Shouldn't have had an error, instead got: "+result);
+        Assert.assertEquals(Strings.collapseWhitespace(result, ""), "{\"bob\":[24,25]}");
+    }
+
+    @Test
+    public void testSupplierSerialization() throws Exception {
+        String result = checkSerializesAs(Strings.toStringSupplier(Streams.byteArrayOfString("x")), null);
+        log.info("SUPPLIER json is: "+result);
+        Assert.assertFalse(result.contains("error"), "Shouldn't have had an error, instead got: "+result);
+    }
+
+    @Test
+    public void testWrappedStreamSerialization() throws Exception {
+        String result = checkSerializesAs(BrooklynTaskTags.tagForStream("TEST", Streams.byteArrayOfString("x")), null);
+        log.info("WRAPPED STREAM json is: "+result);
+        Assert.assertFalse(result.contains("error"), "Shouldn't have had an error, instead got: "+result);
+    }
+
+    @SuppressWarnings("unchecked")
+    protected <T> T checkSerializesAs(Object x, Class<T> type) {
+        ManagementContext mgmt = LocalManagementContextForTests.newInstance();
+        try {
+            ObjectMapper mapper = BrooklynJacksonJsonProvider.newPrivateObjectMapper(mgmt);
+            String tS = mapper.writeValueAsString(x);
+            log.debug("serialized "+x+" as "+tS);
+            Assert.assertTrue(tS.length() < 1000, "Data too long, size "+tS.length()+" for "+x);
+            if (type==null) return (T) tS;
+            return mapper.readValue(tS, type);
+        } catch (Exception e) {
+            throw Exceptions.propagate(e);
+        } finally {
+            Entities.destroyAll(mgmt);
+        }
+    }
+    protected Map<?,?> checkSerializesAsMapWithErrorAndToString(Object x) {
+        Map<?,?> rt = checkSerializesAs(x, Map.class);
+        Assert.assertEquals(rt.get("toString"), x.toString());
+        Assert.assertEquals(rt.get("error"), Boolean.TRUE);
+        return rt;
+    }
+    protected void checkNonSerializableWhenStrict(Object x) {
+        checkNonSerializable(x, true);
+    }
+    protected void checkNonSerializable(Object x, boolean strict) {
+        ManagementContext mgmt = LocalManagementContextForTests.newInstance();
+        try {
+            ObjectMapper mapper = BrooklynJacksonJsonProvider.newPrivateObjectMapper(mgmt);
+            if (strict)
+                BidiSerialization.setStrictSerialization(true);
+            
+            String tS = mapper.writeValueAsString(x);
+            Assert.fail("Should not have serialized "+x+"; instead gave: "+tS);
+            
+        } catch (Exception e) {
+            Exceptions.propagateIfFatal(e);
+            log.info("Got expected error, when serializing "+x+": "+e);
+            
+        } finally {
+            if (strict)
+                BidiSerialization.clearStrictSerialization();
+            Entities.destroyAll(mgmt);
+        }
+    }
+    
+    // Ensure TEXT_PLAIN just returns toString for ManagementContext instance.
+    // Strangely, testWithLauncherSerializingListsContainingEntitiesAndOtherComplexStuff ended up in the 
+    // EntityConfigResource.getPlain code, throwing a ClassCastException.
+    // 
+    // TODO This tests the fix for that ClassCastException, but does not explain why 
+    // testWithLauncherSerializingListsContainingEntitiesAndOtherComplexStuff was calling it.
+    @Test(groups="Integration") //because of time
+    public void testWithAcceptsPlainText() throws Exception {
+        ManagementContext mgmt = LocalManagementContextForTests.newInstance();
+        Server server = null;
+        try {
+            server = BrooklynRestApiLauncher.launcher().managementContext(mgmt).start();
+            HttpClient client = HttpTool.httpClientBuilder().build();
+
+            TestApplication app = TestApplication.Factory.newManagedInstanceForTests(mgmt);
+
+            String serverAddress = "http://localhost:"+((NetworkConnector)server.getConnectors()[0]).getLocalPort();
+            String appUrl = serverAddress + "/v1/applications/" + app.getId();
+            String entityUrl = appUrl + "/entities/" + app.getId();
+            URI configUri = new URIBuilder(entityUrl + "/config/" + TestEntity.CONF_OBJECT.getName())
+                    .addParameter("raw", "true")
+                    .build();
+
+            // assert config here is just mgmt.toString()
+            app.config().set(TestEntity.CONF_OBJECT, mgmt);
+            String content = get(client, configUri, ImmutableMap.of("Accept", MediaType.TEXT_PLAIN));
+            log.info("CONFIG MGMT is:\n"+content);
+            Assert.assertEquals(content, mgmt.toString(), "content="+content);
+            
+        } finally {
+            try {
+                if (server != null) server.stop();
+            } catch (Exception e) {
+                log.warn("failed to stop server: "+e);
+            }
+            Entities.destroyAll(mgmt);
+        }
+    }
+        
+    @Test(groups="Integration") //because of time
+    public void testWithLauncherSerializingListsContainingEntitiesAndOtherComplexStuff() throws Exception {
+        ManagementContext mgmt = LocalManagementContextForTests.newInstance();
+        Server server = null;
+        try {
+            server = BrooklynRestApiLauncher.launcher().managementContext(mgmt).start();
+            HttpClient client = HttpTool.httpClientBuilder().build();
+
+            TestApplication app = TestApplication.Factory.newManagedInstanceForTests(mgmt);
+
+            String serverAddress = "http://localhost:"+((NetworkConnector)server.getConnectors()[0]).getLocalPort();
+            String appUrl = serverAddress + "/v1/applications/" + app.getId();
+            String entityUrl = appUrl + "/entities/" + app.getId();
+            URI configUri = new URIBuilder(entityUrl + "/config/" + TestEntity.CONF_OBJECT.getName())
+                    .addParameter("raw", "true")
+                    .build();
+
+            // assert config here is just mgmt
+            app.config().set(TestEntity.CONF_OBJECT, mgmt);
+            String content = get(client, configUri, ImmutableMap.of("Accept", MediaType.APPLICATION_JSON));
+            log.info("CONFIG MGMT is:\n"+content);
+            @SuppressWarnings("rawtypes")
+            Map values = new Gson().fromJson(content, Map.class);
+            Assert.assertEquals(values, ImmutableMap.of("type", LocalManagementContextForTests.class.getCanonicalName()), "values="+values);
+
+            // assert normal API returns the same, containing links
+            content = get(client, entityUrl, ImmutableMap.of("Accept", MediaType.APPLICATION_JSON));
+            log.info("ENTITY is: \n"+content);
+            values = new Gson().fromJson(content, Map.class);
+            Assert.assertTrue(values.size()>=3, "Map is too small: "+values);
+            Assert.assertTrue(values.size()<=6, "Map is too big: "+values);
+            Assert.assertEquals(values.get("type"), TestApplication.class.getCanonicalName(), "values="+values);
+            Assert.assertNotNull(values.get("links"), "Map should have contained links: values="+values);
+
+            // but config etc returns our nicely json serialized
+            app.config().set(TestEntity.CONF_OBJECT, app);
+            content = get(client, configUri, ImmutableMap.of("Accept", MediaType.APPLICATION_JSON));
+            log.info("CONFIG ENTITY is:\n"+content);
+            values = new Gson().fromJson(content, Map.class);
+            Assert.assertEquals(values, ImmutableMap.of("type", Entity.class.getCanonicalName(), "id", app.getId()), "values="+values);
+
+            // and self-ref gives error + toString
+            SelfRefNonSerializableClass angry = new SelfRefNonSerializableClass();
+            app.config().set(TestEntity.CONF_OBJECT, angry);
+            content = get(client, configUri, ImmutableMap.of("Accept", MediaType.APPLICATION_JSON));
+            log.info("CONFIG ANGRY is:\n"+content);
+            assertErrorObjectMatchingToString(content, angry);
+            
+            // as does Server
+            app.config().set(TestEntity.CONF_OBJECT, server);
+            content = get(client, configUri, ImmutableMap.of("Accept", MediaType.APPLICATION_JSON));
+            // NOTE, if using the default visibility / object mapper, the getters of the object are invoked
+            // resulting in an object which is huge, 7+MB -- and it wreaks havoc w eclipse console regex parsing!
+            // (but with our custom VisibilityChecker server just gives us the nicer error!)
+            log.info("CONFIG SERVER is:\n"+content);
+            assertErrorObjectMatchingToString(content, server);
+            Assert.assertTrue(content.contains(NotSerializableException.class.getCanonicalName()), "server should have contained things which are not serializable");
+            Assert.assertTrue(content.length() < 1024, "content should not have been very long; instead was: "+content.length());
+            
+        } finally {
+            try {
+                if (server != null) server.stop();
+            } catch (Exception e) {
+                log.warn("failed to stop server: "+e);
+            }
+            Entities.destroyAll(mgmt);
+        }
+    }
+
+    private void assertErrorObjectMatchingToString(String content, Object expected) {
+        Object value = new Gson().fromJson(content, Object.class);
+        Assert.assertTrue(value instanceof Map, "Expected map, got: "+value);
+        Assert.assertEquals(((Map<?,?>)value).get("toString"), expected.toString());
+    }
+
+    private String get(HttpClient client, String uri, Map<String, String> headers) {
+        return get(client, URI.create(uri), headers);
+    }
+
+    private String get(HttpClient client, URI uri, Map<String, String> headers) {
+        return HttpTool.httpGet(client, uri, headers).getContentAsString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/resources/brooklyn/scanning.catalog.bom
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/resources/brooklyn/scanning.catalog.bom b/rest/rest-server-jersey/src/test/resources/brooklyn/scanning.catalog.bom
new file mode 100644
index 0000000..cddb832
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/resources/brooklyn/scanning.catalog.bom
@@ -0,0 +1,19 @@
+# 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.
+#
+brooklyn.catalog:
+  scanJavaAnnotations: true

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/resources/fixtures/api-error-basic.json
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/resources/fixtures/api-error-basic.json b/rest/rest-server-jersey/src/test/resources/fixtures/api-error-basic.json
new file mode 100644
index 0000000..7634c50
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/resources/fixtures/api-error-basic.json
@@ -0,0 +1,4 @@
+{
+    "message": "explanatory message",
+    "details": "accompanying details"
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/resources/fixtures/api-error-no-details.json
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/resources/fixtures/api-error-no-details.json b/rest/rest-server-jersey/src/test/resources/fixtures/api-error-no-details.json
new file mode 100644
index 0000000..5762f6f
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/resources/fixtures/api-error-no-details.json
@@ -0,0 +1,3 @@
+{
+    "message": "explanatory message"
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/resources/fixtures/application-list.json
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/resources/fixtures/application-list.json b/rest/rest-server-jersey/src/test/resources/fixtures/application-list.json
new file mode 100644
index 0000000..029cf68
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/resources/fixtures/application-list.json
@@ -0,0 +1,44 @@
+[
+    {
+        "spec":{
+            "name":"tesr",
+            "entities":[
+                {
+                    "name":"tomcat",
+                    "type":"org.apache.brooklyn.entity.webapp.tomcat.TomcatServer",
+                    "config":{ }
+                }
+            ],
+            "locations":[
+                "/v1/locations/0"
+            ]
+        },
+        "status":"STARTING",
+        "links":{
+            "self":"/v1/applications/tesr",
+            "entities":"fixtures/entity-summary-list.json"
+        }
+    },
+    {
+        "spec":{
+            "name":"myapp",
+            "entities":[
+                {
+                    "name":"Vanilla Java App",
+                    "type":"org.apache.brooklyn.entity.java.VanillaJavaApp",
+                    "config":{
+                        "initialSize":"1",
+                        "creationScriptUrl":"http://my.brooklyn.io/storage/foo.sql"
+                    }
+                }
+            ],
+            "locations":["/v1/locations/1"]
+        },
+        "status":"STARTING",
+        "links":{
+            "self":"/v1/applications/myapp",
+            "entities":"fixtures/entity-summary-list.json"
+        }
+    }
+]
+

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/resources/fixtures/application-spec.json
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/resources/fixtures/application-spec.json b/rest/rest-server-jersey/src/test/resources/fixtures/application-spec.json
new file mode 100644
index 0000000..9f042c6
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/resources/fixtures/application-spec.json
@@ -0,0 +1,16 @@
+{
+    "name":"myapp",
+    "entities":[
+        {
+            "name":"Vanilla Java App",
+            "type":"org.apache.brooklyn.entity.java.VanillaJavaApp",
+            "config":{
+                "initialSize":"1",
+                "creationScriptUrl":"http://my.brooklyn.io/storage/foo.sql"
+            }
+        }
+    ],
+    "locations":[
+        "/v1/locations/1"
+    ]
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/resources/fixtures/application-tree.json
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/resources/fixtures/application-tree.json b/rest/rest-server-jersey/src/test/resources/fixtures/application-tree.json
new file mode 100644
index 0000000..e4ce29f
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/resources/fixtures/application-tree.json
@@ -0,0 +1,43 @@
+[
+    {
+        "name":"test",
+        "id":"riBZUjMq",
+        "type":null,
+        "children":[
+            {
+                "name":"tomcat1",
+                "id":"fXyyQ7Ap",
+                "type":"org.apache.brooklyn.entity.webapp.tomcat.TomcatServer"
+            }
+        ]
+    },
+    {
+        "name":"test2",
+        "id":"app-002",
+        "type":null,
+        "children":[
+            {
+                "name":"tomcat1",
+                "id":"child-01",
+                "type":"org.apache.brooklyn.entity.webapp.tomcat.TomcatServer"
+            },
+            {
+                "name":"tomcat2",
+                "id":"child-02",
+                "type":"org.apache.brooklyn.entity.webapp.tomcat.TomcatServer",
+                "children":[
+                    {
+                        "name":"tomcat03",
+                        "id":"child-03",
+                        "type":"org.apache.brooklyn.entity.webapp.tomcat.TomcatServer"
+                    },
+                    {
+                        "name":"tomcat04",
+                        "id":"child-04",
+                        "type":"org.apache.brooklyn.entity.webapp.tomcat.TomcatServer"
+                    }
+                ]
+            }
+        ]
+    }
+]

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/resources/fixtures/application.json
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/resources/fixtures/application.json b/rest/rest-server-jersey/src/test/resources/fixtures/application.json
new file mode 100644
index 0000000..cd6a21a
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/resources/fixtures/application.json
@@ -0,0 +1,22 @@
+{
+    "id":"myapp_id",
+    "spec":{
+        "name":"myapp",
+        "entities":[
+            {
+                "name":"Vanilla Java App",
+                "type":"org.apache.brooklyn.entity.java.VanillaJavaApp",
+                "config":{
+                    "initialSize":"1",
+                    "creationScriptUrl":"http://my.brooklyn.io/storage/foo.sql"
+                }
+            }
+        ],
+        "locations":["/v1/locations/1"]
+    },
+    "status":"STARTING",
+    "links":{
+        "self":"/v1/applications/myapp",
+        "entities":"fixtures/entity-summary-list.json"
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/resources/fixtures/catalog-application-list.json
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/resources/fixtures/catalog-application-list.json b/rest/rest-server-jersey/src/test/resources/fixtures/catalog-application-list.json
new file mode 100644
index 0000000..c4757be
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/resources/fixtures/catalog-application-list.json
@@ -0,0 +1,29 @@
+[
+    {
+        "id": "com.example.app:1.1",
+        "type": "com.example.app",
+        "name": "My example application",
+        "version": "1.1",
+        "description": "My awesome example application, as a catalog item",
+        "planYaml": "services:\n- type: org.apache.brooklyn.entity.software.base.VanillaSoftwareProcess\n  launch.command: echo \"Launch application\"\n  checkRunning.command: echo \"Check running application\"",
+        "iconUrl": "http://my.example.com/icon.png"
+    },
+    {
+        "id": "com.example.app:2.0",
+        "type": "com.example.app",
+        "name": "My example application",
+        "version": "2.0",
+        "description": "My awesome example application, as a catalog item",
+        "planYaml": "services:\n- type: org.apache.brooklyn.entity.software.base.VanillaSoftwareProcess\n  launch.command: echo \"Launch application\"\n  checkRunning.command: echo \"Check running application\"",
+        "iconUrl": "http://my.example.com/icon.png"
+    },
+    {
+        "id": "com.example.other.app:1.0",
+        "type": "com.example.other.app",
+        "name": "Another example application",
+        "version": "1.0",
+        "description": "Another awesome example application, as a catalog item",
+        "planYaml": "services:\n- type: org.apache.brooklyn.entity.software.base.VanillaSoftwareProcess\n  launch.command: echo \"Launch other application\"\n  checkRunning.command: echo \"Check running other application\"",
+        "iconUrl": "http://my.other.example.com/icon.png"
+    }
+]
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/resources/fixtures/catalog-application.json
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/resources/fixtures/catalog-application.json b/rest/rest-server-jersey/src/test/resources/fixtures/catalog-application.json
new file mode 100644
index 0000000..7c72270
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/resources/fixtures/catalog-application.json
@@ -0,0 +1,9 @@
+{
+    "id": "com.example.app:1.1",
+    "type": "com.example.app",
+    "name": "My example application",
+    "version": "1.1",
+    "description": "My awesome example application, as a catalog item",
+    "planYaml": "services:\n- type: org.apache.brooklyn.entity.software.base.VanillaSoftwareProcess\n  launch.command: echo \"Launch application\"\n  checkRunning.command: echo \"Check running application\"",
+    "iconUrl": "http://my.example.com/icon.png"
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/resources/fixtures/effector-summary-list.json
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/resources/fixtures/effector-summary-list.json b/rest/rest-server-jersey/src/test/resources/fixtures/effector-summary-list.json
new file mode 100644
index 0000000..fe2828c
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/resources/fixtures/effector-summary-list.json
@@ -0,0 +1,47 @@
+[
+    {
+        "name":"start",
+        "description":"Start the process/service represented by an entity",
+        "returnType":"void",
+        "parameters":[
+            {
+                "name":"locations",
+                "type":"java.util.Collection",
+                "description":"A list of locations"
+            },
+            {
+                "name":"booleanValue",
+                "type":"java.lang.Boolean",
+                "description":"True or false",
+                "defaultValue": true
+            }
+        ],
+        "links":{
+            "self":"/v1/applications/tesr/entities/ZgoDhGQA/effectors/start",
+            "entity":"/v1/applications/tesr/entities/ZgoDhGQA",
+            "application":"/v1/applications/tesr"
+        }
+    },
+    {
+        "name":"restart",
+        "description":"Restart the process/service represented by an entity",
+        "returnType":"void",
+        "parameters":[ ],
+        "links":{
+            "self":"/v1/applications/tesr/entities/ZgoDhGQA/effectors/restart",
+            "entity":"/v1/applications/tesr/entities/ZgoDhGQA",
+            "application":"/v1/applications/tesr"
+        }
+    },
+    {
+        "name":"stop",
+        "description":"Stop the process/service represented by an entity",
+        "returnType":"void",
+        "parameters":[ ],
+        "links":{
+            "self":"/v1/applications/tesr/entities/ZgoDhGQA/effectors/stop",
+            "entity":"/v1/applications/tesr/entities/ZgoDhGQA",
+            "application":"/v1/applications/tesr"
+        }
+    }
+]
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/resources/fixtures/effector-summary.json
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/resources/fixtures/effector-summary.json b/rest/rest-server-jersey/src/test/resources/fixtures/effector-summary.json
new file mode 100644
index 0000000..eda97dd
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/resources/fixtures/effector-summary.json
@@ -0,0 +1,9 @@
+{
+    "name":"stop",
+    "returnType":"void",
+    "parameters":[],
+    "description":"Effector description",
+    "links":{
+        "self":"/v1/applications/redis-app/entities/redis-ent/effectors/stop"
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/resources/fixtures/entity-only-type.json
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/resources/fixtures/entity-only-type.json b/rest/rest-server-jersey/src/test/resources/fixtures/entity-only-type.json
new file mode 100644
index 0000000..e932ab1
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/resources/fixtures/entity-only-type.json
@@ -0,0 +1,3 @@
+{
+    "type":"org.apache.brooklyn.entity.java.VanillaJavaApp"
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/resources/fixtures/entity-summary-list.json
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/resources/fixtures/entity-summary-list.json b/rest/rest-server-jersey/src/test/resources/fixtures/entity-summary-list.json
new file mode 100644
index 0000000..6e2dc4e
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/resources/fixtures/entity-summary-list.json
@@ -0,0 +1,14 @@
+[
+    {
+        "type":"org.apache.brooklyn.entity.webapp.tomcat.TomcatServer",
+        "links":{
+            "self":"/v1/applications/tesr/entities/zQsqdXzi",
+            "catalog":"/v1/catalog/entities/org.apache.brooklyn.entity.webapp.tomcat.TomcatServer",
+            "application":"/v1/applications/tesr",
+            "children":"/v1/applications/tesr/entities/zQsqdXzi/entities",
+            "effectors":"fixtures/effector-summary-list.json",
+            "sensors":"fixtures/sensor-summary-list.json",
+            "activities":"fixtures/task-summary-list.json"
+        }
+    }
+]

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/resources/fixtures/entity-summary.json
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/resources/fixtures/entity-summary.json b/rest/rest-server-jersey/src/test/resources/fixtures/entity-summary.json
new file mode 100644
index 0000000..05d2e8f
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/resources/fixtures/entity-summary.json
@@ -0,0 +1,13 @@
+{"id":"zQsqdXzi",
+ "name":"MyTomcat",
+ "type":"org.apache.brooklyn.entity.webapp.tomcat.TomcatServer",
+ "links":{
+   "self":"/v1/applications/tesr/entities/zQsqdXzi",
+   "catalog":"/v1/catalog/entities/org.apache.brooklyn.entity.webapp.tomcat.TomcatServer",
+   "application":"/v1/applications/tesr",
+   "children":"/v1/applications/tesr/entities/zQsqdXzi/children",
+   "effectors":"fixtures/effector-summary-list.json",
+   "sensors":"fixtures/sensor-summary-list.json",
+   "activities":"fixtures/task-summary-list.json"
+ }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/resources/fixtures/entity.json
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/resources/fixtures/entity.json b/rest/rest-server-jersey/src/test/resources/fixtures/entity.json
new file mode 100644
index 0000000..dac0509
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/resources/fixtures/entity.json
@@ -0,0 +1,7 @@
+[
+    {
+        "name":"Vanilla Java App",
+        "type":"org.apache.brooklyn.entity.java.VanillaJavaApp",
+        "config":{}
+    }
+]

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/resources/fixtures/ha-summary.json
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/resources/fixtures/ha-summary.json b/rest/rest-server-jersey/src/test/resources/fixtures/ha-summary.json
new file mode 100644
index 0000000..3c3eb7c
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/resources/fixtures/ha-summary.json
@@ -0,0 +1,19 @@
+{
+    "links": {},
+    "masterId": "kDp39gAv",
+    "nodes": {
+        "kDp39gAv": {
+            "nodeId": "kDp39gAv",
+            "nodeUri": "http://10.30.40.50:8081/",
+            "status": "MASTER",
+            "timestampUtc": 1400257858796
+        },
+        "lHNCTtZ4": {
+            "nodeId": "lHNCTtZ4",
+            "nodeUri": "http://10.30.40.60:8081/",
+            "status": "STANDBY",
+            "timestampUtc": 1400257858796
+        }
+    },
+    "ownId": "kDp39gAv"
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/resources/fixtures/location-list.json
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/resources/fixtures/location-list.json b/rest/rest-server-jersey/src/test/resources/fixtures/location-list.json
new file mode 100644
index 0000000..c4e05de
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/resources/fixtures/location-list.json
@@ -0,0 +1,10 @@
+[
+    {
+        "id":"123",
+        "name":"localhost",
+        "spec":"localhost",
+        "links":{
+            "self":"/v1/locations/123"
+        }
+    }
+]
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/resources/fixtures/location-summary.json
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/resources/fixtures/location-summary.json b/rest/rest-server-jersey/src/test/resources/fixtures/location-summary.json
new file mode 100644
index 0000000..d1d0573
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/resources/fixtures/location-summary.json
@@ -0,0 +1,8 @@
+{
+    "id":"123",
+    "name":"localhost",
+    "spec":"localhost",
+    "links":{
+        "self":"/v1/locations/123"
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/resources/fixtures/location-without-credential.json
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/resources/fixtures/location-without-credential.json b/rest/rest-server-jersey/src/test/resources/fixtures/location-without-credential.json
new file mode 100644
index 0000000..af8051d
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/resources/fixtures/location-without-credential.json
@@ -0,0 +1,5 @@
+{
+    "name":"localhost",
+    "spec":"localhost",
+    "config":{"identity":"bob"}
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/resources/fixtures/location.json
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/resources/fixtures/location.json b/rest/rest-server-jersey/src/test/resources/fixtures/location.json
new file mode 100644
index 0000000..a2670cd
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/resources/fixtures/location.json
@@ -0,0 +1,4 @@
+{
+    "name":"localhost",
+    "spec":"localhost"
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/resources/fixtures/sensor-current-state.json
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/resources/fixtures/sensor-current-state.json b/rest/rest-server-jersey/src/test/resources/fixtures/sensor-current-state.json
new file mode 100644
index 0000000..e8084e0
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/resources/fixtures/sensor-current-state.json
@@ -0,0 +1,6 @@
+{
+    "service.state":"running",
+    "jmx.context":"jmx/context",
+    "tomcat.shutdownport":"8081",
+    "service.isUp":"true"
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/resources/fixtures/sensor-summary-list.json
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/resources/fixtures/sensor-summary-list.json b/rest/rest-server-jersey/src/test/resources/fixtures/sensor-summary-list.json
new file mode 100644
index 0000000..6ab457b
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/resources/fixtures/sensor-summary-list.json
@@ -0,0 +1,42 @@
+[
+    {
+        "name":"service.state",
+        "type":"org.apache.brooklyn.entity.lifecycle.Lifecycle",
+        "description":"Service lifecycle state",
+        "links":{
+            "self":"fixtures/service-state.json",
+            "application":"/v1/applications/tesr",
+            "entity":"/v1/applications/tesr/entities/zQsqdXzi"
+        }
+    },
+    {
+        "name":"jmx.context",
+        "type":"java.lang.String",
+        "description":"JMX context path",
+        "links":{
+            "self":"fixtures/service-state.json",
+            "application":"/v1/applications/tesr",
+            "entity":"/v1/applications/tesr/entities/zQsqdXzi"
+        }
+    },
+    {
+        "name":"tomcat.shutdownport",
+        "type":"java.lang.Integer",
+        "description":"Suggested shutdown port",
+        "links":{
+            "self":"fixtures/service-state.json",
+            "application":"/v1/applications/tesr",
+            "entity":"/v1/applications/tesr/entities/zQsqdXzi"
+        }
+    },
+    {
+        "name":"service.isUp",
+        "type":"java.lang.Boolean",
+        "description":"Service has been started successfully and is running",
+        "links":{
+            "self":"fixtures/service-state.json",
+            "application":"/v1/applications/tesr",
+            "entity":"/v1/applications/tesr/entities/zQsqdXzi"
+        }
+    }
+]
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/resources/fixtures/sensor-summary.json
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/resources/fixtures/sensor-summary.json b/rest/rest-server-jersey/src/test/resources/fixtures/sensor-summary.json
new file mode 100644
index 0000000..0803b37
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/resources/fixtures/sensor-summary.json
@@ -0,0 +1,8 @@
+{
+    "name":"redis.uptime",
+    "type":"Integer",
+    "description":"Description",
+    "links":{
+        "self":"/v1/applications/redis-app/entities/redis-ent/sensors/redis.uptime"
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/resources/fixtures/server-version.json
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/resources/fixtures/server-version.json b/rest/rest-server-jersey/src/test/resources/fixtures/server-version.json
new file mode 100644
index 0000000..affd707
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/resources/fixtures/server-version.json
@@ -0,0 +1,14 @@
+{
+    "version": "0.7.0-SNAPSHOT",
+    "buildSha1": "cb4f0a3af2f5042222dd176edc102bfa64e7e0b5",
+    "buildBranch":"versions",
+    "features":[
+        {
+          "name": "Sample Brooklyn Project com.acme.sample:brooklyn-sample v0.1.0-SNAPSHOT",
+          "symbolicName":"com.acme.sample.brooklyn-sample",
+          "version":"0.1.0.SNAPSHOT",
+          "lastModified":"523305000",
+          "Brooklyn-Feature-Build-Id":"e0fee1adf795c84eec4735f039503eb18d9c35cc"
+        }
+    ]
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/resources/fixtures/service-state.json
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/resources/fixtures/service-state.json b/rest/rest-server-jersey/src/test/resources/fixtures/service-state.json
new file mode 100644
index 0000000..61d5e33
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/resources/fixtures/service-state.json
@@ -0,0 +1 @@
+running
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/resources/fixtures/task-summary-list.json
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/resources/fixtures/task-summary-list.json b/rest/rest-server-jersey/src/test/resources/fixtures/task-summary-list.json
new file mode 100644
index 0000000..25b3193
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/resources/fixtures/task-summary-list.json
@@ -0,0 +1,15 @@
+[
+    {
+        "entityId":"VzK45RFC",
+        "entityDisplayName":"tomcat",
+        "displayName":"start",
+        "description":"invoking start on tomcat",
+        "id":"n24NC63Nsu",
+        "rawSubmitTimeUtc":1348663165550,
+        "submitTimeUtc":"2012-09-26 12:39:25",
+        "startTimeUtc":"2012-09-26 12:39:25",
+        "endTimeUtc":"2012-09-26 12:39:33",
+        "currentStatus":"Ended normally",
+        "detailedStatus":"Ended normally after 7940 ms\nResult: null"
+    }
+]
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server/src/main/java/org/apache/brooklyn/rest/RestApiSetup.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/RestApiSetup.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/RestApiSetup.java
index 2a43aa1..d868a77 100644
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/RestApiSetup.java
+++ b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/RestApiSetup.java
@@ -26,8 +26,10 @@ import javax.servlet.DispatcherType;
 import javax.servlet.Filter;
 
 import org.apache.brooklyn.rest.apidoc.RestApiResourceScanner;
+import org.apache.cxf.BusFactory;
 import org.apache.cxf.jaxrs.servlet.CXFNonSpringJaxrsServlet;
-import org.eclipse.jetty.server.handler.ContextHandler;
+import org.apache.cxf.transport.common.gzip.GZIPInInterceptor;
+import org.apache.cxf.transport.common.gzip.GZIPOutInterceptor;
 import org.eclipse.jetty.servlet.ServletContextHandler;
 import org.eclipse.jetty.servlet.ServletHolder;
 
@@ -35,19 +37,21 @@ import io.swagger.config.ScannerFactory;
 
 public class RestApiSetup {
 
-    public static ContextHandler installRestServlet(ServletContextHandler context, Object... providers) {
+    public static void installRest(ServletContextHandler context, Object... providers) {
         initSwagger();
 
         BrooklynRestApp app = new BrooklynRestApp();
         for (Object o : providers) {
             app.singleton(o);
         }
-
         CXFNonSpringJaxrsServlet servlet = new CXFNonSpringJaxrsServlet(app);
+        servlet.setBus(BusFactory.newInstance().createBus());
+        servlet.getBus().getInInterceptors().add(new GZIPInInterceptor());
+        servlet.getBus().getInFaultInterceptors().add(new GZIPInInterceptor());
+        servlet.getBus().getOutInterceptors().add(new GZIPOutInterceptor());
         final ServletHolder servletHolder = new ServletHolder(servlet);
 
         context.addServlet(servletHolder, "/v1/*");
-        return context;
     }
 
     @SafeVarargs

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server/src/main/resources/not-a-jar-file.txt
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/resources/not-a-jar-file.txt b/rest/rest-server/src/main/resources/not-a-jar-file.txt
deleted file mode 100644
index fbc22fe..0000000
--- a/rest/rest-server/src/main/resources/not-a-jar-file.txt
+++ /dev/null
@@ -1,18 +0,0 @@
-# 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.
-
-Test loading of malformed jar file
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server/src/main/resources/reset-catalog.xml
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/resources/reset-catalog.xml b/rest/rest-server/src/main/resources/reset-catalog.xml
deleted file mode 100644
index adef40a..0000000
--- a/rest/rest-server/src/main/resources/reset-catalog.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-    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.
--->
-<catalog>
-    <name>Brooklyn Demos</name>
-
-    <template type="org.apache.brooklyn.entity.stock.BasicApplication" name="Basic application" />
-    <template type="org.apache.brooklyn.test.osgi.entities.SimpleApplication" name="Simple OSGi application">
-        <libraries>
-            <bundle>${bundle-location}</bundle>
-        </libraries>
-    </template>
-    <catalog>
-        <name>Nested catalog</name>
-        <template type="org.apache.brooklyn.test.osgi.entities.SimpleApplication" name="Simple OSGi application">
-            <libraries>
-                <bundle>${bundle-location}</bundle>
-            </libraries>
-        </template>
-    </catalog>
-</catalog>

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncher.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncher.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncher.java
index 0fe421e..8d71dcb 100644
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncher.java
+++ b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncher.java
@@ -220,7 +220,7 @@ public class BrooklynRestApiLauncher {
         context.setAttribute(BrooklynServiceAttributes.BROOKLYN_MANAGEMENT_CONTEXT, managementContext);
 
         installWar(context);
-        RestApiSetup.installRestServlet(context,
+        RestApiSetup.installRest(context,
                 new ManagementContextProvider(managementContext),
                 new ShutdownHandlerProvider(shutdownListener));
         RestApiSetup.installServletFilters(context, this.filters);
@@ -246,22 +246,6 @@ public class BrooklynRestApiLauncher {
                 : createTempWebDirWithIndexHtml("Brooklyn REST API <p> (gui not available)"));
     }
 
-    private ContextHandler servletContextHandler(ManagementContext managementContext) {
-        ResourceConfig config = new DefaultResourceConfig();
-        for (Object r: BrooklynRestApi.getAllResources())
-            config.getSingletons().add(r);
-        addShutdownListener(config, mgmt);
-
-        ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
-        context.setAttribute(BrooklynServiceAttributes.BROOKLYN_MANAGEMENT_CONTEXT, managementContext);
-        ServletHolder servletHolder = new ServletHolder(new ServletContainer(config));
-        context.addServlet(servletHolder, "/*");
-        context.setContextPath("/");
-
-        installBrooklynFilters(context, this.filters);
-        return context;
-    }
-
     /** NB: not fully supported; use one of the other {@link StartMode}s */
     private ContextHandler webXmlContextHandler(ManagementContext mgmt) {
         RestApiSetup.initSwagger();
@@ -344,49 +328,6 @@ public class BrooklynRestApiLauncher {
                 .start();
     }
 
-    public void installAsServletFilter(ServletContextHandler context) {
-        installAsServletFilter(context, DEFAULT_FILTERS);
-    }
-
-    private void installAsServletFilter(ServletContextHandler context, List<Class<? extends Filter>> filters) {
-        installBrooklynFilters(context, filters);
-
-        // now set up the REST servlet resources
-        ResourceConfig config = new DefaultResourceConfig();
-        // load all our REST API modules, JSON, and Swagger
-        for (Object r: BrooklynRestApi.getAllResources())
-            config.getSingletons().add(r);
-
-        // disable caching for dynamic content
-        config.getProperties().put(ResourceConfig.PROPERTY_CONTAINER_RESPONSE_FILTERS, NoCacheFilter.class.getName());
-        // Checks if appropriate request given HA status
-        config.getProperties().put(ResourceConfig.PROPERTY_RESOURCE_FILTER_FACTORIES, org.apache.brooklyn.rest.filter.HaHotCheckResourceFilter.class.getName());
-        // configure to match empty path, or any thing which looks like a file path with /assets/ and extension html, css, js, or png
-        // and treat that as static content
-        config.getProperties().put(ServletContainer.PROPERTY_WEB_PAGE_CONTENT_REGEX, "(/?|[^?]*/assets/[^?]+\\.[A-Za-z0-9_]+)");
-        // and anything which is not matched as a servlet also falls through (but more expensive than a regex check?)
-        config.getFeatures().put(ServletContainer.FEATURE_FILTER_FORWARD_ON_404, true);
-        // finally create this as a _filter_ which falls through to a web app or something (optionally)
-        FilterHolder filterHolder = new FilterHolder(new ServletContainer(config));
-        context.addFilter(filterHolder, "/*", EnumSet.allOf(DispatcherType.class));
-
-        ManagementContext mgmt = OsgiCompat.getManagementContext(context);
-        config.getSingletons().add(new ManagementContextProvider(mgmt));
-        addShutdownListener(config, mgmt);
-    }
-
-    protected synchronized void addShutdownListener(ResourceConfig config, ManagementContext mgmt) {
-        if (shutdownListener!=null) throw new IllegalStateException("Can only retrieve one shutdown listener");
-        shutdownListener = new ServerStoppingShutdownHandler(mgmt);
-        config.getSingletons().add(new ShutdownHandlerProvider(shutdownListener));
-    }
-
-    private static void installBrooklynFilters(ServletContextHandler context, List<Class<? extends Filter>> filters) {
-        for (Class<? extends Filter> filter : filters) {
-            context.addFilter(filter, "/*", EnumSet.allOf(DispatcherType.class));
-        }
-    }
-
     /**
      * Starts the server on all nics (even if security not enabled).
      * @deprecated since 0.6.0; use {@link #launcher()} and set a custom context

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/utils/rest-swagger/src/main/java/org/apache/brooklyn/rest/apidoc/ApiListingResource.java
----------------------------------------------------------------------
diff --git a/utils/rest-swagger/src/main/java/org/apache/brooklyn/rest/apidoc/ApiListingResource.java b/utils/rest-swagger/src/main/java/org/apache/brooklyn/rest/apidoc/ApiListingResource.java
deleted file mode 100644
index 2d08329..0000000
--- a/utils/rest-swagger/src/main/java/org/apache/brooklyn/rest/apidoc/ApiListingResource.java
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright 2015 The Apache Software Foundation.
- *
- * Licensed 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.apidoc;
-
-public class ApiListingResource extends io.swagger.jaxrs.listing.ApiListingResource {
-
-}


[19/34] brooklyn-server git commit: [BROOKLYN-183] REST API using CXF JAX-RS 2.0 implementation

Posted by he...@apache.org.
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-api/src/main/webapp/WEB-INF/web.xml
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/main/webapp/WEB-INF/web.xml b/rest/rest-api/src/main/webapp/WEB-INF/web.xml
deleted file mode 100644
index bab259b..0000000
--- a/rest/rest-api/src/main/webapp/WEB-INF/web.xml
+++ /dev/null
@@ -1,121 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-    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.
--->
-<!DOCTYPE web-app PUBLIC
- "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
- "http://java.sun.com/dtd/web-app_2_3.dtd" >
-
-<web-app>
-    <display-name>Brooklyn REST API v1</display-name>
-
-    <filter>
-        <filter-name>Brooklyn Request Tagging Filter</filter-name>
-        <filter-class>org.apache.brooklyn.rest.filter.RequestTaggingFilter</filter-class>
-    </filter>
-    <filter-mapping>
-        <filter-name>Brooklyn Request Tagging Filter</filter-name>
-        <url-pattern>/*</url-pattern>
-    </filter-mapping>
-
-    <filter>
-        <filter-name>Brooklyn Properties Authentication Filter</filter-name>
-        <filter-class>org.apache.brooklyn.rest.filter.BrooklynPropertiesSecurityFilter</filter-class>
-    </filter>
-    <filter-mapping>
-        <filter-name>Brooklyn Properties Authentication Filter</filter-name>
-        <url-pattern>/*</url-pattern>
-    </filter-mapping>
-
-    <filter>
-        <filter-name>Brooklyn Logging Filter</filter-name>
-        <filter-class>org.apache.brooklyn.rest.filter.LoggingFilter</filter-class>
-    </filter>
-    <filter-mapping>
-        <filter-name>Brooklyn Logging Filter</filter-name>
-        <url-pattern>/*</url-pattern>
-    </filter-mapping>
-
-    <filter>
-        <filter-name>Brooklyn HA Master Filter</filter-name>
-        <filter-class>org.apache.brooklyn.rest.filter.HaMasterCheckFilter</filter-class>
-    </filter>
-    <filter-mapping>
-        <filter-name>Brooklyn HA Master Filter</filter-name>
-        <url-pattern>/*</url-pattern>
-    </filter-mapping>
-    <filter>
-        <filter-name>Brooklyn Swagger Bootstrap</filter-name>
-        <filter-class>org.apache.brooklyn.rest.filter.SwaggerFilter</filter-class>
-    </filter>
-    <filter-mapping>
-        <filter-name>Brooklyn Swagger Bootstrap</filter-name>
-        <url-pattern>/*</url-pattern>
-    </filter-mapping>
-
-<!-- Brooklyn REST is usually run as a filter so static content can be placed in a webapp
-     to which this is added; to run as a servlet directly, replace the filter tags 
-     below (after the comment) with the servlet tags (commented out immediately below),
-     (and do the same for the matching tags at the bottom)
-    <servlet>
-        <servlet-name>Brooklyn REST API v1 Servlet</servlet-name>
-        <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
- -->
-    <filter>
-        <filter-name>Brooklyn REST API v1 Filter</filter-name>
-        <filter-class>com.sun.jersey.spi.container.servlet.ServletContainer</filter-class>
-
-        <!-- load our REST API jersey resources -->
-        <init-param>
-            <param-name>com.sun.jersey.config.property.packages</param-name>
-            <param-value>io.swagger.jaxrs.listing;com.fasterxml.jackson.jaxrs;org.apache.brooklyn.rest.resources;org.apache.brooklyn.rest.util</param-value>
-        </init-param>
-
-        <!-- install Jackson and turn on pojo/json serialization (could add org.codehaus.jackson.jaxrs 
-             above but seems cleaner to pull in just the class -->
-        <init-param>  
-            <param-name>com.sun.jersey.config.property.classnames</param-name>
-            <param-value>com.fasterxml.jackson.jaxrs.JacksonJsonProvider</param-value>
-        </init-param>
-        <init-param>
-            <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
-            <param-value>true</param-value>
-        </init-param>
-        
-        <!-- no need for WADL. of course you can turn it back on it you want. --> 
-        <init-param>
-            <param-name>com.sun.jersey.config.feature.DisableWADL</param-name>
-            <param-value>true</param-value>
-        </init-param>
-        
-    </filter>
-    <filter-mapping>
-        <filter-name>Brooklyn REST API v1 Filter</filter-name>
-        <url-pattern>/*</url-pattern>
-    </filter-mapping>
-<!-- Brooklyn REST as a filter above; replace above 5 lines with those commented out below,
-     to run it as a servlet (see note above) 
-        <load-on-startup>1</load-on-startup>
-    </servlet>
-    <servlet-mapping>
-        <servlet-name>Brooklyn REST API v1 Servlet</servlet-name>
-        <url-pattern>/*</url-pattern>
-    </servlet-mapping>
--->
-
-</web-app>

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-api/src/test/java/org/apache/brooklyn/rest/domain/ApplicationSpecTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/test/java/org/apache/brooklyn/rest/domain/ApplicationSpecTest.java b/rest/rest-api/src/test/java/org/apache/brooklyn/rest/domain/ApplicationSpecTest.java
index b2690d6..82bbb43 100644
--- a/rest/rest-api/src/test/java/org/apache/brooklyn/rest/domain/ApplicationSpecTest.java
+++ b/rest/rest-api/src/test/java/org/apache/brooklyn/rest/domain/ApplicationSpecTest.java
@@ -33,7 +33,7 @@ public class ApplicationSpecTest extends AbstractDomainTest {
         EntitySpec entitySpec = new EntitySpec("Vanilla Java App", "org.apache.brooklyn.entity.java.VanillaJavaApp",
                 ImmutableMap.of("initialSize", "1", "creationScriptUrl", "http://my.brooklyn.io/storage/foo.sql"));
         return ApplicationSpec.builder().name("myapp")
-                .entities(ImmutableSet.of(entitySpec)).locations(ImmutableSet.of("/v1/locations/1"))
+                .entities(ImmutableSet.of(entitySpec)).locations(ImmutableSet.of("/locations/1"))
                 .build();
     }
 

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-api/src/test/java/org/apache/brooklyn/rest/domain/EffectorSummaryTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/test/java/org/apache/brooklyn/rest/domain/EffectorSummaryTest.java b/rest/rest-api/src/test/java/org/apache/brooklyn/rest/domain/EffectorSummaryTest.java
index 71cb64d..f71399c 100644
--- a/rest/rest-api/src/test/java/org/apache/brooklyn/rest/domain/EffectorSummaryTest.java
+++ b/rest/rest-api/src/test/java/org/apache/brooklyn/rest/domain/EffectorSummaryTest.java
@@ -38,7 +38,7 @@ public class EffectorSummaryTest extends AbstractDomainTest {
                 ImmutableSet.<EffectorSummary.ParameterSummary<?>>of(),
                 "Effector description",
                 ImmutableMap.of(
-                        "self", URI.create("/v1/applications/redis-app/entities/redis-ent/effectors/stop")));
+                        "self", URI.create("/applications/redis-app/entities/redis-ent/effectors/stop")));
     }
 
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-api/src/test/java/org/apache/brooklyn/rest/domain/EntitySummaryTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/test/java/org/apache/brooklyn/rest/domain/EntitySummaryTest.java b/rest/rest-api/src/test/java/org/apache/brooklyn/rest/domain/EntitySummaryTest.java
index 2d39cc9..6eb38c8 100644
--- a/rest/rest-api/src/test/java/org/apache/brooklyn/rest/domain/EntitySummaryTest.java
+++ b/rest/rest-api/src/test/java/org/apache/brooklyn/rest/domain/EntitySummaryTest.java
@@ -33,10 +33,10 @@ public class EntitySummaryTest extends AbstractDomainTest {
     @Override
     protected Object getDomainObject() {
         Map<String, URI> links = Maps.newLinkedHashMap();
-        links.put("self", URI.create("/v1/applications/tesr/entities/zQsqdXzi"));
-        links.put("catalog", URI.create("/v1/catalog/entities/org.apache.brooklyn.entity.webapp.tomcat.TomcatServer"));
-        links.put("application", URI.create("/v1/applications/tesr"));
-        links.put("children", URI.create("/v1/applications/tesr/entities/zQsqdXzi/children"));
+        links.put("self", URI.create("/applications/tesr/entities/zQsqdXzi"));
+        links.put("catalog", URI.create("/catalog/entities/org.apache.brooklyn.entity.webapp.tomcat.TomcatServer"));
+        links.put("application", URI.create("/applications/tesr"));
+        links.put("children", URI.create("/applications/tesr/entities/zQsqdXzi/children"));
         links.put("effectors", URI.create("fixtures/effector-summary-list.json"));
         links.put("sensors", URI.create("fixtures/sensor-summary-list.json"));
         links.put("activities", URI.create("fixtures/task-summary-list.json"));

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-api/src/test/java/org/apache/brooklyn/rest/domain/LocationSummaryTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/test/java/org/apache/brooklyn/rest/domain/LocationSummaryTest.java b/rest/rest-api/src/test/java/org/apache/brooklyn/rest/domain/LocationSummaryTest.java
index 6279546..8d40e75 100644
--- a/rest/rest-api/src/test/java/org/apache/brooklyn/rest/domain/LocationSummaryTest.java
+++ b/rest/rest-api/src/test/java/org/apache/brooklyn/rest/domain/LocationSummaryTest.java
@@ -33,7 +33,7 @@ public class LocationSummaryTest extends AbstractDomainTest {
     @Override
     protected Object getDomainObject() {
         Map<String, URI> links = Maps.newLinkedHashMap();
-        links.put("self", URI.create("/v1/locations/123"));
+        links.put("self", URI.create("/locations/123"));
 
         return new LocationSummary("123", "localhost", "localhost", null, null, links);
     }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-api/src/test/resources/fixtures/application-list.json
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/test/resources/fixtures/application-list.json b/rest/rest-api/src/test/resources/fixtures/application-list.json
index 029cf68..a4e7980 100644
--- a/rest/rest-api/src/test/resources/fixtures/application-list.json
+++ b/rest/rest-api/src/test/resources/fixtures/application-list.json
@@ -10,12 +10,12 @@
                 }
             ],
             "locations":[
-                "/v1/locations/0"
+                "/locations/0"
             ]
         },
         "status":"STARTING",
         "links":{
-            "self":"/v1/applications/tesr",
+            "self":"/applications/tesr",
             "entities":"fixtures/entity-summary-list.json"
         }
     },
@@ -32,11 +32,11 @@
                     }
                 }
             ],
-            "locations":["/v1/locations/1"]
+            "locations":["/locations/1"]
         },
         "status":"STARTING",
         "links":{
-            "self":"/v1/applications/myapp",
+            "self":"/applications/myapp",
             "entities":"fixtures/entity-summary-list.json"
         }
     }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-api/src/test/resources/fixtures/application-spec.json
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/test/resources/fixtures/application-spec.json b/rest/rest-api/src/test/resources/fixtures/application-spec.json
index 9f042c6..48e9e5f 100644
--- a/rest/rest-api/src/test/resources/fixtures/application-spec.json
+++ b/rest/rest-api/src/test/resources/fixtures/application-spec.json
@@ -11,6 +11,6 @@
         }
     ],
     "locations":[
-        "/v1/locations/1"
+        "/locations/1"
     ]
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-api/src/test/resources/fixtures/application.json
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/test/resources/fixtures/application.json b/rest/rest-api/src/test/resources/fixtures/application.json
index cd6a21a..c7b9aed 100644
--- a/rest/rest-api/src/test/resources/fixtures/application.json
+++ b/rest/rest-api/src/test/resources/fixtures/application.json
@@ -12,11 +12,11 @@
                 }
             }
         ],
-        "locations":["/v1/locations/1"]
+        "locations":["/locations/1"]
     },
     "status":"STARTING",
     "links":{
-        "self":"/v1/applications/myapp",
+        "self":"/applications/myapp",
         "entities":"fixtures/entity-summary-list.json"
     }
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-api/src/test/resources/fixtures/effector-summary-list.json
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/test/resources/fixtures/effector-summary-list.json b/rest/rest-api/src/test/resources/fixtures/effector-summary-list.json
index fe2828c..2b7e7c5 100644
--- a/rest/rest-api/src/test/resources/fixtures/effector-summary-list.json
+++ b/rest/rest-api/src/test/resources/fixtures/effector-summary-list.json
@@ -17,9 +17,9 @@
             }
         ],
         "links":{
-            "self":"/v1/applications/tesr/entities/ZgoDhGQA/effectors/start",
-            "entity":"/v1/applications/tesr/entities/ZgoDhGQA",
-            "application":"/v1/applications/tesr"
+            "self":"/applications/tesr/entities/ZgoDhGQA/effectors/start",
+            "entity":"/applications/tesr/entities/ZgoDhGQA",
+            "application":"/applications/tesr"
         }
     },
     {
@@ -28,9 +28,9 @@
         "returnType":"void",
         "parameters":[ ],
         "links":{
-            "self":"/v1/applications/tesr/entities/ZgoDhGQA/effectors/restart",
-            "entity":"/v1/applications/tesr/entities/ZgoDhGQA",
-            "application":"/v1/applications/tesr"
+            "self":"/applications/tesr/entities/ZgoDhGQA/effectors/restart",
+            "entity":"/applications/tesr/entities/ZgoDhGQA",
+            "application":"/applications/tesr"
         }
     },
     {
@@ -39,9 +39,9 @@
         "returnType":"void",
         "parameters":[ ],
         "links":{
-            "self":"/v1/applications/tesr/entities/ZgoDhGQA/effectors/stop",
-            "entity":"/v1/applications/tesr/entities/ZgoDhGQA",
-            "application":"/v1/applications/tesr"
+            "self":"/applications/tesr/entities/ZgoDhGQA/effectors/stop",
+            "entity":"/applications/tesr/entities/ZgoDhGQA",
+            "application":"/applications/tesr"
         }
     }
 ]
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-api/src/test/resources/fixtures/effector-summary.json
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/test/resources/fixtures/effector-summary.json b/rest/rest-api/src/test/resources/fixtures/effector-summary.json
index eda97dd..1ba826d 100644
--- a/rest/rest-api/src/test/resources/fixtures/effector-summary.json
+++ b/rest/rest-api/src/test/resources/fixtures/effector-summary.json
@@ -4,6 +4,6 @@
     "parameters":[],
     "description":"Effector description",
     "links":{
-        "self":"/v1/applications/redis-app/entities/redis-ent/effectors/stop"
+        "self":"/applications/redis-app/entities/redis-ent/effectors/stop"
     }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-api/src/test/resources/fixtures/entity-summary-list.json
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/test/resources/fixtures/entity-summary-list.json b/rest/rest-api/src/test/resources/fixtures/entity-summary-list.json
index 6e2dc4e..bf71c36 100644
--- a/rest/rest-api/src/test/resources/fixtures/entity-summary-list.json
+++ b/rest/rest-api/src/test/resources/fixtures/entity-summary-list.json
@@ -2,10 +2,10 @@
     {
         "type":"org.apache.brooklyn.entity.webapp.tomcat.TomcatServer",
         "links":{
-            "self":"/v1/applications/tesr/entities/zQsqdXzi",
-            "catalog":"/v1/catalog/entities/org.apache.brooklyn.entity.webapp.tomcat.TomcatServer",
-            "application":"/v1/applications/tesr",
-            "children":"/v1/applications/tesr/entities/zQsqdXzi/entities",
+            "self":"/applications/tesr/entities/zQsqdXzi",
+            "catalog":"/catalog/entities/org.apache.brooklyn.entity.webapp.tomcat.TomcatServer",
+            "application":"/applications/tesr",
+            "children":"/applications/tesr/entities/zQsqdXzi/entities",
             "effectors":"fixtures/effector-summary-list.json",
             "sensors":"fixtures/sensor-summary-list.json",
             "activities":"fixtures/task-summary-list.json"

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-api/src/test/resources/fixtures/entity-summary.json
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/test/resources/fixtures/entity-summary.json b/rest/rest-api/src/test/resources/fixtures/entity-summary.json
index 05d2e8f..fe0343c 100644
--- a/rest/rest-api/src/test/resources/fixtures/entity-summary.json
+++ b/rest/rest-api/src/test/resources/fixtures/entity-summary.json
@@ -2,10 +2,10 @@
  "name":"MyTomcat",
  "type":"org.apache.brooklyn.entity.webapp.tomcat.TomcatServer",
  "links":{
-   "self":"/v1/applications/tesr/entities/zQsqdXzi",
-   "catalog":"/v1/catalog/entities/org.apache.brooklyn.entity.webapp.tomcat.TomcatServer",
-   "application":"/v1/applications/tesr",
-   "children":"/v1/applications/tesr/entities/zQsqdXzi/children",
+   "self":"/applications/tesr/entities/zQsqdXzi",
+   "catalog":"/catalog/entities/org.apache.brooklyn.entity.webapp.tomcat.TomcatServer",
+   "application":"/applications/tesr",
+   "children":"/applications/tesr/entities/zQsqdXzi/children",
    "effectors":"fixtures/effector-summary-list.json",
    "sensors":"fixtures/sensor-summary-list.json",
    "activities":"fixtures/task-summary-list.json"

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-api/src/test/resources/fixtures/location-list.json
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/test/resources/fixtures/location-list.json b/rest/rest-api/src/test/resources/fixtures/location-list.json
index c4e05de..6faa0b9 100644
--- a/rest/rest-api/src/test/resources/fixtures/location-list.json
+++ b/rest/rest-api/src/test/resources/fixtures/location-list.json
@@ -4,7 +4,7 @@
         "name":"localhost",
         "spec":"localhost",
         "links":{
-            "self":"/v1/locations/123"
+            "self":"/locations/123"
         }
     }
 ]
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-api/src/test/resources/fixtures/location-summary.json
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/test/resources/fixtures/location-summary.json b/rest/rest-api/src/test/resources/fixtures/location-summary.json
index d1d0573..57749aa 100644
--- a/rest/rest-api/src/test/resources/fixtures/location-summary.json
+++ b/rest/rest-api/src/test/resources/fixtures/location-summary.json
@@ -3,6 +3,6 @@
     "name":"localhost",
     "spec":"localhost",
     "links":{
-        "self":"/v1/locations/123"
+        "self":"/locations/123"
     }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-api/src/test/resources/fixtures/sensor-summary-list.json
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/test/resources/fixtures/sensor-summary-list.json b/rest/rest-api/src/test/resources/fixtures/sensor-summary-list.json
index 6ab457b..10d7ee2 100644
--- a/rest/rest-api/src/test/resources/fixtures/sensor-summary-list.json
+++ b/rest/rest-api/src/test/resources/fixtures/sensor-summary-list.json
@@ -5,8 +5,8 @@
         "description":"Service lifecycle state",
         "links":{
             "self":"fixtures/service-state.json",
-            "application":"/v1/applications/tesr",
-            "entity":"/v1/applications/tesr/entities/zQsqdXzi"
+            "application":"/applications/tesr",
+            "entity":"/applications/tesr/entities/zQsqdXzi"
         }
     },
     {
@@ -15,8 +15,8 @@
         "description":"JMX context path",
         "links":{
             "self":"fixtures/service-state.json",
-            "application":"/v1/applications/tesr",
-            "entity":"/v1/applications/tesr/entities/zQsqdXzi"
+            "application":"/applications/tesr",
+            "entity":"/applications/tesr/entities/zQsqdXzi"
         }
     },
     {
@@ -25,8 +25,8 @@
         "description":"Suggested shutdown port",
         "links":{
             "self":"fixtures/service-state.json",
-            "application":"/v1/applications/tesr",
-            "entity":"/v1/applications/tesr/entities/zQsqdXzi"
+            "application":"/applications/tesr",
+            "entity":"/applications/tesr/entities/zQsqdXzi"
         }
     },
     {
@@ -35,8 +35,8 @@
         "description":"Service has been started successfully and is running",
         "links":{
             "self":"fixtures/service-state.json",
-            "application":"/v1/applications/tesr",
-            "entity":"/v1/applications/tesr/entities/zQsqdXzi"
+            "application":"/applications/tesr",
+            "entity":"/applications/tesr/entities/zQsqdXzi"
         }
     }
 ]
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-api/src/test/resources/fixtures/sensor-summary.json
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/test/resources/fixtures/sensor-summary.json b/rest/rest-api/src/test/resources/fixtures/sensor-summary.json
index 0803b37..0ab6c8e 100644
--- a/rest/rest-api/src/test/resources/fixtures/sensor-summary.json
+++ b/rest/rest-api/src/test/resources/fixtures/sensor-summary.json
@@ -3,6 +3,6 @@
     "type":"Integer",
     "description":"Description",
     "links":{
-        "self":"/v1/applications/redis-app/entities/redis-ent/sensors/redis.uptime"
+        "self":"/applications/redis-app/entities/redis-ent/sensors/redis.uptime"
     }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-client/pom.xml
----------------------------------------------------------------------
diff --git a/rest/rest-client/pom.xml b/rest/rest-client/pom.xml
index d133518..afb9d65 100644
--- a/rest/rest-client/pom.xml
+++ b/rest/rest-client/pom.xml
@@ -133,6 +133,13 @@
         </dependency>
         <dependency>
             <groupId>org.apache.brooklyn</groupId>
+            <artifactId>brooklyn-rest-resources</artifactId>
+            <version>${project.version}</version>
+            <classifier>tests</classifier>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.brooklyn</groupId>
             <artifactId>brooklyn-rest-server</artifactId>
             <version>${project.version}</version>
             <classifier>tests</classifier>

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-client/src/test/java/org/apache/brooklyn/rest/client/BrooklynApiRestClientTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-client/src/test/java/org/apache/brooklyn/rest/client/BrooklynApiRestClientTest.java b/rest/rest-client/src/test/java/org/apache/brooklyn/rest/client/BrooklynApiRestClientTest.java
index da3966f..adbdeda 100644
--- a/rest/rest-client/src/test/java/org/apache/brooklyn/rest/client/BrooklynApiRestClientTest.java
+++ b/rest/rest-client/src/test/java/org/apache/brooklyn/rest/client/BrooklynApiRestClientTest.java
@@ -18,7 +18,6 @@
  */
 package org.apache.brooklyn.rest.client;
 
-import java.net.URISyntaxException;
 import java.net.URL;
 import java.nio.charset.Charset;
 import java.nio.file.Files;
@@ -76,7 +75,7 @@ public class BrooklynApiRestClientTest {
                 .securityProvider(TestSecurityProvider.class)
                 .start();
 
-        api = BrooklynApi.newInstance("http://localhost:" + ((NetworkConnector)server.getConnectors()[0]).getPort() + "/",
+        api = BrooklynApi.newInstance("http://localhost:" + ((NetworkConnector)server.getConnectors()[0]).getPort() + "/v1",
                 TestSecurityProvider.USER, TestSecurityProvider.PASSWORD);
     }
 

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/pom.xml
----------------------------------------------------------------------
diff --git a/rest/rest-resources/pom.xml b/rest/rest-resources/pom.xml
new file mode 100644
index 0000000..2f171f8
--- /dev/null
+++ b/rest/rest-resources/pom.xml
@@ -0,0 +1,220 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>brooklyn-rest-resources</artifactId>
+    <packaging>jar</packaging>
+    <name>Brooklyn REST Resources</name>
+    <description>
+        Brooklyn REST Endpoint (CXF)
+    </description>
+
+    <parent>
+        <groupId>org.apache.brooklyn</groupId>
+        <artifactId>brooklyn-parent</artifactId>
+        <version>0.9.0-SNAPSHOT</version><!-- BROOKLYN_VERSION -->
+        <relativePath>../../parent/pom.xml</relativePath>
+    </parent>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.brooklyn</groupId>
+            <artifactId>brooklyn-rest-api</artifactId>
+            <version>${project.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>javax.ws.rs</groupId>
+                    <artifactId>jsr311-api</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.brooklyn</groupId>
+            <artifactId>brooklyn-camp</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.brooklyn.camp</groupId>
+            <artifactId>camp-base</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.brooklyn</groupId>
+            <artifactId>brooklyn-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.brooklyn</groupId>
+            <artifactId>brooklyn-utils-common</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.brooklyn</groupId>
+            <artifactId>brooklyn-software-base</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.brooklyn</groupId>
+            <artifactId>brooklyn-utils-rest-swagger</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>javax.ws.rs</groupId>
+            <artifactId>javax.ws.rs-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cxf</groupId>
+            <artifactId>cxf-rt-frontend-jaxrs</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.yaml</groupId>
+            <artifactId>snakeyaml</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.reflections</groupId>
+            <artifactId>reflections</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.code.findbugs</groupId>
+            <artifactId>jsr305</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.jaxrs</groupId>
+            <artifactId>jackson-jaxrs-json-provider</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.brooklyn</groupId>
+            <artifactId>brooklyn-test-support</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.brooklyn</groupId>
+            <artifactId>brooklyn-policy</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.brooklyn</groupId>
+            <artifactId>brooklyn-core</artifactId>
+            <version>${project.version}</version>
+            <classifier>tests</classifier>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.brooklyn</groupId>
+            <artifactId>brooklyn-software-base</artifactId>
+            <version>${project.version}</version>
+            <classifier>tests</classifier>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.brooklyn</groupId>
+            <artifactId>brooklyn-rest-api</artifactId>
+            <version>${project.version}</version>
+            <classifier>tests</classifier>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.brooklyn</groupId>
+            <artifactId>brooklyn-locations-jclouds</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>javax.ws.rs</groupId>
+                    <artifactId>jsr311-api</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.brooklyn</groupId>
+            <artifactId>brooklyn-software-nosql</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.brooklyn</groupId>
+            <artifactId>brooklyn-software-webapp</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.brooklyn</groupId>
+            <artifactId>brooklyn-software-database</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>hamcrest-all</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.testng</groupId>
+            <artifactId>testng</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cxf</groupId>
+            <artifactId>cxf-rt-transports-local</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cxf</groupId>
+            <artifactId>cxf-rt-transports-http-jetty</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cxf</groupId>
+            <artifactId>cxf-rt-rs-client</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.brooklyn</groupId>
+            <artifactId>brooklyn-rt-osgi</artifactId>
+            <version>${project.version}</version>
+            <classifier>tests</classifier>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <resources>
+            <resource>
+                <directory>${basedir}/src/main/resources</directory>
+                <!-- Required to set values in build-metadata.properties -->
+                <filtering>true</filtering>
+            </resource>
+            <resource>
+                <directory>${basedir}/src/main/webapp</directory>
+            </resource>
+        </resources>
+    </build>
+</project>

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/BrooklynRestApi.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/BrooklynRestApi.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/BrooklynRestApi.java
new file mode 100644
index 0000000..f42548f
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/BrooklynRestApi.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;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.brooklyn.rest.resources.AbstractBrooklynRestResource;
+import org.apache.brooklyn.rest.resources.AccessResource;
+import org.apache.brooklyn.rest.resources.ActivityResource;
+import org.apache.brooklyn.rest.resources.ApidocResource;
+import org.apache.brooklyn.rest.resources.ApplicationResource;
+import org.apache.brooklyn.rest.resources.CatalogResource;
+import org.apache.brooklyn.rest.resources.EffectorResource;
+import org.apache.brooklyn.rest.resources.EntityConfigResource;
+import org.apache.brooklyn.rest.resources.EntityResource;
+import org.apache.brooklyn.rest.resources.LocationResource;
+import org.apache.brooklyn.rest.resources.PolicyConfigResource;
+import org.apache.brooklyn.rest.resources.PolicyResource;
+import org.apache.brooklyn.rest.resources.ScriptResource;
+import org.apache.brooklyn.rest.resources.SensorResource;
+import org.apache.brooklyn.rest.resources.ServerResource;
+import org.apache.brooklyn.rest.resources.UsageResource;
+import org.apache.brooklyn.rest.resources.VersionResource;
+import org.apache.brooklyn.rest.util.DefaultExceptionMapper;
+import org.apache.brooklyn.rest.util.FormMapProvider;
+import org.apache.brooklyn.rest.util.json.BrooklynJacksonJsonProvider;
+
+import com.google.common.collect.Iterables;
+
+import io.swagger.jaxrs.listing.SwaggerSerializers;
+
+
+@SuppressWarnings("deprecation")
+public class BrooklynRestApi {
+
+    public static Iterable<AbstractBrooklynRestResource> getBrooklynRestResources() {
+        List<AbstractBrooklynRestResource> resources = new ArrayList<>();
+        resources.add(new LocationResource());
+        resources.add(new CatalogResource());
+        resources.add(new ApplicationResource());
+        resources.add(new EntityResource());
+        resources.add(new EntityConfigResource());
+        resources.add(new SensorResource());
+        resources.add(new EffectorResource());
+        resources.add(new PolicyResource());
+        resources.add(new PolicyConfigResource());
+        resources.add(new ActivityResource());
+        resources.add(new AccessResource());
+        resources.add(new ScriptResource());
+        resources.add(new ServerResource());
+        resources.add(new UsageResource());
+        resources.add(new VersionResource());
+        return resources;
+    }
+
+    public static Iterable<Object> getApidocResources() {
+        List<Object> resources = new ArrayList<Object>();
+        resources.add(new SwaggerSerializers());
+        resources.add(new ApidocResource());
+        return resources;
+    }
+
+    public static Iterable<Object> getMiscResources() {
+        List<Object> resources = new ArrayList<Object>();
+        resources.add(new DefaultExceptionMapper());
+        resources.add(new BrooklynJacksonJsonProvider());
+        resources.add(new FormMapProvider());
+        return resources;
+    }
+
+    public static Iterable<Object> getAllResources() {
+        return Iterables.concat(getBrooklynRestResources(), getApidocResources(), getMiscResources());
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/BrooklynRestApp.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/BrooklynRestApp.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/BrooklynRestApp.java
new file mode 100644
index 0000000..419715b
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/BrooklynRestApp.java
@@ -0,0 +1,56 @@
+/*
+ * 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;
+
+import java.util.Map;
+import java.util.Set;
+
+import javax.ws.rs.core.Application;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Sets;
+
+public class BrooklynRestApp extends Application {
+    private Set<Object> singletons;
+
+    public BrooklynRestApp() {
+        singletons = Sets.newHashSet(BrooklynRestApi.getAllResources());
+    }
+
+    public BrooklynRestApp singleton(Object singleton) {
+        singletons.add(singleton);
+        return this;
+    }
+
+    @Override
+    public Set<Object> getSingletons() {
+        return singletons;
+    }
+
+    //Uncomment after removing jersey dependencies
+    //@Override
+    public Map<String, Object> getProperties() {
+        return ImmutableMap.<String, Object>of(
+                // Makes sure that all exceptions are handled by our custom DefaultExceptionMapper
+                "default.wae.mapper.least.specific", Boolean.TRUE);
+    }
+
+}
+
+

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/BrooklynWebConfig.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/BrooklynWebConfig.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/BrooklynWebConfig.java
new file mode 100644
index 0000000..69342a0
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/BrooklynWebConfig.java
@@ -0,0 +1,164 @@
+/*
+ * 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;
+
+import org.apache.brooklyn.api.location.PortRange;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.config.ConfigMap;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.config.ConfigPredicates;
+import org.apache.brooklyn.rest.security.provider.DelegatingSecurityProvider;
+import org.apache.brooklyn.rest.security.provider.ExplicitUsersSecurityProvider;
+import org.apache.brooklyn.rest.security.provider.SecurityProvider;
+
+public class BrooklynWebConfig {
+
+    public final static String BASE_NAME = "brooklyn.webconsole";
+    public final static String BASE_NAME_SECURITY = BASE_NAME+".security";
+
+    /**
+     * The session attribute set to indicate the remote address of the HTTP request.
+     * Corresponds to {@link javax.servlet.http.HttpServletRequest#getRemoteAddr()}.
+     */
+    public static final String REMOTE_ADDRESS_SESSION_ATTRIBUTE = "request.remoteAddress";
+
+    /**
+     * The security provider to be loaded by {@link DelegatingSecurityProvider}.
+     * e.g. <code>brooklyn.webconsole.security.provider=org.apache.brooklyn.rest.security.provider.AnyoneSecurityProvider</code>
+     * will allow anyone to log in.
+     */
+    public final static ConfigKey<String> SECURITY_PROVIDER_CLASSNAME = ConfigKeys.newStringConfigKey(
+            BASE_NAME_SECURITY+".provider", "class name of a Brooklyn SecurityProvider",
+            ExplicitUsersSecurityProvider.class.getCanonicalName());
+    public final static ConfigKey<SecurityProvider> SECURITY_PROVIDER_INSTANCE = ConfigKeys.newConfigKey(SecurityProvider.class,
+            SECURITY_PROVIDER_CLASSNAME.getName()+".internal.instance", "instance of a pre-configured security provider");
+    
+    /**
+     * Explicitly set the users/passwords, e.g. in brooklyn.properties:
+     * brooklyn.webconsole.security.users=admin,bob
+     * brooklyn.webconsole.security.user.admin.password=password
+     * brooklyn.webconsole.security.user.bob.password=bobspass
+     */
+    public final static ConfigKey<String> USERS = ConfigKeys.newStringConfigKey(BASE_NAME_SECURITY+".users");
+
+    public final static ConfigKey<String> PASSWORD_FOR_USER(String user) {
+        return ConfigKeys.newStringConfigKey(BASE_NAME_SECURITY + ".user." + user + ".password");
+    }
+    
+    public final static ConfigKey<String> SALT_FOR_USER(String user) {
+        return ConfigKeys.newStringConfigKey(BASE_NAME_SECURITY + ".user." + user + ".salt");
+    }
+    
+    public final static ConfigKey<String> SHA256_FOR_USER(String user) {
+        return ConfigKeys.newStringConfigKey(BASE_NAME_SECURITY + ".user." + user + ".sha256");
+    }
+    
+    public final static ConfigKey<String> LDAP_URL = ConfigKeys.newStringConfigKey(
+            BASE_NAME_SECURITY+".ldap.url");
+
+    public final static ConfigKey<String> LDAP_REALM = ConfigKeys.newStringConfigKey(
+            BASE_NAME_SECURITY+".ldap.realm");
+
+    public final static ConfigKey<String> LDAP_OU = ConfigKeys.newStringConfigKey(
+            BASE_NAME_SECURITY+".ldap.ou");
+
+    public final static ConfigKey<Boolean> HTTPS_REQUIRED = ConfigKeys.newBooleanConfigKey(
+            BASE_NAME+".security.https.required",
+            "Whether HTTPS is required; false here can be overridden by CLI option", false); 
+
+    public final static ConfigKey<PortRange> WEB_CONSOLE_PORT = ConfigKeys.newConfigKey(PortRange.class,
+        BASE_NAME+".port",
+        "Port/range for the web console to listen on; can be overridden by CLI option");
+
+    public final static ConfigKey<String> KEYSTORE_URL = ConfigKeys.newStringConfigKey(
+            BASE_NAME+".security.keystore.url",
+            "Keystore from which to take the certificate to present when running HTTPS; "
+            + "note that normally the password is also required, and an alias for the certificate if the keystore has more than one");
+
+    public final static ConfigKey<String> KEYSTORE_PASSWORD = ConfigKeys.newStringConfigKey(
+            BASE_NAME+".security.keystore.password",
+            "Password for the "+KEYSTORE_URL);
+
+    public final static ConfigKey<String> KEYSTORE_CERTIFICATE_ALIAS = ConfigKeys.newStringConfigKey(
+            BASE_NAME+".security.keystore.certificate.alias",
+            "Alias in "+KEYSTORE_URL+" for the certificate to use; defaults to the first if not supplied");
+
+    public final static ConfigKey<String> TRANSPORT_PROTOCOLS = ConfigKeys.newStringConfigKey(
+            BASE_NAME+".security.transport.protocols",
+            "SSL/TLS protocol versions to use for web console connections",
+            "TLSv1, TLSv1.1, TLSv1.2");
+
+    // https://wiki.mozilla.org/Security/Server_Side_TLS (v3.4)
+    // http://stackoverflow.com/questions/19846020/how-to-map-a-openssls-cipher-list-to-java-jsse
+    // list created on 05.05.2015, Intermediate config from first link
+    public final static ConfigKey<String> TRANSPORT_CIPHERS = ConfigKeys.newStringConfigKey(
+            BASE_NAME+".security.transport.ciphers",
+            "SSL/TLS cipher suites to use for web console connections",
+            "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256," +
+            "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384," +
+            "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,TLS_DHE_DSS_WITH_AES_128_GCM_SHA256," +
+            "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384,TLS_DHE_RSA_WITH_AES_256_GCM_SHA384," +
+            "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256," +
+            "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA," +
+            "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384," +
+            "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA," +
+            "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,TLS_DHE_RSA_WITH_AES_128_CBC_SHA," +
+            "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256,TLS_DHE_RSA_WITH_AES_256_CBC_SHA256," +
+            "TLS_DHE_DSS_WITH_AES_256_CBC_SHA,TLS_DHE_RSA_WITH_AES_256_CBC_SHA," +
+            "TLS_RSA_WITH_AES_128_GCM_SHA256,TLS_RSA_WITH_AES_256_GCM_SHA384," +
+            "TLS_RSA_WITH_AES_128_CBC_SHA256,TLS_RSA_WITH_AES_256_CBC_SHA256," +
+            "TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA," +
+            "TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA,TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA," +
+            "TLS_SRP_SHA_WITH_AES_256_CBC_SHA,TLS_DHE_DSS_WITH_AES_256_CBC_SHA256," +
+            "TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA,TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA," +
+            "TLS_SRP_SHA_WITH_AES_128_CBC_SHA,TLS_DHE_DSS_WITH_AES_128_CBC_SHA," +
+            "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA,TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA," +
+            "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA,TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA," +
+            "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA,TLS_RSA_WITH_CAMELLIA_128_CBC_SHA," +
+            "TLS_RSA_WITH_3DES_EDE_CBC_SHA," +
+            // Same as above but with SSL_ prefix, IBM Java compatibility (cipher is independent of protocol)
+            // https://www-01.ibm.com/support/knowledgecenter/SSYKE2_7.0.0/com.ibm.java.security.component.70.doc/security-component/jsse2Docs/ciphersuites.html
+            "SSL_ECDHE_RSA_WITH_AES_128_GCM_SHA256,SSL_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256," +
+            "SSL_ECDHE_RSA_WITH_AES_256_GCM_SHA384,SSL_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384," +
+            "SSL_DHE_RSA_WITH_AES_128_GCM_SHA256,SSL_DHE_DSS_WITH_AES_128_GCM_SHA256," +
+            "SSL_DHE_DSS_WITH_AES_256_GCM_SHA384,SSL_DHE_RSA_WITH_AES_256_GCM_SHA384," +
+            "SSL_ECDHE_RSA_WITH_AES_128_CBC_SHA256,SSL_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256," +
+            "SSL_ECDHE_RSA_WITH_AES_128_CBC_SHA,SSL_ECDHE_ECDSA_WITH_AES_128_CBC_SHA," +
+            "SSL_ECDHE_RSA_WITH_AES_256_CBC_SHA384,SSL_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384," +
+            "SSL_ECDHE_RSA_WITH_AES_256_CBC_SHA,SSL_ECDHE_ECDSA_WITH_AES_256_CBC_SHA," +
+            "SSL_DHE_RSA_WITH_AES_128_CBC_SHA256,SSL_DHE_RSA_WITH_AES_128_CBC_SHA," +
+            "SSL_DHE_DSS_WITH_AES_128_CBC_SHA256,SSL_DHE_RSA_WITH_AES_256_CBC_SHA256," +
+            "SSL_DHE_DSS_WITH_AES_256_CBC_SHA,SSL_DHE_RSA_WITH_AES_256_CBC_SHA," +
+            "SSL_RSA_WITH_AES_128_GCM_SHA256,SSL_RSA_WITH_AES_256_GCM_SHA384," +
+            "SSL_RSA_WITH_AES_128_CBC_SHA256,SSL_RSA_WITH_AES_256_CBC_SHA256," +
+            "SSL_RSA_WITH_AES_128_CBC_SHA,SSL_RSA_WITH_AES_256_CBC_SHA," +
+            "SSL_SRP_SHA_DSS_WITH_AES_256_CBC_SHA,SSL_SRP_SHA_RSA_WITH_AES_256_CBC_SHA," +
+            "SSL_SRP_SHA_WITH_AES_256_CBC_SHA,SSL_DHE_DSS_WITH_AES_256_CBC_SHA256," +
+            "SSL_SRP_SHA_DSS_WITH_AES_128_CBC_SHA,SSL_SRP_SHA_RSA_WITH_AES_128_CBC_SHA," +
+            "SSL_SRP_SHA_WITH_AES_128_CBC_SHA,SSL_DHE_DSS_WITH_AES_128_CBC_SHA," +
+            "SSL_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA,SSL_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA," +
+            "SSL_RSA_WITH_CAMELLIA_256_CBC_SHA,SSL_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA," +
+            "SSL_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA,SSL_RSA_WITH_CAMELLIA_128_CBC_SHA," +
+            "SSL_RSA_WITH_3DES_EDE_CBC_SHA");
+
+    public final static boolean hasNoSecurityOptions(ConfigMap config) {
+        return config.submap(ConfigPredicates.nameStartsWith(BASE_NAME_SECURITY)).isEmpty();
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/HaHotCheckResourceFilter.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/HaHotCheckResourceFilter.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/HaHotCheckResourceFilter.java
new file mode 100644
index 0000000..13c5cbf
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/HaHotCheckResourceFilter.java
@@ -0,0 +1,159 @@
+/*
+ * 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.filter;
+
+import java.io.IOException;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.Set;
+
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerRequestFilter;
+import javax.ws.rs.container.ResourceInfo;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ContextResolver;
+import javax.ws.rs.ext.Provider;
+
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.api.mgmt.ha.ManagementNodeState;
+import org.apache.brooklyn.rest.domain.ApiError;
+import org.apache.brooklyn.util.text.Strings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableSet;
+
+/** 
+ * Checks that if the method or resource class corresponding to a request
+ * has a {@link HaHotStateRequired} annotation,
+ * that the server is in that state (and up). 
+ * Requests with {@link #SKIP_CHECK_HEADER} set as a header skip this check.
+ * <p>
+ * This follows a different pattern to {@link HaMasterCheckFilter} 
+ * as this needs to know the method being invoked. 
+ */
+@Provider
+public class HaHotCheckResourceFilter implements ContainerRequestFilter {
+    public static final String SKIP_CHECK_HEADER = "Brooklyn-Allow-Non-Master-Access";
+    
+    private static final Logger log = LoggerFactory.getLogger(HaHotCheckResourceFilter.class);
+    
+    private static final Set<ManagementNodeState> HOT_STATES = ImmutableSet.of(
+            ManagementNodeState.MASTER, ManagementNodeState.HOT_STANDBY, ManagementNodeState.HOT_BACKUP);
+
+    // Not quite standards compliant. Should instead be:
+    // @Context Providers providers
+    // ....
+    // ContextResolver<ManagementContext> resolver = providers.getContextResolver(ManagementContext.class, MediaType.WILDCARD_TYPE)
+    // ManagementContext engine = resolver.get(ManagementContext.class);
+    @Context
+    private ContextResolver<ManagementContext> mgmt;
+
+    @Context
+    private ResourceInfo resourceInfo;
+    
+    public HaHotCheckResourceFilter() {
+    }
+
+    @VisibleForTesting
+    public HaHotCheckResourceFilter(ContextResolver<ManagementContext> mgmt) {
+        this.mgmt = mgmt;
+    }
+
+    private ManagementContext mgmt() {
+        return mgmt.getContext(ManagementContext.class);
+    }
+
+    @Override
+    public void filter(ContainerRequestContext requestContext) throws IOException {
+        String problem = lookForProblem(requestContext);
+        if (Strings.isNonBlank(problem)) {
+            log.warn("Disallowing web request as "+problem+": "+requestContext.getUriInfo()+"/"+resourceInfo.getResourceMethod()+" (caller should set '"+SKIP_CHECK_HEADER+"' to force)");
+            requestContext.abortWith(ApiError.builder()
+                .message("This request is only permitted against an active hot Brooklyn server")
+                .errorCode(Response.Status.FORBIDDEN).build().asJsonResponse());
+        }
+    }
+
+    private String lookForProblem(ContainerRequestContext requestContext) {
+        if (isSkipCheckHeaderSet(requestContext)) 
+            return null;
+        
+        if (!isHaHotStateRequired())
+            return null;
+        
+        String problem = lookForProblemIfServerNotRunning(mgmt());
+        if (Strings.isNonBlank(problem)) 
+            return problem;
+        
+        if (!isHaHotStatus())
+            return "server not in required HA hot state";
+        if (isStateNotYetValid())
+            return "server not yet completed loading data for required HA hot state";
+        
+        return null;
+    }
+    
+    public static String lookForProblemIfServerNotRunning(ManagementContext mgmt) {
+        if (mgmt==null) return "no management context available";
+        if (!mgmt.isRunning()) return "server no longer running";
+        if (!mgmt.isStartupComplete()) return "server not in required startup-completed state";
+        return null;
+    }
+    
+    // Maybe there should be a separate state to indicate that we have switched state
+    // but still haven't finished rebinding. (Previously there was a time delay and an
+    // isRebinding check, but introducing RebindManager#isAwaitingInitialRebind() seems cleaner.)
+    private boolean isStateNotYetValid() {
+        return mgmt().getRebindManager().isAwaitingInitialRebind();
+    }
+
+    private boolean isHaHotStateRequired() {
+        // TODO support super annotations
+        Method m = resourceInfo.getResourceMethod();
+        return getAnnotation(m, HaHotStateRequired.class) != null;
+    }
+    
+    private <T extends Annotation> T getAnnotation(Method m, Class<T> annotation) {
+        T am = m.getAnnotation(annotation);
+        if (am != null) {
+            return am;
+        }
+        Class<?> superClass = m.getDeclaringClass();
+        T ac = superClass.getAnnotation(annotation);
+        if (ac != null) {
+            return ac;
+        }
+        // TODO could look in super classes but not needed now, we are in control of where to put the annotation
+        return null;
+    }
+
+    private boolean isSkipCheckHeaderSet(ContainerRequestContext requestContext) {
+        return "true".equalsIgnoreCase(requestContext.getHeaderString(SKIP_CHECK_HEADER));
+    }
+
+    private boolean isHaHotStatus() {
+        ManagementNodeState state = mgmt().getHighAvailabilityManager().getNodeState();
+        return HOT_STATES.contains(state);
+    }
+
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/HaHotStateRequired.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/HaHotStateRequired.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/HaHotStateRequired.java
new file mode 100644
index 0000000..babfe43
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/HaHotStateRequired.java
@@ -0,0 +1,38 @@
+/*
+ * 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.filter;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * When a REST method (or its containing class) is marked with this annotation
+ * requests to it will fail with a 403 response if the instance is not in MASTER
+ * mode (or has recently switched or is still rebinding). Guards the method so
+ * that when it returns the caller can be certain of the response. For example
+ * if the response is 404, then the resource doesn't exist as opposed to
+ * not being loaded from persistence store yet.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Inherited
+public @interface HaHotStateRequired {}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/NoCacheFilter.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/NoCacheFilter.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/NoCacheFilter.java
new file mode 100644
index 0000000..97fdda1
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/NoCacheFilter.java
@@ -0,0 +1,40 @@
+/*
+ * 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.filter;
+
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerResponseContext;
+import javax.ws.rs.container.ContainerResponseFilter;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.Provider;
+
+@Provider
+public class NoCacheFilter implements ContainerResponseFilter {
+
+    @Override
+    public void filter(ContainerRequestContext request, ContainerResponseContext response) {
+        //https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching_FAQ
+        MultivaluedMap<String, Object> headers = response.getHeaders();
+        headers.putSingle(HttpHeaders.CACHE_CONTROL, "no-cache, no-store");
+        headers.putSingle("Pragma", "no-cache");
+        headers.putSingle(HttpHeaders.EXPIRES, "0");
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/AbstractBrooklynRestResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/AbstractBrooklynRestResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/AbstractBrooklynRestResource.java
new file mode 100644
index 0000000..9882eb3
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/AbstractBrooklynRestResource.java
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import javax.annotation.Nullable;
+import javax.servlet.ServletContext;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.core.config.render.RendererHints;
+import org.apache.brooklyn.core.mgmt.ManagementContextInjectable;
+import org.apache.brooklyn.rest.util.BrooklynRestResourceUtils;
+import org.apache.brooklyn.rest.util.OsgiCompat;
+import org.apache.brooklyn.rest.util.WebResourceUtils;
+import org.apache.brooklyn.rest.util.json.BrooklynJacksonJsonProvider;
+import org.apache.brooklyn.util.core.task.Tasks;
+import org.apache.brooklyn.util.guava.Maybe;
+import org.apache.brooklyn.util.time.Duration;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public abstract class AbstractBrooklynRestResource implements ManagementContextInjectable {
+
+    // can be injected by jersey when ManagementContext in not injected manually
+    // (seems there is no way to make this optional so note it _must_ be injected;
+    // most of the time that happens for free, but with test framework it doesn't,
+    // so we have set up a NullServletContextProvider in our tests) 
+    private @Context ServletContext servletContext;
+
+    protected @Context UriInfo ui;
+
+    
+    private ManagementContext managementContext;
+    private BrooklynRestResourceUtils brooklynRestResourceUtils;
+    private ObjectMapper mapper;
+
+    public ManagementContext mgmt() {
+        return mgmtMaybe().get();
+    }
+    
+    protected synchronized Maybe<ManagementContext> mgmtMaybe() {
+        if (managementContext!=null) return Maybe.of(managementContext);
+        managementContext = OsgiCompat.getManagementContext(servletContext);
+        if (managementContext!=null) return Maybe.of(managementContext);
+        
+        return Maybe.absent("ManagementContext not available for Brooklyn Jersey Resource "+this);
+    }
+    
+    @Override
+    public void setManagementContext(ManagementContext managementContext) {
+        if (this.managementContext!=null) {
+            if (this.managementContext.equals(managementContext)) return;
+            throw new IllegalStateException("ManagementContext cannot be changed: specified twice for Brooklyn Jersey Resource "+this);
+        }
+        this.managementContext = managementContext;
+    }
+
+    public synchronized BrooklynRestResourceUtils brooklyn() {
+        if (brooklynRestResourceUtils!=null) return brooklynRestResourceUtils;
+        brooklynRestResourceUtils = new BrooklynRestResourceUtils(mgmt());
+        return brooklynRestResourceUtils;
+    }
+    
+    protected ObjectMapper mapper() {
+        if (mapper==null)
+            mapper = BrooklynJacksonJsonProvider.findAnyObjectMapper(servletContext, managementContext);
+        return mapper;
+    }
+
+    /** @deprecated since 0.7.0 use {@link #getValueForDisplay(Object, boolean, boolean, Boolean, EntityLocal, Duration)} */ @Deprecated
+    protected Object getValueForDisplay(Object value, boolean preferJson, boolean isJerseyReturnValue) {
+        return resolving(value).preferJson(preferJson).asJerseyOutermostReturnValue(isJerseyReturnValue).resolve();
+    }
+
+    protected RestValueResolver resolving(Object v) {
+        return new RestValueResolver(v).mapper(mapper());
+    }
+
+    public static class RestValueResolver {
+        final private Object valueToResolve;
+        private @Nullable ObjectMapper mapper;
+        private boolean preferJson;
+        private boolean isJerseyReturnValue;
+        private @Nullable Boolean raw; 
+        private @Nullable Entity entity;
+        private @Nullable Duration timeout;
+        private @Nullable Object rendererHintSource;
+        
+        public static RestValueResolver resolving(Object v) { return new RestValueResolver(v); }
+        
+        private RestValueResolver(Object v) { valueToResolve = v; }
+        
+        public RestValueResolver mapper(ObjectMapper mapper) { this.mapper = mapper; return this; }
+        
+        /** whether JSON is the ultimate product; 
+         * main effect here is to give null for null if true, else to give empty string 
+         * <p>
+         * conversion to JSON for complex types is done subsequently (often by the framework)
+         * <p>
+         * default is true */
+        public RestValueResolver preferJson(boolean preferJson) { this.preferJson = preferJson; return this; }
+        /** whether an outermost string must be wrapped in quotes, because a String return object is treated as
+         * already JSON-encoded
+         * <p>
+         * default is false */
+        public RestValueResolver asJerseyOutermostReturnValue(boolean asJerseyReturnJson) {
+            isJerseyReturnValue = asJerseyReturnJson;
+            return this;
+        }
+        public RestValueResolver raw(Boolean raw) { this.raw = raw; return this; }
+        public RestValueResolver context(Entity entity) { this.entity = entity; return this; }
+        public RestValueResolver timeout(Duration timeout) { this.timeout = timeout; return this; }
+        public RestValueResolver renderAs(Object rendererHintSource) { this.rendererHintSource = rendererHintSource; return this; }
+
+        public Object resolve() {
+            Object valueResult = getImmediateValue(valueToResolve, entity, timeout);
+            if (valueResult==UNRESOLVED) valueResult = valueToResolve;
+            if (rendererHintSource!=null && Boolean.FALSE.equals(raw)) {
+                valueResult = RendererHints.applyDisplayValueHintUnchecked(rendererHintSource, valueResult);
+            }
+            return WebResourceUtils.getValueForDisplay(mapper, valueResult, preferJson, isJerseyReturnValue);
+        }
+        
+        private static Object UNRESOLVED = "UNRESOLVED".toCharArray();
+        
+        private static Object getImmediateValue(Object value, @Nullable Entity context, @Nullable Duration timeout) {
+            return Tasks.resolving(value)
+                    .as(Object.class)
+                    .defaultValue(UNRESOLVED)
+                    .timeout(timeout)
+                    .context(context)
+                    .swallowExceptions()
+                    .get();
+        }
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/AccessResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/AccessResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/AccessResource.java
new file mode 100644
index 0000000..bb40edf
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/AccessResource.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import javax.ws.rs.core.Response;
+
+import org.apache.brooklyn.core.mgmt.internal.AccessManager;
+import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
+import org.apache.brooklyn.rest.api.AccessApi;
+import org.apache.brooklyn.rest.domain.AccessSummary;
+import org.apache.brooklyn.rest.transform.AccessTransformer;
+
+import com.google.common.annotations.Beta;
+
+@Beta
+public class AccessResource extends AbstractBrooklynRestResource implements AccessApi {
+
+    @Override
+    public AccessSummary get() {
+        AccessManager accessManager = ((ManagementContextInternal) mgmt()).getAccessManager();
+        return AccessTransformer.accessSummary(accessManager, ui.getBaseUriBuilder());
+    }
+
+    @Override
+    public Response locationProvisioningAllowed(boolean allowed) {
+        AccessManager accessManager = ((ManagementContextInternal) mgmt()).getAccessManager();
+        accessManager.setLocationProvisioningAllowed(allowed);
+        return Response.status(Response.Status.OK).build();
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ActivityResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ActivityResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ActivityResource.java
new file mode 100644
index 0000000..f29827b
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ActivityResource.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+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.core.mgmt.entitlement.Entitlements;
+import org.apache.brooklyn.rest.api.ActivityApi;
+import org.apache.brooklyn.rest.domain.TaskSummary;
+import org.apache.brooklyn.rest.transform.TaskTransformer;
+import org.apache.brooklyn.rest.util.WebResourceUtils;
+
+import com.google.common.collect.Collections2;
+import com.google.common.collect.Lists;
+
+public class ActivityResource extends AbstractBrooklynRestResource implements ActivityApi {
+
+    @Override
+    public TaskSummary get(String taskId) {
+        Task<?> t = mgmt().getExecutionManager().getTask(taskId);
+        if (t == null) {
+            throw WebResourceUtils.notFound("Cannot find task '%s'", taskId);
+        }
+        checkEntityEntitled(t);
+
+        return TaskTransformer.fromTask(ui.getBaseUriBuilder()).apply(t);
+    }
+
+    @Override
+    public List<TaskSummary> children(String taskId) {
+        Task<?> t = mgmt().getExecutionManager().getTask(taskId);
+        if (t == null) {
+            throw WebResourceUtils.notFound("Cannot find task '%s'", taskId);
+        }
+        checkEntityEntitled(t);
+        
+        if (!(t instanceof HasTaskChildren)) {
+            return Collections.emptyList();
+        }
+        return new LinkedList<TaskSummary>(Collections2.transform(Lists.newArrayList(((HasTaskChildren) t).getChildren()),
+                TaskTransformer.fromTask(ui.getBaseUriBuilder())));
+    }
+
+    @Override
+    public String stream(String taskId, String streamId) {
+        Task<?> t = mgmt().getExecutionManager().getTask(taskId);
+        if (t == null) {
+            throw WebResourceUtils.notFound("Cannot find task '%s'", taskId);
+        }
+        checkEntityEntitled(t);
+        checkStreamEntitled(t, streamId);
+        
+        WrappedStream stream = BrooklynTaskTags.stream(t, streamId);
+        if (stream == null) {
+            throw WebResourceUtils.notFound("Cannot find stream '%s' in task '%s'", streamId, taskId);
+        }
+        return stream.streamContents.get();
+    }
+    
+    protected void checkEntityEntitled(Task<?> task) {
+        Entity entity = BrooklynTaskTags.getContextEntity(task);
+        if (entity != null && !Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY, entity)) {
+            throw WebResourceUtils.forbidden("User '%s' is not authorized to see activity of entity '%s'",
+                    Entitlements.getEntitlementContext().user(), entity);
+        }
+    }
+    
+    protected void checkStreamEntitled(Task<?> task, String streamId) {
+        Entity entity = BrooklynTaskTags.getContextEntity(task);
+        Entitlements.TaskAndItem<String> item = new Entitlements.TaskAndItem<String>(task, streamId);
+        if (entity != null && !Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ACTIVITY_STREAMS, item)) {
+            throw WebResourceUtils.forbidden("User '%s' is not authorized to see activity stream of entity '%s'",
+                    Entitlements.getEntitlementContext().user(), entity);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ApidocResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ApidocResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ApidocResource.java
new file mode 100644
index 0000000..220e4e3
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ApidocResource.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.resources;
+
+
+import javax.ws.rs.Path;
+
+import org.apache.brooklyn.rest.apidoc.ApiListingResource;
+
+import io.swagger.annotations.Api;
+
+/**
+ * @author Ciprian Ciubotariu <ch...@gmx.net>
+ */
+@Api("API Documentation")
+@Path("/apidoc")
+public class ApidocResource extends ApiListingResource {
+
+}


[31/34] brooklyn-server git commit: move common ha filter code to shared class

Posted by he...@apache.org.
move common ha filter code to shared class

and ensure classes not in scope aren't referenced


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

Branch: refs/heads/master
Commit: 3ab00406bc484ec4d8c667d1a068977a25e2aba6
Parents: 7f047ba
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Thu Feb 18 12:15:08 2016 +0000
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Thu Feb 18 13:28:54 2016 +0000

----------------------------------------------------------------------
 .../rest/filter/HaHotCheckHelperAbstract.java   | 82 ++++++++++++++++++++
 .../rest/filter/HaHotCheckResourceFilter.java   | 72 +++++------------
 .../rest/resources/CatalogResourceTest.java     |  2 +
 .../rest/filter/HaMasterCheckFilter.java        | 35 ++++-----
 .../brooklyn/rest/BrooklynRestApiLauncher.java  |  4 -
 5 files changed, 118 insertions(+), 77 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/3ab00406/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/HaHotCheckHelperAbstract.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/HaHotCheckHelperAbstract.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/HaHotCheckHelperAbstract.java
new file mode 100644
index 0000000..7f183a6
--- /dev/null
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/HaHotCheckHelperAbstract.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.filter;
+
+import java.util.Set;
+
+import javax.ws.rs.core.Response;
+
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.api.mgmt.ha.ManagementNodeState;
+import org.apache.brooklyn.rest.domain.ApiError;
+import org.apache.brooklyn.util.guava.Maybe;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.Beta;
+import com.google.common.collect.ImmutableSet;
+
+@Beta
+public abstract class HaHotCheckHelperAbstract {
+
+    private static final Logger log = LoggerFactory.getLogger(HaHotCheckHelperAbstract.class);
+    
+    public static final String SKIP_CHECK_HEADER = "Brooklyn-Allow-Non-Master-Access";
+
+    private static final Set<ManagementNodeState> HOT_STATES = ImmutableSet.of(
+        ManagementNodeState.MASTER, ManagementNodeState.HOT_STANDBY, ManagementNodeState.HOT_BACKUP);
+
+    /** Returns a string describing the problem if mgmt is null or not running; returns absent if no problems */
+    public static Maybe<String> getProblemMessageIfServerNotRunning(ManagementContext mgmt) {
+        if (mgmt==null) return Maybe.of("no management context available");
+        if (!mgmt.isRunning()) return Maybe.of("server no longer running");
+        if (!mgmt.isStartupComplete()) return Maybe.of("server not in required startup-completed state");
+        return Maybe.absent();
+    }
+    
+    public Maybe<String> getProblemMessageIfServerNotRunning() {
+        return getProblemMessageIfServerNotRunning(mgmt());
+    }
+
+    public Response disallowResponse(String problem, Object info) {
+        log.warn("Disallowing web request as "+problem+": "+info+" (caller should set '"+HaHotCheckHelperAbstract.SKIP_CHECK_HEADER+"' to force)");
+        return ApiError.builder()
+            .message("This request is only permitted against an active master Brooklyn server")
+            .errorCode(Response.Status.FORBIDDEN).build().asJsonResponse();
+    }
+
+    public boolean isSkipCheckHeaderSet(String headerValueString) {
+        return "true".equalsIgnoreCase(headerValueString);
+    }
+
+    public boolean isHaHotStatus() {
+        ManagementNodeState state = mgmt().getHighAvailabilityManager().getNodeState();
+        return HOT_STATES.contains(state);
+    }
+
+    public abstract ManagementContext mgmt();
+
+    // Maybe there should be a separate state to indicate that we have switched state
+    // but still haven't finished rebinding. (Previously there was a time delay and an
+    // isRebinding check, but introducing RebindManager#isAwaitingInitialRebind() seems cleaner.)
+    public boolean isStateNotYetValid() {
+        return mgmt().getRebindManager().isAwaitingInitialRebind();
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/3ab00406/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/HaHotCheckResourceFilter.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/HaHotCheckResourceFilter.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/HaHotCheckResourceFilter.java
index 13c5cbf..3c9c129 100644
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/HaHotCheckResourceFilter.java
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/HaHotCheckResourceFilter.java
@@ -21,25 +21,20 @@ package org.apache.brooklyn.rest.filter;
 import java.io.IOException;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Method;
-import java.util.Set;
 
 import javax.ws.rs.container.ContainerRequestContext;
 import javax.ws.rs.container.ContainerRequestFilter;
 import javax.ws.rs.container.ResourceInfo;
 import javax.ws.rs.core.Context;
-import javax.ws.rs.core.Response;
 import javax.ws.rs.ext.ContextResolver;
 import javax.ws.rs.ext.Provider;
 
 import org.apache.brooklyn.api.mgmt.ManagementContext;
-import org.apache.brooklyn.api.mgmt.ha.ManagementNodeState;
-import org.apache.brooklyn.rest.domain.ApiError;
+import org.apache.brooklyn.rest.util.BrooklynRestResourceUtils;
+import org.apache.brooklyn.util.guava.Maybe;
 import org.apache.brooklyn.util.text.Strings;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.ImmutableSet;
 
 /** 
  * Checks that if the method or resource class corresponding to a request
@@ -52,13 +47,8 @@ import com.google.common.collect.ImmutableSet;
  */
 @Provider
 public class HaHotCheckResourceFilter implements ContainerRequestFilter {
-    public static final String SKIP_CHECK_HEADER = "Brooklyn-Allow-Non-Master-Access";
+    public static final String SKIP_CHECK_HEADER = HaHotCheckHelperAbstract.SKIP_CHECK_HEADER;
     
-    private static final Logger log = LoggerFactory.getLogger(HaHotCheckResourceFilter.class);
-    
-    private static final Set<ManagementNodeState> HOT_STATES = ImmutableSet.of(
-            ManagementNodeState.MASTER, ManagementNodeState.HOT_STANDBY, ManagementNodeState.HOT_BACKUP);
-
     // Not quite standards compliant. Should instead be:
     // @Context Providers providers
     // ....
@@ -70,6 +60,12 @@ public class HaHotCheckResourceFilter implements ContainerRequestFilter {
     @Context
     private ResourceInfo resourceInfo;
     
+    private HaHotCheckHelperAbstract helper = new HaHotCheckHelperAbstract() {
+        public ManagementContext mgmt() {
+            return mgmt.getContext(ManagementContext.class);
+        }
+    };
+    
     public HaHotCheckResourceFilter() {
     }
 
@@ -78,60 +74,44 @@ public class HaHotCheckResourceFilter implements ContainerRequestFilter {
         this.mgmt = mgmt;
     }
 
-    private ManagementContext mgmt() {
-        return mgmt.getContext(ManagementContext.class);
-    }
-
     @Override
     public void filter(ContainerRequestContext requestContext) throws IOException {
         String problem = lookForProblem(requestContext);
         if (Strings.isNonBlank(problem)) {
-            log.warn("Disallowing web request as "+problem+": "+requestContext.getUriInfo()+"/"+resourceInfo.getResourceMethod()+" (caller should set '"+SKIP_CHECK_HEADER+"' to force)");
-            requestContext.abortWith(ApiError.builder()
-                .message("This request is only permitted against an active hot Brooklyn server")
-                .errorCode(Response.Status.FORBIDDEN).build().asJsonResponse());
+            requestContext.abortWith(helper.disallowResponse(problem, requestContext.getUriInfo()+"/"+resourceInfo.getResourceMethod()));
         }
     }
 
     private String lookForProblem(ContainerRequestContext requestContext) {
-        if (isSkipCheckHeaderSet(requestContext)) 
+        if (helper.isSkipCheckHeaderSet(requestContext.getHeaderString(SKIP_CHECK_HEADER))) 
             return null;
         
         if (!isHaHotStateRequired())
             return null;
         
-        String problem = lookForProblemIfServerNotRunning(mgmt());
-        if (Strings.isNonBlank(problem)) 
-            return problem;
+        Maybe<String> problem = helper.getProblemMessageIfServerNotRunning();
+        if (problem.isPresent()) 
+            return problem.get();
         
-        if (!isHaHotStatus())
+        if (!helper.isHaHotStatus())
             return "server not in required HA hot state";
-        if (isStateNotYetValid())
+        if (helper.isStateNotYetValid())
             return "server not yet completed loading data for required HA hot state";
         
         return null;
     }
-    
+
+    /** @deprecated since 0.9.0 use {@link BrooklynRestResourceUtils#getProblemMessageIfServerNotRunning(ManagementContext)} */
     public static String lookForProblemIfServerNotRunning(ManagementContext mgmt) {
-        if (mgmt==null) return "no management context available";
-        if (!mgmt.isRunning()) return "server no longer running";
-        if (!mgmt.isStartupComplete()) return "server not in required startup-completed state";
-        return null;
+        return HaHotCheckHelperAbstract.getProblemMessageIfServerNotRunning(mgmt).orNull();
     }
     
-    // Maybe there should be a separate state to indicate that we have switched state
-    // but still haven't finished rebinding. (Previously there was a time delay and an
-    // isRebinding check, but introducing RebindManager#isAwaitingInitialRebind() seems cleaner.)
-    private boolean isStateNotYetValid() {
-        return mgmt().getRebindManager().isAwaitingInitialRebind();
-    }
-
-    private boolean isHaHotStateRequired() {
+    protected boolean isHaHotStateRequired() {
         // TODO support super annotations
         Method m = resourceInfo.getResourceMethod();
         return getAnnotation(m, HaHotStateRequired.class) != null;
     }
-    
+
     private <T extends Annotation> T getAnnotation(Method m, Class<T> annotation) {
         T am = m.getAnnotation(annotation);
         if (am != null) {
@@ -146,14 +126,4 @@ public class HaHotCheckResourceFilter implements ContainerRequestFilter {
         return null;
     }
 
-    private boolean isSkipCheckHeaderSet(ContainerRequestContext requestContext) {
-        return "true".equalsIgnoreCase(requestContext.getHeaderString(SKIP_CHECK_HEADER));
-    }
-
-    private boolean isHaHotStatus() {
-        ManagementNodeState state = mgmt().getHighAvailabilityManager().getNodeState();
-        return HOT_STATES.contains(state);
-    }
-
-
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/3ab00406/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/CatalogResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/CatalogResourceTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/CatalogResourceTest.java
index 8d5d017..f3374dc 100644
--- a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/CatalogResourceTest.java
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/CatalogResourceTest.java
@@ -126,6 +126,7 @@ public class CatalogResourceTest extends BrooklynRestResourceTest {
         // an InterfacesTag should be created for every catalog item
         assertEquals(entityItem.getTags().size(), 1);
         Object tag = entityItem.getTags().iterator().next();
+        @SuppressWarnings("unchecked")
         List<String> actualInterfaces = ((Map<String, List<String>>) tag).get("traits");
         List<Class<?>> expectedInterfaces = Reflections.getAllInterfaces(TestEntity.class);
         assertEquals(actualInterfaces.size(), expectedInterfaces.size());
@@ -138,6 +139,7 @@ public class CatalogResourceTest extends BrooklynRestResourceTest {
     }
 
     @Test
+    // osgi may fail in IDE, typically works on CLI though
     public void testRegisterOsgiPolicyTopLevelSyntax() {
         TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH);
 

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/3ab00406/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/HaMasterCheckFilter.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/HaMasterCheckFilter.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/HaMasterCheckFilter.java
index 991d58c..bfb1caf 100644
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/HaMasterCheckFilter.java
+++ b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/filter/HaMasterCheckFilter.java
@@ -30,18 +30,14 @@ import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
-import javax.ws.rs.core.Response;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import org.apache.brooklyn.api.mgmt.ManagementContext;
 import org.apache.brooklyn.api.mgmt.ha.ManagementNodeState;
-import org.apache.brooklyn.rest.domain.ApiError;
+import org.apache.brooklyn.rest.util.OsgiCompat;
 import org.apache.brooklyn.rest.util.WebResourceUtils;
-import org.apache.brooklyn.util.text.Strings;
+import org.apache.brooklyn.util.guava.Maybe;
 
 import com.google.common.collect.Sets;
-import org.apache.brooklyn.rest.util.OsgiCompat;
 
 /**
  * Checks that for requests that want HA master state, the server is up and in that state.
@@ -52,13 +48,17 @@ import org.apache.brooklyn.rest.util.OsgiCompat;
 // TODO Merge with HaHotCheckResourceFilter so the functionality is available in Karaf
 public class HaMasterCheckFilter implements Filter {
 
-    private static final Logger log = LoggerFactory.getLogger(HaMasterCheckFilter.class);
-    
     private static final Set<String> SAFE_STANDBY_METHODS = Sets.newHashSet("GET", "HEAD");
 
     protected ServletContext servletContext;
     protected ManagementContext mgmt;
 
+    private HaHotCheckHelperAbstract helper = new HaHotCheckHelperAbstract() {
+        public ManagementContext mgmt() {
+            return mgmt;
+        }
+    };
+
     @Override
     public void init(FilterConfig config) throws ServletException {
         servletContext = config.getServletContext();
@@ -66,15 +66,15 @@ public class HaMasterCheckFilter implements Filter {
     }
 
     private String lookForProblem(ServletRequest request) {
-        if (isSkipCheckHeaderSet(request)) 
+        if (helper.isSkipCheckHeaderSet(((HttpServletRequest)request).getHeader(HaHotCheckHelperAbstract.SKIP_CHECK_HEADER))) 
             return null;
         
         if (!isMasterRequiredForRequest(request))
             return null;
         
-        String problem = HaHotCheckResourceFilter.lookForProblemIfServerNotRunning(mgmt);
-        if (Strings.isNonBlank(problem)) 
-            return problem;
+        Maybe<String> problem = helper.getProblemMessageIfServerNotRunning();
+        if (problem.isPresent()) 
+            return problem.get();
         
         if (!isMaster()) 
             return "server not in required HA master state";
@@ -86,10 +86,7 @@ public class HaMasterCheckFilter implements Filter {
     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
         String problem = lookForProblem(request);
         if (problem!=null) {
-            log.warn("Disallowing web request as "+problem+": "+request.getParameterMap()+" (caller should set '"+HaHotCheckResourceFilter.SKIP_CHECK_HEADER+"' to force)");
-            WebResourceUtils.applyJsonResponse(servletContext, ApiError.builder()
-                .message("This request is only permitted against an active master Brooklyn server")
-                .errorCode(Response.Status.FORBIDDEN).build().asJsonResponse(), (HttpServletResponse)response);
+            WebResourceUtils.applyJsonResponse(servletContext, helper.disallowResponse(problem, request.getParameterMap()), (HttpServletResponse)response);
         } else {
             chain.doFilter(request, response);
         }
@@ -124,10 +121,4 @@ public class HaMasterCheckFilter implements Filter {
         return true;
     }
 
-    private boolean isSkipCheckHeaderSet(ServletRequest httpRequest) {
-        if (httpRequest instanceof HttpServletRequest)
-            return "true".equalsIgnoreCase(((HttpServletRequest)httpRequest).getHeader(HaHotCheckResourceFilter.SKIP_CHECK_HEADER));
-        return false;
-    }
-
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/3ab00406/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncher.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncher.java b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncher.java
index 8d71dcb..d9f0f1a 100644
--- a/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncher.java
+++ b/rest/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncher.java
@@ -36,7 +36,6 @@ import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
 import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
 import org.apache.brooklyn.core.server.BrooklynServerConfig;
 import org.apache.brooklyn.core.server.BrooklynServiceAttributes;
-import org.apache.brooklyn.rest.apidoc.RestApiResourceScanner;
 import org.apache.brooklyn.rest.filter.BrooklynPropertiesSecurityFilter;
 import org.apache.brooklyn.rest.filter.HaMasterCheckFilter;
 import org.apache.brooklyn.rest.filter.LoggingFilter;
@@ -44,7 +43,6 @@ import org.apache.brooklyn.rest.filter.RequestTaggingFilter;
 import org.apache.brooklyn.rest.security.provider.AnyoneSecurityProvider;
 import org.apache.brooklyn.rest.security.provider.SecurityProvider;
 import org.apache.brooklyn.rest.util.ManagementContextProvider;
-import org.apache.brooklyn.rest.util.OsgiCompat;
 import org.apache.brooklyn.rest.util.ServerStoppingShutdownHandler;
 import org.apache.brooklyn.rest.util.ShutdownHandlerProvider;
 import org.apache.brooklyn.util.core.ResourceUtils;
@@ -68,8 +66,6 @@ import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 import com.google.common.io.Files;
 
-import io.swagger.config.ScannerFactory;
-
 /** Convenience and demo for launching programmatically. Also used for automated tests.
  * <p>
  * BrooklynLauncher has a more full-featured CLI way to start, 


[29/34] brooklyn-server git commit: This closes #15

Posted by he...@apache.org.
This closes #15

simple merge conflicts resolved (imports, and extra param/code in places)


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

Branch: refs/heads/master
Commit: 79b98c679b97cd34464abf4a78ddb93cc809b195
Parents: a5103bb abd2d5f
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Thu Feb 18 10:54:31 2016 +0000
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Thu Feb 18 10:56:30 2016 +0000

----------------------------------------------------------------------
 camp/camp-server-jersey/pom.xml                 | 225 ++++++
 .../brooklyn/camp/server/RestApiSetup.java      |  53 ++
 .../rest/resource/ApiListingResource.java       | 260 +++++++
 .../rest/resource/ApidocRestResource.java       |  32 +
 .../src/main/webapp/WEB-INF/web.xml             | 142 ++++
 camp/camp-server/pom.xml                        |  21 +-
 .../brooklyn/camp/server/CampRestApp.java       |  36 +
 .../brooklyn/camp/server/RestApiSetup.java      |  41 ++
 .../brooklyn/camp/server/rest/CampServer.java   |  32 +-
 .../rest/resource/ApidocRestResource.java       |   2 +-
 camp/pom.xml                                    |   1 +
 karaf/apache-brooklyn/pom.xml                   |   2 +-
 .../main/resources/etc/org.apache.cxf.osgi.cfg  |  20 +
 karaf/features/src/main/feature/feature.xml     |  30 +-
 karaf/itest/pom.xml                             |  13 +
 .../java/org/apache/brooklyn/AssemblyTest.java  |   4 +-
 launcher/pom.xml                                |  33 +-
 .../org/apache/brooklyn/launcher/Activator.java |   2 +
 .../brooklyn/launcher/BrooklynWebServer.java    |  95 +--
 .../camp/BrooklynCampPlatformLauncher.java      |   1 +
 .../jsgui/BrooklynJavascriptGuiLauncher.java    |   2 +-
 .../BrooklynJavascriptGuiLauncherTest.java      |  18 +-
 locations/jclouds/pom.xml                       |  12 +
 parent/pom.xml                                  | 194 +++--
 pom.xml                                         |   8 +-
 rest/rest-api/pom.xml                           |  21 +-
 .../org/apache/brooklyn/rest/api/AccessApi.java |   2 +-
 .../apache/brooklyn/rest/api/ActivityApi.java   |   2 +-
 .../brooklyn/rest/api/ApplicationApi.java       |   2 +-
 .../apache/brooklyn/rest/api/CatalogApi.java    |  28 +-
 .../apache/brooklyn/rest/api/EffectorApi.java   |   2 +-
 .../org/apache/brooklyn/rest/api/EntityApi.java |   4 +-
 .../brooklyn/rest/api/EntityConfigApi.java      |   4 +-
 .../apache/brooklyn/rest/api/LocationApi.java   |   2 +-
 .../org/apache/brooklyn/rest/api/PolicyApi.java |   2 +-
 .../brooklyn/rest/api/PolicyConfigApi.java      |   2 +-
 .../org/apache/brooklyn/rest/api/ScriptApi.java |   2 +-
 .../org/apache/brooklyn/rest/api/SensorApi.java |   5 +-
 .../org/apache/brooklyn/rest/api/ServerApi.java |   2 +-
 .../org/apache/brooklyn/rest/api/UsageApi.java  |   2 +-
 .../apache/brooklyn/rest/api/VersionApi.java    |   4 +-
 rest/rest-api/src/main/webapp/WEB-INF/web.xml   | 121 ----
 .../rest/domain/ApplicationSpecTest.java        |   2 +-
 .../rest/domain/EffectorSummaryTest.java        |   2 +-
 .../brooklyn/rest/domain/EntitySummaryTest.java |   8 +-
 .../rest/domain/LocationSummaryTest.java        |   2 +-
 .../resources/fixtures/application-list.json    |   8 +-
 .../resources/fixtures/application-spec.json    |   2 +-
 .../test/resources/fixtures/application.json    |   4 +-
 .../fixtures/effector-summary-list.json         |  18 +-
 .../resources/fixtures/effector-summary.json    |   2 +-
 .../resources/fixtures/entity-summary-list.json |   8 +-
 .../test/resources/fixtures/entity-summary.json |   8 +-
 .../test/resources/fixtures/location-list.json  |   2 +-
 .../resources/fixtures/location-summary.json    |   2 +-
 .../resources/fixtures/sensor-summary-list.json |  16 +-
 .../test/resources/fixtures/sensor-summary.json |   2 +-
 rest/rest-client/pom.xml                        |   7 +
 .../rest/client/BrooklynApiRestClientTest.java  |   3 +-
 .../rest/client/BrooklynApiUtilTest.java        |   2 +-
 rest/rest-resources/pom.xml                     | 220 ++++++
 .../apache/brooklyn/rest/BrooklynRestApi.java   |  91 +++
 .../apache/brooklyn/rest/BrooklynRestApp.java   |  56 ++
 .../apache/brooklyn/rest/BrooklynWebConfig.java | 164 +++++
 .../rest/filter/HaHotCheckResourceFilter.java   | 159 +++++
 .../rest/filter/HaHotStateRequired.java         |  38 +
 .../brooklyn/rest/filter/NoCacheFilter.java     |  40 ++
 .../resources/AbstractBrooklynRestResource.java | 156 +++++
 .../brooklyn/rest/resources/AccessResource.java |  46 ++
 .../rest/resources/ActivityResource.java        |  99 +++
 .../brooklyn/rest/resources/ApidocResource.java |  34 +
 .../rest/resources/ApplicationResource.java     | 474 +++++++++++++
 .../rest/resources/CatalogResource.java         | 516 ++++++++++++++
 .../rest/resources/EffectorResource.java        | 114 +++
 .../rest/resources/EntityConfigResource.java    | 206 ++++++
 .../brooklyn/rest/resources/EntityResource.java | 223 ++++++
 .../rest/resources/LocationResource.java        | 186 +++++
 .../rest/resources/PolicyConfigResource.java    | 108 +++
 .../brooklyn/rest/resources/PolicyResource.java | 135 ++++
 .../brooklyn/rest/resources/ScriptResource.java | 102 +++
 .../brooklyn/rest/resources/SensorResource.java | 184 +++++
 .../brooklyn/rest/resources/ServerResource.java | 497 +++++++++++++
 .../brooklyn/rest/resources/UsageResource.java  | 256 +++++++
 .../rest/resources/VersionResource.java         |  32 +
 .../brooklyn/rest/security/PasswordHasher.java  |  32 +
 .../provider/AbstractSecurityProvider.java      |  56 ++
 .../provider/AnyoneSecurityProvider.java        |  40 ++
 .../provider/BlackholeSecurityProvider.java     |  40 ++
 ...nUserWithRandomPasswordSecurityProvider.java |  73 ++
 .../provider/DelegatingSecurityProvider.java    | 165 +++++
 .../provider/ExplicitUsersSecurityProvider.java | 117 ++++
 .../security/provider/LdapSecurityProvider.java | 132 ++++
 .../security/provider/SecurityProvider.java     |  35 +
 .../rest/transform/AccessTransformer.java       |  42 ++
 .../rest/transform/ApplicationTransformer.java  | 125 ++++
 .../transform/BrooklynFeatureTransformer.java   |  45 ++
 .../rest/transform/CatalogTransformer.java      | 188 +++++
 .../rest/transform/EffectorTransformer.java     |  91 +++
 .../rest/transform/EntityTransformer.java       | 182 +++++
 .../transform/HighAvailabilityTransformer.java  |  50 ++
 .../rest/transform/LocationTransformer.java     | 202 ++++++
 .../rest/transform/PolicyTransformer.java       |  97 +++
 .../rest/transform/SensorTransformer.java       |  88 +++
 .../rest/transform/TaskTransformer.java         | 153 ++++
 .../rest/util/BrooklynRestResourceUtils.java    | 609 ++++++++++++++++
 .../rest/util/DefaultExceptionMapper.java       | 117 ++++
 .../brooklyn/rest/util/EntityLocationUtils.java |  85 +++
 .../brooklyn/rest/util/FormMapProvider.java     |  86 +++
 .../rest/util/ManagementContextProvider.java    |  45 ++
 .../apache/brooklyn/rest/util/OsgiCompat.java   |  40 ++
 .../brooklyn/rest/util/ShutdownHandler.java     |  23 +
 .../rest/util/ShutdownHandlerProvider.java      |  45 ++
 .../brooklyn/rest/util/URLParamEncoder.java     |  27 +
 .../brooklyn/rest/util/WebResourceUtils.java    | 231 ++++++
 .../rest/util/json/BidiSerialization.java       | 173 +++++
 .../util/json/BrooklynJacksonJsonProvider.java  | 173 +++++
 .../json/ConfigurableSerializerProvider.java    |  90 +++
 .../ErrorAndToStringUnknownTypeSerializer.java  | 123 ++++
 .../rest/util/json/MultimapSerializer.java      |  64 ++
 ...StrictPreferringFieldsVisibilityChecker.java | 108 +++
 .../resources/OSGI-INF/blueprint/service.xml    | 112 +++
 .../main/resources/build-metadata.properties    |  18 +
 .../brooklyn/rest/domain/ApplicationTest.java   |  86 +++
 .../brooklyn/rest/domain/SensorSummaryTest.java | 102 +++
 .../brooklyn/rest/filter/HaHotCheckTest.java    | 120 ++++
 .../rest/resources/AccessResourceTest.java      |  67 ++
 .../rest/resources/ApidocResourceTest.java      | 135 ++++
 .../ApplicationResourceIntegrationTest.java     | 132 ++++
 .../rest/resources/ApplicationResourceTest.java | 697 ++++++++++++++++++
 .../rest/resources/CatalogResetTest.java        | 118 ++++
 .../rest/resources/CatalogResourceTest.java     | 510 ++++++++++++++
 .../rest/resources/DelegatingPrintStream.java   | 183 +++++
 .../rest/resources/DescendantsTest.java         | 128 ++++
 .../resources/EntityConfigResourceTest.java     | 171 +++++
 .../rest/resources/EntityResourceTest.java      | 188 +++++
 .../rest/resources/ErrorResponseTest.java       |  98 +++
 .../rest/resources/LocationResourceTest.java    | 190 +++++
 .../rest/resources/PolicyResourceTest.java      | 143 ++++
 .../rest/resources/ScriptResourceTest.java      |  56 ++
 .../SensorResourceIntegrationTest.java          |  74 ++
 .../rest/resources/SensorResourceTest.java      | 267 +++++++
 .../rest/resources/ServerResourceTest.java      | 168 +++++
 .../rest/resources/ServerShutdownTest.java      | 164 +++++
 .../rest/resources/UsageResourceTest.java       | 444 ++++++++++++
 .../rest/resources/VersionResourceTest.java     |  46 ++
 .../rest/security/PasswordHasherTest.java       |  37 +
 .../test/config/render/TestRendererHints.java   |  36 +
 .../brooklynnode/DeployBlueprintTest.java       |  97 +++
 .../rest/testing/BrooklynRestApiTest.java       | 223 ++++++
 .../rest/testing/BrooklynRestResourceTest.java  | 212 ++++++
 .../rest/testing/mocks/CapitalizePolicy.java    |  33 +
 .../rest/testing/mocks/EverythingGroup.java     |  27 +
 .../rest/testing/mocks/EverythingGroupImpl.java |  32 +
 .../rest/testing/mocks/NameMatcherGroup.java    |  30 +
 .../testing/mocks/NameMatcherGroupImpl.java     |  33 +
 .../rest/testing/mocks/RestMockApp.java         |  24 +
 .../rest/testing/mocks/RestMockAppBuilder.java  |  39 ++
 .../testing/mocks/RestMockSimpleEntity.java     | 103 +++
 .../testing/mocks/RestMockSimplePolicy.java     |  64 ++
 .../util/BrooklynRestResourceUtilsTest.java     | 213 ++++++
 .../rest/util/EntityLocationUtilsTest.java      |  72 ++
 .../rest/util/HaHotStateCheckClassResource.java |  38 +
 .../rest/util/HaHotStateCheckResource.java      |  44 ++
 .../brooklyn/rest/util/TestShutdownHandler.java |  37 +
 .../json/BrooklynJacksonSerializerTest.java     | 264 +++++++
 .../META-INF/cxf/org.apache.cxf.Logger          |  18 +
 .../resources/brooklyn/scanning.catalog.bom     |  19 +
 .../src/test/resources/not-a-jar-file.txt       |  18 +
 .../src/test/resources/reset-catalog.xml        |  37 +
 rest/rest-server-jersey/pom.xml                 | 317 +++++++++
 .../org/apache/brooklyn/rest/RestApiSetup.java  |  78 +++
 .../rest/filter/HaHotCheckResourceFilter.java   | 163 +++++
 .../brooklyn/rest/filter/NoCacheFilter.java     |  40 ++
 .../brooklyn/rest/filter/SwaggerFilter.java     |  79 +++
 .../rest/resources/ApiListingResource.java      | 260 +++++++
 .../brooklyn/rest/resources/ApidocResource.java |  33 +
 .../brooklyn/rest/util/FormMapProvider.java     |  81 +++
 .../main/resources/build-metadata.properties    |  18 +
 .../src/main/webapp/WEB-INF/web.xml             | 144 ++++
 .../BrooklynPropertiesSecurityFilterTest.java   | 151 ++++
 .../brooklyn/rest/BrooklynRestApiLauncher.java  | 499 +++++++++++++
 .../rest/BrooklynRestApiLauncherTest.java       |  77 ++
 .../BrooklynRestApiLauncherTestFixture.java     | 109 +++
 .../apache/brooklyn/rest/HaHotCheckTest.java    | 130 ++++
 .../brooklyn/rest/HaMasterCheckFilterTest.java  | 218 ++++++
 .../rest/domain/AbstractDomainTest.java         |  44 ++
 .../brooklyn/rest/domain/ApiErrorTest.java      |  71 ++
 .../rest/domain/ApplicationSpecTest.java        |  40 ++
 .../brooklyn/rest/domain/ApplicationTest.java   |  87 +++
 .../rest/domain/EffectorSummaryTest.java        |  44 ++
 .../brooklyn/rest/domain/EntitySpecTest.java    |  48 ++
 .../brooklyn/rest/domain/EntitySummaryTest.java |  48 ++
 .../brooklyn/rest/domain/LocationSpecTest.java  |  58 ++
 .../rest/domain/LocationSummaryTest.java        |  41 ++
 .../brooklyn/rest/domain/SensorSummaryTest.java | 103 +++
 .../rest/domain/VersionSummaryTest.java         |  49 ++
 .../AbstractRestApiEntitlementsTest.java        | 111 +++
 .../ActivityApiEntitlementsTest.java            | 123 ++++
 .../AuthenticateAnyoneSecurityProvider.java     |  41 ++
 .../EntityConfigApiEntitlementsTest.java        | 103 +++
 .../entitlement/SensorApiEntitlementsTest.java  | 108 +++
 .../entitlement/ServerApiEntitlementsTest.java  |  34 +
 .../StaticDelegatingEntitlementManager.java     |  37 +
 .../rest/resources/AccessResourceTest.java      |  68 ++
 .../rest/resources/ApidocResourceTest.java      | 173 +++++
 .../ApplicationResourceIntegrationTest.java     | 133 ++++
 .../rest/resources/ApplicationResourceTest.java | 701 +++++++++++++++++++
 .../rest/resources/CatalogResetTest.java        | 113 +++
 .../rest/resources/CatalogResourceTest.java     | 515 ++++++++++++++
 .../rest/resources/DelegatingPrintStream.java   | 183 +++++
 .../rest/resources/DescendantsTest.java         | 130 ++++
 .../resources/EntityConfigResourceTest.java     | 172 +++++
 .../rest/resources/EntityResourceTest.java      | 189 +++++
 .../rest/resources/ErrorResponseTest.java       |  98 +++
 .../rest/resources/LocationResourceTest.java    | 188 +++++
 .../rest/resources/PolicyResourceTest.java      | 145 ++++
 .../rest/resources/ScriptResourceTest.java      |  54 ++
 .../SensorResourceIntegrationTest.java          |  82 +++
 .../rest/resources/SensorResourceTest.java      | 271 +++++++
 .../ServerResourceIntegrationTest.java          | 125 ++++
 .../rest/resources/ServerResourceTest.java      | 168 +++++
 .../rest/resources/ServerShutdownTest.java      | 185 +++++
 .../rest/resources/UsageResourceTest.java       | 443 ++++++++++++
 .../rest/resources/VersionResourceTest.java     |  52 ++
 .../rest/security/PasswordHasherTest.java       |  37 +
 .../security/provider/TestSecurityProvider.java |  46 ++
 .../test/config/render/TestRendererHints.java   |  36 +
 .../brooklynnode/DeployBlueprintTest.java       |  89 +++
 .../rest/testing/BrooklynRestApiTest.java       | 223 ++++++
 .../rest/testing/BrooklynRestResourceTest.java  | 154 ++++
 .../rest/testing/mocks/CapitalizePolicy.java    |  33 +
 .../rest/testing/mocks/EverythingGroup.java     |  27 +
 .../rest/testing/mocks/EverythingGroupImpl.java |  32 +
 .../rest/testing/mocks/NameMatcherGroup.java    |  30 +
 .../testing/mocks/NameMatcherGroupImpl.java     |  33 +
 .../rest/testing/mocks/RestMockApp.java         |  24 +
 .../rest/testing/mocks/RestMockAppBuilder.java  |  39 ++
 .../testing/mocks/RestMockSimpleEntity.java     | 103 +++
 .../testing/mocks/RestMockSimplePolicy.java     |  64 ++
 .../util/BrooklynRestResourceUtilsTest.java     | 213 ++++++
 .../rest/util/EntityLocationUtilsTest.java      |  72 ++
 .../rest/util/HaHotStateCheckClassResource.java |  38 +
 .../rest/util/HaHotStateCheckResource.java      |  44 ++
 .../rest/util/NoOpRecordingShutdownHandler.java |  39 ++
 .../util/NullHttpServletRequestProvider.java    |  46 ++
 .../rest/util/NullServletConfigProvider.java    |  51 ++
 .../brooklyn/rest/util/RestApiTestUtils.java    |  58 ++
 .../util/ServerStoppingShutdownHandler.java     |  75 ++
 .../json/BrooklynJacksonSerializerTest.java     | 399 +++++++++++
 .../resources/brooklyn/scanning.catalog.bom     |  19 +
 .../resources/fixtures/api-error-basic.json     |   4 +
 .../fixtures/api-error-no-details.json          |   3 +
 .../resources/fixtures/application-list.json    |  44 ++
 .../resources/fixtures/application-spec.json    |  16 +
 .../resources/fixtures/application-tree.json    |  43 ++
 .../test/resources/fixtures/application.json    |  22 +
 .../fixtures/catalog-application-list.json      |  29 +
 .../resources/fixtures/catalog-application.json |   9 +
 .../fixtures/effector-summary-list.json         |  47 ++
 .../resources/fixtures/effector-summary.json    |   9 +
 .../resources/fixtures/entity-only-type.json    |   3 +
 .../resources/fixtures/entity-summary-list.json |  14 +
 .../test/resources/fixtures/entity-summary.json |  13 +
 .../src/test/resources/fixtures/entity.json     |   7 +
 .../src/test/resources/fixtures/ha-summary.json |  19 +
 .../test/resources/fixtures/location-list.json  |  10 +
 .../resources/fixtures/location-summary.json    |   8 +
 .../fixtures/location-without-credential.json   |   5 +
 .../src/test/resources/fixtures/location.json   |   4 +
 .../fixtures/sensor-current-state.json          |   6 +
 .../resources/fixtures/sensor-summary-list.json |  42 ++
 .../test/resources/fixtures/sensor-summary.json |   8 +
 .../test/resources/fixtures/server-version.json |  14 +
 .../test/resources/fixtures/service-state.json  |   1 +
 .../resources/fixtures/task-summary-list.json   |  15 +
 rest/rest-server/pom.xml                        |  76 +-
 .../apache/brooklyn/rest/BrooklynRestApi.java   |  89 ---
 .../apache/brooklyn/rest/BrooklynWebConfig.java | 158 -----
 .../org/apache/brooklyn/rest/RestApiSetup.java  |  72 ++
 .../BrooklynPropertiesSecurityFilter.java       |  19 +-
 .../rest/filter/HaHotCheckResourceFilter.java   | 150 ----
 .../rest/filter/HaHotStateRequired.java         |  36 -
 .../rest/filter/HaMasterCheckFilter.java        |  18 +-
 .../brooklyn/rest/filter/LoggingFilter.java     |   2 +
 .../brooklyn/rest/filter/NoCacheFilter.java     |  40 --
 .../rest/filter/RequestTaggingFilter.java       |   1 +
 .../brooklyn/rest/filter/SwaggerFilter.java     |  76 --
 .../resources/AbstractBrooklynRestResource.java | 152 ----
 .../brooklyn/rest/resources/AccessResource.java |  46 --
 .../rest/resources/ActivityResource.java        |  99 ---
 .../brooklyn/rest/resources/ApidocResource.java |  31 -
 .../rest/resources/ApplicationResource.java     | 474 -------------
 .../rest/resources/CatalogResource.java         | 523 --------------
 .../rest/resources/EffectorResource.java        | 114 ---
 .../rest/resources/EntityConfigResource.java    | 206 ------
 .../brooklyn/rest/resources/EntityResource.java | 223 ------
 .../rest/resources/LocationResource.java        | 184 -----
 .../rest/resources/PolicyConfigResource.java    | 108 ---
 .../brooklyn/rest/resources/PolicyResource.java | 131 ----
 .../brooklyn/rest/resources/ScriptResource.java | 102 ---
 .../brooklyn/rest/resources/SensorResource.java | 184 -----
 .../brooklyn/rest/resources/ServerResource.java | 495 -------------
 .../brooklyn/rest/resources/UsageResource.java  | 256 -------
 .../rest/resources/VersionResource.java         |  32 -
 .../brooklyn/rest/security/PasswordHasher.java  |  32 -
 .../provider/AbstractSecurityProvider.java      |  56 --
 .../provider/AnyoneSecurityProvider.java        |  40 --
 .../provider/BlackholeSecurityProvider.java     |  40 --
 ...nUserWithRandomPasswordSecurityProvider.java |  73 --
 .../provider/DelegatingSecurityProvider.java    | 166 -----
 .../provider/ExplicitUsersSecurityProvider.java | 118 ----
 .../security/provider/LdapSecurityProvider.java | 132 ----
 .../security/provider/SecurityProvider.java     |  35 -
 .../rest/transform/AccessTransformer.java       |  39 --
 .../rest/transform/ApplicationTransformer.java  | 116 ---
 .../transform/BrooklynFeatureTransformer.java   |  45 --
 .../rest/transform/CatalogTransformer.java      | 192 -----
 .../rest/transform/EffectorTransformer.java     |  85 ---
 .../rest/transform/EntityTransformer.java       | 165 -----
 .../transform/HighAvailabilityTransformer.java  |  50 --
 .../rest/transform/LocationTransformer.java     | 193 -----
 .../rest/transform/PolicyTransformer.java       |  83 ---
 .../rest/transform/SensorTransformer.java       |  84 ---
 .../rest/transform/TaskTransformer.java         | 146 ----
 .../rest/util/BrooklynRestResourceUtils.java    | 608 ----------------
 .../rest/util/DefaultExceptionMapper.java       | 107 ---
 .../brooklyn/rest/util/EntityLocationUtils.java |  85 ---
 .../brooklyn/rest/util/FormMapProvider.java     |  81 ---
 .../rest/util/ManagementContextProvider.java    |  33 -
 .../apache/brooklyn/rest/util/OsgiCompat.java   |  46 --
 .../brooklyn/rest/util/ShutdownHandler.java     |  23 -
 .../rest/util/ShutdownHandlerProvider.java      |  30 -
 .../brooklyn/rest/util/URLParamEncoder.java     |  27 -
 .../brooklyn/rest/util/WebResourceUtils.java    | 195 ------
 .../rest/util/json/BidiSerialization.java       | 173 -----
 .../util/json/BrooklynJacksonJsonProvider.java  | 165 -----
 .../json/ConfigurableSerializerProvider.java    |  89 ---
 .../ErrorAndToStringUnknownTypeSerializer.java  | 123 ----
 .../rest/util/json/MultimapSerializer.java      |  62 --
 ...StrictPreferringFieldsVisibilityChecker.java | 108 ---
 .../src/main/resources/not-a-jar-file.txt       |  18 -
 .../src/main/resources/reset-catalog.xml        |  37 -
 .../rest-server/src/main/webapp/WEB-INF/web.xml |  96 +--
 .../BrooklynPropertiesSecurityFilterTest.java   |   6 +-
 .../brooklyn/rest/BrooklynRestApiLauncher.java  | 115 +--
 .../rest/BrooklynRestApiLauncherTest.java       |   6 -
 .../BrooklynRestApiLauncherTestFixture.java     |  22 +-
 .../apache/brooklyn/rest/HaHotCheckTest.java    | 129 ----
 .../brooklyn/rest/HaMasterCheckFilterTest.java  |   2 +-
 .../brooklyn/rest/domain/ApplicationTest.java   |  87 ---
 .../brooklyn/rest/domain/SensorSummaryTest.java | 101 ---
 .../rest/resources/AccessResourceTest.java      |  68 --
 .../rest/resources/ApidocResourceTest.java      | 173 -----
 .../ApplicationResourceIntegrationTest.java     | 133 ----
 .../rest/resources/ApplicationResourceTest.java | 701 -------------------
 .../rest/resources/CatalogResetTest.java        | 113 ---
 .../rest/resources/CatalogResourceTest.java     | 514 --------------
 .../rest/resources/DelegatingPrintStream.java   | 183 -----
 .../rest/resources/DescendantsTest.java         | 130 ----
 .../resources/EntityConfigResourceTest.java     | 172 -----
 .../rest/resources/EntityResourceTest.java      | 189 -----
 .../rest/resources/ErrorResponseTest.java       |  98 ---
 .../rest/resources/LocationResourceTest.java    | 189 -----
 .../rest/resources/PolicyResourceTest.java      | 145 ----
 .../rest/resources/ScriptResourceTest.java      |  54 --
 .../SensorResourceIntegrationTest.java          |  82 ---
 .../rest/resources/SensorResourceTest.java      | 271 -------
 .../ServerResourceIntegrationTest.java          |  12 +-
 .../rest/resources/ServerResourceTest.java      | 168 -----
 .../rest/resources/ServerShutdownTest.java      | 185 -----
 .../rest/resources/UsageResourceTest.java       | 443 ------------
 .../rest/resources/VersionResourceTest.java     |  52 --
 .../rest/security/PasswordHasherTest.java       |  37 -
 .../brooklynnode/DeployBlueprintTest.java       |  89 ---
 .../rest/testing/BrooklynRestApiTest.java       | 204 ------
 .../rest/testing/BrooklynRestResourceTest.java  | 154 ----
 .../rest/testing/mocks/CapitalizePolicy.java    |  33 -
 .../rest/testing/mocks/EverythingGroup.java     |  27 -
 .../rest/testing/mocks/EverythingGroupImpl.java |  32 -
 .../rest/testing/mocks/NameMatcherGroup.java    |  30 -
 .../testing/mocks/NameMatcherGroupImpl.java     |  33 -
 .../rest/testing/mocks/RestMockApp.java         |  24 -
 .../rest/testing/mocks/RestMockAppBuilder.java  |  39 --
 .../testing/mocks/RestMockSimpleEntity.java     | 103 ---
 .../testing/mocks/RestMockSimplePolicy.java     |  64 --
 .../util/BrooklynRestResourceUtilsTest.java     | 213 ------
 .../rest/util/EntityLocationUtilsTest.java      |  72 --
 .../rest/util/HaHotStateCheckClassResource.java |  38 -
 .../rest/util/HaHotStateCheckResource.java      |  44 --
 .../util/NullHttpServletRequestProvider.java    |  46 --
 .../rest/util/NullServletConfigProvider.java    |  51 --
 ...rooklynJacksonSerializerIntegrationTest.java | 173 +++++
 .../json/BrooklynJacksonSerializerTest.java     | 399 -----------
 .../resources/brooklyn/scanning.catalog.bom     |  19 -
 utils/rest-swagger/pom.xml                      |  28 +-
 .../rest/apidoc/ApiListingResource.java         | 260 -------
 .../rest/apidoc/RestApiResourceScanner.java     |  23 +-
 397 files changed, 26076 insertions(+), 14868 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/79b98c67/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ApplicationResource.java
----------------------------------------------------------------------
diff --cc rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ApplicationResource.java
index 0000000,986855e..f24dcf9
mode 000000,100644..100644
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ApplicationResource.java
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ApplicationResource.java
@@@ -1,0 -1,451 +1,474 @@@
+ /*
+  * Licensed to the Apache Software Foundation (ASF) under one
+  * or more contributor license agreements.  See the NOTICE file
+  * distributed with this work for additional information
+  * regarding copyright ownership.  The ASF licenses this file
+  * to you under the Apache License, Version 2.0 (the
+  * "License"); you may not use this file except in compliance
+  * with the License.  You may obtain a copy of the License at
+  *
+  *     http://www.apache.org/licenses/LICENSE-2.0
+  *
+  * Unless required by applicable law or agreed to in writing,
+  * software distributed under the License is distributed on an
+  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  * KIND, either express or implied.  See the License for the
+  * specific language governing permissions and limitations
+  * under the License.
+  */
+ package org.apache.brooklyn.rest.resources;
+ 
+ import static com.google.common.base.Preconditions.checkNotNull;
+ import static javax.ws.rs.core.Response.created;
+ import static javax.ws.rs.core.Response.status;
+ import static javax.ws.rs.core.Response.Status.ACCEPTED;
+ import static org.apache.brooklyn.rest.util.WebResourceUtils.serviceAbsoluteUriBuilder;
+ 
+ import java.net.URI;
+ import java.net.URISyntaxException;
+ import java.util.Collection;
+ import java.util.Collections;
+ import java.util.Iterator;
+ import java.util.List;
+ import java.util.Map;
+ 
+ import javax.ws.rs.core.Context;
+ import javax.ws.rs.core.Response;
+ import javax.ws.rs.core.Response.ResponseBuilder;
+ import javax.ws.rs.core.UriInfo;
+ 
+ import org.apache.brooklyn.api.entity.Application;
+ import org.apache.brooklyn.api.entity.Entity;
+ import org.apache.brooklyn.api.entity.EntitySpec;
+ import org.apache.brooklyn.api.entity.Group;
+ import org.apache.brooklyn.api.location.Location;
+ import org.apache.brooklyn.api.mgmt.Task;
+ import org.apache.brooklyn.api.objs.BrooklynObject;
+ import org.apache.brooklyn.api.sensor.AttributeSensor;
+ import org.apache.brooklyn.api.sensor.Sensor;
+ import org.apache.brooklyn.api.typereg.RegisteredType;
+ import org.apache.brooklyn.core.config.ConstraintViolationException;
+ import org.apache.brooklyn.core.entity.Attributes;
+ import org.apache.brooklyn.core.entity.EntityPredicates;
+ import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
+ import org.apache.brooklyn.core.entity.trait.Startable;
+ import org.apache.brooklyn.core.mgmt.EntityManagementUtils;
+ import org.apache.brooklyn.core.mgmt.EntityManagementUtils.CreationResult;
+ import org.apache.brooklyn.core.mgmt.entitlement.EntitlementPredicates;
+ import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
+ import org.apache.brooklyn.core.mgmt.entitlement.Entitlements.EntityAndItem;
+ import org.apache.brooklyn.core.mgmt.entitlement.Entitlements.StringAndArgument;
+ import org.apache.brooklyn.core.sensor.Sensors;
+ import org.apache.brooklyn.core.typereg.RegisteredTypeLoadingContexts;
+ import org.apache.brooklyn.core.typereg.RegisteredTypes;
+ import org.apache.brooklyn.entity.group.AbstractGroup;
+ import org.apache.brooklyn.rest.api.ApplicationApi;
+ import org.apache.brooklyn.rest.domain.ApplicationSpec;
+ import org.apache.brooklyn.rest.domain.ApplicationSummary;
+ import org.apache.brooklyn.rest.domain.EntityDetail;
+ import org.apache.brooklyn.rest.domain.EntitySummary;
+ import org.apache.brooklyn.rest.domain.TaskSummary;
+ import org.apache.brooklyn.rest.filter.HaHotStateRequired;
+ import org.apache.brooklyn.rest.transform.ApplicationTransformer;
+ import org.apache.brooklyn.rest.transform.EntityTransformer;
+ import org.apache.brooklyn.rest.transform.TaskTransformer;
+ import org.apache.brooklyn.rest.util.BrooklynRestResourceUtils;
+ import org.apache.brooklyn.rest.util.WebResourceUtils;
+ import org.apache.brooklyn.util.collections.MutableMap;
+ import org.apache.brooklyn.util.core.ResourceUtils;
+ import org.apache.brooklyn.util.exceptions.Exceptions;
+ import org.apache.brooklyn.util.exceptions.UserFacingException;
+ import org.apache.brooklyn.util.guava.Maybe;
+ import org.apache.brooklyn.util.javalang.JavaClassNames;
++import org.apache.brooklyn.util.net.Urls;
+ import org.apache.brooklyn.util.text.Strings;
+ import org.slf4j.Logger;
+ import org.slf4j.LoggerFactory;
+ 
 -import com.google.common.base.Throwables;
++import com.google.common.base.Preconditions;
+ import com.google.common.collect.FluentIterable;
+ import com.google.common.collect.ImmutableMap;
+ import com.google.common.collect.Iterables;
+ import com.google.common.collect.Lists;
+ 
+ @HaHotStateRequired
+ public class ApplicationResource extends AbstractBrooklynRestResource implements ApplicationApi {
+ 
+     private static final Logger log = LoggerFactory.getLogger(ApplicationResource.class);
+ 
+     @Context
+     private UriInfo uriInfo;
+ 
+     private EntityDetail fromEntity(Entity entity) {
+         Boolean serviceUp = entity.getAttribute(Attributes.SERVICE_UP);
+ 
+         Lifecycle serviceState = entity.getAttribute(Attributes.SERVICE_STATE_ACTUAL);
+ 
+         String iconUrl = entity.getIconUrl();
+         if (iconUrl!=null) {
+             if (brooklyn().isUrlServerSideAndSafe(iconUrl))
+                 // route to server if it is a server-side url
+                 iconUrl = EntityTransformer.entityUri(entity, ui.getBaseUriBuilder())+"/icon";
+         }
+ 
+         List<EntitySummary> children = Lists.newArrayList();
+         if (!entity.getChildren().isEmpty()) {
+             for (Entity child : entity.getChildren()) {
+                 children.add(fromEntity(child));
+             }
+         }
+ 
+         String parentId = null;
+         if (entity.getParent()!= null) {
+             parentId = entity.getParent().getId();
+         }
+ 
+         List<String> groupIds = Lists.newArrayList();
+         if (!entity.groups().isEmpty()) {
+             groupIds.addAll(entitiesIdAsArray(entity.groups()));
+         }
+ 
+         List<Map<String, String>> members = Lists.newArrayList();
+         if (entity instanceof Group) {
+             // use attribute instead of method in case it is read-only
+             Collection<Entity> memberEntities = entity.getAttribute(AbstractGroup.GROUP_MEMBERS);
+             if (memberEntities != null && !memberEntities.isEmpty())
+                 members.addAll(entitiesIdAndNameAsList(memberEntities));
+         }
+ 
+         return new EntityDetail(
+                 entity.getApplicationId(),
+                 entity.getId(),
+                 parentId,
+                 entity.getDisplayName(),
+                 entity.getEntityType().getName(),
+                 serviceUp,
+                 serviceState,
+                 iconUrl,
+                 entity.getCatalogItemId(),
+                 children,
+                 groupIds,
+                 members);
+     }
+ 
+     private List<Map<String, String>> entitiesIdAndNameAsList(Collection<? extends Entity> entities) {
+         List<Map<String, String>> members = Lists.newArrayList();
+         for (Entity entity : entities) {
+             if (Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY, entity)) {
+                 members.add(ImmutableMap.of("id", entity.getId(), "name", entity.getDisplayName()));
+             }
+         }
+         return members;
+     }
+ 
+     private List<String> entitiesIdAsArray(Iterable<? extends Entity> entities) {
+         List<String> ids = Lists.newArrayList();
+         for (Entity entity : entities) {
+             if (Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY, entity)) {
+                 ids.add(entity.getId());
+             }
+         }
+         return ids;
+     }
+ 
+     @Override
+     public List<EntityDetail> fetch(String entityIds) {
+ 
+         List<EntityDetail> entitySummaries = Lists.newArrayList();
+         for (Entity application : mgmt().getApplications()) {
+             entitySummaries.add(fromEntity(application));
+         }
+ 
+         if (entityIds != null) {
+             for (String entityId: entityIds.split(",")) {
+                 Entity entity = mgmt().getEntityManager().getEntity(entityId.trim());
+                 while (entity != null && entity.getParent() != null) {
+                     if (Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY, entity)) {
+                         entitySummaries.add(fromEntity(entity));
+                     }
+                     entity = entity.getParent();
+                 }
+             }
+         }
+         return entitySummaries;
+     }
+ 
+     @Override
+     public List<ApplicationSummary> list(String typeRegex) {
+         if (Strings.isBlank(typeRegex)) {
+             typeRegex = ".*";
+         }
+         return FluentIterable
+                 .from(mgmt().getApplications())
+                 .filter(EntitlementPredicates.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY))
+                 .filter(EntityPredicates.hasInterfaceMatching(typeRegex))
+                 .transform(ApplicationTransformer.fromApplication(ui.getBaseUriBuilder()))
+                 .toList();
+     }
+ 
+     @Override
+     public ApplicationSummary get(String application) {
+         return ApplicationTransformer.summaryFromApplication(brooklyn().getApplication(application), ui.getBaseUriBuilder());
+     }
+ 
+     @Override
+     public Response create(ApplicationSpec applicationSpec) {
+         return createFromAppSpec(applicationSpec);
+     }
+ 
+     /** @deprecated since 0.7.0 see #create */ @Deprecated
+     protected Response createFromAppSpec(ApplicationSpec applicationSpec) {
+         if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.DEPLOY_APPLICATION, applicationSpec)) {
+             throw WebResourceUtils.forbidden("User '%s' is not authorized to start application %s",
+                 Entitlements.getEntitlementContext().user(), applicationSpec);
+         }
+ 
+         checkApplicationTypesAreValid(applicationSpec);
+         checkLocationsAreValid(applicationSpec);
+         // TODO duplicate prevention
+         List<Location> locations = brooklyn().getLocations(applicationSpec);
+         Application app = brooklyn().create(applicationSpec);
+         Task<?> t = brooklyn().start(app, locations);
+         TaskSummary ts = TaskTransformer.fromTask(ui.getBaseUriBuilder()).apply(t);
+         URI ref = serviceAbsoluteUriBuilder(uriInfo.getBaseUriBuilder(), ApplicationApi.class, "get")
+                 .build(app.getApplicationId());
+         return created(ref).entity(ts).build();
+     }
+ 
+     @Override
+     public Response createFromYaml(String yaml) {
+         // First of all, see if it's a URL
 -        URI uri;
++        Preconditions.checkNotNull(yaml, "Blueprint must not be null");
++        URI uri = null;
+         try {
 -            uri = new URI(yaml);
++            String yamlUrl = yaml.trim();
++            if (Urls.isUrlWithProtocol(yamlUrl)) {
++                uri = new URI(yamlUrl);
++            }
+         } catch (URISyntaxException e) {
+             // It's not a URI then...
+             uri = null;
+         }
+         if (uri != null) {
+             log.debug("Create app called with URI; retrieving contents: {}", uri);
 -            yaml = ResourceUtils.create(mgmt()).getResourceAsString(uri.toString());
++            try {
++                yaml = ResourceUtils.create(mgmt()).getResourceAsString(uri.toString());
++            } catch (Exception e) {
++                Exceptions.propagateIfFatal(e);
++                throw new UserFacingException("Cannot resolve URL: "+uri, e);
++            }
+         }
+ 
+         log.debug("Creating app from yaml:\n{}", yaml);
 -        EntitySpec<? extends Application> spec = createEntitySpecForApplication(yaml);
+ 
++        EntitySpec<? extends Application> spec;
++        try {
++            spec = createEntitySpecForApplication(yaml);
++        } catch (Exception e) {
++            Exceptions.propagateIfFatal(e);
++            UserFacingException userFacing = Exceptions.getFirstThrowableOfType(e, UserFacingException.class);
++            if (userFacing!=null) {
++                log.debug("Throwing "+userFacing+", wrapped in "+e);
++                throw userFacing;
++            }
++            throw WebResourceUtils.badRequest(e, "Error in blueprint");
++        }
++        
+         if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.DEPLOY_APPLICATION, spec)) {
+             throw WebResourceUtils.forbidden("User '%s' is not authorized to start application %s",
+                 Entitlements.getEntitlementContext().user(), yaml);
+         }
+ 
 -        return launch(yaml, spec);
++        try {
++            return launch(yaml, spec);
++        } catch (Exception e) {
++            throw WebResourceUtils.badRequest(e, "Error launching blueprint");
++        }
+     }
+ 
+     private Response launch(String yaml, EntitySpec<? extends Application> spec) {
+         try {
+             Application app = EntityManagementUtils.createUnstarted(mgmt(), spec);
+             CreationResult<Application,Void> result = EntityManagementUtils.start(app);
+ 
+             boolean isEntitled = Entitlements.isEntitled(
+                     mgmt().getEntitlementManager(),
+                     Entitlements.INVOKE_EFFECTOR,
+                     EntityAndItem.of(app, StringAndArgument.of(Startable.START.getName(), null)));
+ 
+             if (!isEntitled) {
+                 throw WebResourceUtils.forbidden("User '%s' is not authorized to start application %s",
+                     Entitlements.getEntitlementContext().user(), spec.getType());
+             }
+ 
+             log.info("Launched from YAML: " + yaml + " -> " + app + " (" + result.task() + ")");
+ 
+             URI ref = serviceAbsoluteUriBuilder(ui.getBaseUriBuilder(), ApplicationApi.class, "get").build(app.getApplicationId());
+             ResponseBuilder response = created(ref);
+             if (result.task() != null)
+                 response.entity(TaskTransformer.fromTask(ui.getBaseUriBuilder()).apply(result.task()));
+             return response.build();
+         } catch (ConstraintViolationException e) {
+             throw new UserFacingException(e);
+         } catch (Exception e) {
+             throw Exceptions.propagate(e);
+         }
+     }
+ 
+     @Override
+     public Response createPoly(byte[] inputToAutodetectType) {
+         log.debug("Creating app from autodetecting input");
+ 
+         boolean looksLikeLegacy = false;
+         Exception legacyFormatException = null;
+         // attempt legacy format
+         try {
+             ApplicationSpec appSpec = mapper().readValue(inputToAutodetectType, ApplicationSpec.class);
+             if (appSpec.getType() != null || appSpec.getEntities() != null) {
+                 looksLikeLegacy = true;
+             }
+             return createFromAppSpec(appSpec);
+         } catch (Exception e) {
+             Exceptions.propagateIfFatal(e);
+             legacyFormatException = e;
+             log.debug("Input is not legacy ApplicationSpec JSON (will try others): "+e, e);
+         }
+ 
+         //TODO infer encoding from request
+         String potentialYaml = new String(inputToAutodetectType);
 -        EntitySpec<? extends Application> spec = createEntitySpecForApplication(potentialYaml);
++        EntitySpec<? extends Application> spec;
++        try {
++            spec = createEntitySpecForApplication(potentialYaml);
++        } catch (Exception e) {
++            Exceptions.propagateIfFatal(e);
++            
++            // TODO if not yaml/json - try ZIP, etc
++            
++            throw WebResourceUtils.badRequest(e, "Error in blueprint");
++        }
+ 
 -        // TODO not json - try ZIP, etc
+ 
+         if (spec != null) {
+             return launch(potentialYaml, spec);
+         } else if (looksLikeLegacy) {
 -            throw Throwables.propagate(legacyFormatException);
++            throw Exceptions.propagate(legacyFormatException);
+         } else {
+             return Response.serverError().entity("Unsupported format; not able to autodetect.").build();
+         }
+     }
+ 
+     @Override
+     public Response createFromForm(String contents) {
+         log.debug("Creating app from form");
+         return createPoly(contents.getBytes());
+     }
+ 
+     @Override
+     public Response delete(String application) {
+         Application app = brooklyn().getApplication(application);
+         if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.INVOKE_EFFECTOR, Entitlements.EntityAndItem.of(app,
+             StringAndArgument.of(Entitlements.LifecycleEffectors.DELETE, null)))) {
+             throw WebResourceUtils.forbidden("User '%s' is not authorized to delete application %s",
+                 Entitlements.getEntitlementContext().user(), app);
+         }
+         Task<?> t = brooklyn().destroy(app);
+         TaskSummary ts = TaskTransformer.fromTask(ui.getBaseUriBuilder()).apply(t);
+         return status(ACCEPTED).entity(ts).build();
+     }
+ 
+     private EntitySpec<? extends Application> createEntitySpecForApplication(String potentialYaml) {
 -        try {
 -            return EntityManagementUtils.createEntitySpecForApplication(mgmt(), potentialYaml);
 -        } catch (Exception e) {
 -            // An IllegalArgumentException for creating the entity spec gets wrapped in a ISE, and possibly a Compound.
 -            // But we want to return a 400 rather than 500, so ensure we throw IAE.
 -            IllegalArgumentException iae = (IllegalArgumentException) Exceptions.getFirstThrowableOfType(e, IllegalArgumentException.class);
 -            if (iae != null) {
 -                throw new IllegalArgumentException("Cannot create spec for app: "+iae.getMessage(), e);
 -            } else {
 -                throw Exceptions.propagate(e);
 -            }
 -        }
++        return EntityManagementUtils.createEntitySpecForApplication(mgmt(), potentialYaml);
+     }
+ 
+     private void checkApplicationTypesAreValid(ApplicationSpec applicationSpec) {
+         String appType = applicationSpec.getType();
+         if (appType != null) {
+             checkEntityTypeIsValid(appType);
+ 
+             if (applicationSpec.getEntities() != null) {
+                 throw WebResourceUtils.preconditionFailed("Application given explicit type '%s' must not define entities", appType);
+             }
+             return;
+         }
+ 
+         for (org.apache.brooklyn.rest.domain.EntitySpec entitySpec : applicationSpec.getEntities()) {
+             String entityType = entitySpec.getType();
+             checkEntityTypeIsValid(checkNotNull(entityType, "entityType"));
+         }
+     }
+ 
+     private void checkSpecTypeIsValid(String type, Class<? extends BrooklynObject> subType) {
+         Maybe<RegisteredType> typeV = RegisteredTypes.tryValidate(mgmt().getTypeRegistry().get(type), RegisteredTypeLoadingContexts.spec(subType));
+         if (!typeV.isNull()) {
+             // found, throw if any problem
+             typeV.get();
+             return;
+         }
+ 
+         // not found, try classloading
+         try {
+             brooklyn().getCatalogClassLoader().loadClass(type);
+         } catch (ClassNotFoundException e) {
+             log.debug("Class not found for type '" + type + "'; reporting 404", e);
+             throw WebResourceUtils.notFound("Undefined type '%s'", type);
+         }
+         log.info(JavaClassNames.simpleClassName(subType)+" type '{}' not defined in catalog but is on classpath; continuing", type);
+     }
+ 
+     private void checkEntityTypeIsValid(String type) {
+         checkSpecTypeIsValid(type, Entity.class);
+     }
+ 
+     @SuppressWarnings("deprecation")
+     private void checkLocationsAreValid(ApplicationSpec applicationSpec) {
+         for (String locationId : applicationSpec.getLocations()) {
+             locationId = BrooklynRestResourceUtils.fixLocation(locationId);
+             if (!brooklyn().getLocationRegistry().canMaybeResolve(locationId) && brooklyn().getLocationRegistry().getDefinedLocationById(locationId)==null) {
+                 throw WebResourceUtils.notFound("Undefined location '%s'", locationId);
+             }
+         }
+     }
+ 
+     @Override
+     public List<EntitySummary> getDescendants(String application, String typeRegex) {
+         return EntityTransformer.entitySummaries(brooklyn().descendantsOfType(application, application, typeRegex), ui.getBaseUriBuilder());
+     }
+ 
+     @Override
+     public Map<String, Object> getDescendantsSensor(String application, String sensor, String typeRegex) {
+         Iterable<Entity> descs = brooklyn().descendantsOfType(application, application, typeRegex);
+         return getSensorMap(sensor, descs);
+     }
+ 
+     public static Map<String, Object> getSensorMap(String sensor, Iterable<Entity> descs) {
+         if (Iterables.isEmpty(descs))
+             return Collections.emptyMap();
+         Map<String, Object> result = MutableMap.of();
+         Iterator<Entity> di = descs.iterator();
+         Sensor<?> s = null;
+         while (di.hasNext()) {
+             Entity potentialSource = di.next();
+             s = potentialSource.getEntityType().getSensor(sensor);
+             if (s!=null) break;
+         }
+         if (s==null)
+             s = Sensors.newSensor(Object.class, sensor);
+         if (!(s instanceof AttributeSensor<?>)) {
+             log.warn("Cannot retrieve non-attribute sensor "+s+" for entities; returning empty map");
+             return result;
+         }
+         for (Entity e: descs) {
+             Object v = null;
+             try {
+                 v = e.getAttribute((AttributeSensor<?>)s);
+             } catch (Exception exc) {
+                 Exceptions.propagateIfFatal(exc);
+                 log.warn("Error retrieving sensor "+s+" for "+e+" (ignoring): "+exc);
+             }
+             if (v!=null)
+                 result.put(e.getId(), v);
+         }
+         return result;
+     }
+ 
+ }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/79b98c67/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java
----------------------------------------------------------------------
diff --cc rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java
index 0000000,d7b206a..77f8470
mode 000000,100644..100644
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java
@@@ -1,0 -1,509 +1,516 @@@
+ /*
+  * Licensed to the Apache Software Foundation (ASF) under one
+  * or more contributor license agreements.  See the NOTICE file
+  * distributed with this work for additional information
+  * regarding copyright ownership.  The ASF licenses this file
+  * to you under the Apache License, Version 2.0 (the
+  * "License"); you may not use this file except in compliance
+  * with the License.  You may obtain a copy of the License at
+  *
+  *     http://www.apache.org/licenses/LICENSE-2.0
+  *
+  * Unless required by applicable law or agreed to in writing,
+  * software distributed under the License is distributed on an
+  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  * KIND, either express or implied.  See the License for the
+  * specific language governing permissions and limitations
+  * under the License.
+  */
+ package org.apache.brooklyn.rest.resources;
+ 
+ import java.net.URI;
+ import java.util.ArrayList;
+ import java.util.Iterator;
+ import java.util.List;
+ import java.util.Map;
+ import java.util.NoSuchElementException;
+ import java.util.Set;
+ 
+ import javax.annotation.Nullable;
+ import javax.ws.rs.core.MediaType;
+ import javax.ws.rs.core.Response;
+ import javax.ws.rs.core.Response.Status;
+ 
+ import org.apache.brooklyn.api.catalog.CatalogItem;
+ import org.apache.brooklyn.api.entity.Application;
+ import org.apache.brooklyn.api.entity.Entity;
+ import org.apache.brooklyn.api.entity.EntitySpec;
+ import org.apache.brooklyn.api.location.Location;
+ import org.apache.brooklyn.api.location.LocationSpec;
+ import org.apache.brooklyn.api.policy.Policy;
+ import org.apache.brooklyn.api.policy.PolicySpec;
+ import org.apache.brooklyn.api.typereg.RegisteredType;
+ import org.apache.brooklyn.core.catalog.CatalogPredicates;
+ import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog;
+ import org.apache.brooklyn.core.catalog.internal.CatalogDto;
+ import org.apache.brooklyn.core.catalog.internal.CatalogItemComparator;
+ import org.apache.brooklyn.core.catalog.internal.CatalogUtils;
+ import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
+ import org.apache.brooklyn.core.mgmt.entitlement.Entitlements.StringAndArgument;
+ import org.apache.brooklyn.core.typereg.RegisteredTypeLoadingContexts;
+ import org.apache.brooklyn.core.typereg.RegisteredTypePredicates;
+ import org.apache.brooklyn.core.typereg.RegisteredTypes;
+ import org.apache.brooklyn.rest.api.CatalogApi;
+ import org.apache.brooklyn.rest.domain.ApiError;
+ import org.apache.brooklyn.rest.domain.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.filter.HaHotStateRequired;
+ import org.apache.brooklyn.rest.transform.CatalogTransformer;
+ import org.apache.brooklyn.rest.util.WebResourceUtils;
+ import org.apache.brooklyn.util.collections.MutableMap;
+ import org.apache.brooklyn.util.collections.MutableSet;
+ import org.apache.brooklyn.util.core.ResourceUtils;
+ import org.apache.brooklyn.util.exceptions.Exceptions;
+ import org.apache.brooklyn.util.text.StringPredicates;
+ import org.apache.brooklyn.util.text.Strings;
+ import org.slf4j.Logger;
+ import org.slf4j.LoggerFactory;
+ 
+ import com.google.common.base.Function;
+ import com.google.common.base.Predicate;
+ import com.google.common.base.Predicates;
+ import com.google.common.collect.FluentIterable;
+ import com.google.common.collect.ImmutableList;
+ import com.google.common.collect.Lists;
+ import com.google.common.io.Files;
+ import javax.ws.rs.core.UriInfo;
+ import org.apache.brooklyn.util.guava.Maybe;
+ 
+ @HaHotStateRequired
+ public class CatalogResource extends AbstractBrooklynRestResource implements CatalogApi {
+ 
+     private static final Logger log = LoggerFactory.getLogger(CatalogResource.class);
+     
+     @SuppressWarnings("rawtypes")
+     private Function<CatalogItem, CatalogItemSummary> toCatalogItemSummary(final UriInfo ui) {
+         return new Function<CatalogItem, CatalogItemSummary>() {
+             @Override
+             public CatalogItemSummary apply(@Nullable CatalogItem input) {
+                 return CatalogTransformer.catalogItemSummary(brooklyn(), input, ui.getBaseUriBuilder());
+             }
+         };
+     };
+ 
+     static Set<String> missingIcons = MutableSet.of();
+     
+     @Override
+     public Response create(String yaml) {
+         if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.ADD_CATALOG_ITEM, yaml)) {
+             throw WebResourceUtils.forbidden("User '%s' is not authorized to add catalog item",
+                 Entitlements.getEntitlementContext().user());
+         }
+         
+         Iterable<? extends CatalogItem<?, ?>> items; 
+         try {
+             items = brooklyn().getCatalog().addItems(yaml);
 -        } catch (IllegalArgumentException e) {
 -            return Response.status(Status.BAD_REQUEST)
 -                    .type(MediaType.APPLICATION_JSON)
 -                    .entity(ApiError.of(e))
 -                    .build();
++        } catch (Exception e) {
++            Exceptions.propagateIfFatal(e);
++            return ApiError.of(e).asBadRequestResponseJson();
+         }
+ 
+         log.info("REST created catalog items: "+items);
+ 
+         Map<String,Object> result = MutableMap.of();
+         
+         for (CatalogItem<?,?> item: items) {
 -            result.put(item.getId(), CatalogTransformer.catalogItemSummary(brooklyn(), item, ui.getBaseUriBuilder()));
++            try {
++                result.put(item.getId(), CatalogTransformer.catalogItemSummary(brooklyn(), item, ui.getBaseUriBuilder()));
++            } catch (Throwable t) {
++                log.warn("Error loading catalog item '"+item+"' (rethrowing): "+t);
++                // unfortunately items are already added to the catalog and hard to remove,
++                // but at least let the user know;
++                // happens eg if a class refers to a missing class, like 
++                // loading nosql items including mongo without the mongo bson class on the classpath 
++                throw Exceptions.propagateAnnotated("At least one unusable item was added ("+item.getId()+")", t);
++            }
+         }
+         return Response.status(Status.CREATED).entity(result).build();
+     }
+ 
+     @SuppressWarnings("deprecation")
+     @Override
+     public Response resetXml(String xml, boolean ignoreErrors) {
+         if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_CATALOG_ITEM, null) ||
+             !Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.ADD_CATALOG_ITEM, null)) {
+             throw WebResourceUtils.forbidden("User '%s' is not authorized to modify catalog",
+                 Entitlements.getEntitlementContext().user());
+         }
+ 
+         ((BasicBrooklynCatalog)mgmt().getCatalog()).reset(CatalogDto.newDtoFromXmlContents(xml, "REST reset"), !ignoreErrors);
+         return Response.ok().build();
+     }
+     
+     @Override
+     @Deprecated
+     public void deleteEntity_0_7_0(String entityId) throws Exception {
+         if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_CATALOG_ITEM, StringAndArgument.of(entityId, "delete"))) {
+             throw WebResourceUtils.forbidden("User '%s' is not authorized to modify catalog",
+                 Entitlements.getEntitlementContext().user());
+         }
+         try {
+             Maybe<RegisteredType> item = RegisteredTypes.tryValidate(mgmt().getTypeRegistry().get(entityId), RegisteredTypeLoadingContexts.spec(Entity.class));
+             if (item.isNull()) {
+                 throw WebResourceUtils.notFound("Entity with id '%s' not found", entityId);
+             }
+             if (item.isAbsent()) {
+                 throw WebResourceUtils.notFound("Item with id '%s' is not an entity", entityId);
+             }
+ 
+             brooklyn().getCatalog().deleteCatalogItem(item.get().getSymbolicName(), item.get().getVersion());
+ 
+         } catch (NoSuchElementException e) {
+             // shouldn't come here
+             throw WebResourceUtils.notFound("Entity with id '%s' could not be deleted", entityId);
+ 
+         }
+     }
+ 
+     @Override
+     public void deleteApplication(String symbolicName, String version) throws Exception {
+         deleteEntity(symbolicName, version);
+     }
+ 
+     @Override
+     public void deleteEntity(String symbolicName, String version) throws Exception {
+         if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_CATALOG_ITEM, StringAndArgument.of(symbolicName+(Strings.isBlank(version) ? "" : ":"+version), "delete"))) {
+             throw WebResourceUtils.forbidden("User '%s' is not authorized to modify catalog",
+                 Entitlements.getEntitlementContext().user());
+         }
+         
+         RegisteredType item = mgmt().getTypeRegistry().get(symbolicName, version);
+         if (item == null) {
+             throw WebResourceUtils.notFound("Entity with id '%s:%s' not found", symbolicName, version);
+         } else if (!RegisteredTypePredicates.IS_ENTITY.apply(item) && !RegisteredTypePredicates.IS_APPLICATION.apply(item)) {
+             throw WebResourceUtils.preconditionFailed("Item with id '%s:%s' not an entity", symbolicName, version);
+         } else {
+             brooklyn().getCatalog().deleteCatalogItem(item.getSymbolicName(), item.getVersion());
+         }
+     }
+ 
+     @Override
+     public void deletePolicy(String policyId, String version) throws Exception {
+         if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_CATALOG_ITEM, StringAndArgument.of(policyId+(Strings.isBlank(version) ? "" : ":"+version), "delete"))) {
+             throw WebResourceUtils.forbidden("User '%s' is not authorized to modify catalog",
+                 Entitlements.getEntitlementContext().user());
+         }
+         
+         RegisteredType item = mgmt().getTypeRegistry().get(policyId, version);
+         if (item == null) {
+             throw WebResourceUtils.notFound("Policy with id '%s:%s' not found", policyId, version);
+         } else if (!RegisteredTypePredicates.IS_POLICY.apply(item)) {
+             throw WebResourceUtils.preconditionFailed("Item with id '%s:%s' not a policy", policyId, version);
+         } else {
+             brooklyn().getCatalog().deleteCatalogItem(item.getSymbolicName(), item.getVersion());
+         }
+     }
+ 
+     @Override
+     public void deleteLocation(String locationId, String version) throws Exception {
+         if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_CATALOG_ITEM, StringAndArgument.of(locationId+(Strings.isBlank(version) ? "" : ":"+version), "delete"))) {
+             throw WebResourceUtils.forbidden("User '%s' is not authorized to modify catalog",
+                 Entitlements.getEntitlementContext().user());
+         }
+         
+         RegisteredType item = mgmt().getTypeRegistry().get(locationId, version);
+         if (item == null) {
+             throw WebResourceUtils.notFound("Location with id '%s:%s' not found", locationId, version);
+         } else if (!RegisteredTypePredicates.IS_LOCATION.apply(item)) {
+             throw WebResourceUtils.preconditionFailed("Item with id '%s:%s' not a location", locationId, version);
+         } else {
+             brooklyn().getCatalog().deleteCatalogItem(item.getSymbolicName(), item.getVersion());
+         }
+     }
+ 
+     @Override
+     public List<CatalogEntitySummary> listEntities(String regex, String fragment, boolean allVersions) {
+         Predicate<CatalogItem<Entity, EntitySpec<?>>> filter =
+                 Predicates.and(
+                         CatalogPredicates.IS_ENTITY,
+                         CatalogPredicates.<Entity, EntitySpec<?>>disabled(false));
+         List<CatalogItemSummary> result = getCatalogItemSummariesMatchingRegexFragment(filter, regex, fragment, allVersions);
+         return castList(result, CatalogEntitySummary.class);
+     }
+ 
+     @Override
+     public List<CatalogItemSummary> listApplications(String regex, String fragment, boolean allVersions) {
+         @SuppressWarnings("unchecked")
+         Predicate<CatalogItem<Application, EntitySpec<? extends Application>>> filter =
+                 Predicates.and(
+                         CatalogPredicates.IS_TEMPLATE,
+                         CatalogPredicates.<Application,EntitySpec<? extends Application>>deprecated(false),
+                         CatalogPredicates.<Application,EntitySpec<? extends Application>>disabled(false));
+         return getCatalogItemSummariesMatchingRegexFragment(filter, regex, fragment, allVersions);
+     }
+ 
+     @Override
+     @Deprecated
+     public CatalogEntitySummary getEntity_0_7_0(String entityId) {
+         if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CATALOG_ITEM, entityId)) {
+             throw WebResourceUtils.forbidden("User '%s' is not authorized to see catalog entry",
+                 Entitlements.getEntitlementContext().user());
+         }
+ 
+         CatalogItem<Entity,EntitySpec<?>> result =
+                 CatalogUtils.getCatalogItemOptionalVersion(mgmt(), Entity.class, entityId);
+ 
+         if (result==null) {
+             throw WebResourceUtils.notFound("Entity with id '%s' not found", entityId);
+         }
+ 
+         return CatalogTransformer.catalogEntitySummary(brooklyn(), result, ui.getBaseUriBuilder());
+     }
+     
+     @Override
+     public CatalogEntitySummary getEntity(String symbolicName, String version) {
+         if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CATALOG_ITEM, symbolicName+(Strings.isBlank(version)?"":":"+version))) {
+             throw WebResourceUtils.forbidden("User '%s' is not authorized to see catalog entry",
+                 Entitlements.getEntitlementContext().user());
+         }
+ 
+         //TODO These casts are not pretty, we could just provide separate get methods for the different types?
+         //Or we could provide asEntity/asPolicy cast methods on the CataloItem doing a safety check internally
+         @SuppressWarnings("unchecked")
+         CatalogItem<Entity, EntitySpec<?>> result =
+               (CatalogItem<Entity, EntitySpec<?>>) brooklyn().getCatalog().getCatalogItem(symbolicName, version);
+ 
+         if (result==null) {
+             throw WebResourceUtils.notFound("Entity with id '%s:%s' not found", symbolicName, version);
+         }
+ 
+         return CatalogTransformer.catalogEntitySummary(brooklyn(), result, ui.getBaseUriBuilder());
+     }
+ 
+     @Override
+     @Deprecated
+     public CatalogEntitySummary getApplication_0_7_0(String applicationId) throws Exception {
+         return getEntity_0_7_0(applicationId);
+     }
+ 
+     @Override
+     public CatalogEntitySummary getApplication(String symbolicName, String version) {
+         return getEntity(symbolicName, version);
+     }
+ 
+     @Override
+     public List<CatalogPolicySummary> listPolicies(String regex, String fragment, boolean allVersions) {
+         Predicate<CatalogItem<Policy, PolicySpec<?>>> filter =
+                 Predicates.and(
+                         CatalogPredicates.IS_POLICY,
+                         CatalogPredicates.<Policy, PolicySpec<?>>disabled(false));
+         List<CatalogItemSummary> result = getCatalogItemSummariesMatchingRegexFragment(filter, regex, fragment, allVersions);
+         return castList(result, CatalogPolicySummary.class);
+     }
+ 
+     @Override
+     @Deprecated
+     public CatalogPolicySummary getPolicy_0_7_0(String policyId) {
+         if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CATALOG_ITEM, policyId)) {
+             throw WebResourceUtils.forbidden("User '%s' is not authorized to see catalog entry",
+                 Entitlements.getEntitlementContext().user());
+         }
+ 
+         CatalogItem<? extends Policy, PolicySpec<?>> result =
+             CatalogUtils.getCatalogItemOptionalVersion(mgmt(), Policy.class, policyId);
+ 
+         if (result==null) {
+             throw WebResourceUtils.notFound("Policy with id '%s' not found", policyId);
+         }
+ 
+         return CatalogTransformer.catalogPolicySummary(brooklyn(), result, ui.getBaseUriBuilder());
+     }
+ 
+     @Override
+     public CatalogPolicySummary getPolicy(String policyId, String version) throws Exception {
+         if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CATALOG_ITEM, policyId+(Strings.isBlank(version)?"":":"+version))) {
+             throw WebResourceUtils.forbidden("User '%s' is not authorized to see catalog entry",
+                 Entitlements.getEntitlementContext().user());
+         }
+ 
+         @SuppressWarnings("unchecked")
+         CatalogItem<? extends Policy, PolicySpec<?>> result =
+                 (CatalogItem<? extends Policy, PolicySpec<?>>)brooklyn().getCatalog().getCatalogItem(policyId, version);
+ 
+         if (result==null) {
+           throw WebResourceUtils.notFound("Policy with id '%s:%s' not found", policyId, version);
+         }
+ 
+         return CatalogTransformer.catalogPolicySummary(brooklyn(), result, ui.getBaseUriBuilder());
+     }
+ 
+     @Override
+     public List<CatalogLocationSummary> listLocations(String regex, String fragment, boolean allVersions) {
+         Predicate<CatalogItem<Location, LocationSpec<?>>> filter =
+                 Predicates.and(
+                         CatalogPredicates.IS_LOCATION,
+                         CatalogPredicates.<Location, LocationSpec<?>>disabled(false));
+         List<CatalogItemSummary> result = getCatalogItemSummariesMatchingRegexFragment(filter, regex, fragment, allVersions);
+         return castList(result, CatalogLocationSummary.class);
+     }
+ 
+     @Override
+     @Deprecated
+     public CatalogLocationSummary getLocation_0_7_0(String locationId) {
+         if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CATALOG_ITEM, locationId)) {
+             throw WebResourceUtils.forbidden("User '%s' is not authorized to see catalog entry",
+                 Entitlements.getEntitlementContext().user());
+         }
+ 
+         CatalogItem<? extends Location, LocationSpec<?>> result =
+             CatalogUtils.getCatalogItemOptionalVersion(mgmt(), Location.class, locationId);
+ 
+         if (result==null) {
+             throw WebResourceUtils.notFound("Location with id '%s' not found", locationId);
+         }
+ 
+         return CatalogTransformer.catalogLocationSummary(brooklyn(), result, ui.getBaseUriBuilder());
+     }
+ 
+     @Override
+     public CatalogLocationSummary getLocation(String locationId, String version) throws Exception {
+         if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CATALOG_ITEM, locationId+(Strings.isBlank(version)?"":":"+version))) {
+             throw WebResourceUtils.forbidden("User '%s' is not authorized to see catalog entry",
+                 Entitlements.getEntitlementContext().user());
+         }
+ 
+         @SuppressWarnings("unchecked")
+         CatalogItem<? extends Location, LocationSpec<?>> result =
+                 (CatalogItem<? extends Location, LocationSpec<?>>)brooklyn().getCatalog().getCatalogItem(locationId, version);
+ 
+         if (result==null) {
+           throw WebResourceUtils.notFound("Location with id '%s:%s' not found", locationId, version);
+         }
+ 
+         return CatalogTransformer.catalogLocationSummary(brooklyn(), result, ui.getBaseUriBuilder());
+     }
+ 
+     @SuppressWarnings({ "unchecked", "rawtypes" })
+     private <T,SpecT> List<CatalogItemSummary> getCatalogItemSummariesMatchingRegexFragment(Predicate<CatalogItem<T,SpecT>> type, String regex, String fragment, boolean allVersions) {
+         List filters = new ArrayList();
+         filters.add(type);
+         if (Strings.isNonEmpty(regex))
+             filters.add(CatalogPredicates.xml(StringPredicates.containsRegex(regex)));
+         if (Strings.isNonEmpty(fragment))
+             filters.add(CatalogPredicates.xml(StringPredicates.containsLiteralIgnoreCase(fragment)));
+         if (!allVersions)
+             filters.add(CatalogPredicates.isBestVersion(mgmt()));
+         
+         filters.add(CatalogPredicates.entitledToSee(mgmt()));
+ 
+         ImmutableList<CatalogItem<Object, Object>> sortedItems =
+                 FluentIterable.from(brooklyn().getCatalog().getCatalogItems())
+                     .filter(Predicates.and(filters))
+                     .toSortedList(CatalogItemComparator.getInstance());
+         return Lists.transform(sortedItems, toCatalogItemSummary(ui));
+     }
+ 
+     @Override
+     @Deprecated
+     public Response getIcon_0_7_0(String itemId) {
+         if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CATALOG_ITEM, itemId)) {
+             throw WebResourceUtils.forbidden("User '%s' is not authorized to see catalog entry",
+                 Entitlements.getEntitlementContext().user());
+         }
+ 
+         return getCatalogItemIcon( mgmt().getTypeRegistry().get(itemId) );
+     }
+ 
+     @Override
+     public Response getIcon(String itemId, String version) {
+         if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CATALOG_ITEM, itemId+(Strings.isBlank(version)?"":":"+version))) {
+             throw WebResourceUtils.forbidden("User '%s' is not authorized to see catalog entry",
+                 Entitlements.getEntitlementContext().user());
+         }
+         
+         return getCatalogItemIcon(mgmt().getTypeRegistry().get(itemId, version));
+     }
+ 
+     @Override
+     public void setDeprecatedLegacy(String itemId, boolean deprecated) {
+         log.warn("Use of deprecated \"/catalog/entities/{itemId}/deprecated/{deprecated}\" for "+itemId
+                 +"; use \"/catalog/entities/{itemId}/deprecated\" with request body");
+         setDeprecated(itemId, deprecated);
+     }
+     
+     @SuppressWarnings("deprecation")
+     @Override
+     public void setDeprecated(String itemId, boolean deprecated) {
+         if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_CATALOG_ITEM, StringAndArgument.of(itemId, "deprecated"))) {
+             throw WebResourceUtils.forbidden("User '%s' is not authorized to modify catalog",
+                     Entitlements.getEntitlementContext().user());
+         }
+         CatalogUtils.setDeprecated(mgmt(), itemId, deprecated);
+     }
+ 
+     @SuppressWarnings("deprecation")
+     @Override
+     public void setDisabled(String itemId, boolean disabled) {
+         if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_CATALOG_ITEM, StringAndArgument.of(itemId, "disabled"))) {
+             throw WebResourceUtils.forbidden("User '%s' is not authorized to modify catalog",
+                     Entitlements.getEntitlementContext().user());
+         }
+         CatalogUtils.setDisabled(mgmt(), itemId, disabled);
+     }
+ 
+     private Response getCatalogItemIcon(RegisteredType result) {
+         String url = result.getIconUrl();
+         if (url==null) {
+             log.debug("No icon available for "+result+"; returning "+Status.NO_CONTENT);
+             return Response.status(Status.NO_CONTENT).build();
+         }
+         
+         if (brooklyn().isUrlServerSideAndSafe(url)) {
+             // classpath URL's we will serve IF they end with a recognised image format;
+             // paths (ie non-protocol) and 
+             // NB, for security, file URL's are NOT served
+             log.debug("Loading and returning "+url+" as icon for "+result);
+             
+             MediaType mime = WebResourceUtils.getImageMediaTypeFromExtension(Files.getFileExtension(url));
+             try {
+                 Object content = ResourceUtils.create(CatalogUtils.newClassLoadingContext(mgmt(), result)).getResourceFromUrl(url);
+                 return Response.ok(content, mime).build();
+             } catch (Exception e) {
+                 Exceptions.propagateIfFatal(e);
+                 synchronized (missingIcons) {
+                     if (missingIcons.add(url)) {
+                         // note: this can be quite common when running from an IDE, as resources may not be copied;
+                         // a mvn build should sort it out (the IDE will then find the resources, until you clean or maybe refresh...)
+                         log.warn("Missing icon data for "+result.getId()+", expected at: "+url+" (subsequent messages will log debug only)");
+                         log.debug("Trace for missing icon data at "+url+": "+e, e);
+                     } else {
+                         log.debug("Missing icon data for "+result.getId()+", expected at: "+url+" (already logged WARN and error details)");
+                     }
+                 }
+                 throw WebResourceUtils.notFound("Icon unavailable for %s", result.getId());
+             }
+         }
+         
+         log.debug("Returning redirect to "+url+" as icon for "+result);
+         
+         // for anything else we do a redirect (e.g. http / https; perhaps ftp)
+         return Response.temporaryRedirect(URI.create(url)).build();
+     }
+ 
+     // TODO Move to an appropriate utility class?
+     @SuppressWarnings("unchecked")
+     private static <T> List<T> castList(List<? super T> list, Class<T> elementType) {
+         List<T> result = Lists.newArrayList();
+         Iterator<? super T> li = list.iterator();
+         while (li.hasNext()) {
+             try {
+                 result.add((T) li.next());
+             } catch (Throwable throwable) {
+                 if (throwable instanceof NoClassDefFoundError) {
+                     // happens if class cannot be loaded for any reason during transformation - don't treat as fatal
+                 } else {
+                     Exceptions.propagateIfFatal(throwable);
+                 }
+                 
+                 // item cannot be transformed; we will have logged a warning earlier
+                 log.debug("Ignoring invalid catalog item: "+throwable);
+             }
+         }
+         return result;
+     }
+ }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/79b98c67/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/DefaultExceptionMapper.java
----------------------------------------------------------------------
diff --cc rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/DefaultExceptionMapper.java
index 0000000,1926d5e..6797a64
mode 000000,100644..100644
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/DefaultExceptionMapper.java
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/DefaultExceptionMapper.java
@@@ -1,0 -1,111 +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.util;
+ 
+ import java.util.Set;
+ 
+ import javax.ws.rs.WebApplicationException;
+ import javax.ws.rs.core.MediaType;
+ import javax.ws.rs.core.Response;
+ import javax.ws.rs.core.Response.Status;
+ import javax.ws.rs.ext.ExceptionMapper;
+ import javax.ws.rs.ext.Provider;
+ 
+ import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
+ import org.apache.brooklyn.rest.domain.ApiError;
+ import org.apache.brooklyn.rest.domain.ApiError.Builder;
+ import org.apache.brooklyn.util.collections.MutableSet;
+ import org.apache.brooklyn.util.core.flags.ClassCoercionException;
+ import org.apache.brooklyn.util.exceptions.Exceptions;
+ import org.apache.brooklyn.util.exceptions.UserFacingException;
+ import org.apache.brooklyn.util.text.Strings;
+ import org.slf4j.Logger;
+ import org.slf4j.LoggerFactory;
+ import org.yaml.snakeyaml.error.YAMLException;
+ 
+ @Provider
+ public class DefaultExceptionMapper implements ExceptionMapper<Throwable> {
+ 
+     private static final Logger LOG = LoggerFactory.getLogger(DefaultExceptionMapper.class);
+ 
+     static Set<Class<?>> warnedUnknownExceptions = MutableSet.of();
+     
+     /**
+      * Maps a throwable to a response.
+      * <p/>
+      * Returns {@link WebApplicationException#getResponse} if the exception is an instance of
+      * {@link WebApplicationException}. Otherwise maps known exceptions to responses. If no
+      * mapping is found a {@link Status#INTERNAL_SERVER_ERROR} is assumed.
+      */
+     @Override
+     public Response toResponse(Throwable throwable1) {
+         // EofException is thrown when the connection is reset,
+         // for example when refreshing the browser window.
+         // Don't depend on jetty, could be running in other environments as well.
+         if (throwable1.getClass().getName().equals("org.eclipse.jetty.io.EofException")) {
+             if (LOG.isTraceEnabled()) {
+                 LOG.trace("REST request running as {} threw: {}", Entitlements.getEntitlementContext(), 
+                         Exceptions.collapse(throwable1));
+             }
+             return null;
+         }
+ 
+         LOG.debug("REST request running as {} threw: {}", Entitlements.getEntitlementContext(), 
+             Exceptions.collapse(throwable1));
+         if (LOG.isTraceEnabled()) {
+             LOG.trace("Full details of "+Entitlements.getEntitlementContext()+" "+throwable1, throwable1);
+         }
+ 
+         Throwable throwable2 = Exceptions.getFirstInteresting(throwable1);
+         // Some methods will throw this, which gets converted automatically
+         if (throwable2 instanceof WebApplicationException) {
+             WebApplicationException wae = (WebApplicationException) throwable2;
+             return wae.getResponse();
+         }
+ 
+         // The nicest way for methods to provide errors, wrap as this, and the stack trace will be suppressed
+         if (throwable2 instanceof UserFacingException) {
+             return ApiError.of(throwable2.getMessage()).asBadRequestResponseJson();
+         }
+ 
+         // For everything else, a trace is supplied
+         
+         // Assume ClassCoercionExceptions are caused by TypeCoercions from input parameters gone wrong
+         // And IllegalArgumentException for malformed input parameters.
+         if (throwable2 instanceof ClassCoercionException || throwable2 instanceof IllegalArgumentException) {
+             return ApiError.of(throwable2).asBadRequestResponseJson();
+         }
+ 
+         // YAML exception 
+         if (throwable2 instanceof YAMLException) {
+             return ApiError.builder().message(throwable2.getMessage()).prefixMessage("Invalid YAML").build().asBadRequestResponseJson();
+         }
+ 
+         if (!Exceptions.isPrefixBoring(throwable2)) {
+             if ( warnedUnknownExceptions.add( throwable2.getClass() )) {
+                 LOG.warn("REST call generated exception type "+throwable2.getClass()+" unrecognized in "+getClass()+" (subsequent occurrences will be logged debug only): " + throwable2, throwable2);
+             }
+         }
++
++        // Before saying server error, look for a user-facing exception anywhere in the hierarchy
++        UserFacingException userFacing = Exceptions.getFirstThrowableOfType(throwable1, UserFacingException.class);
++        if (userFacing instanceof UserFacingException) {
++            return ApiError.of(userFacing.getMessage()).asBadRequestResponseJson();
++        }
+         
+         Builder rb = ApiError.builderFromThrowable(Exceptions.collapse(throwable2));
+         if (Strings.isBlank(rb.getMessage()))
+             rb.message("Internal error. Contact server administrator to consult logs for more details.");
+         return rb.build().asResponse(Status.INTERNAL_SERVER_ERROR, MediaType.APPLICATION_JSON_TYPE);
+     }
+ }


[11/34] brooklyn-server git commit: [BROOKLYN-183] REST API using CXF JAX-RS 2.0 implementation

Posted by he...@apache.org.
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/UsageResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/UsageResourceTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/UsageResourceTest.java
new file mode 100644
index 0000000..21fd246
--- /dev/null
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/UsageResourceTest.java
@@ -0,0 +1,444 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+
+import javax.ws.rs.core.Response;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.location.LocationSpec;
+import org.apache.brooklyn.api.location.NoMachinesAvailableException;
+import org.apache.brooklyn.core.mgmt.internal.LocalUsageManager;
+import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
+import org.apache.brooklyn.core.test.entity.TestApplication;
+import org.apache.brooklyn.entity.software.base.SoftwareProcessEntityTest;
+import org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation;
+import org.apache.brooklyn.location.ssh.SshMachineLocation;
+import org.apache.brooklyn.rest.domain.ApplicationSpec;
+import org.apache.brooklyn.rest.domain.Status;
+import org.apache.brooklyn.rest.domain.TaskSummary;
+import org.apache.brooklyn.rest.domain.UsageStatistic;
+import org.apache.brooklyn.rest.domain.UsageStatistics;
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+import org.apache.brooklyn.rest.testing.mocks.RestMockSimpleEntity;
+import org.apache.brooklyn.util.repeat.Repeater;
+import org.apache.brooklyn.util.time.Time;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import javax.ws.rs.core.GenericType;
+
+@Test( // by using a different suite name we disallow interleaving other tests between the methods of this test class, which wrecks the test fixtures
+        suiteName = "UsageResourceTest")
+public class UsageResourceTest extends BrooklynRestResourceTest {
+
+    @SuppressWarnings("unused")
+    private static final Logger LOG = LoggerFactory.getLogger(UsageResourceTest.class);
+
+    private static final long TIMEOUT_MS = 10*1000;
+    
+    private Calendar testStartTime;
+    
+    private final ApplicationSpec simpleSpec = ApplicationSpec.builder().name("simple-app").
+            entities(ImmutableSet.of(new org.apache.brooklyn.rest.domain.EntitySpec("simple-ent", RestMockSimpleEntity.class.getName()))).
+            locations(ImmutableSet.of("localhost")).
+            build();
+
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() {
+        ((ManagementContextInternal)getManagementContext()).getStorage().remove(LocalUsageManager.APPLICATION_USAGE_KEY);
+        ((ManagementContextInternal)getManagementContext()).getStorage().remove(LocalUsageManager.LOCATION_USAGE_KEY);
+        testStartTime = new GregorianCalendar();
+    }
+
+    @Test
+    public void testListApplicationUsages() throws Exception {
+        // Create an app
+        Calendar preStart = new GregorianCalendar();
+        String appId = createApp(simpleSpec);
+        Calendar postStart = new GregorianCalendar();
+        
+        // We will retrieve usage from one millisecond after start; this guarantees to not be  
+        // told about both STARTING+RUNNING, which could otherwise happen if they are in the 
+        // same milliscond.
+        Calendar afterPostStart = Time.newCalendarFromMillisSinceEpochUtc(postStart.getTime().getTime()+1);
+        
+        // Check that app's usage is returned
+        Response response = client().path("/usage/applications").get();
+        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
+        Iterable<UsageStatistics> usages = response.readEntity(new GenericType<List<UsageStatistics>>() {});
+        UsageStatistics usage = Iterables.getOnlyElement(usages);
+        assertAppUsage(usage, appId, ImmutableList.of(Status.STARTING, Status.RUNNING), roundDown(preStart), postStart);
+
+        // check app ignored if endCalendar before app started
+        response = client().path("/usage/applications").query("start", 0).query("end", preStart.getTime().getTime()-1).get();
+        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
+        usages = response.readEntity(new GenericType<List<UsageStatistics>>() {});
+        assertTrue(Iterables.isEmpty(usages), "usages="+usages);
+        
+        // Wait, so that definitely asking about things that have happened (not things in the future, 
+        // or events that are happening this exact same millisecond)
+        waitForFuture(afterPostStart.getTime().getTime());
+
+        // Check app start + end date truncated, even if running for longer (i.e. only tell us about this time window).
+        // Note that start==end means we get a snapshot of the apps in use at that exact time.
+        //
+        // The start/end times in UsageStatistic are in String format, and are rounded down to the nearest second.
+        // The comparison does use the milliseconds passed in the REST call though.
+        // The rounding down result should be the same as roundDown(afterPostStart), because that is the time-window
+        // we asked for.
+        response = client().path("/usage/applications").query("start", afterPostStart.getTime().getTime()).query("end", afterPostStart.getTime().getTime()).get();
+        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
+        usages = response.readEntity(new GenericType<List<UsageStatistics>>() {});
+        usage = Iterables.getOnlyElement(usages);
+        assertAppUsage(usage, appId, ImmutableList.of(Status.RUNNING), roundDown(preStart), postStart);
+        assertAppUsageTimesTruncated(usage, roundDown(afterPostStart), roundDown(afterPostStart));
+
+        // Delete the app
+        Calendar preDelete = new GregorianCalendar();
+        deleteApp(appId);
+        Calendar postDelete = new GregorianCalendar();
+
+        // Deleted app still returned, if in time range
+        response = client().path("/usage/applications").get();
+        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
+        usages = response.readEntity(new GenericType<List<UsageStatistics>>() {});
+        usage = Iterables.getOnlyElement(usages);
+        assertAppUsage(usage, appId, ImmutableList.of(Status.STARTING, Status.RUNNING, Status.DESTROYED), roundDown(preStart), postDelete);
+        assertAppUsage(ImmutableList.copyOf(usage.getStatistics()).subList(2, 3), appId, ImmutableList.of(Status.DESTROYED), roundDown(preDelete), postDelete);
+
+        long afterPostDelete = postDelete.getTime().getTime()+1;
+        waitForFuture(afterPostDelete);
+        
+        response = client().path("/usage/applications").query("start", afterPostDelete).get();
+        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
+        usages = response.readEntity(new GenericType<List<UsageStatistics>>() {});
+        assertTrue(Iterables.isEmpty(usages), "usages="+usages);
+    }
+
+    @Test
+    public void testGetApplicationUsagesForNonExistantApp() throws Exception {
+        Response response = client().path("/usage/applications/wrongid").get();
+        assertEquals(response.getStatus(), Response.Status.NOT_FOUND.getStatusCode());
+    }
+    
+    @Test
+    public void testGetApplicationUsage() throws Exception {
+        // Create an app
+        Calendar preStart = new GregorianCalendar();
+        String appId = createApp(simpleSpec);
+        Calendar postStart = new GregorianCalendar();
+        
+        // Normal request returns all
+        Response response = client().path("/usage/applications/" + appId).get();
+        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
+        UsageStatistics usage = response.readEntity(new GenericType<UsageStatistics>() {});
+        assertAppUsage(usage, appId, ImmutableList.of(Status.STARTING, Status.RUNNING), roundDown(preStart), postStart);
+
+        // Time-constrained requests
+        response = client().path("/usage/applications/" + appId).query("start", "1970-01-01T00:00:00-0100").get();
+        usage = response.readEntity(new GenericType<UsageStatistics>() {});
+        assertAppUsage(usage, appId, ImmutableList.of(Status.STARTING, Status.RUNNING), roundDown(preStart), postStart);
+        
+        response = client().path("/usage/applications/" + appId).query("start", "9999-01-01T00:00:00+0100").get();
+        assertTrue(response.getStatus() >= 400, "end defaults to NOW, so future start should fail, instead got code "+response.getStatus());
+        
+        response = client().path("/usage/applications/" + appId).query("start", "9999-01-01T00:00:00%2B0100").query("end", "9999-01-02T00:00:00%2B0100").get();
+        usage = response.readEntity(new GenericType<UsageStatistics>() {});
+        assertTrue(usage.getStatistics().isEmpty());
+
+        response = client().path("/usage/applications/" + appId).query("end", "9999-01-01T00:00:00+0100").get();
+        usage = response.readEntity(new GenericType<UsageStatistics>() {});
+        assertAppUsage(usage, appId, ImmutableList.of(Status.STARTING, Status.RUNNING), roundDown(preStart), postStart);
+
+        response = client().path("/usage/applications/" + appId).query("start", "9999-01-01T00:00:00+0100").query("end", "9999-02-01T00:00:00+0100").get();
+        usage = response.readEntity(new GenericType<UsageStatistics>() {});
+        assertTrue(usage.getStatistics().isEmpty());
+
+        response = client().path("/usage/applications/" + appId).query("start", "1970-01-01T00:00:00-0100").query("end", "9999-01-01T00:00:00+0100").get();
+        usage = response.readEntity(new GenericType<UsageStatistics>() {});
+        assertAppUsage(usage, appId, ImmutableList.of(Status.STARTING, Status.RUNNING), roundDown(preStart), postStart);
+        
+        response = client().path("/usage/applications/" + appId).query("end", "1970-01-01T00:00:00-0100").get();
+        usage = response.readEntity(new GenericType<UsageStatistics>() {});
+        assertTrue(usage.getStatistics().isEmpty());
+        
+        // Delete the app
+        Calendar preDelete = new GregorianCalendar();
+        deleteApp(appId);
+        Calendar postDelete = new GregorianCalendar();
+
+        // Deleted app still returned, if in time range
+        response = client().path("/usage/applications/" + appId).get();
+        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
+        usage = response.readEntity(new GenericType<UsageStatistics>() {});
+        assertAppUsage(usage, appId, ImmutableList.of(Status.STARTING, Status.RUNNING, Status.DESTROYED), roundDown(preStart), postDelete);
+        assertAppUsage(ImmutableList.copyOf(usage.getStatistics()).subList(2, 3), appId, ImmutableList.of(Status.DESTROYED), roundDown(preDelete), postDelete);
+
+        // Deleted app not returned if terminated before time range begins
+        long afterPostDelete = postDelete.getTime().getTime()+1;
+        waitForFuture(afterPostDelete);
+        response = client().path("/usage/applications/" + appId).query("start", afterPostDelete).get();
+        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
+        usage = response.readEntity(new GenericType<UsageStatistics>() {});
+        assertTrue(usage.getStatistics().isEmpty(), "usages="+usage);
+    }
+
+    @Test
+    public void testGetMachineUsagesForNonExistantMachine() throws Exception {
+        Response response = client().path("/usage/machines/wrongid").get();
+        assertEquals(response.getStatus(), Response.Status.NOT_FOUND.getStatusCode());
+    }
+
+    @Test
+    public void testGetMachineUsagesInitiallyEmpty() throws Exception {
+        // All machines: empty
+        Response response = client().path("/usage/machines").get();
+        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
+        Iterable<UsageStatistics> usages = response.readEntity(new GenericType<List<UsageStatistics>>() {});
+        assertTrue(Iterables.isEmpty(usages));
+        
+        // Specific machine that does not exist: get 404
+        response = client().path("/usage/machines/machineIdThatDoesNotExist").get();
+        assertEquals(response.getStatus(), Response.Status.NOT_FOUND.getStatusCode());
+    }
+
+    @Test
+    public void testListAndGetMachineUsage() throws Exception {
+        Location location = getManagementContext().getLocationManager().createLocation(LocationSpec.create(DynamicLocalhostMachineProvisioningLocation.class));
+        TestApplication app = getManagementContext().getEntityManager().createEntity(EntitySpec.create(TestApplication.class));
+        SoftwareProcessEntityTest.MyService entity = app.createAndManageChild(org.apache.brooklyn.api.entity.EntitySpec.create(SoftwareProcessEntityTest.MyService.class));
+        
+        Calendar preStart = new GregorianCalendar();
+        app.start(ImmutableList.of(location));
+        Calendar postStart = new GregorianCalendar();
+        Location machine = Iterables.getOnlyElement(entity.getLocations());
+
+        // All machines
+        Response response = client().path("/usage/machines").get();
+        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
+        Iterable<UsageStatistics> usages = response.readEntity(new GenericType<List<UsageStatistics>>() {});
+        UsageStatistics usage = Iterables.getOnlyElement(usages);
+        assertMachineUsage(usage, app.getId(), machine.getId(), ImmutableList.of(Status.ACCEPTED), roundDown(preStart), postStart);
+
+        // Specific machine
+        response = client().path("/usage/machines/"+machine.getId()).get();
+        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
+        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
+        usage = response.readEntity(new GenericType<UsageStatistics>() {});
+        assertMachineUsage(usage, app.getId(), machine.getId(), ImmutableList.of(Status.ACCEPTED), roundDown(preStart), postStart);
+    }
+
+    @Test
+    public void testListMachinesUsageForApp() throws Exception {
+        Location location = getManagementContext().getLocationManager().createLocation(LocationSpec.create(DynamicLocalhostMachineProvisioningLocation.class));
+        TestApplication app = getManagementContext().getEntityManager().createEntity(EntitySpec.create(TestApplication.class));
+        SoftwareProcessEntityTest.MyService entity = app.createAndManageChild(org.apache.brooklyn.api.entity.EntitySpec.create(SoftwareProcessEntityTest.MyService.class));
+        String appId = app.getId();
+        
+        Calendar preStart = new GregorianCalendar();
+        app.start(ImmutableList.of(location));
+        Calendar postStart = new GregorianCalendar();
+        Location machine = Iterables.getOnlyElement(entity.getLocations());
+
+        // For running machine
+        Response response = client().path("/usage/machines").query("application", appId).get();
+        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
+        Iterable<UsageStatistics> usages = response.readEntity(new GenericType<List<UsageStatistics>>() {});
+        UsageStatistics usage = Iterables.getOnlyElement(usages);
+        assertMachineUsage(usage, app.getId(), machine.getId(), ImmutableList.of(Status.ACCEPTED), roundDown(preStart), postStart);
+        
+        // Stop the machine
+        Calendar preStop = new GregorianCalendar();
+        app.stop();
+        Calendar postStop = new GregorianCalendar();
+        
+        // Deleted machine still returned, if in time range
+        response = client().path("/usage/machines").query("application", appId).get();
+        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
+        usages = response.readEntity(new GenericType<List<UsageStatistics>>() {});
+        usage = Iterables.getOnlyElement(usages);
+        assertMachineUsage(usage, app.getId(), machine.getId(), ImmutableList.of(Status.ACCEPTED, Status.DESTROYED), roundDown(preStart), postStop);
+        assertMachineUsage(ImmutableList.copyOf(usage.getStatistics()).subList(1,2), appId, machine.getId(), ImmutableList.of(Status.DESTROYED), roundDown(preStop), postStop);
+
+        // Terminated machines ignored if terminated since start-time
+        long futureTime = postStop.getTime().getTime()+1;
+        waitForFuture(futureTime);
+        response = client().path("/usage/applications").query("start", futureTime).get();
+        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
+        usages = response.readEntity(new GenericType<List<UsageStatistics>>() {});
+        assertTrue(Iterables.isEmpty(usages), "usages="+usages);
+    }
+
+    private String createApp(ApplicationSpec spec) {
+        Response response = clientDeploy(spec);
+        assertEquals(response.getStatus(), Response.Status.CREATED.getStatusCode());
+        TaskSummary createTask = response.readEntity(TaskSummary.class);
+        waitForTask(createTask.getId());
+        return createTask.getEntityId();
+    }
+    
+    private void deleteApp(String appId) {
+        Response response = client().path("/applications/"+appId)
+                .delete();
+        assertEquals(response.getStatus(), Response.Status.ACCEPTED.getStatusCode());
+        TaskSummary deletionTask = response.readEntity(TaskSummary.class);
+        waitForTask(deletionTask.getId());
+    }
+    
+    private void assertCalendarOrders(Object context, Calendar... Calendars) {
+        if (Calendars.length <= 1) return;
+        
+        long[] times = new long[Calendars.length];
+        for (int i = 0; i < times.length; i++) {
+            times[i] = millisSinceStart(Calendars[i]);
+        }
+        String err = "context="+context+"; Calendars="+Arrays.toString(Calendars) + "; CalendarsSanitized="+Arrays.toString(times);
+        
+        Calendar Calendar = Calendars[0];
+        for (int i = 1; i < Calendars.length; i++) {
+            assertTrue(Calendar.getTime().getTime() <= Calendars[i].getTime().getTime(), err);
+        }
+    }
+    
+    private void waitForTask(final String taskId) {
+        boolean success = Repeater.create()
+                .repeat(new Runnable() { public void run() {}})
+                .until(new Callable<Boolean>() {
+                    @Override public Boolean call() {
+                        Response response = client().path("/activities/"+taskId).get();
+                        if (response.getStatus() == Response.Status.NOT_FOUND.getStatusCode()) {
+                            return true;
+                        }
+                        TaskSummary summary = response.readEntity(TaskSummary.class);
+                        return summary != null && summary.getEndTimeUtc() != null;
+                    }})
+                .every(10L, TimeUnit.MILLISECONDS)
+                .limitTimeTo(TIMEOUT_MS, TimeUnit.MILLISECONDS)
+                .run();
+        assertTrue(success, "task "+taskId+" not finished");
+    }
+
+    private long millisSinceStart(Calendar time) {
+        return time.getTime().getTime() - testStartTime.getTime().getTime();
+    }
+    
+    private Calendar roundDown(Calendar calendar) {
+        long time = calendar.getTime().getTime();
+        long timeDown = ((long)(time / 1000)) * 1000;
+        return Time.newCalendarFromMillisSinceEpochUtc(timeDown);
+    }
+    
+    @SuppressWarnings("unused")
+    private Calendar roundUp(Calendar calendar) {
+        long time = calendar.getTime().getTime();
+        long timeDown = ((long)(time / 1000)) * 1000;
+        long timeUp = (time == timeDown) ? time : timeDown + 1000;
+        return Time.newCalendarFromMillisSinceEpochUtc(timeUp);
+    }
+
+    private void assertMachineUsage(UsageStatistics usage, String appId, String machineId, List<Status> states, Calendar pre, Calendar post) throws Exception {
+        assertUsage(usage.getStatistics(), appId, machineId, states, pre, post, false);
+    }
+    
+    private void assertMachineUsage(Iterable<UsageStatistic> usages, String appId, String machineId, List<Status> states, Calendar pre, Calendar post) throws Exception {
+        assertUsage(usages, appId, machineId, states, pre, post, false);
+    }
+    
+    private void assertAppUsage(UsageStatistics usage, String appId, List<Status> states, Calendar pre, Calendar post) throws Exception {
+        assertUsage(usage.getStatistics(), appId, appId, states, pre, post, false);
+    }
+    
+    private void assertAppUsage(Iterable<UsageStatistic> usages, String appId, List<Status> states, Calendar pre, Calendar post) throws Exception {
+        assertUsage(usages, appId, appId, states, pre, post, false);
+    }
+
+    private void assertUsage(Iterable<UsageStatistic> usages, String appId, String id, List<Status> states, Calendar pre, Calendar post, boolean allowGaps) throws Exception {
+        String errMsg = "usages="+usages;
+        Calendar now = new GregorianCalendar();
+        Calendar lowerBound = pre;
+        Calendar strictStart = null;
+        
+        assertEquals(Iterables.size(usages), states.size(), errMsg);
+        for (int i = 0; i < Iterables.size(usages); i++) {
+            UsageStatistic usage = Iterables.get(usages, i);
+            Calendar usageStart = Time.parseCalendar(usage.getStart());
+            Calendar usageEnd = Time.parseCalendar(usage.getEnd());
+            assertEquals(usage.getId(), id, errMsg);
+            assertEquals(usage.getApplicationId(), appId, errMsg);
+            assertEquals(usage.getStatus(), states.get(i), errMsg);
+            assertCalendarOrders(usages, lowerBound, usageStart, post);
+            assertCalendarOrders(usages, usageEnd, now);
+            if (strictStart != null) {
+                assertEquals(usageStart, strictStart, errMsg);
+            }
+            if (!allowGaps) {
+                strictStart = usageEnd;
+            }
+            lowerBound = usageEnd;
+        }
+    }
+
+    private void assertAppUsageTimesTruncated(UsageStatistics usages, Calendar strictStart, Calendar strictEnd) throws Exception {
+        String errMsg = "strictStart="+Time.makeDateString(strictStart)+"; strictEnd="+Time.makeDateString(strictEnd)+";usages="+usages;
+        Calendar usageStart = Time.parseCalendar(Iterables.getFirst(usages.getStatistics(), null).getStart());
+        Calendar usageEnd = Time.parseCalendar(Iterables.getLast(usages.getStatistics()).getStart());
+        // time zones might be different - so must convert to date
+        assertEquals(usageStart.getTime(), strictStart.getTime(), "usageStart="+Time.makeDateString(usageStart)+";"+errMsg);
+        assertEquals(usageEnd.getTime(), strictEnd.getTime(), errMsg);
+    }
+    
+    public static class DynamicLocalhostMachineProvisioningLocation extends LocalhostMachineProvisioningLocation {
+        @Override
+        public SshMachineLocation obtain(Map<?, ?> flags) throws NoMachinesAvailableException {
+            return super.obtain(flags);
+        }
+        
+        @Override
+        public void release(SshMachineLocation machine) {
+            super.release(machine);
+            super.machines.remove(machine);
+            getManagementContext().getLocationManager().unmanage(machine);
+        }
+    }
+
+    private void waitForFuture(long futureTime) throws InterruptedException {
+        long now;
+        while ((now = System.currentTimeMillis()) < futureTime) {
+            Thread.sleep(futureTime - now);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/VersionResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/VersionResourceTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/VersionResourceTest.java
new file mode 100644
index 0000000..fad05cc
--- /dev/null
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/VersionResourceTest.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import javax.ws.rs.core.Response;
+
+import org.testng.annotations.Test;
+
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+
+@Test(singleThreaded = true,
+        // by using a different suite name we disallow interleaving other tests between the methods of this test class, which wrecks the test fixtures
+        suiteName = "VersionResourceTest")
+public class VersionResourceTest extends BrooklynRestResourceTest {
+
+    @Test
+    public void testGetVersion() {
+        Response response = client().path("/version")
+                .get();
+
+        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
+        String version = response.readEntity(String.class);
+
+        assertTrue(version.matches("^\\d+\\.\\d+\\.\\d+.*"));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/security/PasswordHasherTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/security/PasswordHasherTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/security/PasswordHasherTest.java
new file mode 100644
index 0000000..575d6a4
--- /dev/null
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/security/PasswordHasherTest.java
@@ -0,0 +1,37 @@
+/*
+ * 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;
+
+import static org.testng.Assert.assertEquals;
+
+import org.testng.annotations.Test;
+
+public class PasswordHasherTest {
+
+    @Test
+    public void testHashSha256() throws Exception {
+        // Note: expected hash values generated externally:
+        // echo -n mysaltmypassword | openssl dgst -sha256
+
+        assertEquals(PasswordHasher.sha256("mysalt", "mypassword"), "d02878b06efa88579cd84d9e50b211c0a7caa92cf243bad1622c66081f7e2692");
+        assertEquals(PasswordHasher.sha256("", "mypassword"), "89e01536ac207279409d4de1e5253e01f4a1769e696db0d6062ca9b8f56767c8");
+        assertEquals(PasswordHasher.sha256(null, "mypassword"), "89e01536ac207279409d4de1e5253e01f4a1769e696db0d6062ca9b8f56767c8");
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/test/config/render/TestRendererHints.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/test/config/render/TestRendererHints.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/test/config/render/TestRendererHints.java
new file mode 100644
index 0000000..91294db
--- /dev/null
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/test/config/render/TestRendererHints.java
@@ -0,0 +1,36 @@
+/*
+ * 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.test.config.render;
+
+import org.apache.brooklyn.core.config.render.RendererHints;
+
+/** Methods used when testing the {@link RendererHints} regiostry. */
+public class TestRendererHints {
+
+    /** Clear the registry. 
+     *
+     *  MUST be used by a single test only.
+     *  TestNG interleaves the tests (sequentially) which results in tearDown 
+     *  executing in the middle of another class' tests. Only one tearDown may
+     *  call this method.
+     **/
+    public static void clearRegistry() {
+        RendererHints.getRegistry().clear();
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/test/entity/brooklynnode/DeployBlueprintTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/test/entity/brooklynnode/DeployBlueprintTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/test/entity/brooklynnode/DeployBlueprintTest.java
new file mode 100644
index 0000000..0113d39
--- /dev/null
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/test/entity/brooklynnode/DeployBlueprintTest.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.test.entity.brooklynnode;
+
+import static org.testng.Assert.assertEquals;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.mgmt.EntityManager;
+import org.apache.brooklyn.entity.brooklynnode.BrooklynNode;
+import org.apache.brooklyn.entity.brooklynnode.BrooklynNode.DeployBlueprintEffector;
+import org.apache.brooklyn.entity.stock.BasicApplication;
+import org.apache.brooklyn.feed.http.JsonFunctions;
+import org.apache.brooklyn.test.HttpTestUtils;
+import org.apache.brooklyn.util.guava.Functionals;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+
+public class DeployBlueprintTest extends BrooklynRestResourceTest {
+
+    @Override
+    protected boolean useLocalScannedCatalog() {
+        return true;
+    }
+
+    @Override
+    protected String getEndpointAddress() {
+        return ENDPOINT_ADDRESS_HTTP + "v1";
+    }
+
+    private static final Logger log = LoggerFactory.getLogger(DeployBlueprintTest.class);
+//
+//    Server server;
+//
+//    @BeforeMethod(alwaysRun=true)
+//    public void setUp() throws Exception {
+//        server = newServer();
+//        useServerForTest(server);
+//    }
+//
+    @Test
+    public void testStartsAppViaEffector() throws Exception {
+        URI webConsoleUri = URI.create(ENDPOINT_ADDRESS_HTTP); // BrooklynNode will append "/v1" to it
+
+        EntitySpec<BrooklynNode> spec = EntitySpec.create(BrooklynNode.class);
+        EntityManager mgr = getManagementContext().getEntityManager(); // getManagementContextFromJettyServerAttributes(server).getEntityManager();
+        BrooklynNode node = mgr.createEntity(spec);
+        node.sensors().set(BrooklynNode.WEB_CONSOLE_URI, webConsoleUri);
+        mgr.manage(node);
+        Map<String, String> params = ImmutableMap.of(DeployBlueprintEffector.BLUEPRINT_CAMP_PLAN.getName(), "{ services: [ serviceType: \"java:"+BasicApplication.class.getName()+"\" ] }");
+        String id = node.invoke(BrooklynNode.DEPLOY_BLUEPRINT, params).getUnchecked();
+
+        log.info("got: "+id);
+
+        String apps = HttpTestUtils.getContent(getEndpointAddress() + "/applications");
+        List<String> appType = parseJsonList(apps, ImmutableList.of("spec", "type"), String.class);
+        assertEquals(appType, ImmutableList.of(BasicApplication.class.getName()));
+
+        String status = HttpTestUtils.getContent(getEndpointAddress()+"/applications/"+id+"/entities/"+id+"/sensors/service.status");
+        log.info("STATUS: "+status);
+    }
+
+    private <T> List<T> parseJsonList(String json, List<String> elements, Class<T> clazz) {
+        Function<String, List<T>> func = Functionals.chain(
+                JsonFunctions.asJson(),
+                JsonFunctions.forEach(Functionals.chain(
+                        JsonFunctions.walk(elements),
+                        JsonFunctions.cast(clazz))));
+        return func.apply(json);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestApiTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestApiTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestApiTest.java
new file mode 100644
index 0000000..853afb0
--- /dev/null
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestApiTest.java
@@ -0,0 +1,223 @@
+/*
+ * 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.testing;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.brooklyn.api.location.LocationRegistry;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.camp.brooklyn.BrooklynCampPlatformLauncherNoServer;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.internal.BrooklynProperties;
+import org.apache.brooklyn.core.location.BasicLocationRegistry;
+import org.apache.brooklyn.core.mgmt.ManagementContextInjectable;
+import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
+import org.apache.brooklyn.core.server.BrooklynServerConfig;
+import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
+import org.apache.brooklyn.rest.BrooklynRestApi;
+import org.apache.brooklyn.rest.util.BrooklynRestResourceUtils;
+import org.apache.brooklyn.rest.util.ManagementContextProvider;
+import org.apache.brooklyn.rest.util.ShutdownHandlerProvider;
+import org.apache.brooklyn.rest.util.TestShutdownHandler;
+import org.apache.brooklyn.rest.util.json.BrooklynJacksonJsonProvider;
+import org.apache.cxf.jaxrs.client.JAXRSClientFactory;
+import org.reflections.util.ClasspathHelper;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public abstract class BrooklynRestApiTest {
+
+    public static final String SCANNING_CATALOG_BOM_URL = "classpath://brooklyn/scanning.catalog.bom";
+
+    protected ManagementContext manager;
+    
+    
+    protected TestShutdownHandler shutdownListener = createShutdownHandler();
+    protected final static String ENDPOINT_ADDRESS_LOCAL = "local://";
+    protected final static String ENDPOINT_ADDRESS_HTTP = "http://localhost:9998/";
+
+    protected Set<Class<?>> resourceClasses;
+    protected Set<Object> resourceBeans;
+
+    @BeforeClass(alwaysRun = true)
+    public void setUpClass() throws Exception {
+        if (!isMethodInit()) {
+            initClass();
+        }
+    }
+
+    @AfterClass(alwaysRun = true)
+    public void tearDownClass() throws Exception {
+        if (!isMethodInit()) {
+            destroyClass();
+        }
+    }
+
+    @BeforeMethod(alwaysRun = true)
+    public void setUpMethod() throws Exception {
+        if (isMethodInit()) {
+            initClass();
+        }
+        initMethod();
+    }
+
+    @AfterMethod(alwaysRun = true)
+    public void tearDownMethod() throws Exception {
+        if (isMethodInit()) {
+            destroyClass();
+        }
+        destroyMethod();
+    }
+    
+    protected void initClass() throws Exception {
+        resourceClasses = new HashSet<>();
+        resourceBeans = new HashSet<>();
+    }
+
+    protected void destroyClass() throws Exception {
+        destroyManagementContext();
+        resourceClasses = null;
+        resourceBeans = null;
+    }
+
+    protected void initMethod() throws Exception {
+        resetShutdownListener();
+    }
+
+    protected void destroyMethod() throws Exception {
+    }
+    
+    /**
+     * @return true to start/destroy the test environment for each method.
+     *          Returns false by default to speed up testing.
+     */
+    protected boolean isMethodInit() {
+        return false;
+    }
+
+    protected void resetShutdownListener() {
+        shutdownListener.reset();
+    }
+
+    protected void destroyManagementContext() {
+        if (manager!=null) {
+            Entities.destroyAll(manager);
+            resourceClasses = null;
+            resourceBeans = null;
+            manager = null;
+        }
+    }
+
+    protected boolean useLocalScannedCatalog() {
+        return false;
+    }
+    
+    private TestShutdownHandler createShutdownHandler() {
+        return new TestShutdownHandler();
+    }
+
+    protected synchronized ManagementContext getManagementContext() {
+        if (manager==null) {
+            if (useLocalScannedCatalog()) {
+                manager = new LocalManagementContext();
+                forceUseOfDefaultCatalogWithJavaClassPath();
+            } else {
+                manager = new LocalManagementContextForTests();
+            }
+            manager.getHighAvailabilityManager().disabled();
+            BasicLocationRegistry.setupLocationRegistryForTesting(manager);
+            
+            new BrooklynCampPlatformLauncherNoServer()
+                .useManagementContext(manager)
+                .launch();
+        }
+        return manager;
+    }
+    
+    protected String getEndpointAddress() {
+        return ENDPOINT_ADDRESS_HTTP;
+    }
+
+    protected ObjectMapper mapper() {
+        return BrooklynJacksonJsonProvider.findSharedObjectMapper(null, getManagementContext());
+    }
+
+    public LocationRegistry getLocationRegistry() {
+        return new BrooklynRestResourceUtils(getManagementContext()).getLocationRegistry();
+    }
+
+    protected final void addResource(Object resource) {
+        if (resource instanceof Class) {
+            resourceClasses.add((Class<?>)resource);
+        } else {
+            resourceBeans.add(resource);
+        }
+        if (resource instanceof ManagementContextInjectable) {
+            ((ManagementContextInjectable)resource).setManagementContext(getManagementContext());
+        }
+    }
+
+    protected final void addProvider(Class<?> provider) {
+        addResource(provider);
+    }
+
+    protected void addDefaultResources() {
+        addResource(new ShutdownHandlerProvider(shutdownListener));
+        addResource(new ManagementContextProvider(getManagementContext()));
+    }
+
+
+    /** intended for overriding if you only want certain resources added, or additional ones added */
+    protected void addBrooklynResources() {
+        for (Object r: BrooklynRestApi.getBrooklynRestResources())
+            addResource(r);
+    }
+
+    protected final void setUpResources() {
+        addDefaultResources();
+        addBrooklynResources();
+        for (Object r: BrooklynRestApi.getMiscResources())
+            addResource(r);
+    }
+
+    public <T> T resource(Class<T> clazz) {
+        return JAXRSClientFactory.create(getEndpointAddress(), clazz);
+    }
+
+    public <T> T resource(String uri, Class<T> clazz) {
+        return JAXRSClientFactory.create(getEndpointAddress() + uri, clazz);
+    }
+
+    private void forceUseOfDefaultCatalogWithJavaClassPath() {
+        // don't use any catalog.xml which is set
+        ((BrooklynProperties)manager.getConfig()).put(BrooklynServerConfig.BROOKLYN_CATALOG_URL, SCANNING_CATALOG_BOM_URL);
+        // sets URLs for a surefire
+        ((LocalManagementContext)manager).setBaseClassPathForScanning(ClasspathHelper.forJavaClassPath());
+        // this also works
+//        ((LocalManagementContext)manager).setBaseClassPathForScanning(ClasspathHelper.forPackage("brooklyn"));
+        // but this (near-default behaviour) does not
+//        ((LocalManagementContext)manager).setBaseClassLoader(getClass().getClassLoader());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestResourceTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestResourceTest.java
new file mode 100644
index 0000000..505bb58
--- /dev/null
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestResourceTest.java
@@ -0,0 +1,212 @@
+/*
+ * 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.testing;
+
+import static org.testng.Assert.assertTrue;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.apache.brooklyn.api.entity.Application;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.rest.domain.ApplicationSpec;
+import org.apache.brooklyn.rest.domain.ApplicationSummary;
+import org.apache.brooklyn.rest.domain.Status;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.repeat.Repeater;
+import org.apache.brooklyn.util.time.Duration;
+import org.apache.cxf.endpoint.Server;
+import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
+import org.apache.cxf.jaxrs.client.WebClient;
+import org.apache.cxf.jaxrs.utils.ResourceUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.collect.ImmutableList;
+
+public abstract class BrooklynRestResourceTest extends BrooklynRestApiTest {
+
+    private static final Logger log = LoggerFactory.getLogger(BrooklynRestResourceTest.class);
+
+    private static Server server;
+    protected List<?> clientProviders;
+    
+    class DefaultTestApp extends javax.ws.rs.core.Application {
+        @Override
+        public Set<Class<?>> getClasses() {
+            return resourceClasses;
+        }
+
+        @Override
+        public Set<Object> getSingletons() {
+            return resourceBeans;
+        }
+
+    };
+
+    @Override
+    public void initClass() throws Exception {
+        super.initClass();
+        startServer();
+    }
+
+    @Override
+    public void destroyClass() throws Exception {
+        stopServer();
+        super.destroyClass();
+    }
+
+    protected synchronized void startServer() throws Exception {
+        if (server == null) {
+            setUpResources();
+            JAXRSServerFactoryBean sf = ResourceUtils.createApplication(createRestApp(), true);
+            if (clientProviders == null) {
+                clientProviders = sf.getProviders();
+            }
+            configureCXF(sf);
+            sf.setAddress(getEndpointAddress());
+            sf.setFeatures(ImmutableList.of(new org.apache.cxf.feature.LoggingFeature()));
+            server = sf.create();
+        }
+    }
+
+    private javax.ws.rs.core.Application createRestApp() {
+        return new DefaultTestApp();
+    }
+
+    /** Allows subclasses to customize the CXF server bean. */
+    protected void configureCXF(JAXRSServerFactoryBean sf) {
+    }
+
+    public synchronized void stopServer() throws Exception {
+        if (server != null) {
+            server.stop();
+            server.destroy();
+            server = null;
+        }
+    }
+
+
+    protected Response clientDeploy(ApplicationSpec spec) {
+        try {
+            // dropwizard TestClient won't skip deserialization of trivial things like string and byte[] and inputstream
+            // if we pass in an object it serializes, so we have to serialize things ourselves
+            return client().path("/applications")
+                .post(Entity.entity(new ObjectMapper().writer().writeValueAsBytes(spec), MediaType.APPLICATION_OCTET_STREAM));
+        } catch (Exception e) {
+            throw Exceptions.propagate(e);
+        }
+    }
+    
+    protected void waitForApplicationToBeRunning(final URI applicationRef) {
+        waitForApplicationToBeRunning(applicationRef, Duration.minutes(3));
+    }
+    protected void waitForApplicationToBeRunning(final URI applicationRef, Duration timeout) {
+        if (applicationRef==null)
+            throw new NullPointerException("No application URI available (consider using BrooklynRestResourceTest.clientDeploy)");
+        
+        boolean started = Repeater.create("Wait for application startup")
+                .until(new Callable<Boolean>() {
+                    @Override
+                    public Boolean call() throws Exception {
+                        Status status = getApplicationStatus(applicationRef);
+                        if (status == Status.ERROR) {
+                            Assert.fail("Application failed with ERROR");
+                        }
+                        return status == Status.RUNNING;
+                    }
+                })
+                .backoffTo(Duration.ONE_SECOND)
+                .limitTimeTo(timeout)
+                .run();
+        
+        if (!started) {
+            log.warn("Did not start application "+applicationRef+":");
+            Collection<Application> apps = getManagementContext().getApplications();
+            for (Application app: apps)
+                Entities.dumpInfo(app);
+        }
+        assertTrue(started);
+    }
+
+    protected Status getApplicationStatus(URI uri) {
+        return client().path(uri).get(ApplicationSummary.class).getStatus();
+    }
+
+    protected void waitForPageFoundResponse(final String resource, final Class<?> clazz) {
+        boolean found = Repeater.create("Wait for page found")
+                .until(new Callable<Boolean>() {
+                    @Override
+                    public Boolean call() throws Exception {
+                        try {
+                            client().path(resource).get(clazz);
+                            return true;
+                        } catch (WebApplicationException e) {
+                            return false;
+                        }
+                    }
+                })
+                .every(1, TimeUnit.SECONDS)
+                .limitTimeTo(30, TimeUnit.SECONDS)
+                .run();
+        assertTrue(found);
+    }
+    
+    protected void waitForPageNotFoundResponse(final String resource, final Class<?> clazz) {
+        boolean success = Repeater.create("Wait for page not found")
+                .until(new Callable<Boolean>() {
+                    @Override
+                    public Boolean call() throws Exception {
+                        try {
+                            client().path(resource).get(clazz);
+                            return false;
+                        } catch (WebApplicationException e) {
+                            return e.getResponse().getStatus() == 404;
+                        }
+                    }
+                })
+                .every(1, TimeUnit.SECONDS)
+                .limitTimeTo(30, TimeUnit.SECONDS)
+                .run();
+        assertTrue(success);
+    }
+
+    protected static Entity<byte[]> toJsonEntity(Object obj) throws IOException {
+        // TODO: figure out how to have CXF actually send empty maps instead of replacing them with nulls without this workaround
+        // see cxf's AbstractClient.checkIfBodyEmpty
+        return Entity.entity(new ObjectMapper().writer().writeValueAsBytes(obj), MediaType.APPLICATION_JSON);
+    }
+
+    public WebClient client() {
+        return WebClient.create(getEndpointAddress(), clientProviders);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/mocks/CapitalizePolicy.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/mocks/CapitalizePolicy.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/mocks/CapitalizePolicy.java
new file mode 100644
index 0000000..7d80a6f
--- /dev/null
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/mocks/CapitalizePolicy.java
@@ -0,0 +1,33 @@
+/*
+ * 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.testing.mocks;
+
+import org.apache.brooklyn.api.entity.EntityLocal;
+import org.apache.brooklyn.core.policy.AbstractPolicy;
+
+@SuppressWarnings("deprecation")
+public class CapitalizePolicy extends AbstractPolicy {
+
+    @Override
+    public void setEntity(EntityLocal entity) {
+        super.setEntity(entity);
+        // TODO subscribe to foo and emit an enriched sensor on different channel which is capitalized
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/mocks/EverythingGroup.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/mocks/EverythingGroup.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/mocks/EverythingGroup.java
new file mode 100644
index 0000000..707c4a3
--- /dev/null
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/mocks/EverythingGroup.java
@@ -0,0 +1,27 @@
+/*
+ * 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.testing.mocks;
+
+import org.apache.brooklyn.api.entity.Group;
+import org.apache.brooklyn.api.entity.ImplementedBy;
+
+@ImplementedBy(EverythingGroupImpl.class)
+public interface EverythingGroup extends Group {
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/mocks/EverythingGroupImpl.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/mocks/EverythingGroupImpl.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/mocks/EverythingGroupImpl.java
new file mode 100644
index 0000000..8b2c98b
--- /dev/null
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/mocks/EverythingGroupImpl.java
@@ -0,0 +1,32 @@
+/*
+ * 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.testing.mocks;
+
+import org.apache.brooklyn.entity.group.DynamicGroupImpl;
+
+import com.google.common.base.Predicates;
+
+public class EverythingGroupImpl extends DynamicGroupImpl implements EverythingGroup {
+
+    public EverythingGroupImpl() {
+        super();
+        config().set(ENTITY_FILTER, Predicates.alwaysTrue());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/mocks/NameMatcherGroup.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/mocks/NameMatcherGroup.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/mocks/NameMatcherGroup.java
new file mode 100644
index 0000000..f9a2e21
--- /dev/null
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/mocks/NameMatcherGroup.java
@@ -0,0 +1,30 @@
+/*
+ * 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.testing.mocks;
+
+import org.apache.brooklyn.api.entity.Group;
+import org.apache.brooklyn.api.entity.ImplementedBy;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+
+@ImplementedBy(NameMatcherGroupImpl.class)
+public interface NameMatcherGroup extends Group {
+
+    public static final ConfigKey<String> NAME_REGEX = ConfigKeys.newStringConfigKey("namematchergroup.regex");
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/mocks/NameMatcherGroupImpl.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/mocks/NameMatcherGroupImpl.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/mocks/NameMatcherGroupImpl.java
new file mode 100644
index 0000000..bec416f
--- /dev/null
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/mocks/NameMatcherGroupImpl.java
@@ -0,0 +1,33 @@
+/*
+ * 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.testing.mocks;
+
+import org.apache.brooklyn.core.entity.EntityPredicates;
+import org.apache.brooklyn.entity.group.DynamicGroupImpl;
+import org.apache.brooklyn.util.text.StringPredicates;
+
+public class NameMatcherGroupImpl extends DynamicGroupImpl implements NameMatcherGroup {
+
+    @Override
+    public void init() {
+        super.init();
+        config().set(ENTITY_FILTER, EntityPredicates.displayNameSatisfies(StringPredicates.matchesRegex(getConfig(NAME_REGEX))));
+        rescanEntities();
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockApp.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockApp.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockApp.java
new file mode 100644
index 0000000..6d92e65
--- /dev/null
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockApp.java
@@ -0,0 +1,24 @@
+/*
+ * 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.testing.mocks;
+
+import org.apache.brooklyn.core.entity.AbstractApplication;
+
+public class RestMockApp extends AbstractApplication {
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockAppBuilder.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockAppBuilder.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockAppBuilder.java
new file mode 100644
index 0000000..1ca10bd
--- /dev/null
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockAppBuilder.java
@@ -0,0 +1,39 @@
+/*
+ * 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.testing.mocks;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.core.entity.StartableApplication;
+import org.apache.brooklyn.core.entity.factory.ApplicationBuilder;
+import org.apache.brooklyn.util.javalang.Reflections;
+
+public class RestMockAppBuilder extends ApplicationBuilder {
+
+    public RestMockAppBuilder() {
+        super(EntitySpec.create(StartableApplication.class).impl(RestMockApp.class));
+    }
+    
+    @Override
+    protected void doBuild() {
+        addChild(EntitySpec.create(Entity.class).impl(RestMockSimpleEntity.class)
+            .additionalInterfaces(Reflections.getAllInterfaces(RestMockSimpleEntity.class))
+            .displayName("child1"));
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockSimpleEntity.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockSimpleEntity.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockSimpleEntity.java
new file mode 100644
index 0000000..58d24aa
--- /dev/null
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockSimpleEntity.java
@@ -0,0 +1,103 @@
+/*
+ * 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.testing.mocks;
+
+import java.util.Map;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.EntityLocal;
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.annotation.Effector;
+import org.apache.brooklyn.core.annotation.EffectorParam;
+import org.apache.brooklyn.core.config.BasicConfigKey;
+import org.apache.brooklyn.core.effector.MethodEffector;
+import org.apache.brooklyn.core.sensor.BasicAttributeSensor;
+import org.apache.brooklyn.entity.software.base.AbstractSoftwareProcessSshDriver;
+import org.apache.brooklyn.entity.software.base.SoftwareProcessImpl;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.brooklyn.location.ssh.SshMachineLocation;
+import org.apache.brooklyn.util.core.flags.SetFromFlag;
+
+public class RestMockSimpleEntity extends SoftwareProcessImpl {
+
+    private static final Logger log = LoggerFactory.getLogger(RestMockSimpleEntity.class);
+    
+    public RestMockSimpleEntity() {
+        super();
+    }
+
+    public RestMockSimpleEntity(Entity parent) {
+        super(parent);
+    }
+
+    public RestMockSimpleEntity(@SuppressWarnings("rawtypes") Map flags, Entity parent) {
+        super(flags, parent);
+    }
+
+    public RestMockSimpleEntity(@SuppressWarnings("rawtypes") Map flags) {
+        super(flags);
+    }
+    
+    @Override
+    protected void connectSensors() {
+        super.connectSensors();
+        connectServiceUpIsRunning();
+    }
+
+    @SetFromFlag("sampleConfig")
+    public static final ConfigKey<String> SAMPLE_CONFIG = new BasicConfigKey<String>(
+            String.class, "brooklyn.rest.mock.sample.config", "Mock sample config", "DEFAULT_VALUE");
+
+    public static final AttributeSensor<String> SAMPLE_SENSOR = new BasicAttributeSensor<String>(
+            String.class, "brooklyn.rest.mock.sample.sensor", "Mock sample sensor");
+
+    public static final MethodEffector<String> SAMPLE_EFFECTOR = new MethodEffector<String>(RestMockSimpleEntity.class, "sampleEffector");
+    
+    @Effector
+    public String sampleEffector(@EffectorParam(name="param1", description="param one") String param1, 
+            @EffectorParam(name="param2") Integer param2) {
+        log.info("Invoked sampleEffector("+param1+","+param2+")");
+        String result = ""+param1+param2;
+        sensors().set(SAMPLE_SENSOR, result);
+        return result;
+    }
+
+    @SuppressWarnings("rawtypes")
+    @Override
+    public Class getDriverInterface() {
+        return MockSshDriver.class;
+    }
+    
+    public static class MockSshDriver extends AbstractSoftwareProcessSshDriver {
+        public MockSshDriver(EntityLocal entity, SshMachineLocation machine) {
+            super(entity, machine);
+        }
+        public boolean isRunning() { return true; }
+        public void stop() {}
+        public void kill() {}
+        public void install() {}
+        public void customize() {}
+        public void launch() {}
+        public void setup() { }
+        public void copyInstallResources() { }
+        public void copyRuntimeResources() { }
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockSimplePolicy.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockSimplePolicy.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockSimplePolicy.java
new file mode 100644
index 0000000..e15cdd1
--- /dev/null
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockSimplePolicy.java
@@ -0,0 +1,64 @@
+/*
+ * 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.testing.mocks;
+
+import java.util.Map;
+
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.BasicConfigKey;
+import org.apache.brooklyn.core.policy.AbstractPolicy;
+import org.apache.brooklyn.util.core.flags.SetFromFlag;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class RestMockSimplePolicy extends AbstractPolicy {
+
+    @SuppressWarnings("unused")
+    private static final Logger log = LoggerFactory.getLogger(RestMockSimplePolicy.class);
+
+    public RestMockSimplePolicy() {
+        super();
+    }
+
+    @SuppressWarnings("rawtypes")
+    public RestMockSimplePolicy(Map flags) {
+        super(flags);
+    }
+
+    @SetFromFlag("sampleConfig")
+    public static final ConfigKey<String> SAMPLE_CONFIG = BasicConfigKey.builder(String.class)
+            .name("brooklyn.rest.mock.sample.config")
+            .description("Mock sample config")
+            .defaultValue("DEFAULT_VALUE")
+            .reconfigurable(true)
+            .build();
+
+    @SetFromFlag
+    public static final ConfigKey<Integer> INTEGER_CONFIG = BasicConfigKey.builder(Integer.class)
+            .name("brooklyn.rest.mock.sample.integer")
+            .description("Mock integer config")
+            .defaultValue(1)
+            .reconfigurable(true)
+            .build();
+
+    @Override
+    protected <T> void doReconfigureConfig(ConfigKey<T> key, T val) {
+        // no-op
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/util/BrooklynRestResourceUtilsTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/util/BrooklynRestResourceUtilsTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/util/BrooklynRestResourceUtilsTest.java
new file mode 100644
index 0000000..48908e3
--- /dev/null
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/util/BrooklynRestResourceUtilsTest.java
@@ -0,0 +1,213 @@
+/*
+ * 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.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Map;
+
+import org.apache.brooklyn.api.catalog.Catalog;
+import org.apache.brooklyn.api.entity.Application;
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.policy.Policy;
+import org.apache.brooklyn.core.catalog.internal.CatalogItemBuilder;
+import org.apache.brooklyn.core.catalog.internal.CatalogTemplateItemDto;
+import org.apache.brooklyn.core.catalog.internal.CatalogUtils;
+import org.apache.brooklyn.core.entity.AbstractApplication;
+import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
+import org.apache.brooklyn.core.objs.proxy.EntityProxy;
+import org.apache.brooklyn.core.policy.AbstractPolicy;
+import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
+import org.apache.brooklyn.core.test.entity.TestApplication;
+import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.apache.brooklyn.entity.stock.BasicEntity;
+import org.apache.brooklyn.rest.domain.ApplicationSpec;
+import org.apache.brooklyn.rest.domain.EntitySpec;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+
+public class BrooklynRestResourceUtilsTest {
+
+    private LocalManagementContext managementContext;
+    private BrooklynRestResourceUtils util;
+
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        managementContext = LocalManagementContextForTests.newInstance();
+        util = new BrooklynRestResourceUtils(managementContext);
+    }
+    
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws Exception {
+        if (managementContext != null) managementContext.terminate();
+    }
+
+    @Test
+    public void testCreateAppFromImplClass() {
+        ApplicationSpec spec = ApplicationSpec.builder()
+                .name("myname")
+                .type(SampleNoOpApplication.class.getName())
+                .locations(ImmutableSet.of("localhost"))
+                .build();
+        Application app = util.create(spec);
+        
+        assertEquals(ImmutableList.copyOf(managementContext.getApplications()), ImmutableList.of(app));
+        assertEquals(app.getDisplayName(), "myname");
+        assertTrue(app instanceof EntityProxy);
+        assertTrue(app instanceof MyInterface);
+        assertFalse(app instanceof SampleNoOpApplication);
+    }
+
+    @Test
+    public void testCreateAppFromCatalogByType() {
+        createAppFromCatalog(SampleNoOpApplication.class.getName());
+    }
+
+    @Test
+    public void testCreateAppFromCatalogByName() {
+        createAppFromCatalog("app.noop");
+    }
+
+    @Test
+    public void testCreateAppFromCatalogById() {
+        createAppFromCatalog("app.noop:0.0.1");
+    }
+
+    @Test
+    @SuppressWarnings("deprecation")
+    public void testCreateAppFromCatalogByTypeMultipleItems() {
+        CatalogTemplateItemDto item = CatalogItemBuilder.newTemplate("app.noop", "0.0.2-SNAPSHOT")
+                .javaType(SampleNoOpApplication.class.getName())
+                .build();
+        managementContext.getCatalog().addItem(item);
+        createAppFromCatalog(SampleNoOpApplication.class.getName());
+    }
+
+    @SuppressWarnings("deprecation")
+    private void createAppFromCatalog(String type) {
+        CatalogTemplateItemDto item = CatalogItemBuilder.newTemplate("app.noop", "0.0.1")
+            .javaType(SampleNoOpApplication.class.getName())
+            .build();
+        managementContext.getCatalog().addItem(item);
+        
+        ApplicationSpec spec = ApplicationSpec.builder()
+                .name("myname")
+                .type(type)
+                .locations(ImmutableSet.of("localhost"))
+                .build();
+        Application app = util.create(spec);
+
+        assertEquals(app.getCatalogItemId(), "app.noop:0.0.1");
+    }
+
+    @Test
+    public void testEntityAppFromCatalogByType() {
+        createEntityFromCatalog(BasicEntity.class.getName());
+    }
+
+    @Test
+    public void testEntityAppFromCatalogByName() {
+        createEntityFromCatalog("app.basic");
+    }
+
+    @Test
+    public void testEntityAppFromCatalogById() {
+        createEntityFromCatalog("app.basic:0.0.1");
+    }
+
+    @Test
+    @SuppressWarnings("deprecation")
+    public void testEntityAppFromCatalogByTypeMultipleItems() {
+        CatalogTemplateItemDto item = CatalogItemBuilder.newTemplate("app.basic", "0.0.2-SNAPSHOT")
+                .javaType(SampleNoOpApplication.class.getName())
+                .build();
+        managementContext.getCatalog().addItem(item);
+        createEntityFromCatalog(BasicEntity.class.getName());
+    }
+
+    @SuppressWarnings("deprecation")
+    private void createEntityFromCatalog(String type) {
+        String symbolicName = "app.basic";
+        String version = "0.0.1";
+        CatalogTemplateItemDto item = CatalogItemBuilder.newTemplate(symbolicName, version)
+            .javaType(BasicEntity.class.getName())
+            .build();
+        managementContext.getCatalog().addItem(item);
+
+        ApplicationSpec spec = ApplicationSpec.builder()
+                .name("myname")
+                .entities(ImmutableSet.of(new EntitySpec(type)))
+                .locations(ImmutableSet.of("localhost"))
+                .build();
+        Application app = util.create(spec);
+
+        Entity entity = Iterables.getOnlyElement(app.getChildren());
+        assertEquals(entity.getCatalogItemId(), CatalogUtils.getVersionedId(symbolicName, version));
+    }
+
+    @Test
+    public void testNestedApplications() {
+        // hierarchy is: app -> subapp -> subentity (where subentity has a policy)
+        
+        Application app = managementContext.getEntityManager().createEntity(org.apache.brooklyn.api.entity.EntitySpec.create(TestApplication.class)
+                .displayName("app")
+                .child(org.apache.brooklyn.api.entity.EntitySpec.create(TestApplication.class)
+                        .displayName("subapp")
+                        .child(org.apache.brooklyn.api.entity.EntitySpec.create(TestEntity.class)
+                                .displayName("subentity")
+                                .policy(org.apache.brooklyn.api.policy.PolicySpec.create(MyPolicy.class)
+                                        .displayName("mypolicy")))));
+
+        Application subapp = (Application) Iterables.getOnlyElement(app.getChildren());
+        TestEntity subentity = (TestEntity) Iterables.getOnlyElement(subapp.getChildren());
+        
+        Entity subappRetrieved = util.getEntity(app.getId(), subapp.getId());
+        assertEquals(subappRetrieved.getDisplayName(), "subapp");
+        
+        Entity subentityRetrieved = util.getEntity(app.getId(), subentity.getId());
+        assertEquals(subentityRetrieved.getDisplayName(), "subentity");
+        
+        Policy subappPolicy = util.getPolicy(app.getId(), subentity.getId(), "mypolicy");
+        assertEquals(subappPolicy.getDisplayName(), "mypolicy");
+    }
+
+    public interface MyInterface {
+    }
+
+    @Catalog(name="Sample No-Op Application",
+            description="Application which does nothing, included only as part of the test cases.",
+            iconUrl="")
+    public static class SampleNoOpApplication extends AbstractApplication implements MyInterface {
+    }
+    
+    public static class MyPolicy extends AbstractPolicy {
+        public MyPolicy() {
+        }
+        public MyPolicy(Map<String, ?> flags) {
+            super(flags);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/util/EntityLocationUtilsTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/util/EntityLocationUtilsTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/util/EntityLocationUtilsTest.java
new file mode 100644
index 0000000..f0c65e4
--- /dev/null
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/util/EntityLocationUtilsTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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.testng.Assert.assertEquals;
+
+import java.util.Arrays;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.location.AbstractLocation;
+import org.apache.brooklyn.core.location.geo.HostGeoInfo;
+import org.apache.brooklyn.core.location.internal.LocationInternal;
+import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
+import org.apache.brooklyn.entity.software.base.SoftwareProcess;
+import org.apache.brooklyn.rest.testing.mocks.RestMockSimpleEntity;
+
+import com.google.common.collect.ImmutableList;
+
+public class EntityLocationUtilsTest extends BrooklynAppUnitTestSupport {
+
+    private static final Logger log = LoggerFactory.getLogger(EntityLocationUtilsTest.class);
+    
+    private Location loc;
+    
+    @BeforeMethod(alwaysRun=true)
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        loc = mgmt.getLocationRegistry().resolve("localhost");
+        ((AbstractLocation)loc).setHostGeoInfo(new HostGeoInfo("localhost", "localhost", 50, 0));
+    }
+    
+    @Test
+    public void testCount() {
+        @SuppressWarnings("unused")
+        SoftwareProcess r1 = app.createAndManageChild(EntitySpec.create(SoftwareProcess.class, RestMockSimpleEntity.class));
+        SoftwareProcess r2 = app.createAndManageChild(EntitySpec.create(SoftwareProcess.class, RestMockSimpleEntity.class));
+        Entities.start(app, Arrays.<Location>asList(loc));
+
+        Entities.dumpInfo(app);
+
+        log.info("r2loc: "+r2.getLocations());
+        log.info("props: "+((LocationInternal)r2.getLocations().iterator().next()).config().getBag().getAllConfig());
+
+        Map<Location, Integer> counts = new EntityLocationUtils(mgmt).countLeafEntitiesByLocatedLocations();
+        log.info("count: "+counts);
+        assertEquals(ImmutableList.copyOf(counts.values()), ImmutableList.of(2), "counts="+counts);
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/util/HaHotStateCheckClassResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/util/HaHotStateCheckClassResource.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/util/HaHotStateCheckClassResource.java
new file mode 100644
index 0000000..80e9c46
--- /dev/null
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/util/HaHotStateCheckClassResource.java
@@ -0,0 +1,38 @@
+/*
+ * 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 javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import org.apache.brooklyn.rest.filter.HaHotStateRequired;
+
+@Path("/ha/class")
+@Produces(MediaType.APPLICATION_JSON)
+@HaHotStateRequired
+public class HaHotStateCheckClassResource {
+
+    @GET
+    @Path("fail")
+    public String fail() {
+        return "FAIL";
+    }
+}


[07/34] brooklyn-server git commit: [BROOKLYN-183] REST API using CXF JAX-RS 2.0 implementation

Posted by he...@apache.org.
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/LdapSecurityProvider.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/LdapSecurityProvider.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/LdapSecurityProvider.java
deleted file mode 100644
index d3636e9..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/LdapSecurityProvider.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * 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-server/src/main/java/org/apache/brooklyn/rest/security/provider/SecurityProvider.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/SecurityProvider.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/SecurityProvider.java
deleted file mode 100644
index 57d1400..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/SecurityProvider.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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-server/src/main/java/org/apache/brooklyn/rest/transform/AccessTransformer.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/transform/AccessTransformer.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/transform/AccessTransformer.java
deleted file mode 100644
index 62e698e..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/transform/AccessTransformer.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * 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;
-
-/**
- * @author Adam Lowe
- */
-public class AccessTransformer {
-
-    public static AccessSummary accessSummary(AccessManager manager) {
-        String selfUri = "/v1/access/";
-        ImmutableMap<String, URI> links = ImmutableMap.of("self", URI.create(selfUri));
-
-        return new AccessSummary(manager.isLocationProvisioningAllowed(), links);
-    }
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/transform/ApplicationTransformer.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/transform/ApplicationTransformer.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/transform/ApplicationTransformer.java
deleted file mode 100644
index 8f3ddbc..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/transform/ApplicationTransformer.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * 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;
-
-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/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/transform/BrooklynFeatureTransformer.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/transform/BrooklynFeatureTransformer.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/transform/BrooklynFeatureTransformer.java
deleted file mode 100644
index c8477cf..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/transform/BrooklynFeatureTransformer.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * 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-server/src/main/java/org/apache/brooklyn/rest/transform/CatalogTransformer.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/transform/CatalogTransformer.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/transform/CatalogTransformer.java
deleted file mode 100644
index 514d9c9..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/transform/CatalogTransformer.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * 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 java.util.concurrent.atomic.AtomicInteger;
-
-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;
-
-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) {
-        Set<EntityConfigSummary> config = Sets.newLinkedHashSet();
-        Set<SensorSummary> sensors = Sets.newTreeSet(SummaryComparators.nameComparator());
-        Set<EffectorSummary> effectors = Sets.newTreeSet(SummaryComparators.nameComparator());
-
-        EntitySpec<?> spec = null;
-        try {
-            @SuppressWarnings({ "unchecked", "rawtypes" })
-            // the raw type isn't needed according to eclipse IDE, but jenkins maven fails without it;
-            // must be a java version or compiler thing. don't remove even though it looks okay without it!
-            EntitySpec<?> specRaw = (EntitySpec<?>) b.getCatalog().createSpec((CatalogItem) item);
-            spec = specRaw;
-            EntityDynamicType typeMap = BrooklynTypes.getDefinedEntityType(spec.getType());
-            EntityType type = typeMap.getSnapshot();
-
-            AtomicInteger paramPriorityCnt = new AtomicInteger();
-            for (SpecParameter<?> input: spec.getParameters()) {
-                config.add(EntityTransformer.entityConfigSummary(input, paramPriorityCnt));
-                if (input.getSensor()!=null)
-                    sensors.add(SensorTransformer.sensorSummaryForCatalog(input.getSensor()));
-            }
-            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()),
-            makeTags(spec, item), config, sensors, effectors,
-            item.isDeprecated(), makeLinks(item));
-    }
-
-    @SuppressWarnings({ "unchecked", "rawtypes" })
-    public static CatalogItemSummary catalogItemSummary(BrooklynRestResourceUtils b, CatalogItem item) {
-        try {
-            switch (item.getCatalogItemType()) {
-            case TEMPLATE:
-            case ENTITY:
-                return catalogEntitySummary(b, item);
-            case POLICY:
-                return catalogPolicySummary(b, item);
-            case LOCATION:
-                return catalogLocationSummary(b, 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.tags().getTags(), 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.tags().getTags(), 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.tags().getTags(), 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;
-    }
-
-    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-server/src/main/java/org/apache/brooklyn/rest/transform/EffectorTransformer.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/transform/EffectorTransformer.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/transform/EffectorTransformer.java
deleted file mode 100644
index a434659..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/transform/EffectorTransformer.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * 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;
-
-public class EffectorTransformer {
-
-    public static EffectorSummary effectorSummary(final Entity 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/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/transform/EntityTransformer.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/transform/EntityTransformer.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/transform/EntityTransformer.java
deleted file mode 100644
index f5079b9..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/transform/EntityTransformer.java
+++ /dev/null
@@ -1,165 +0,0 @@
-/*
- * 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 java.util.concurrent.atomic.AtomicInteger;
-
-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.rest.util.WebResourceUtils;
-import org.apache.brooklyn.util.collections.MutableMap;
-import org.apache.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(Entity 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);
-    }
-
-    public static EntityConfigSummary entityConfigSummary(SpecParameter<?> input, AtomicInteger paramPriorityCnt) {
-        // Increment the priority because the config container is a set. Server-side we are using an ordered set
-        // which results in correctly ordered items on the wire (as a list). Clients which use the java bindings
-        // though will push the items in an unordered set - so give them means to recover the correct order.
-        Double priority = input.isPinned() ? Double.valueOf(paramPriorityCnt.incrementAndGet()) : null;
-        return entityConfigSummary(input.getConfigKey(), input.getLabel(), priority, null);
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/transform/HighAvailabilityTransformer.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/transform/HighAvailabilityTransformer.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/transform/HighAvailabilityTransformer.java
deleted file mode 100644
index 41b0f22..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/transform/HighAvailabilityTransformer.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * 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-server/src/main/java/org/apache/brooklyn/rest/transform/LocationTransformer.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/transform/LocationTransformer.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/transform/LocationTransformer.java
deleted file mode 100644
index 71639e7..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/transform/LocationTransformer.java
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * 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;
-
-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) {
-        return newInstance(null, id, locationSpec, LocationDetailLevel.LOCAL_EXCLUDING_SECRET);
-    }
-    @SuppressWarnings("deprecation")
-    public static LocationSummary newInstance(ManagementContext mgmt, String id, org.apache.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/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/transform/PolicyTransformer.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/transform/PolicyTransformer.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/transform/PolicyTransformer.java
deleted file mode 100644
index b29e010..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/transform/PolicyTransformer.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * 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;
-
-/**
- * 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, Entity 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, Entity 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/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/transform/SensorTransformer.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/transform/SensorTransformer.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/transform/SensorTransformer.java
deleted file mode 100644
index b06fd81..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/transform/SensorTransformer.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * 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.net.URLParamEncoder;
-import org.apache.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/brooklyn-server/blob/6f624c78/rest/rest-server/src/main/java/org/apache/brooklyn/rest/transform/TaskTransformer.java
----------------------------------------------------------------------
diff --git a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/transform/TaskTransformer.java b/rest/rest-server/src/main/java/org/apache/brooklyn/rest/transform/TaskTransformer.java
deleted file mode 100644
index e53f781..0000000
--- a/rest/rest-server/src/main/java/org/apache/brooklyn/rest/transform/TaskTransformer.java
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * 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;
-
-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);
-    }
-}


[24/34] brooklyn-server git commit: REST API optional Jersey compatibility

Posted by he...@apache.org.
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceIntegrationTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceIntegrationTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceIntegrationTest.java
new file mode 100644
index 0000000..865b6f7
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceIntegrationTest.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.net.URI;
+import java.util.Set;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.Test;
+import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
+import org.apache.brooklyn.rest.domain.ApplicationSpec;
+import org.apache.brooklyn.rest.domain.ApplicationSummary;
+import org.apache.brooklyn.rest.domain.EntitySpec;
+import org.apache.brooklyn.rest.domain.EntitySummary;
+import org.apache.brooklyn.rest.domain.SensorSummary;
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+import org.apache.brooklyn.test.Asserts;
+import org.apache.brooklyn.util.collections.MutableMap;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.sun.jersey.api.client.ClientResponse;
+import com.sun.jersey.api.client.GenericType;
+
+@Test(singleThreaded = true)
+public class ApplicationResourceIntegrationTest extends BrooklynRestResourceTest {
+
+    @SuppressWarnings("unused")
+    private static final Logger log = LoggerFactory.getLogger(ApplicationResourceIntegrationTest.class);
+
+    private final ApplicationSpec redisSpec = ApplicationSpec.builder().name("redis-app")
+            .entities(ImmutableSet.of(new EntitySpec("redis-ent", "org.apache.brooklyn.entity.nosql.redis.RedisStore")))
+            .locations(ImmutableSet.of("localhost"))
+            .build();
+
+    @Test(groups="Integration")
+    public void testDeployRedisApplication() throws Exception {
+        ClientResponse response = clientDeploy(redisSpec);
+
+        assertEquals(response.getStatus(), 201);
+        assertEquals(getManagementContext().getApplications().size(), 1);
+        assertTrue(response.getLocation().getPath().startsWith("/v1/applications/"), "path="+response.getLocation().getPath()); // path uses id, rather than app name
+
+        waitForApplicationToBeRunning(response.getLocation());
+    }
+
+    @Test(groups="Integration", dependsOnMethods = "testDeployRedisApplication")
+    public void testListEntities() {
+        Set<EntitySummary> entities = client().resource("/v1/applications/redis-app/entities")
+                .get(new GenericType<Set<EntitySummary>>() {});
+
+        for (EntitySummary entity : entities) {
+            client().resource(entity.getLinks().get("self")).get(ClientResponse.class);
+            // TODO assertions on the above call?
+
+            Set<EntitySummary> children = client().resource(entity.getLinks().get("children"))
+                    .get(new GenericType<Set<EntitySummary>>() {});
+            assertEquals(children.size(), 0);
+        }
+    }
+
+    @Test(groups="Integration", dependsOnMethods = "testDeployRedisApplication")
+    public void testListSensorsRedis() {
+        Set<SensorSummary> sensors = client().resource("/v1/applications/redis-app/entities/redis-ent/sensors")
+                .get(new GenericType<Set<SensorSummary>>() {});
+        assertTrue(sensors.size() > 0);
+        SensorSummary uptime = Iterables.find(sensors, new Predicate<SensorSummary>() {
+            @Override
+            public boolean apply(SensorSummary sensorSummary) {
+                return sensorSummary.getName().equals("redis.uptime");
+            }
+        });
+        assertEquals(uptime.getType(), "java.lang.Integer");
+    }
+
+    @Test(groups="Integration", dependsOnMethods = { "testListSensorsRedis", "testListEntities" })
+    public void testTriggerRedisStopEffector() throws Exception {
+        ClientResponse response = client().resource("/v1/applications/redis-app/entities/redis-ent/effectors/stop")
+                .type(MediaType.APPLICATION_JSON_TYPE)
+                .post(ClientResponse.class, ImmutableMap.of());
+        assertEquals(response.getStatus(), Response.Status.ACCEPTED.getStatusCode());
+
+        final URI stateSensor = URI.create("/v1/applications/redis-app/entities/redis-ent/sensors/service.state");
+        final String expectedStatus = Lifecycle.STOPPED.toString();
+        Asserts.succeedsEventually(MutableMap.of("timeout", 60 * 1000), new Runnable() {
+            public void run() {
+                // Accept with and without quotes; if don't specify "Accepts" header, then
+                // might get back json or plain text (depending on compiler / java runtime 
+                // used for SensorApi!)
+                String val = client().resource(stateSensor).get(String.class);
+                assertTrue(expectedStatus.equalsIgnoreCase(val) || ("\""+expectedStatus+"\"").equalsIgnoreCase(val), "state="+val);
+            }
+        });
+    }
+
+    @Test(groups="Integration", dependsOnMethods = "testTriggerRedisStopEffector" )
+    public void testDeleteRedisApplication() throws Exception {
+        int size = getManagementContext().getApplications().size();
+        ClientResponse response = client().resource("/v1/applications/redis-app")
+                .delete(ClientResponse.class);
+
+        waitForPageNotFoundResponse("/v1/applications/redis-app", ApplicationSummary.class);
+
+        assertEquals(response.getStatus(), Response.Status.ACCEPTED.getStatusCode());
+        assertEquals(getManagementContext().getApplications().size(), size-1);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java
new file mode 100644
index 0000000..8b49763
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java
@@ -0,0 +1,701 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import static com.google.common.collect.Iterables.find;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeoutException;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+
+import org.apache.brooklyn.api.entity.Application;
+import org.apache.brooklyn.core.entity.Attributes;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.entity.EntityFunctions;
+import org.apache.brooklyn.core.entity.EntityPredicates;
+import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
+import org.apache.brooklyn.core.location.AbstractLocation;
+import org.apache.brooklyn.core.location.LocationConfigKeys;
+import org.apache.brooklyn.core.location.geo.HostGeoInfo;
+import org.apache.brooklyn.core.location.internal.LocationInternal;
+import org.apache.brooklyn.entity.stock.BasicApplication;
+import org.apache.brooklyn.entity.stock.BasicEntity;
+import org.apache.brooklyn.rest.domain.ApiError;
+import org.apache.brooklyn.rest.domain.ApplicationSpec;
+import org.apache.brooklyn.rest.domain.ApplicationSummary;
+import org.apache.brooklyn.rest.domain.CatalogEntitySummary;
+import org.apache.brooklyn.rest.domain.CatalogItemSummary;
+import org.apache.brooklyn.rest.domain.EffectorSummary;
+import org.apache.brooklyn.rest.domain.EntityConfigSummary;
+import org.apache.brooklyn.rest.domain.EntitySpec;
+import org.apache.brooklyn.rest.domain.EntitySummary;
+import org.apache.brooklyn.rest.domain.PolicySummary;
+import org.apache.brooklyn.rest.domain.SensorSummary;
+import org.apache.brooklyn.rest.domain.Status;
+import org.apache.brooklyn.rest.domain.TaskSummary;
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+import org.apache.brooklyn.rest.testing.mocks.CapitalizePolicy;
+import org.apache.brooklyn.rest.testing.mocks.NameMatcherGroup;
+import org.apache.brooklyn.rest.testing.mocks.RestMockApp;
+import org.apache.brooklyn.rest.testing.mocks.RestMockAppBuilder;
+import org.apache.brooklyn.rest.testing.mocks.RestMockSimpleEntity;
+import org.apache.brooklyn.test.Asserts;
+import org.apache.brooklyn.util.collections.CollectionFunctionals;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.http.HttpAsserts;
+import org.apache.brooklyn.util.text.Strings;
+import org.apache.brooklyn.util.time.Duration;
+import org.apache.http.HttpHeaders;
+import org.apache.http.entity.ContentType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Maps;
+import com.sun.jersey.api.client.ClientResponse;
+import com.sun.jersey.api.client.GenericType;
+import com.sun.jersey.api.client.UniformInterfaceException;
+import com.sun.jersey.api.client.WebResource;
+import com.sun.jersey.core.util.MultivaluedMapImpl;
+
+@Test(singleThreaded = true)
+public class ApplicationResourceTest extends BrooklynRestResourceTest {
+
+    /*
+     * In simpleSpec, not using EverythingGroup because caused problems! The group is a child of the
+     * app, and the app is a member of the group. It failed in jenkins with:
+     *   BasicApplicationImpl{id=GSPjBCe4} GSPjBCe4
+     *     service.isUp: true
+     *     service.problems: {service-lifecycle-indicators-from-children-and-members=Required entity not healthy: EverythingGroupImpl{id=KQ4mSEOJ}}
+     *     service.state: on-fire
+     *     service.state.expected: running @ 1412003485617 / Mon Sep 29 15:11:25 UTC 2014
+     *   EverythingGroupImpl{id=KQ4mSEOJ} KQ4mSEOJ
+     *     service.isUp: true
+     *     service.problems: {service-lifecycle-indicators-from-children-and-members=Required entities not healthy: BasicApplicationImpl{id=GSPjBCe4}, EverythingGroupImpl{id=KQ4mSEOJ}}
+     *     service.state: on-fire
+     * I'm guessing there's a race: the app was not yet healthy because EverythingGroup hadn't set itself to running; 
+     * but then the EverythingGroup would never transition to healthy because one of its members was not healthy.
+     */
+
+    private static final Logger log = LoggerFactory.getLogger(ApplicationResourceTest.class);
+    
+    private final ApplicationSpec simpleSpec = ApplicationSpec.builder().name("simple-app")
+          .entities(ImmutableSet.of(
+                  new EntitySpec("simple-ent", RestMockSimpleEntity.class.getName()),
+                  new EntitySpec("simple-group", NameMatcherGroup.class.getName(), ImmutableMap.of("namematchergroup.regex", "simple-ent"))
+          ))
+          .locations(ImmutableSet.of("localhost"))
+          .build();
+
+    // Convenience for finding an EntitySummary within a collection, based on its name
+    private static Predicate<EntitySummary> withName(final String name) {
+        return new Predicate<EntitySummary>() {
+            public boolean apply(EntitySummary input) {
+                return name.equals(input.getName());
+            }
+        };
+    }
+
+    // Convenience for finding a Map within a collection, based on the value of one of its keys
+    private static Predicate<? super Map<?,?>> withValueForKey(final Object key, final Object value) {
+        return new Predicate<Object>() {
+            public boolean apply(Object input) {
+                if (!(input instanceof Map)) return false;
+                return value.equals(((Map<?, ?>) input).get(key));
+            }
+        };
+    }
+
+    @Test
+    public void testGetUndefinedApplication() {
+        try {
+            client().resource("/v1/applications/dummy-not-found").get(ApplicationSummary.class);
+        } catch (UniformInterfaceException e) {
+            assertEquals(e.getResponse().getStatus(), 404);
+        }
+    }
+
+    private static void assertRegexMatches(String actual, String patternExpected) {
+        if (actual==null) Assert.fail("Actual value is null; expected "+patternExpected);
+        if (!actual.matches(patternExpected)) {
+            Assert.fail("Text '"+actual+"' does not match expected pattern "+patternExpected);
+        }
+    }
+
+    @Test
+    public void testDeployApplication() throws Exception {
+        ClientResponse response = clientDeploy(simpleSpec);
+
+        HttpAsserts.assertHealthyStatusCode(response.getStatus());
+        assertEquals(getManagementContext().getApplications().size(), 1);
+        assertRegexMatches(response.getLocation().getPath(), "/v1/applications/.*");
+        // Object taskO = response.getEntity(Object.class);
+        TaskSummary task = response.getEntity(TaskSummary.class);
+        log.info("deployed, got " + task);
+        assertEquals(task.getEntityId(), getManagementContext().getApplications().iterator().next().getApplicationId());
+
+        waitForApplicationToBeRunning(response.getLocation());
+    }
+
+    @Test(dependsOnMethods = { "testDeleteApplication" })
+    // this must happen after we've deleted the main application, as testLocatedLocations assumes a single location
+    public void testDeployApplicationImpl() throws Exception {
+    ApplicationSpec spec = ApplicationSpec.builder()
+            .type(RestMockApp.class.getCanonicalName())
+            .name("simple-app-impl")
+            .locations(ImmutableSet.of("localhost"))
+            .build();
+
+        ClientResponse response = clientDeploy(spec);
+        assertTrue(response.getStatus() / 100 == 2, "response is " + response);
+
+        // Expect app to be running
+        URI appUri = response.getLocation();
+        waitForApplicationToBeRunning(response.getLocation());
+        assertEquals(client().resource(appUri).get(ApplicationSummary.class).getSpec().getName(), "simple-app-impl");
+    }
+
+    @Test(dependsOnMethods = { "testDeployApplication", "testLocatedLocation" })
+    public void testDeployApplicationFromInterface() throws Exception {
+        ApplicationSpec spec = ApplicationSpec.builder()
+                .type(BasicApplication.class.getCanonicalName())
+                .name("simple-app-interface")
+                .locations(ImmutableSet.of("localhost"))
+                .build();
+
+        ClientResponse response = clientDeploy(spec);
+        assertTrue(response.getStatus() / 100 == 2, "response is " + response);
+
+        // Expect app to be running
+        URI appUri = response.getLocation();
+        waitForApplicationToBeRunning(response.getLocation());
+        assertEquals(client().resource(appUri).get(ApplicationSummary.class).getSpec().getName(), "simple-app-interface");
+    }
+
+    @Test(dependsOnMethods = { "testDeployApplication", "testLocatedLocation" })
+    public void testDeployApplicationFromBuilder() throws Exception {
+        ApplicationSpec spec = ApplicationSpec.builder()
+                .type(RestMockAppBuilder.class.getCanonicalName())
+                .name("simple-app-builder")
+                .locations(ImmutableSet.of("localhost"))
+                .build();
+
+        ClientResponse response = clientDeploy(spec);
+        assertTrue(response.getStatus() / 100 == 2, "response is " + response);
+
+        // Expect app to be running
+        URI appUri = response.getLocation();
+        waitForApplicationToBeRunning(response.getLocation(), Duration.TEN_SECONDS);
+        assertEquals(client().resource(appUri).get(ApplicationSummary.class).getSpec().getName(), "simple-app-builder");
+
+        // Expect app to have the child-entity
+        Set<EntitySummary> entities = client().resource(appUri.toString() + "/entities")
+                .get(new GenericType<Set<EntitySummary>>() {});
+        assertEquals(entities.size(), 1);
+        assertEquals(Iterables.getOnlyElement(entities).getName(), "child1");
+        assertEquals(Iterables.getOnlyElement(entities).getType(), RestMockSimpleEntity.class.getCanonicalName());
+    }
+
+    @Test(dependsOnMethods = { "testDeployApplication", "testLocatedLocation" })
+    public void testDeployApplicationYaml() throws Exception {
+        String yaml = "{ name: simple-app-yaml, location: localhost, services: [ { serviceType: "+BasicApplication.class.getCanonicalName()+" } ] }";
+
+        ClientResponse response = client().resource("/v1/applications")
+                .entity(yaml, "application/x-yaml")
+                .post(ClientResponse.class);
+        assertTrue(response.getStatus()/100 == 2, "response is "+response);
+
+        // Expect app to be running
+        URI appUri = response.getLocation();
+        waitForApplicationToBeRunning(response.getLocation());
+        assertEquals(client().resource(appUri).get(ApplicationSummary.class).getSpec().getName(), "simple-app-yaml");
+    }
+
+    @Test
+    public void testReferenceCatalogEntity() throws Exception {
+        getManagementContext().getCatalog().addItems("{ name: "+BasicEntity.class.getName()+", "
+            + "services: [ { type: "+BasicEntity.class.getName()+" } ] }");
+
+        String yaml = "{ name: simple-app-yaml, location: localhost, services: [ { type: " + BasicEntity.class.getName() + " } ] }";
+
+        ClientResponse response = client().resource("/v1/applications")
+                .entity(yaml, "application/x-yaml")
+                .post(ClientResponse.class);
+        assertTrue(response.getStatus()/100 == 2, "response is "+response);
+
+        // Expect app to be running
+        URI appUri = response.getLocation();
+        waitForApplicationToBeRunning(response.getLocation());
+        assertEquals(client().resource(appUri).get(ApplicationSummary.class).getSpec().getName(), "simple-app-yaml");
+
+        ClientResponse response2 = client().resource(appUri.getPath())
+                .delete(ClientResponse.class);
+        assertEquals(response2.getStatus(), Response.Status.ACCEPTED.getStatusCode());
+    }
+
+    @Test
+    public void testDeployWithInvalidEntityType() {
+        try {
+            clientDeploy(ApplicationSpec.builder()
+                    .name("invalid-app")
+                    .entities(ImmutableSet.of(new EntitySpec("invalid-ent", "not.existing.entity")))
+                    .locations(ImmutableSet.of("localhost"))
+                    .build());
+
+        } catch (UniformInterfaceException e) {
+            ApiError error = e.getResponse().getEntity(ApiError.class);
+            assertEquals(error.getMessage(), "Undefined type 'not.existing.entity'");
+        }
+    }
+
+    @Test
+    public void testDeployWithInvalidLocation() {
+        try {
+            clientDeploy(ApplicationSpec.builder()
+                    .name("invalid-app")
+                    .entities(ImmutableSet.of(new EntitySpec("simple-ent", RestMockSimpleEntity.class.getName())))
+                    .locations(ImmutableSet.of("3423"))
+                    .build());
+
+        } catch (UniformInterfaceException e) {
+            ApiError error = e.getResponse().getEntity(ApiError.class);
+            assertEquals(error.getMessage(), "Undefined location '3423'");
+        }
+    }
+
+    @Test(dependsOnMethods = "testDeployApplication")
+    public void testListEntities() {
+        Set<EntitySummary> entities = client().resource("/v1/applications/simple-app/entities")
+                .get(new GenericType<Set<EntitySummary>>() {});
+
+        assertEquals(entities.size(), 2);
+
+        EntitySummary entity = Iterables.find(entities, withName("simple-ent"), null);
+        EntitySummary group = Iterables.find(entities, withName("simple-group"), null);
+        Assert.assertNotNull(entity);
+        Assert.assertNotNull(group);
+
+        client().resource(entity.getLinks().get("self")).get(ClientResponse.class);
+
+        Set<EntitySummary> children = client().resource(entity.getLinks().get("children"))
+                .get(new GenericType<Set<EntitySummary>>() {});
+        assertEquals(children.size(), 0);
+    }
+
+    @Test(dependsOnMethods = "testDeployApplication")
+    public void testListApplications() {
+        Set<ApplicationSummary> applications = client().resource("/v1/applications")
+                .get(new GenericType<Set<ApplicationSummary>>() { });
+        log.info("Applications listed are: " + applications);
+        for (ApplicationSummary app : applications) {
+            if (simpleSpec.getName().equals(app.getSpec().getName())) return;
+        }
+        Assert.fail("simple-app not found in list of applications: "+applications);
+    }
+
+    @Test(dependsOnMethods = "testDeployApplication")
+    public void testGetApplicationOnFire() {
+        Application app = Iterables.find(manager.getApplications(), EntityPredicates.displayNameEqualTo(simpleSpec.getName()));
+        Lifecycle origState = app.getAttribute(Attributes.SERVICE_STATE_ACTUAL);
+        
+        ApplicationSummary summary = client().resource("/v1/applications/"+app.getId())
+                .get(ApplicationSummary.class);
+        assertEquals(summary.getStatus(), Status.RUNNING);
+
+        app.sensors().set(Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
+        try {
+            ApplicationSummary summary2 = client().resource("/v1/applications/"+app.getId())
+                    .get(ApplicationSummary.class);
+            log.info("Application: " + summary2);
+            assertEquals(summary2.getStatus(), Status.ERROR);
+            
+        } finally {
+            app.sensors().set(Attributes.SERVICE_STATE_ACTUAL, origState);
+        }
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Test(dependsOnMethods = "testDeployApplication")
+    public void testFetchApplicationsAndEntity() {
+        Collection apps = client().resource("/v1/applications/fetch").get(Collection.class);
+        log.info("Applications fetched are: " + apps);
+
+        Map app = null;
+        for (Object appI : apps) {
+            Object name = ((Map) appI).get("name");
+            if ("simple-app".equals(name)) {
+                app = (Map) appI;
+            }
+            if (ImmutableSet.of("simple-ent", "simple-group").contains(name))
+                Assert.fail(name + " should not be listed at high level: " + apps);
+        }
+
+        Assert.assertNotNull(app);
+        Assert.assertFalse(Strings.isBlank((String) app.get("applicationId")),
+                "expected value for applicationId, was: " + app.get("applicationId"));
+        Assert.assertFalse(Strings.isBlank((String) app.get("serviceState")),
+                "expected value for serviceState, was: " + app.get("serviceState"));
+        Assert.assertNotNull(app.get("serviceUp"), "expected non-null value for serviceUp");
+
+        Collection children = (Collection) app.get("children");
+        Assert.assertEquals(children.size(), 2);
+
+        Map entitySummary = (Map) Iterables.find(children, withValueForKey("name", "simple-ent"), null);
+        Map groupSummary = (Map) Iterables.find(children, withValueForKey("name", "simple-group"), null);
+        Assert.assertNotNull(entitySummary);
+        Assert.assertNotNull(groupSummary);
+
+        String itemIds = app.get("id") + "," + entitySummary.get("id") + "," + groupSummary.get("id");
+        Collection entities = client().resource("/v1/applications/fetch?items="+itemIds)
+                .get(Collection.class);
+        log.info("Applications+Entities fetched are: " + entities);
+
+        Assert.assertEquals(entities.size(), apps.size() + 2);
+        Map entityDetails = (Map) Iterables.find(entities, withValueForKey("name", "simple-ent"), null);
+        Map groupDetails = (Map) Iterables.find(entities, withValueForKey("name", "simple-group"), null);
+        Assert.assertNotNull(entityDetails);
+        Assert.assertNotNull(groupDetails);
+
+        Assert.assertEquals(entityDetails.get("parentId"), app.get("id"));
+        Assert.assertNull(entityDetails.get("children"));
+        Assert.assertEquals(groupDetails.get("parentId"), app.get("id"));
+        Assert.assertNull(groupDetails.get("children"));
+
+        Collection entityGroupIds = (Collection) entityDetails.get("groupIds");
+        Assert.assertNotNull(entityGroupIds);
+        Assert.assertEquals(entityGroupIds.size(), 1);
+        Assert.assertEquals(entityGroupIds.iterator().next(), groupDetails.get("id"));
+
+        Collection groupMembers = (Collection) groupDetails.get("members");
+        Assert.assertNotNull(groupMembers);
+
+        for (Application appi : getManagementContext().getApplications()) {
+            Entities.dumpInfo(appi);
+        }
+        log.info("MEMBERS: " + groupMembers);
+
+        Assert.assertEquals(groupMembers.size(), 1);
+        Map entityMemberDetails = (Map) Iterables.find(groupMembers, withValueForKey("name", "simple-ent"), null);
+        Assert.assertNotNull(entityMemberDetails);
+        Assert.assertEquals(entityMemberDetails.get("id"), entityDetails.get("id"));
+    }
+
+    @Test(dependsOnMethods = "testDeployApplication")
+    public void testListSensors() {
+        Set<SensorSummary> sensors = client().resource("/v1/applications/simple-app/entities/simple-ent/sensors")
+                .get(new GenericType<Set<SensorSummary>>() { });
+        assertTrue(sensors.size() > 0);
+        SensorSummary sample = Iterables.find(sensors, new Predicate<SensorSummary>() {
+            @Override
+            public boolean apply(SensorSummary sensorSummary) {
+                return sensorSummary.getName().equals(RestMockSimpleEntity.SAMPLE_SENSOR.getName());
+            }
+        });
+        assertEquals(sample.getType(), "java.lang.String");
+    }
+
+    @Test(dependsOnMethods = "testDeployApplication")
+    public void testListConfig() {
+        Set<EntityConfigSummary> config = client().resource("/v1/applications/simple-app/entities/simple-ent/config")
+                .get(new GenericType<Set<EntityConfigSummary>>() { });
+        assertTrue(config.size() > 0);
+        System.out.println(("CONFIG: " + config));
+    }
+
+    @Test(dependsOnMethods = "testListConfig")
+    public void testListConfig2() {
+        Set<EntityConfigSummary> config = client().resource("/v1/applications/simple-app/entities/simple-ent/config")
+                .get(new GenericType<Set<EntityConfigSummary>>() {});
+        assertTrue(config.size() > 0);
+        System.out.println(("CONFIG: " + config));
+    }
+
+    @Test(dependsOnMethods = "testDeployApplication")
+    public void testListEffectors() {
+        Set<EffectorSummary> effectors = client().resource("/v1/applications/simple-app/entities/simple-ent/effectors")
+                .get(new GenericType<Set<EffectorSummary>>() {});
+
+        assertTrue(effectors.size() > 0);
+
+        EffectorSummary sampleEffector = find(effectors, new Predicate<EffectorSummary>() {
+            @Override
+            public boolean apply(EffectorSummary input) {
+                return input.getName().equals("sampleEffector");
+            }
+        });
+        assertEquals(sampleEffector.getReturnType(), "java.lang.String");
+    }
+
+    @Test(dependsOnMethods = "testListSensors")
+    public void testTriggerSampleEffector() throws InterruptedException, IOException {
+        ClientResponse response = client()
+                .resource("/v1/applications/simple-app/entities/simple-ent/effectors/"+RestMockSimpleEntity.SAMPLE_EFFECTOR.getName())
+                .type(MediaType.APPLICATION_JSON_TYPE)
+                .post(ClientResponse.class, ImmutableMap.of("param1", "foo", "param2", 4));
+
+        assertEquals(response.getStatus(), Response.Status.ACCEPTED.getStatusCode());
+
+        String result = response.getEntity(String.class);
+        assertEquals(result, "foo4");
+    }
+
+    @Test(dependsOnMethods = "testListSensors")
+    public void testTriggerSampleEffectorWithFormData() throws InterruptedException, IOException {
+        MultivaluedMap<String, String> data = new MultivaluedMapImpl();
+        data.add("param1", "foo");
+        data.add("param2", "4");
+        ClientResponse response = client()
+                .resource("/v1/applications/simple-app/entities/simple-ent/effectors/"+RestMockSimpleEntity.SAMPLE_EFFECTOR.getName())
+                .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE)
+                .post(ClientResponse.class, data);
+
+        assertEquals(response.getStatus(), Response.Status.ACCEPTED.getStatusCode());
+
+        String result = response.getEntity(String.class);
+        assertEquals(result, "foo4");
+    }
+
+    @Test(dependsOnMethods = "testTriggerSampleEffector")
+    public void testBatchSensorValues() {
+        WebResource resource = client().resource("/v1/applications/simple-app/entities/simple-ent/sensors/current-state");
+        Map<String, Object> sensors = resource.get(new GenericType<Map<String, Object>>() {});
+        assertTrue(sensors.size() > 0);
+        assertEquals(sensors.get(RestMockSimpleEntity.SAMPLE_SENSOR.getName()), "foo4");
+    }
+
+    @Test(dependsOnMethods = "testBatchSensorValues")
+    public void testReadEachSensor() {
+    Set<SensorSummary> sensors = client().resource("/v1/applications/simple-app/entities/simple-ent/sensors")
+            .get(new GenericType<Set<SensorSummary>>() {});
+
+        Map<String, String> readings = Maps.newHashMap();
+        for (SensorSummary sensor : sensors) {
+            try {
+                readings.put(sensor.getName(), client().resource(sensor.getLinks().get("self")).accept(MediaType.TEXT_PLAIN).get(String.class));
+            } catch (UniformInterfaceException uie) {
+                if (uie.getResponse().getStatus() == 204) { // no content
+                    readings.put(sensor.getName(), null);
+                } else {
+                    Exceptions.propagate(uie);
+                }
+            }
+        }
+
+        assertEquals(readings.get(RestMockSimpleEntity.SAMPLE_SENSOR.getName()), "foo4");
+    }
+
+    @Test(dependsOnMethods = "testTriggerSampleEffector")
+    public void testPolicyWhichCapitalizes() {
+        String policiesEndpoint = "/v1/applications/simple-app/entities/simple-ent/policies";
+        Set<PolicySummary> policies = client().resource(policiesEndpoint).get(new GenericType<Set<PolicySummary>>(){});
+        assertEquals(policies.size(), 0);
+
+        ClientResponse response = client().resource(policiesEndpoint)
+                .queryParam("type", CapitalizePolicy.class.getCanonicalName())
+                .type(MediaType.APPLICATION_JSON_TYPE)
+                .post(ClientResponse.class, Maps.newHashMap());
+        assertEquals(response.getStatus(), 200);
+        PolicySummary policy = response.getEntity(PolicySummary.class);
+        assertNotNull(policy.getId());
+        String newPolicyId = policy.getId();
+        log.info("POLICY CREATED: " + newPolicyId);
+        policies = client().resource(policiesEndpoint).get(new GenericType<Set<PolicySummary>>() {});
+        assertEquals(policies.size(), 1);
+
+        Lifecycle status = client().resource(policiesEndpoint + "/" + newPolicyId).get(Lifecycle.class);
+        log.info("POLICY STATUS: " + status);
+
+        response = client().resource(policiesEndpoint+"/"+newPolicyId+"/start")
+                .post(ClientResponse.class);
+        assertEquals(response.getStatus(), 204);
+        status = client().resource(policiesEndpoint + "/" + newPolicyId).get(Lifecycle.class);
+        assertEquals(status, Lifecycle.RUNNING);
+
+        response = client().resource(policiesEndpoint+"/"+newPolicyId+"/stop")
+                .post(ClientResponse.class);
+        assertEquals(response.getStatus(), 204);
+        status = client().resource(policiesEndpoint + "/" + newPolicyId).get(Lifecycle.class);
+        assertEquals(status, Lifecycle.STOPPED);
+
+        response = client().resource(policiesEndpoint+"/"+newPolicyId+"/destroy")
+                .post(ClientResponse.class);
+        assertEquals(response.getStatus(), 204);
+
+        response = client().resource(policiesEndpoint+"/"+newPolicyId).get(ClientResponse.class);
+        log.info("POLICY STATUS RESPONSE AFTER DESTROY: " + response.getStatus());
+        assertEquals(response.getStatus(), 404);
+
+        policies = client().resource(policiesEndpoint).get(new GenericType<Set<PolicySummary>>() {});
+        assertEquals(0, policies.size());
+    }
+
+    @SuppressWarnings({ "rawtypes" })
+    @Test(dependsOnMethods = "testDeployApplication")
+    public void testLocatedLocation() {
+        log.info("starting testLocatedLocations");
+        testListApplications();
+
+        LocationInternal l = (LocationInternal) getManagementContext().getApplications().iterator().next().getLocations().iterator().next();
+        if (l.config().getLocalRaw(LocationConfigKeys.LATITUDE).isAbsent()) {
+            log.info("Supplying fake locations for localhost because could not be autodetected");
+            ((AbstractLocation) l).setHostGeoInfo(new HostGeoInfo("localhost", "localhost", 50, 0));
+        }
+        Map result = client().resource("/v1/locations/usage/LocatedLocations")
+                .get(Map.class);
+        log.info("LOCATIONS: " + result);
+        Assert.assertEquals(result.size(), 1);
+        Map details = (Map) result.values().iterator().next();
+        assertEquals(details.get("leafEntityCount"), 2);
+    }
+
+    @Test(dependsOnMethods = {"testListEffectors", "testFetchApplicationsAndEntity", "testTriggerSampleEffector", "testListApplications","testReadEachSensor","testPolicyWhichCapitalizes","testLocatedLocation"})
+    public void testDeleteApplication() throws TimeoutException, InterruptedException {
+        waitForPageFoundResponse("/v1/applications/simple-app", ApplicationSummary.class);
+        Collection<Application> apps = getManagementContext().getApplications();
+        log.info("Deleting simple-app from " + apps);
+        int size = apps.size();
+
+        ClientResponse response = client().resource("/v1/applications/simple-app")
+                .delete(ClientResponse.class);
+
+        assertEquals(response.getStatus(), Response.Status.ACCEPTED.getStatusCode());
+        TaskSummary task = response.getEntity(TaskSummary.class);
+        assertTrue(task.getDescription().toLowerCase().contains("destroy"), task.getDescription());
+        assertTrue(task.getDescription().toLowerCase().contains("simple-app"), task.getDescription());
+
+        waitForPageNotFoundResponse("/v1/applications/simple-app", ApplicationSummary.class);
+
+        log.info("App appears gone, apps are: " + getManagementContext().getApplications());
+        // more logging above, for failure in the check below
+
+        Asserts.eventually(
+                EntityFunctions.applications(getManagementContext()),
+                Predicates.compose(Predicates.equalTo(size-1), CollectionFunctionals.sizeFunction()) );
+    }
+
+    @Test
+    public void testDisabledApplicationCatalog() throws TimeoutException, InterruptedException {
+        String itemSymbolicName = "my.catalog.item.id.for.disabling";
+        String itemVersion = "1.0";
+        String serviceType = "org.apache.brooklyn.entity.stock.BasicApplication";
+        
+        // Deploy the catalog item
+        addTestCatalogItem(itemSymbolicName, "template", itemVersion, serviceType);
+        List<CatalogEntitySummary> itemSummaries = client().resource("/v1/catalog/applications")
+                .queryParam("fragment", itemSymbolicName).queryParam("allVersions", "true").get(new GenericType<List<CatalogEntitySummary>>() {});
+        CatalogItemSummary itemSummary = Iterables.getOnlyElement(itemSummaries);
+        String itemVersionedId = String.format("%s:%s", itemSummary.getSymbolicName(), itemSummary.getVersion());
+        assertEquals(itemSummary.getId(), itemVersionedId);
+
+        try {
+            // Create an app before disabling: this should work
+            String yaml = "{ name: my-app, location: localhost, services: [ { type: \""+itemVersionedId+"\" } ] }";
+            ClientResponse response = client().resource("/v1/applications")
+                    .entity(yaml, "application/x-yaml")
+                    .post(ClientResponse.class);
+            HttpAsserts.assertHealthyStatusCode(response.getStatus());
+            waitForPageFoundResponse("/v1/applications/my-app", ApplicationSummary.class);
+    
+            // Deprecate
+            deprecateCatalogItem(itemSymbolicName, itemVersion, true);
+
+            // Create an app when deprecated: this should work
+            String yaml2 = "{ name: my-app2, location: localhost, services: [ { type: \""+itemVersionedId+"\" } ] }";
+            ClientResponse response2 = client().resource("/v1/applications")
+                    .entity(yaml2, "application/x-yaml")
+                    .post(ClientResponse.class);
+            HttpAsserts.assertHealthyStatusCode(response2.getStatus());
+            waitForPageFoundResponse("/v1/applications/my-app2", ApplicationSummary.class);
+    
+            // Disable
+            disableCatalogItem(itemSymbolicName, itemVersion, true);
+
+            // Now try creating an app; this should fail because app is disabled
+            String yaml3 = "{ name: my-app3, location: localhost, services: [ { type: \""+itemVersionedId+"\" } ] }";
+            ClientResponse response3 = client().resource("/v1/applications")
+                    .entity(yaml3, "application/x-yaml")
+                    .post(ClientResponse.class);
+            HttpAsserts.assertClientErrorStatusCode(response3.getStatus());
+            assertTrue(response3.getEntity(String.class).contains("cannot be matched"));
+            waitForPageNotFoundResponse("/v1/applications/my-app3", ApplicationSummary.class);
+            
+        } finally {
+            client().resource("/v1/applications/my-app")
+                    .delete(ClientResponse.class);
+
+            client().resource("/v1/applications/my-app2")
+                    .delete(ClientResponse.class);
+
+            client().resource("/v1/applications/my-app3")
+                    .delete(ClientResponse.class);
+
+            client().resource("/v1/catalog/entities/"+itemVersionedId+"/"+itemVersion)
+                    .delete(ClientResponse.class);
+        }
+    }
+
+    private void deprecateCatalogItem(String symbolicName, String version, boolean deprecated) {
+        String id = String.format("%s:%s", symbolicName, version);
+        ClientResponse response = client().resource(String.format("/v1/catalog/entities/%s/deprecated", id))
+                    .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+                    .post(ClientResponse.class, deprecated);
+        assertEquals(response.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
+    }
+    
+    private void disableCatalogItem(String symbolicName, String version, boolean disabled) {
+        String id = String.format("%s:%s", symbolicName, version);
+        ClientResponse response = client().resource(String.format("/v1/catalog/entities/%s/disabled", id))
+                .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+                .post(ClientResponse.class, disabled);
+        assertEquals(response.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
+    }
+
+    private void addTestCatalogItem(String catalogItemId, String itemType, String version, String service) {
+        String yaml =
+                "brooklyn.catalog:\n"+
+                "  id: " + catalogItemId + "\n"+
+                "  name: My Catalog App\n"+
+                (itemType!=null ? "  item_type: "+itemType+"\n" : "")+
+                "  description: My description\n"+
+                "  icon_url: classpath:///redis-logo.png\n"+
+                "  version: " + version + "\n"+
+                "\n"+
+                "services:\n"+
+                "- type: " + service + "\n";
+
+        client().resource("/v1/catalog").post(yaml);
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/CatalogResetTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/CatalogResetTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/CatalogResetTest.java
new file mode 100644
index 0000000..cadea54
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/CatalogResetTest.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import static org.testng.Assert.assertNotNull;
+
+import javax.ws.rs.core.MediaType;
+
+import org.apache.http.HttpStatus;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+import org.apache.brooklyn.api.catalog.BrooklynCatalog;
+import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry;
+import org.apache.brooklyn.test.http.TestHttpRequestHandler;
+import org.apache.brooklyn.test.http.TestHttpServer;
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+import org.apache.brooklyn.util.core.ResourceUtils;
+
+import com.sun.jersey.api.client.UniformInterfaceException;
+
+public class CatalogResetTest extends BrooklynRestResourceTest {
+
+    private TestHttpServer server;
+    private String serverUrl;
+
+    @BeforeClass(alwaysRun=true)
+    @Override
+    public void setUp() throws Exception {
+        useLocalScannedCatalog();
+        super.setUp();
+        server = new TestHttpServer()
+            .handler("/404", new TestHttpRequestHandler().code(HttpStatus.SC_NOT_FOUND).response("Not Found"))
+            .handler("/200", new TestHttpRequestHandler().response("OK"))
+            .start();
+        serverUrl = server.getUrl();
+    }
+
+    @Override
+    protected void addBrooklynResources() {
+        addResource(new CatalogResource());
+    }
+
+    @AfterClass(alwaysRun=true)
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+        server.stop();
+    }
+
+    @Test(expectedExceptions=UniformInterfaceException.class, expectedExceptionsMessageRegExp="Client response status: 500")
+    public void testConnectionError() throws Exception {
+        reset("http://0.0.0.0/can-not-connect", false);
+    }
+
+    @Test
+    public void testConnectionErrorIgnore() throws Exception {
+        reset("http://0.0.0.0/can-not-connect", true);
+    }
+
+    @Test(expectedExceptions=UniformInterfaceException.class, expectedExceptionsMessageRegExp="Client response status: 500")
+    public void testResourceMissingError() throws Exception {
+        reset(serverUrl + "/404", false);
+    }
+
+    @Test
+    public void testResourceMissingIgnore() throws Exception {
+        reset(serverUrl + "/404", true);
+    }
+
+    @Test(expectedExceptions=UniformInterfaceException.class, expectedExceptionsMessageRegExp="Client response status: 500")
+    public void testResourceInvalidError() throws Exception {
+        reset(serverUrl + "/200", false);
+    }
+
+    @Test
+    public void testResourceInvalidIgnore() throws Exception {
+        reset(serverUrl + "/200", true);
+    }
+
+    private void reset(String bundleLocation, boolean ignoreErrors) throws Exception {
+        String xml = ResourceUtils.create(this).getResourceAsString("classpath://reset-catalog.xml");
+        client().resource("/v1/catalog/reset")
+            .queryParam("ignoreErrors", Boolean.toString(ignoreErrors))
+            .header("Content-type", MediaType.APPLICATION_XML)
+            .post(xml.replace("${bundle-location}", bundleLocation));
+        //if above succeeds assert catalog contents
+        assertItems();
+    }
+    
+    private void assertItems() {
+        BrooklynTypeRegistry types = getManagementContext().getTypeRegistry();
+        assertNotNull(types.get("org.apache.brooklyn.entity.stock.BasicApplication", BrooklynCatalog.DEFAULT_VERSION));
+        assertNotNull(types.get("org.apache.brooklyn.test.osgi.entities.SimpleApplication", BrooklynCatalog.DEFAULT_VERSION));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/CatalogResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/CatalogResourceTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/CatalogResourceTest.java
new file mode 100644
index 0000000..4e4d79e
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/CatalogResourceTest.java
@@ -0,0 +1,513 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.awt.*;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URL;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.apache.brooklyn.api.typereg.OsgiBundleWithUrl;
+import org.apache.brooklyn.api.typereg.RegisteredType;
+import org.apache.brooklyn.core.catalog.internal.CatalogUtils;
+import org.apache.brooklyn.core.mgmt.osgi.OsgiStandaloneTest;
+import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.apache.brooklyn.policy.autoscaling.AutoScalerPolicy;
+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.testing.BrooklynRestResourceTest;
+import org.apache.brooklyn.test.support.TestResourceUnavailableException;
+import org.apache.brooklyn.util.javalang.Reflections;
+import org.apache.http.HttpHeaders;
+import org.apache.http.entity.ContentType;
+import org.eclipse.jetty.http.HttpStatus;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+import org.testng.reporters.Files;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.Iterables;
+import com.sun.jersey.api.client.ClientResponse;
+import com.sun.jersey.api.client.GenericType;
+
+public class CatalogResourceTest extends BrooklynRestResourceTest {
+
+    private static final Logger log = LoggerFactory.getLogger(CatalogResourceTest.class);
+    
+    private static String TEST_VERSION = "0.1.2";
+
+    @BeforeClass(alwaysRun=true)
+    @Override
+    public void setUp() throws Exception {
+        useLocalScannedCatalog();
+        super.setUp();
+    }
+    
+    @Override
+    protected void addBrooklynResources() {
+        addResource(new CatalogResource());
+    }
+
+    @Test
+    /** based on CampYamlLiteTest */
+    public void testRegisterCustomEntityTopLevelSyntaxWithBundleWhereEntityIsFromCoreAndIconFromBundle() {
+        TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH);
+
+        String symbolicName = "my.catalog.entity.id";
+        String bundleUrl = OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL;
+        String yaml =
+                "brooklyn.catalog:\n"+
+                "  id: " + symbolicName + "\n"+
+                "  name: My Catalog App\n"+
+                "  description: My description\n"+
+                "  icon_url: classpath:/org/apache/brooklyn/test/osgi/entities/icon.gif\n"+
+                "  version: " + TEST_VERSION + "\n"+
+                "  libraries:\n"+
+                "  - url: " + bundleUrl + "\n"+
+                "\n"+
+                "services:\n"+
+                "- type: org.apache.brooklyn.core.test.entity.TestEntity\n";
+
+        ClientResponse response = client().resource("/v1/catalog")
+                .post(ClientResponse.class, yaml);
+
+        assertEquals(response.getStatus(), Response.Status.CREATED.getStatusCode());
+
+        CatalogEntitySummary entityItem = client().resource("/v1/catalog/entities/"+symbolicName + "/" + TEST_VERSION)
+                .get(CatalogEntitySummary.class);
+
+        Assert.assertNotNull(entityItem.getPlanYaml());
+        Assert.assertTrue(entityItem.getPlanYaml().contains("org.apache.brooklyn.core.test.entity.TestEntity"));
+
+        assertEquals(entityItem.getId(), ver(symbolicName));
+        assertEquals(entityItem.getSymbolicName(), symbolicName);
+        assertEquals(entityItem.getVersion(), TEST_VERSION);
+
+        // and internally let's check we have libraries
+        RegisteredType item = getManagementContext().getTypeRegistry().get(symbolicName, TEST_VERSION);
+        Assert.assertNotNull(item);
+        Collection<OsgiBundleWithUrl> libs = item.getLibraries();
+        assertEquals(libs.size(), 1);
+        assertEquals(Iterables.getOnlyElement(libs).getUrl(), bundleUrl);
+
+        // now let's check other things on the item
+        assertEquals(entityItem.getName(), "My Catalog App");
+        assertEquals(entityItem.getDescription(), "My description");
+        assertEquals(URI.create(entityItem.getIconUrl()).getPath(), "/v1/catalog/icon/" + symbolicName + "/" + entityItem.getVersion());
+        assertEquals(item.getIconUrl(), "classpath:/org/apache/brooklyn/test/osgi/entities/icon.gif");
+
+        // an InterfacesTag should be created for every catalog item
+        assertEquals(entityItem.getTags().size(), 1);
+        Object tag = entityItem.getTags().iterator().next();
+        List<String> actualInterfaces = ((Map<String, List<String>>) tag).get("traits");
+        List<Class<?>> expectedInterfaces = Reflections.getAllInterfaces(TestEntity.class);
+        assertEquals(actualInterfaces.size(), expectedInterfaces.size());
+        for (Class<?> expectedInterface : expectedInterfaces) {
+            assertTrue(actualInterfaces.contains(expectedInterface.getName()));
+        }
+
+        byte[] iconData = client().resource("/v1/catalog/icon/" + symbolicName + "/" + TEST_VERSION).get(byte[].class);
+        assertEquals(iconData.length, 43);
+    }
+
+    @Test
+    public void testRegisterOsgiPolicyTopLevelSyntax() {
+        TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH);
+
+        String symbolicName = "my.catalog.policy.id";
+        String policyType = "org.apache.brooklyn.test.osgi.entities.SimplePolicy";
+        String bundleUrl = OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL;
+
+        String yaml =
+                "brooklyn.catalog:\n"+
+                "  id: " + symbolicName + "\n"+
+                "  name: My Catalog App\n"+
+                "  description: My description\n"+
+                "  version: " + TEST_VERSION + "\n" +
+                "  libraries:\n"+
+                "  - url: " + bundleUrl + "\n"+
+                "\n"+
+                "brooklyn.policies:\n"+
+                "- type: " + policyType;
+
+        CatalogPolicySummary entityItem = Iterables.getOnlyElement( client().resource("/v1/catalog")
+                .post(new GenericType<Map<String,CatalogPolicySummary>>() {}, yaml).values() );
+
+        Assert.assertNotNull(entityItem.getPlanYaml());
+        Assert.assertTrue(entityItem.getPlanYaml().contains(policyType));
+        assertEquals(entityItem.getId(), ver(symbolicName));
+        assertEquals(entityItem.getSymbolicName(), symbolicName);
+        assertEquals(entityItem.getVersion(), TEST_VERSION);
+    }
+
+    @Test
+    public void testListAllEntities() {
+        List<CatalogEntitySummary> entities = client().resource("/v1/catalog/entities")
+                .get(new GenericType<List<CatalogEntitySummary>>() {});
+        assertTrue(entities.size() > 0);
+    }
+
+    @Test
+    public void testListAllEntitiesAsItem() {
+        // ensure things are happily downcasted and unknown properties ignored (e.g. sensors, effectors)
+        List<CatalogItemSummary> entities = client().resource("/v1/catalog/entities")
+                .get(new GenericType<List<CatalogItemSummary>>() {});
+        assertTrue(entities.size() > 0);
+    }
+
+    @Test
+    public void testFilterListOfEntitiesByName() {
+        List<CatalogEntitySummary> entities = client().resource("/v1/catalog/entities")
+                .queryParam("fragment", "brOOkLynENTITYmiRrOr").get(new GenericType<List<CatalogEntitySummary>>() {});
+        assertEquals(entities.size(), 1);
+
+        log.info("BrooklynEntityMirror-like entities are: " + entities);
+
+        List<CatalogEntitySummary> entities2 = client().resource("/v1/catalog/entities")
+                .queryParam("regex", "[Bb]ro+klynEntityMi[ro]+").get(new GenericType<List<CatalogEntitySummary>>() {});
+        assertEquals(entities2.size(), 1);
+
+        assertEquals(entities, entities2);
+    
+        List<CatalogEntitySummary> entities3 = client().resource("/v1/catalog/entities")
+                .queryParam("fragment", "bweqQzZ").get(new GenericType<List<CatalogEntitySummary>>() {});
+        assertEquals(entities3.size(), 0);
+
+        List<CatalogEntitySummary> entities4 = client().resource("/v1/catalog/entities")
+                .queryParam("regex", "bweq+z+").get(new GenericType<List<CatalogEntitySummary>>() {});
+        assertEquals(entities4.size(), 0);
+    }
+
+    @Test
+    @Deprecated
+    // If we move to using a yaml catalog item, the details will be of the wrapping app,
+    // not of the entity itself, so the test won't make sense any more.
+    public void testGetCatalogEntityDetails() {
+        CatalogEntitySummary details = client()
+                .resource(URI.create("/v1/catalog/entities/org.apache.brooklyn.entity.brooklynnode.BrooklynNode"))
+                .get(CatalogEntitySummary.class);
+        assertTrue(details.toString().contains("download.url"), "expected more config, only got: "+details);
+    }
+
+    @Test
+    @Deprecated
+    // If we move to using a yaml catalog item, the details will be of the wrapping app,
+    // not of the entity itself, so the test won't make sense any more.
+    public void testGetCatalogEntityPlusVersionDetails() {
+        CatalogEntitySummary details = client()
+                .resource(URI.create("/v1/catalog/entities/org.apache.brooklyn.entity.brooklynnode.BrooklynNode:0.0.0.SNAPSHOT"))
+                .get(CatalogEntitySummary.class);
+        assertTrue(details.toString().contains("download.url"), "expected more config, only got: "+details);
+    }
+
+    @Test
+    public void testGetCatalogEntityIconDetails() throws IOException {
+        String catalogItemId = "testGetCatalogEntityIconDetails";
+        addTestCatalogItemBrooklynNodeAsEntity(catalogItemId);
+        ClientResponse response = client().resource(URI.create("/v1/catalog/icon/" + catalogItemId + "/" + TEST_VERSION))
+                .get(ClientResponse.class);
+        response.bufferEntity();
+        Assert.assertEquals(response.getStatus(), 200);
+        Assert.assertEquals(response.getType(), MediaType.valueOf("image/jpeg"));
+        Image image = Toolkit.getDefaultToolkit().createImage(Files.readFile(response.getEntityInputStream()));
+        Assert.assertNotNull(image);
+    }
+
+    private void addTestCatalogItemBrooklynNodeAsEntity(String catalogItemId) {
+        addTestCatalogItem(catalogItemId, null, TEST_VERSION, "org.apache.brooklyn.entity.brooklynnode.BrooklynNode");
+    }
+
+    private void addTestCatalogItem(String catalogItemId, String itemType, String version, String service) {
+        String yaml =
+                "brooklyn.catalog:\n"+
+                "  id: " + catalogItemId + "\n"+
+                "  name: My Catalog App\n"+
+                (itemType!=null ? "  item_type: "+itemType+"\n" : "")+
+                "  description: My description\n"+
+                "  icon_url: classpath:///brooklyn-test-logo.jpg\n"+
+                "  version: " + version + "\n"+
+                "\n"+
+                "services:\n"+
+                "- type: " + service + "\n";
+
+        client().resource("/v1/catalog").post(yaml);
+    }
+
+    private enum DeprecateStyle {
+        NEW_STYLE,
+        LEGACY_STYLE
+    }
+    private void deprecateCatalogItem(DeprecateStyle style, String symbolicName, String version, boolean deprecated) {
+        String id = String.format("%s:%s", symbolicName, version);
+        ClientResponse response;
+        if (style == DeprecateStyle.NEW_STYLE) {
+            response = client().resource(String.format("/v1/catalog/entities/%s/deprecated", id))
+                    .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+                    .post(ClientResponse.class, deprecated);
+        } else {
+            response = client().resource(String.format("/v1/catalog/entities/%s/deprecated/%s", id, deprecated))
+                    .post(ClientResponse.class);
+        }
+        assertEquals(response.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
+    }
+
+    private void disableCatalogItem(String symbolicName, String version, boolean disabled) {
+        String id = String.format("%s:%s", symbolicName, version);
+        ClientResponse getDisableResponse = client().resource(String.format("/v1/catalog/entities/%s/disabled", id))
+                .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+                .post(ClientResponse.class, disabled);
+        assertEquals(getDisableResponse.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
+    }
+
+    @Test
+    public void testListPolicies() {
+        Set<CatalogPolicySummary> policies = client().resource("/v1/catalog/policies")
+                .get(new GenericType<Set<CatalogPolicySummary>>() {});
+
+        assertTrue(policies.size() > 0);
+        CatalogItemSummary asp = null;
+        for (CatalogItemSummary p : policies) {
+            if (AutoScalerPolicy.class.getName().equals(p.getType()))
+                asp = p;
+        }
+        Assert.assertNotNull(asp, "didn't find AutoScalerPolicy");
+    }
+
+    @Test
+    public void testLocationAddGetAndRemove() {
+        String symbolicName = "my.catalog.location.id";
+        String locationType = "localhost";
+        String yaml = Joiner.on("\n").join(
+                "brooklyn.catalog:",
+                "  id: " + symbolicName,
+                "  name: My Catalog Location",
+                "  description: My description",
+                "  version: " + TEST_VERSION,
+                "",
+                "brooklyn.locations:",
+                "- type: " + locationType);
+
+        // Create location item
+        Map<String, CatalogLocationSummary> items = client().resource("/v1/catalog")
+                .post(new GenericType<Map<String,CatalogLocationSummary>>() {}, yaml);
+        CatalogLocationSummary locationItem = Iterables.getOnlyElement(items.values());
+
+        Assert.assertNotNull(locationItem.getPlanYaml());
+        Assert.assertTrue(locationItem.getPlanYaml().contains(locationType));
+        assertEquals(locationItem.getId(), ver(symbolicName));
+        assertEquals(locationItem.getSymbolicName(), symbolicName);
+        assertEquals(locationItem.getVersion(), TEST_VERSION);
+
+        // Retrieve location item
+        CatalogLocationSummary location = client().resource("/v1/catalog/locations/"+symbolicName+"/"+TEST_VERSION)
+                .get(CatalogLocationSummary.class);
+        assertEquals(location.getSymbolicName(), symbolicName);
+
+        // Retrieve all locations
+        Set<CatalogLocationSummary> locations = client().resource("/v1/catalog/locations")
+                .get(new GenericType<Set<CatalogLocationSummary>>() {});
+        boolean found = false;
+        for (CatalogLocationSummary contender : locations) {
+            if (contender.getSymbolicName().equals(symbolicName)) {
+                found = true;
+                break;
+            }
+        }
+        Assert.assertTrue(found, "contenders="+locations);
+        
+        // Delete
+        ClientResponse deleteResponse = client().resource("/v1/catalog/locations/"+symbolicName+"/"+TEST_VERSION)
+                .delete(ClientResponse.class);
+        assertEquals(deleteResponse.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
+
+        ClientResponse getPostDeleteResponse = client().resource("/v1/catalog/locations/"+symbolicName+"/"+TEST_VERSION)
+                .get(ClientResponse.class);
+        assertEquals(getPostDeleteResponse.getStatus(), Response.Status.NOT_FOUND.getStatusCode());
+    }
+
+    @Test
+    public void testDeleteCustomEntityFromCatalog() {
+        String symbolicName = "my.catalog.app.id.to.subsequently.delete";
+        String yaml =
+                "name: "+symbolicName+"\n"+
+                // FIXME name above should be unnecessary when brooklyn.catalog below is working
+                "brooklyn.catalog:\n"+
+                "  id: " + symbolicName + "\n"+
+                "  name: My Catalog App To Be Deleted\n"+
+                "  description: My description\n"+
+                "  version: " + TEST_VERSION + "\n"+
+                "\n"+
+                "services:\n"+
+                "- type: org.apache.brooklyn.core.test.entity.TestEntity\n";
+
+        client().resource("/v1/catalog")
+                .post(ClientResponse.class, yaml);
+
+        ClientResponse deleteResponse = client().resource("/v1/catalog/entities/"+symbolicName+"/"+TEST_VERSION)
+                .delete(ClientResponse.class);
+
+        assertEquals(deleteResponse.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
+
+        ClientResponse getPostDeleteResponse = client().resource("/v1/catalog/entities/"+symbolicName+"/"+TEST_VERSION)
+                .get(ClientResponse.class);
+        assertEquals(getPostDeleteResponse.getStatus(), Response.Status.NOT_FOUND.getStatusCode());
+    }
+
+    @Test
+    public void testSetDeprecated() {
+        runSetDeprecated(DeprecateStyle.NEW_STYLE);
+    }
+    
+    // Uses old-style "/v1/catalog/{itemId}/deprecated/true", rather than the "true" in the request body.
+    @Test
+    @Deprecated
+    public void testSetDeprecatedLegacy() {
+        runSetDeprecated(DeprecateStyle.LEGACY_STYLE);
+    }
+
+    protected void runSetDeprecated(DeprecateStyle style) {
+        String symbolicName = "my.catalog.item.id.for.deprecation";
+        String serviceType = "org.apache.brooklyn.entity.stock.BasicApplication";
+        addTestCatalogItem(symbolicName, "template", TEST_VERSION, serviceType);
+        addTestCatalogItem(symbolicName, "template", "2.0", serviceType);
+        try {
+            List<CatalogEntitySummary> applications = client().resource("/v1/catalog/applications")
+                    .queryParam("fragment", symbolicName).queryParam("allVersions", "true").get(new GenericType<List<CatalogEntitySummary>>() {});
+            assertEquals(applications.size(), 2);
+            CatalogItemSummary summary0 = applications.get(0);
+            CatalogItemSummary summary1 = applications.get(1);
+    
+            // Deprecate: that app should be excluded
+            deprecateCatalogItem(style, summary0.getSymbolicName(), summary0.getVersion(), true);
+    
+            List<CatalogEntitySummary> applicationsAfterDeprecation = client().resource("/v1/catalog/applications")
+                    .queryParam("fragment", "basicapp").queryParam("allVersions", "true").get(new GenericType<List<CatalogEntitySummary>>() {});
+    
+            assertEquals(applicationsAfterDeprecation.size(), 1);
+            assertTrue(applicationsAfterDeprecation.contains(summary1));
+    
+            // Un-deprecate: that app should be included again
+            deprecateCatalogItem(style, summary0.getSymbolicName(), summary0.getVersion(), false);
+    
+            List<CatalogEntitySummary> applicationsAfterUnDeprecation = client().resource("/v1/catalog/applications")
+                    .queryParam("fragment", "basicapp").queryParam("allVersions", "true").get(new GenericType<List<CatalogEntitySummary>>() {});
+    
+            assertEquals(applications, applicationsAfterUnDeprecation);
+        } finally {
+            client().resource("/v1/catalog/entities/"+symbolicName+"/"+TEST_VERSION)
+                    .delete(ClientResponse.class);
+            client().resource("/v1/catalog/entities/"+symbolicName+"/"+"2.0")
+                    .delete(ClientResponse.class);
+        }
+    }
+
+    @Test
+    public void testSetDisabled() {
+        String symbolicName = "my.catalog.item.id.for.disabling";
+        String serviceType = "org.apache.brooklyn.entity.stock.BasicApplication";
+        addTestCatalogItem(symbolicName, "template", TEST_VERSION, serviceType);
+        addTestCatalogItem(symbolicName, "template", "2.0", serviceType);
+        try {
+            List<CatalogEntitySummary> applications = client().resource("/v1/catalog/applications")
+                    .queryParam("fragment", symbolicName).queryParam("allVersions", "true").get(new GenericType<List<CatalogEntitySummary>>() {});
+            assertEquals(applications.size(), 2);
+            CatalogItemSummary summary0 = applications.get(0);
+            CatalogItemSummary summary1 = applications.get(1);
+    
+            // Disable: that app should be excluded
+            disableCatalogItem(summary0.getSymbolicName(), summary0.getVersion(), true);
+    
+            List<CatalogEntitySummary> applicationsAfterDisabled = client().resource("/v1/catalog/applications")
+                    .queryParam("fragment", "basicapp").queryParam("allVersions", "true").get(new GenericType<List<CatalogEntitySummary>>() {});
+    
+            assertEquals(applicationsAfterDisabled.size(), 1);
+            assertTrue(applicationsAfterDisabled.contains(summary1));
+    
+            // Un-disable: that app should be included again
+            disableCatalogItem(summary0.getSymbolicName(), summary0.getVersion(), false);
+    
+            List<CatalogEntitySummary> applicationsAfterUnDisabled = client().resource("/v1/catalog/applications")
+                    .queryParam("fragment", "basicapp").queryParam("allVersions", "true").get(new GenericType<List<CatalogEntitySummary>>() {});
+    
+            assertEquals(applications, applicationsAfterUnDisabled);
+        } finally {
+            client().resource("/v1/catalog/entities/"+symbolicName+"/"+TEST_VERSION)
+                    .delete(ClientResponse.class);
+            client().resource("/v1/catalog/entities/"+symbolicName+"/"+"2.0")
+                    .delete(ClientResponse.class);
+        }
+    }
+
+    @Test
+    public void testAddUnreachableItem() {
+        addInvalidCatalogItem("http://0.0.0.0/can-not-connect");
+    }
+
+    @Test
+    public void testAddInvalidItem() {
+        //equivalent to HTTP response 200 text/html
+        addInvalidCatalogItem("classpath://not-a-jar-file.txt");
+    }
+
+    @Test
+    public void testAddMissingItem() {
+        //equivalent to HTTP response 404 text/html
+        addInvalidCatalogItem("classpath://missing-jar-file.txt");
+    }
+
+    private void addInvalidCatalogItem(String bundleUrl) {
+        String symbolicName = "my.catalog.entity.id";
+        String yaml =
+                "brooklyn.catalog:\n"+
+                "  id: " + symbolicName + "\n"+
+                "  name: My Catalog App\n"+
+                "  description: My description\n"+
+                "  icon_url: classpath:/org/apache/brooklyn/test/osgi/entities/icon.gif\n"+
+                "  version: " + TEST_VERSION + "\n"+
+                "  libraries:\n"+
+                "  - url: " + bundleUrl + "\n"+
+                "\n"+
+                "services:\n"+
+                "- type: org.apache.brooklyn.core.test.entity.TestEntity\n";
+
+        ClientResponse response = client().resource("/v1/catalog")
+                .post(ClientResponse.class, yaml);
+
+        assertEquals(response.getStatus(), HttpStatus.INTERNAL_SERVER_ERROR_500);
+    }
+
+    private static String ver(String id) {
+        return CatalogUtils.getVersionedId(id, TEST_VERSION);
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/DelegatingPrintStream.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/DelegatingPrintStream.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/DelegatingPrintStream.java
new file mode 100644
index 0000000..713dccb
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/DelegatingPrintStream.java
@@ -0,0 +1,183 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.util.Locale;
+
+public abstract class DelegatingPrintStream extends PrintStream {
+    
+    public DelegatingPrintStream() {
+        super(new IllegalOutputStream());
+    }
+
+    public static class IllegalOutputStream extends OutputStream {
+        @Override public void write(int b) {
+            throw new IllegalStateException("should not write to this output stream");
+        }
+        @Override public void write(byte[] b, int off, int len) {
+            throw new IllegalStateException("should not write to this output stream");
+        }
+    }
+    
+    protected abstract PrintStream getDelegate();
+
+    public int hashCode() {
+        return getDelegate().hashCode();
+    }
+
+    public void write(byte[] b) throws IOException {
+        getDelegate().write(b);
+    }
+
+    public boolean equals(Object obj) {
+        return getDelegate().equals(obj);
+    }
+
+    public String toString() {
+        return getDelegate().toString();
+    }
+
+    public void flush() {
+        getDelegate().flush();
+    }
+
+    public void close() {
+        getDelegate().close();
+    }
+
+    public boolean checkError() {
+        return getDelegate().checkError();
+    }
+
+    public void write(int b) {
+        getDelegate().write(b);
+    }
+
+    public void write(byte[] buf, int off, int len) {
+        getDelegate().write(buf, off, len);
+    }
+
+    public void print(boolean b) {
+        getDelegate().print(b);
+    }
+
+    public void print(char c) {
+        getDelegate().print(c);
+    }
+
+    public void print(int i) {
+        getDelegate().print(i);
+    }
+
+    public void print(long l) {
+        getDelegate().print(l);
+    }
+
+    public void print(float f) {
+        getDelegate().print(f);
+    }
+
+    public void print(double d) {
+        getDelegate().print(d);
+    }
+
+    public void print(char[] s) {
+        getDelegate().print(s);
+    }
+
+    public void print(String s) {
+        getDelegate().print(s);
+    }
+
+    public void print(Object obj) {
+        getDelegate().print(obj);
+    }
+
+    public void println() {
+        getDelegate().println();
+    }
+
+    public void println(boolean x) {
+        getDelegate().println(x);
+    }
+
+    public void println(char x) {
+        getDelegate().println(x);
+    }
+
+    public void println(int x) {
+        getDelegate().println(x);
+    }
+
+    public void println(long x) {
+        getDelegate().println(x);
+    }
+
+    public void println(float x) {
+        getDelegate().println(x);
+    }
+
+    public void println(double x) {
+        getDelegate().println(x);
+    }
+
+    public void println(char[] x) {
+        getDelegate().println(x);
+    }
+
+    public void println(String x) {
+        getDelegate().println(x);
+    }
+
+    public void println(Object x) {
+        getDelegate().println(x);
+    }
+
+    public PrintStream printf(String format, Object... args) {
+        return getDelegate().printf(format, args);
+    }
+
+    public PrintStream printf(Locale l, String format, Object... args) {
+        return getDelegate().printf(l, format, args);
+    }
+
+    public PrintStream format(String format, Object... args) {
+        return getDelegate().format(format, args);
+    }
+
+    public PrintStream format(Locale l, String format, Object... args) {
+        return getDelegate().format(l, format, args);
+    }
+
+    public PrintStream append(CharSequence csq) {
+        return getDelegate().append(csq);
+    }
+
+    public PrintStream append(CharSequence csq, int start, int end) {
+        return getDelegate().append(csq, start, end);
+    }
+
+    public PrintStream append(char c) {
+        return getDelegate().append(c);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/DescendantsTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/DescendantsTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/DescendantsTest.java
new file mode 100644
index 0000000..3f42c8d
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/DescendantsTest.java
@@ -0,0 +1,130 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeoutException;
+
+import org.apache.brooklyn.api.entity.Application;
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.core.sensor.Sensors;
+import org.apache.brooklyn.rest.domain.ApplicationSpec;
+import org.apache.brooklyn.rest.domain.EntitySpec;
+import org.apache.brooklyn.rest.domain.EntitySummary;
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+import org.apache.brooklyn.rest.testing.mocks.RestMockSimpleEntity;
+import org.apache.brooklyn.util.collections.MutableList;
+import org.apache.brooklyn.util.text.StringEscapes;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.sun.jersey.api.client.ClientHandlerException;
+import com.sun.jersey.api.client.ClientResponse;
+import com.sun.jersey.api.client.GenericType;
+import com.sun.jersey.api.client.UniformInterfaceException;
+
+@Test(singleThreaded = true)
+public class DescendantsTest extends BrooklynRestResourceTest {
+
+    private static final Logger log = LoggerFactory.getLogger(DescendantsTest.class);
+
+    private final ApplicationSpec simpleSpec = ApplicationSpec.builder().name("simple-app").
+        entities(ImmutableSet.of(
+            new EntitySpec("simple-ent-1", RestMockSimpleEntity.class.getName()),
+            new EntitySpec("simple-ent-2", RestMockSimpleEntity.class.getName()))).
+        locations(ImmutableSet.of("localhost")).
+        build();
+
+    @Test
+    public void testDescendantsInSimpleDeployedApplication() throws InterruptedException, TimeoutException, UniformInterfaceException, ClientHandlerException, IOException {
+        ClientResponse response = clientDeploy(simpleSpec);
+        assertTrue(response.getStatus()/100 == 2, "response is "+response);
+        Application application = Iterables.getOnlyElement( getManagementContext().getApplications() );
+        List<Entity> entities = MutableList.copyOf( application.getChildren() );
+        log.debug("Created app "+application+" with children entities "+entities);
+        assertEquals(entities.size(), 2);
+        
+        Set<EntitySummary> descs;
+        descs = client().resource("/v1/applications/"+application.getApplicationId()+"/descendants")
+            .get(new GenericType<Set<EntitySummary>>() {});
+        // includes itself
+        assertEquals(descs.size(), 3);
+        
+        descs = client().resource("/v1/applications/"+application.getApplicationId()+"/descendants"
+            + "?typeRegex="+StringEscapes.escapeUrlParam(".*\\.RestMockSimpleEntity"))
+            .get(new GenericType<Set<EntitySummary>>() {});
+        assertEquals(descs.size(), 2);
+        
+        descs = client().resource("/v1/applications/"+application.getApplicationId()+"/descendants"
+            + "?typeRegex="+StringEscapes.escapeUrlParam(".*\\.BestBockSimpleEntity"))
+            .get(new GenericType<Set<EntitySummary>>() {});
+        assertEquals(descs.size(), 0);
+
+        descs = client().resource("/v1/applications/"+application.getApplicationId()
+            + "/entities/"+entities.get(1).getId()
+            + "/descendants"
+            + "?typeRegex="+StringEscapes.escapeUrlParam(".*\\.RestMockSimpleEntity"))
+            .get(new GenericType<Set<EntitySummary>>() {});
+        assertEquals(descs.size(), 1);
+        
+        Map<String,Object> sensors = client().resource("/v1/applications/"+application.getApplicationId()+"/descendants/sensor/foo"
+            + "?typeRegex="+StringEscapes.escapeUrlParam(".*\\.RestMockSimpleEntity"))
+            .get(new GenericType<Map<String,Object>>() {});
+        assertEquals(sensors.size(), 0);
+
+        long v = 0;
+        application.sensors().set(Sensors.newLongSensor("foo"), v);
+        for (Entity e: entities)
+            e.sensors().set(Sensors.newLongSensor("foo"), v+=123);
+        
+        sensors = client().resource("/v1/applications/"+application.getApplicationId()+"/descendants/sensor/foo")
+            .get(new GenericType<Map<String,Object>>() {});
+        assertEquals(sensors.size(), 3);
+        assertEquals(sensors.get(entities.get(1).getId()), 246);
+        
+        sensors = client().resource("/v1/applications/"+application.getApplicationId()+"/descendants/sensor/foo"
+            + "?typeRegex="+StringEscapes.escapeUrlParam(".*\\.RestMockSimpleEntity"))
+            .get(new GenericType<Map<String,Object>>() {});
+        assertEquals(sensors.size(), 2);
+        
+        sensors = client().resource("/v1/applications/"+application.getApplicationId()+"/"
+            + "entities/"+entities.get(1).getId()+"/"
+            + "descendants/sensor/foo"
+            + "?typeRegex="+StringEscapes.escapeUrlParam(".*\\.RestMockSimpleEntity"))
+            .get(new GenericType<Map<String,Object>>() {});
+        assertEquals(sensors.size(), 1);
+
+        sensors = client().resource("/v1/applications/"+application.getApplicationId()+"/"
+            + "entities/"+entities.get(1).getId()+"/"
+            + "descendants/sensor/foo"
+            + "?typeRegex="+StringEscapes.escapeUrlParam(".*\\.FestPockSimpleEntity"))
+            .get(new GenericType<Map<String,Object>>() {});
+        assertEquals(sensors.size(), 0);
+    }
+    
+}


[34/34] brooklyn-server git commit: Merge remote-tracking branch 'apache-git/master' into osgi

Posted by he...@apache.org.
Merge remote-tracking branch 'apache-git/master' into osgi


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

Branch: refs/heads/master
Commit: fa19e8f5f6c4bfe2967b87d5e0f3efcbcc1f8304
Parents: bdd502f a2820e5
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Thu Feb 18 15:45:02 2016 +0000
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Thu Feb 18 15:45:02 2016 +0000

----------------------------------------------------------------------
 .../spi/dsl/methods/BrooklynDslCommon.java      | 73 ++++++++++++++++++++
 .../brooklyn/spi/dsl/methods/DslComponent.java  | 56 +++++++++++++++
 2 files changed, 129 insertions(+)
----------------------------------------------------------------------



[22/34] brooklyn-server git commit: REST API optional Jersey compatibility

Posted by he...@apache.org.
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/UsageResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/UsageResourceTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/UsageResourceTest.java
new file mode 100644
index 0000000..72392fe
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/UsageResourceTest.java
@@ -0,0 +1,443 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+
+import javax.ws.rs.core.Response;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.location.LocationSpec;
+import org.apache.brooklyn.api.location.NoMachinesAvailableException;
+import org.apache.brooklyn.core.mgmt.internal.LocalUsageManager;
+import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
+import org.apache.brooklyn.core.test.entity.TestApplication;
+import org.apache.brooklyn.entity.software.base.SoftwareProcessEntityTest;
+import org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation;
+import org.apache.brooklyn.location.ssh.SshMachineLocation;
+import org.apache.brooklyn.rest.domain.ApplicationSpec;
+import org.apache.brooklyn.rest.domain.Status;
+import org.apache.brooklyn.rest.domain.TaskSummary;
+import org.apache.brooklyn.rest.domain.UsageStatistic;
+import org.apache.brooklyn.rest.domain.UsageStatistics;
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+import org.apache.brooklyn.rest.testing.mocks.RestMockSimpleEntity;
+import org.apache.brooklyn.util.repeat.Repeater;
+import org.apache.brooklyn.util.time.Time;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.sun.jersey.api.client.ClientResponse;
+import com.sun.jersey.api.client.GenericType;
+
+public class UsageResourceTest extends BrooklynRestResourceTest {
+
+    @SuppressWarnings("unused")
+    private static final Logger LOG = LoggerFactory.getLogger(UsageResourceTest.class);
+
+    private static final long TIMEOUT_MS = 10*1000;
+    
+    private Calendar testStartTime;
+    
+    private final ApplicationSpec simpleSpec = ApplicationSpec.builder().name("simple-app").
+            entities(ImmutableSet.of(new org.apache.brooklyn.rest.domain.EntitySpec("simple-ent", RestMockSimpleEntity.class.getName()))).
+            locations(ImmutableSet.of("localhost")).
+            build();
+
+    @BeforeMethod(alwaysRun=true)
+    public void setUpMethod() {
+        ((ManagementContextInternal)getManagementContext()).getStorage().remove(LocalUsageManager.APPLICATION_USAGE_KEY);
+        ((ManagementContextInternal)getManagementContext()).getStorage().remove(LocalUsageManager.LOCATION_USAGE_KEY);
+        testStartTime = new GregorianCalendar();
+    }
+
+    @Test
+    public void testListApplicationUsages() throws Exception {
+        // Create an app
+        Calendar preStart = new GregorianCalendar();
+        String appId = createApp(simpleSpec);
+        Calendar postStart = new GregorianCalendar();
+        
+        // We will retrieve usage from one millisecond after start; this guarantees to not be  
+        // told about both STARTING+RUNNING, which could otherwise happen if they are in the 
+        // same milliscond.
+        Calendar afterPostStart = Time.newCalendarFromMillisSinceEpochUtc(postStart.getTime().getTime()+1);
+        
+        // Check that app's usage is returned
+        ClientResponse response = client().resource("/v1/usage/applications").get(ClientResponse.class);
+        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
+        Iterable<UsageStatistics> usages = response.getEntity(new GenericType<List<UsageStatistics>>() {});
+        UsageStatistics usage = Iterables.getOnlyElement(usages);
+        assertAppUsage(usage, appId, ImmutableList.of(Status.STARTING, Status.RUNNING), roundDown(preStart), postStart);
+
+        // check app ignored if endCalendar before app started
+        response = client().resource("/v1/usage/applications?start="+0+"&end="+(preStart.getTime().getTime()-1)).get(ClientResponse.class);
+        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
+        usages = response.getEntity(new GenericType<List<UsageStatistics>>() {});
+        assertTrue(Iterables.isEmpty(usages), "usages="+usages);
+        
+        // Wait, so that definitely asking about things that have happened (not things in the future, 
+        // or events that are happening this exact same millisecond)
+        waitForFuture(afterPostStart.getTime().getTime());
+
+        // Check app start + end date truncated, even if running for longer (i.e. only tell us about this time window).
+        // Note that start==end means we get a snapshot of the apps in use at that exact time.
+        //
+        // The start/end times in UsageStatistic are in String format, and are rounded down to the nearest second.
+        // The comparison does use the milliseconds passed in the REST call though.
+        // The rounding down result should be the same as roundDown(afterPostStart), because that is the time-window
+        // we asked for.
+        response = client().resource("/v1/usage/applications?start="+afterPostStart.getTime().getTime()+"&end="+afterPostStart.getTime().getTime()).get(ClientResponse.class);
+        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
+        usages = response.getEntity(new GenericType<List<UsageStatistics>>() {});
+        usage = Iterables.getOnlyElement(usages);
+        assertAppUsage(usage, appId, ImmutableList.of(Status.RUNNING), roundDown(preStart), postStart);
+        assertAppUsageTimesTruncated(usage, roundDown(afterPostStart), roundDown(afterPostStart));
+
+        // Delete the app
+        Calendar preDelete = new GregorianCalendar();
+        deleteApp(appId);
+        Calendar postDelete = new GregorianCalendar();
+
+        // Deleted app still returned, if in time range
+        response = client().resource("/v1/usage/applications").get(ClientResponse.class);
+        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
+        usages = response.getEntity(new GenericType<List<UsageStatistics>>() {});
+        usage = Iterables.getOnlyElement(usages);
+        assertAppUsage(usage, appId, ImmutableList.of(Status.STARTING, Status.RUNNING, Status.DESTROYED), roundDown(preStart), postDelete);
+        assertAppUsage(ImmutableList.copyOf(usage.getStatistics()).subList(2, 3), appId, ImmutableList.of(Status.DESTROYED), roundDown(preDelete), postDelete);
+
+        long afterPostDelete = postDelete.getTime().getTime()+1;
+        waitForFuture(afterPostDelete);
+        
+        response = client().resource("/v1/usage/applications?start=" + afterPostDelete).get(ClientResponse.class);
+        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
+        usages = response.getEntity(new GenericType<List<UsageStatistics>>() {});
+        assertTrue(Iterables.isEmpty(usages), "usages="+usages);
+    }
+
+    @Test
+    public void testGetApplicationUsagesForNonExistantApp() throws Exception {
+        ClientResponse response = client().resource("/v1/usage/applications/wrongid").get(ClientResponse.class);
+        assertEquals(response.getStatus(), Response.Status.NOT_FOUND.getStatusCode());
+    }
+    
+    @Test
+    public void testGetApplicationUsage() throws Exception {
+        // Create an app
+        Calendar preStart = new GregorianCalendar();
+        String appId = createApp(simpleSpec);
+        Calendar postStart = new GregorianCalendar();
+        
+        // Normal request returns all
+        ClientResponse response = client().resource("/v1/usage/applications/" + appId).get(ClientResponse.class);
+        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
+        UsageStatistics usage = response.getEntity(new GenericType<UsageStatistics>() {});
+        assertAppUsage(usage, appId, ImmutableList.of(Status.STARTING, Status.RUNNING), roundDown(preStart), postStart);
+
+        // Time-constrained requests
+        response = client().resource("/v1/usage/applications/" + appId + "?start=1970-01-01T00:00:00-0100").get(ClientResponse.class);
+        usage = response.getEntity(new GenericType<UsageStatistics>() {});
+        assertAppUsage(usage, appId, ImmutableList.of(Status.STARTING, Status.RUNNING), roundDown(preStart), postStart);
+        
+        response = client().resource("/v1/usage/applications/" + appId + "?start=9999-01-01T00:00:00+0100").get(ClientResponse.class);
+        assertTrue(response.getStatus() >= 400, "end defaults to NOW, so future start should fail, instead got code "+response.getStatus());
+        
+        response = client().resource("/v1/usage/applications/" + appId + "?start=9999-01-01T00:00:00%2B0100&end=9999-01-02T00:00:00%2B0100").get(ClientResponse.class);
+        usage = response.getEntity(new GenericType<UsageStatistics>() {});
+        assertTrue(usage.getStatistics().isEmpty());
+
+        response = client().resource("/v1/usage/applications/" + appId + "?end=9999-01-01T00:00:00+0100").get(ClientResponse.class);
+        usage = response.getEntity(new GenericType<UsageStatistics>() {});
+        assertAppUsage(usage, appId, ImmutableList.of(Status.STARTING, Status.RUNNING), roundDown(preStart), postStart);
+
+        response = client().resource("/v1/usage/applications/" + appId + "?start=9999-01-01T00:00:00+0100&end=9999-02-01T00:00:00+0100").get(ClientResponse.class);
+        usage = response.getEntity(new GenericType<UsageStatistics>() {});
+        assertTrue(usage.getStatistics().isEmpty());
+
+        response = client().resource("/v1/usage/applications/" + appId + "?start=1970-01-01T00:00:00-0100&end=9999-01-01T00:00:00+0100").get(ClientResponse.class);
+        usage = response.getEntity(new GenericType<UsageStatistics>() {});
+        assertAppUsage(usage, appId, ImmutableList.of(Status.STARTING, Status.RUNNING), roundDown(preStart), postStart);
+        
+        response = client().resource("/v1/usage/applications/" + appId + "?end=1970-01-01T00:00:00-0100").get(ClientResponse.class);
+        usage = response.getEntity(new GenericType<UsageStatistics>() {});
+        assertTrue(usage.getStatistics().isEmpty());
+        
+        // Delete the app
+        Calendar preDelete = new GregorianCalendar();
+        deleteApp(appId);
+        Calendar postDelete = new GregorianCalendar();
+
+        // Deleted app still returned, if in time range
+        response = client().resource("/v1/usage/applications/" + appId).get(ClientResponse.class);
+        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
+        usage = response.getEntity(new GenericType<UsageStatistics>() {});
+        assertAppUsage(usage, appId, ImmutableList.of(Status.STARTING, Status.RUNNING, Status.DESTROYED), roundDown(preStart), postDelete);
+        assertAppUsage(ImmutableList.copyOf(usage.getStatistics()).subList(2, 3), appId, ImmutableList.of(Status.DESTROYED), roundDown(preDelete), postDelete);
+
+        // Deleted app not returned if terminated before time range begins
+        long afterPostDelete = postDelete.getTime().getTime()+1;
+        waitForFuture(afterPostDelete);
+        response = client().resource("/v1/usage/applications/" + appId +"?start=" + afterPostDelete).get(ClientResponse.class);
+        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
+        usage = response.getEntity(new GenericType<UsageStatistics>() {});
+        assertTrue(usage.getStatistics().isEmpty(), "usages="+usage);
+    }
+
+    @Test
+    public void testGetMachineUsagesForNonExistantMachine() throws Exception {
+        ClientResponse response = client().resource("/v1/usage/machines/wrongid").get(ClientResponse.class);
+        assertEquals(response.getStatus(), Response.Status.NOT_FOUND.getStatusCode());
+    }
+
+    @Test
+    public void testGetMachineUsagesInitiallyEmpty() throws Exception {
+        // All machines: empty
+        ClientResponse response = client().resource("/v1/usage/machines").get(ClientResponse.class);
+        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
+        Iterable<UsageStatistics> usages = response.getEntity(new GenericType<List<UsageStatistics>>() {});
+        assertTrue(Iterables.isEmpty(usages));
+        
+        // Specific machine that does not exist: get 404
+        response = client().resource("/v1/usage/machines/machineIdThatDoesNotExist").get(ClientResponse.class);
+        assertEquals(response.getStatus(), Response.Status.NOT_FOUND.getStatusCode());
+    }
+
+    @Test
+    public void testListAndGetMachineUsage() throws Exception {
+        Location location = getManagementContext().getLocationManager().createLocation(LocationSpec.create(DynamicLocalhostMachineProvisioningLocation.class));
+        TestApplication app = getManagementContext().getEntityManager().createEntity(EntitySpec.create(TestApplication.class));
+        SoftwareProcessEntityTest.MyService entity = app.createAndManageChild(org.apache.brooklyn.api.entity.EntitySpec.create(SoftwareProcessEntityTest.MyService.class));
+        
+        Calendar preStart = new GregorianCalendar();
+        app.start(ImmutableList.of(location));
+        Calendar postStart = new GregorianCalendar();
+        Location machine = Iterables.getOnlyElement(entity.getLocations());
+
+        // All machines
+        ClientResponse response = client().resource("/v1/usage/machines").get(ClientResponse.class);
+        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
+        Iterable<UsageStatistics> usages = response.getEntity(new GenericType<List<UsageStatistics>>() {});
+        UsageStatistics usage = Iterables.getOnlyElement(usages);
+        assertMachineUsage(usage, app.getId(), machine.getId(), ImmutableList.of(Status.ACCEPTED), roundDown(preStart), postStart);
+
+        // Specific machine
+        response = client().resource("/v1/usage/machines/"+machine.getId()).get(ClientResponse.class);
+        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
+        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
+        usage = response.getEntity(new GenericType<UsageStatistics>() {});
+        assertMachineUsage(usage, app.getId(), machine.getId(), ImmutableList.of(Status.ACCEPTED), roundDown(preStart), postStart);
+    }
+
+    @Test
+    public void testListMachinesUsageForApp() throws Exception {
+        Location location = getManagementContext().getLocationManager().createLocation(LocationSpec.create(DynamicLocalhostMachineProvisioningLocation.class));
+        TestApplication app = getManagementContext().getEntityManager().createEntity(EntitySpec.create(TestApplication.class));
+        SoftwareProcessEntityTest.MyService entity = app.createAndManageChild(org.apache.brooklyn.api.entity.EntitySpec.create(SoftwareProcessEntityTest.MyService.class));
+        String appId = app.getId();
+        
+        Calendar preStart = new GregorianCalendar();
+        app.start(ImmutableList.of(location));
+        Calendar postStart = new GregorianCalendar();
+        Location machine = Iterables.getOnlyElement(entity.getLocations());
+
+        // For running machine
+        ClientResponse response = client().resource("/v1/usage/machines?application="+appId).get(ClientResponse.class);
+        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
+        Iterable<UsageStatistics> usages = response.getEntity(new GenericType<List<UsageStatistics>>() {});
+        UsageStatistics usage = Iterables.getOnlyElement(usages);
+        assertMachineUsage(usage, app.getId(), machine.getId(), ImmutableList.of(Status.ACCEPTED), roundDown(preStart), postStart);
+        
+        // Stop the machine
+        Calendar preStop = new GregorianCalendar();
+        app.stop();
+        Calendar postStop = new GregorianCalendar();
+        
+        // Deleted machine still returned, if in time range
+        response = client().resource("/v1/usage/machines?application=" + appId).get(ClientResponse.class);
+        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
+        usages = response.getEntity(new GenericType<List<UsageStatistics>>() {});
+        usage = Iterables.getOnlyElement(usages);
+        assertMachineUsage(usage, app.getId(), machine.getId(), ImmutableList.of(Status.ACCEPTED, Status.DESTROYED), roundDown(preStart), postStop);
+        assertMachineUsage(ImmutableList.copyOf(usage.getStatistics()).subList(1,2), appId, machine.getId(), ImmutableList.of(Status.DESTROYED), roundDown(preStop), postStop);
+
+        // Terminated machines ignored if terminated since start-time
+        long futureTime = postStop.getTime().getTime()+1;
+        waitForFuture(futureTime);
+        response = client().resource("/v1/usage/applications?start=" + futureTime).get(ClientResponse.class);
+        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
+        usages = response.getEntity(new GenericType<List<UsageStatistics>>() {});
+        assertTrue(Iterables.isEmpty(usages), "usages="+usages);
+    }
+
+    private String createApp(ApplicationSpec spec) {
+        ClientResponse response = clientDeploy(spec);
+        assertEquals(response.getStatus(), Response.Status.CREATED.getStatusCode());
+        TaskSummary createTask = response.getEntity(TaskSummary.class);
+        waitForTask(createTask.getId());
+        return createTask.getEntityId();
+    }
+    
+    private void deleteApp(String appId) {
+        ClientResponse response = client().resource("/v1/applications/"+appId)
+                .delete(ClientResponse.class);
+        assertEquals(response.getStatus(), Response.Status.ACCEPTED.getStatusCode());
+        TaskSummary deletionTask = response.getEntity(TaskSummary.class);
+        waitForTask(deletionTask.getId());
+    }
+    
+    private void assertCalendarOrders(Object context, Calendar... Calendars) {
+        if (Calendars.length <= 1) return;
+        
+        long[] times = new long[Calendars.length];
+        for (int i = 0; i < times.length; i++) {
+            times[i] = millisSinceStart(Calendars[i]);
+        }
+        String err = "context="+context+"; Calendars="+Arrays.toString(Calendars) + "; CalendarsSanitized="+Arrays.toString(times);
+        
+        Calendar Calendar = Calendars[0];
+        for (int i = 1; i < Calendars.length; i++) {
+            assertTrue(Calendar.getTime().getTime() <= Calendars[i].getTime().getTime(), err);
+        }
+    }
+    
+    private void waitForTask(final String taskId) {
+        boolean success = Repeater.create()
+                .repeat(new Runnable() { public void run() {}})
+                .until(new Callable<Boolean>() {
+                    @Override public Boolean call() {
+                        ClientResponse response = client().resource("/v1/activities/"+taskId).get(ClientResponse.class);
+                        if (response.getStatus() == Response.Status.NOT_FOUND.getStatusCode()) {
+                            return true;
+                        }
+                        TaskSummary summary = response.getEntity(TaskSummary.class);
+                        return summary != null && summary.getEndTimeUtc() != null;
+                    }})
+                .every(10L, TimeUnit.MILLISECONDS)
+                .limitTimeTo(TIMEOUT_MS, TimeUnit.MILLISECONDS)
+                .run();
+        assertTrue(success, "task "+taskId+" not finished");
+    }
+
+    private long millisSinceStart(Calendar time) {
+        return time.getTime().getTime() - testStartTime.getTime().getTime();
+    }
+    
+    private Calendar roundDown(Calendar calendar) {
+        long time = calendar.getTime().getTime();
+        long timeDown = ((long)(time / 1000)) * 1000;
+        return Time.newCalendarFromMillisSinceEpochUtc(timeDown);
+    }
+    
+    @SuppressWarnings("unused")
+    private Calendar roundUp(Calendar calendar) {
+        long time = calendar.getTime().getTime();
+        long timeDown = ((long)(time / 1000)) * 1000;
+        long timeUp = (time == timeDown) ? time : timeDown + 1000;
+        return Time.newCalendarFromMillisSinceEpochUtc(timeUp);
+    }
+
+    private void assertMachineUsage(UsageStatistics usage, String appId, String machineId, List<Status> states, Calendar pre, Calendar post) throws Exception {
+        assertUsage(usage.getStatistics(), appId, machineId, states, pre, post, false);
+    }
+    
+    private void assertMachineUsage(Iterable<UsageStatistic> usages, String appId, String machineId, List<Status> states, Calendar pre, Calendar post) throws Exception {
+        assertUsage(usages, appId, machineId, states, pre, post, false);
+    }
+    
+    private void assertAppUsage(UsageStatistics usage, String appId, List<Status> states, Calendar pre, Calendar post) throws Exception {
+        assertUsage(usage.getStatistics(), appId, appId, states, pre, post, false);
+    }
+    
+    private void assertAppUsage(Iterable<UsageStatistic> usages, String appId, List<Status> states, Calendar pre, Calendar post) throws Exception {
+        assertUsage(usages, appId, appId, states, pre, post, false);
+    }
+
+    private void assertUsage(Iterable<UsageStatistic> usages, String appId, String id, List<Status> states, Calendar pre, Calendar post, boolean allowGaps) throws Exception {
+        String errMsg = "usages="+usages;
+        Calendar now = new GregorianCalendar();
+        Calendar lowerBound = pre;
+        Calendar strictStart = null;
+        
+        assertEquals(Iterables.size(usages), states.size(), errMsg);
+        for (int i = 0; i < Iterables.size(usages); i++) {
+            UsageStatistic usage = Iterables.get(usages, i);
+            Calendar usageStart = Time.parseCalendar(usage.getStart());
+            Calendar usageEnd = Time.parseCalendar(usage.getEnd());
+            assertEquals(usage.getId(), id, errMsg);
+            assertEquals(usage.getApplicationId(), appId, errMsg);
+            assertEquals(usage.getStatus(), states.get(i), errMsg);
+            assertCalendarOrders(usages, lowerBound, usageStart, post);
+            assertCalendarOrders(usages, usageEnd, now);
+            if (strictStart != null) {
+                assertEquals(usageStart, strictStart, errMsg);
+            }
+            if (!allowGaps) {
+                strictStart = usageEnd;
+            }
+            lowerBound = usageEnd;
+        }
+    }
+
+    private void assertAppUsageTimesTruncated(UsageStatistics usages, Calendar strictStart, Calendar strictEnd) throws Exception {
+        String errMsg = "strictStart="+Time.makeDateString(strictStart)+"; strictEnd="+Time.makeDateString(strictEnd)+";usages="+usages;
+        Calendar usageStart = Time.parseCalendar(Iterables.getFirst(usages.getStatistics(), null).getStart());
+        Calendar usageEnd = Time.parseCalendar(Iterables.getLast(usages.getStatistics()).getStart());
+        // time zones might be different - so must convert to date
+        assertEquals(usageStart.getTime(), strictStart.getTime(), "usageStart="+Time.makeDateString(usageStart)+";"+errMsg);
+        assertEquals(usageEnd.getTime(), strictEnd.getTime(), errMsg);
+    }
+    
+    public static class DynamicLocalhostMachineProvisioningLocation extends LocalhostMachineProvisioningLocation {
+        @Override
+        public SshMachineLocation obtain(Map<?, ?> flags) throws NoMachinesAvailableException {
+            return super.obtain(flags);
+        }
+        
+        @Override
+        public void release(SshMachineLocation machine) {
+            super.release(machine);
+            super.machines.remove(machine);
+            getManagementContext().getLocationManager().unmanage(machine);
+        }
+    }
+
+    private void waitForFuture(long futureTime) throws InterruptedException {
+        long now;
+        while ((now = System.currentTimeMillis()) < futureTime) {
+            Thread.sleep(futureTime - now);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/VersionResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/VersionResourceTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/VersionResourceTest.java
new file mode 100644
index 0000000..384feb0
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/VersionResourceTest.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import javax.ws.rs.core.Response;
+
+import org.testng.annotations.Test;
+
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+
+import com.sun.jersey.api.client.ClientResponse;
+
+public class VersionResourceTest extends BrooklynRestResourceTest {
+
+    @Test
+    public void testGetVersion() {
+        ClientResponse response = client().resource("/v1/version")
+                .get(ClientResponse.class);
+
+        assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
+        String version = response.getEntity(String.class);
+// TODO johnmccabe - 19/12/2015 :: temporarily disabled while the repo split work is ongoing,
+// must be restored when switching back to a valid brooklyn version
+//        assertTrue(version.matches("^\\d+\\.\\d+\\.\\d+.*"));
+        assertTrue(true);
+    }
+
+    @SuppressWarnings("deprecation")
+    @Override
+    protected void addBrooklynResources() {
+        addResource(new VersionResource());
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/security/PasswordHasherTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/security/PasswordHasherTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/security/PasswordHasherTest.java
new file mode 100644
index 0000000..575d6a4
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/security/PasswordHasherTest.java
@@ -0,0 +1,37 @@
+/*
+ * 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;
+
+import static org.testng.Assert.assertEquals;
+
+import org.testng.annotations.Test;
+
+public class PasswordHasherTest {
+
+    @Test
+    public void testHashSha256() throws Exception {
+        // Note: expected hash values generated externally:
+        // echo -n mysaltmypassword | openssl dgst -sha256
+
+        assertEquals(PasswordHasher.sha256("mysalt", "mypassword"), "d02878b06efa88579cd84d9e50b211c0a7caa92cf243bad1622c66081f7e2692");
+        assertEquals(PasswordHasher.sha256("", "mypassword"), "89e01536ac207279409d4de1e5253e01f4a1769e696db0d6062ca9b8f56767c8");
+        assertEquals(PasswordHasher.sha256(null, "mypassword"), "89e01536ac207279409d4de1e5253e01f4a1769e696db0d6062ca9b8f56767c8");
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/security/provider/TestSecurityProvider.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/security/provider/TestSecurityProvider.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/security/provider/TestSecurityProvider.java
new file mode 100644
index 0000000..cad251f
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/security/provider/TestSecurityProvider.java
@@ -0,0 +1,46 @@
+/*
+ * 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;
+
+import org.apache.http.auth.UsernamePasswordCredentials;
+
+public class TestSecurityProvider implements SecurityProvider {
+
+    public static final String USER = "test";
+    public static final String PASSWORD = "opensesame";
+    public static final UsernamePasswordCredentials CREDENTIAL =
+            new UsernamePasswordCredentials(TestSecurityProvider.USER, TestSecurityProvider.PASSWORD);
+
+    @Override
+    public boolean isAuthenticated(HttpSession session) {
+        return false;
+    }
+
+    @Override
+    public boolean authenticate(HttpSession session, String user, String password) {
+        return USER.equals(user) && PASSWORD.equals(password);
+    }
+
+    @Override
+    public boolean logout(HttpSession session) {
+        return false;
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/test/config/render/TestRendererHints.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/test/config/render/TestRendererHints.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/test/config/render/TestRendererHints.java
new file mode 100644
index 0000000..91294db
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/test/config/render/TestRendererHints.java
@@ -0,0 +1,36 @@
+/*
+ * 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.test.config.render;
+
+import org.apache.brooklyn.core.config.render.RendererHints;
+
+/** Methods used when testing the {@link RendererHints} regiostry. */
+public class TestRendererHints {
+
+    /** Clear the registry. 
+     *
+     *  MUST be used by a single test only.
+     *  TestNG interleaves the tests (sequentially) which results in tearDown 
+     *  executing in the middle of another class' tests. Only one tearDown may
+     *  call this method.
+     **/
+    public static void clearRegistry() {
+        RendererHints.getRegistry().clear();
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/test/entity/brooklynnode/DeployBlueprintTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/test/entity/brooklynnode/DeployBlueprintTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/test/entity/brooklynnode/DeployBlueprintTest.java
new file mode 100644
index 0000000..2ab62a9
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/test/entity/brooklynnode/DeployBlueprintTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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.test.entity.brooklynnode;
+
+import static org.testng.Assert.assertEquals;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.mgmt.EntityManager;
+import org.apache.brooklyn.entity.brooklynnode.BrooklynNode;
+import org.apache.brooklyn.entity.brooklynnode.BrooklynNode.DeployBlueprintEffector;
+import org.apache.brooklyn.entity.stock.BasicApplication;
+import org.apache.brooklyn.feed.http.JsonFunctions;
+import org.apache.brooklyn.rest.BrooklynRestApiLauncherTestFixture;
+import org.apache.brooklyn.test.HttpTestUtils;
+import org.apache.brooklyn.util.guava.Functionals;
+import org.eclipse.jetty.server.Server;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+public class DeployBlueprintTest extends BrooklynRestApiLauncherTestFixture {
+
+    private static final Logger log = LoggerFactory.getLogger(DeployBlueprintTest.class);
+
+    Server server;
+
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        server = newServer();
+        useServerForTest(server);
+    }
+
+    @Test
+    public void testStartsAppViaEffector() throws Exception {
+        URI webConsoleUri = URI.create(getBaseUri());
+
+        EntitySpec<BrooklynNode> spec = EntitySpec.create(BrooklynNode.class);
+        EntityManager mgr = getManagementContextFromJettyServerAttributes(server).getEntityManager();
+        BrooklynNode node = mgr.createEntity(spec);
+        node.sensors().set(BrooklynNode.WEB_CONSOLE_URI, webConsoleUri);
+        mgr.manage(node);
+        Map<String, String> params = ImmutableMap.of(DeployBlueprintEffector.BLUEPRINT_CAMP_PLAN.getName(), "{ services: [ serviceType: \"java:"+BasicApplication.class.getName()+"\" ] }");
+        String id = node.invoke(BrooklynNode.DEPLOY_BLUEPRINT, params).getUnchecked();
+
+        log.info("got: "+id);
+
+        String apps = HttpTestUtils.getContent(webConsoleUri.toString()+"/v1/applications");
+        List<String> appType = parseJsonList(apps, ImmutableList.of("spec", "type"), String.class);
+        assertEquals(appType, ImmutableList.of(BasicApplication.class.getName()));
+        
+        String status = HttpTestUtils.getContent(webConsoleUri.toString()+"/v1/applications/"+id+"/entities/"+id+"/sensors/service.status");
+        log.info("STATUS: "+status);
+    }
+    
+    private <T> List<T> parseJsonList(String json, List<String> elements, Class<T> clazz) {
+        Function<String, List<T>> func = Functionals.chain(
+                JsonFunctions.asJson(),
+                JsonFunctions.forEach(Functionals.chain(
+                        JsonFunctions.walk(elements),
+                        JsonFunctions.cast(clazz))));
+        return func.apply(json);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestApiTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestApiTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestApiTest.java
new file mode 100644
index 0000000..bf3f8af
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestApiTest.java
@@ -0,0 +1,223 @@
+/*
+ * 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.testing;
+
+import java.net.URI;
+
+import org.apache.brooklyn.api.location.LocationRegistry;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.camp.brooklyn.BrooklynCampPlatformLauncherNoServer;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.location.BasicLocationRegistry;
+import org.apache.brooklyn.core.mgmt.ManagementContextInjectable;
+import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
+import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
+import org.apache.brooklyn.rest.BrooklynRestApi;
+import org.apache.brooklyn.rest.BrooklynRestApiLauncherTest;
+import org.apache.brooklyn.rest.util.BrooklynRestResourceUtils;
+import org.apache.brooklyn.rest.util.ManagementContextProvider;
+import org.apache.brooklyn.rest.util.NoOpRecordingShutdownHandler;
+import org.apache.brooklyn.rest.util.NullHttpServletRequestProvider;
+import org.apache.brooklyn.rest.util.NullServletConfigProvider;
+import org.apache.brooklyn.rest.util.ShutdownHandlerProvider;
+import org.apache.brooklyn.rest.util.json.BrooklynJacksonJsonProvider;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeMethod;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.base.Preconditions;
+import com.sun.jersey.api.client.Client;
+import com.sun.jersey.api.client.WebResource;
+import com.sun.jersey.api.core.DefaultResourceConfig;
+import com.sun.jersey.test.framework.AppDescriptor;
+import com.sun.jersey.test.framework.JerseyTest;
+import com.sun.jersey.test.framework.LowLevelAppDescriptor;
+import com.sun.jersey.test.framework.spi.container.TestContainer;
+import com.sun.jersey.test.framework.spi.container.TestContainerException;
+import com.sun.jersey.test.framework.spi.container.TestContainerFactory;
+import com.sun.jersey.test.framework.spi.container.inmemory.InMemoryTestContainerFactory;
+
+public abstract class BrooklynRestApiTest {
+
+    protected ManagementContext manager;
+    
+    protected boolean useLocalScannedCatalog = false;
+    protected NoOpRecordingShutdownHandler shutdownListener = createShutdownHandler();
+
+    @BeforeMethod(alwaysRun = true)
+    public void setUpMethod() {
+        shutdownListener.reset();
+    }
+    
+    protected synchronized void useLocalScannedCatalog() {
+        if (manager!=null && !useLocalScannedCatalog)
+            throw new IllegalStateException("useLocalScannedCatalog must be specified before manager is accessed/created");
+        useLocalScannedCatalog = true;
+    }
+    
+    private NoOpRecordingShutdownHandler createShutdownHandler() {
+        return new NoOpRecordingShutdownHandler();
+    }
+
+    protected synchronized ManagementContext getManagementContext() {
+        if (manager==null) {
+            if (useLocalScannedCatalog) {
+                manager = new LocalManagementContext();
+                BrooklynRestApiLauncherTest.forceUseOfDefaultCatalogWithJavaClassPath(manager);
+            } else {
+                manager = new LocalManagementContextForTests();
+            }
+            manager.getHighAvailabilityManager().disabled();
+            BasicLocationRegistry.setupLocationRegistryForTesting(manager);
+            
+            new BrooklynCampPlatformLauncherNoServer()
+                .useManagementContext(manager)
+                .launch();
+        }
+        return manager;
+    }
+    
+    protected ObjectMapper mapper() {
+        return BrooklynJacksonJsonProvider.findSharedObjectMapper(null, getManagementContext());
+    }
+    
+    @AfterClass
+    public void tearDown() throws Exception {
+        destroyManagementContext();
+    }
+
+    protected void destroyManagementContext() {
+        if (manager!=null) {
+            Entities.destroyAll(manager);
+            manager = null;
+        }
+    }
+    
+    public LocationRegistry getLocationRegistry() {
+        return new BrooklynRestResourceUtils(getManagementContext()).getLocationRegistry();
+    }
+
+    private JerseyTest jerseyTest;
+    protected DefaultResourceConfig config = new DefaultResourceConfig();
+    
+    protected final void addResource(Object resource) {
+        Preconditions.checkNotNull(config, "Must run before setUpJersey");
+        
+        if (resource instanceof Class)
+            config.getClasses().add((Class<?>)resource);
+        else
+            config.getSingletons().add(resource);
+        
+        if (resource instanceof ManagementContextInjectable) {
+            ((ManagementContextInjectable)resource).setManagementContext(getManagementContext());
+        }
+    }
+    
+    protected final void addProvider(Class<?> provider) {
+        Preconditions.checkNotNull(config, "Must run before setUpJersey");
+        
+        config.getClasses().add(provider);
+    }
+    
+    protected void addDefaultResources() {
+        // seems we have to provide our own injector because the jersey test framework 
+        // doesn't inject ServletConfig and it all blows up
+        // and the servlet config provider must be an instance; addClasses doesn't work for some reason
+        addResource(new NullServletConfigProvider());
+        addResource(new ManagementContextProvider(getManagementContext()));
+        addProvider(NullHttpServletRequestProvider.class);
+        addResource(new ShutdownHandlerProvider(shutdownListener));
+    }
+
+    protected final void setUpResources() {
+        addDefaultResources();
+        addBrooklynResources();
+        for (Object r: BrooklynRestApi.getMiscResources())
+            addResource(r);
+    }
+
+    /** intended for overriding if you only want certain resources added, or additional ones added */
+    protected void addBrooklynResources() {
+        for (Object r: BrooklynRestApi.getBrooklynRestResources())
+            addResource(r);
+    }
+
+    protected void setUpJersey() {
+        setUpResources();
+        
+        jerseyTest = createJerseyTest();
+        config = null;
+        try {
+            jerseyTest.setUp();
+        } catch (Exception e) {
+            throw Exceptions.propagate(e);
+        }
+    }
+
+    protected JerseyTest createJerseyTest() {
+        return new JerseyTest() {
+            @Override
+            protected AppDescriptor configure() {
+                return new LowLevelAppDescriptor.Builder(config).build();
+            }
+
+            @Override
+            protected TestContainerFactory getTestContainerFactory() throws TestContainerException {
+                return new TestContainerFactory() {
+                    TestContainerFactory delegate = new InMemoryTestContainerFactory();
+                    
+                    @Override
+                    public Class<? extends AppDescriptor> supports() {
+                        return delegate.supports();
+                    }
+                    
+                    @Override
+                    public TestContainer create(URI baseUri, AppDescriptor ad)
+                            throws IllegalArgumentException {
+                        URI uri = URI.create(baseUri.toString() + "v1/");
+                        System.out.println(uri);;
+                        return delegate.create(uri, (LowLevelAppDescriptor)ad);
+                    }
+                };
+            }
+        };
+    }
+    
+    protected void tearDownJersey() {
+        if (jerseyTest != null) {
+            try {
+                jerseyTest.tearDown();
+            } catch (Exception e) {
+                throw Exceptions.propagate(e);
+            }
+        }
+        config = new DefaultResourceConfig();
+    }
+
+    public Client client() {
+        Preconditions.checkNotNull(jerseyTest, "Must run setUpJersey first");
+        return jerseyTest.client();
+    }
+
+    public WebResource resource(String uri) {
+        Preconditions.checkNotNull(jerseyTest, "Must run setUpJersey first");
+        return jerseyTest.resource().path(uri);
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestResourceTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestResourceTest.java
new file mode 100644
index 0000000..b94e73c
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestResourceTest.java
@@ -0,0 +1,154 @@
+/*
+ * 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.testing;
+
+import static org.testng.Assert.assertTrue;
+
+import java.net.URI;
+import java.util.Collection;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+
+import javax.ws.rs.core.MediaType;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.apache.brooklyn.api.entity.Application;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.rest.domain.ApplicationSpec;
+import org.apache.brooklyn.rest.domain.ApplicationSummary;
+import org.apache.brooklyn.rest.domain.Status;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.repeat.Repeater;
+import org.apache.brooklyn.util.time.Duration;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.sun.jersey.api.client.ClientResponse;
+import com.sun.jersey.api.client.UniformInterfaceException;
+import com.sun.jersey.spi.inject.Errors;
+
+public abstract class BrooklynRestResourceTest extends BrooklynRestApiTest {
+
+    private static final Logger log = LoggerFactory.getLogger(BrooklynRestResourceTest.class);
+    
+    @BeforeClass(alwaysRun = true)
+    public void setUp() throws Exception {
+        // need this to debug jersey inject errors
+        java.util.logging.Logger.getLogger(Errors.class.getName()).setLevel(Level.INFO);
+
+        setUpJersey();
+    }
+
+    @Override
+    @AfterClass(alwaysRun = true)
+    public void tearDown() throws Exception {
+        tearDownJersey();
+        super.tearDown();
+    }
+
+
+    protected ClientResponse clientDeploy(ApplicationSpec spec) {
+        try {
+            // dropwizard TestClient won't skip deserialization of trivial things like string and byte[] and inputstream
+            // if we pass in an object it serializes, so we have to serialize things ourselves
+            return client().resource("/v1/applications")
+                .entity(new ObjectMapper().writer().writeValueAsBytes(spec), MediaType.APPLICATION_OCTET_STREAM)
+                .post(ClientResponse.class);
+        } catch (Exception e) {
+            throw Exceptions.propagate(e);
+        }
+    }
+    
+    protected void waitForApplicationToBeRunning(final URI applicationRef) {
+        waitForApplicationToBeRunning(applicationRef, Duration.minutes(3));
+    }
+    protected void waitForApplicationToBeRunning(final URI applicationRef, Duration timeout) {
+        if (applicationRef==null)
+            throw new NullPointerException("No application URI available (consider using BrooklynRestResourceTest.clientDeploy)");
+        
+        boolean started = Repeater.create("Wait for application startup")
+                .until(new Callable<Boolean>() {
+                    @Override
+                    public Boolean call() throws Exception {
+                        Status status = getApplicationStatus(applicationRef);
+                        if (status == Status.ERROR) {
+                            Assert.fail("Application failed with ERROR");
+                        }
+                        return status == Status.RUNNING;
+                    }
+                })
+                .backoffTo(Duration.ONE_SECOND)
+                .limitTimeTo(timeout)
+                .run();
+        
+        if (!started) {
+            log.warn("Did not start application "+applicationRef+":");
+            Collection<Application> apps = getManagementContext().getApplications();
+            for (Application app: apps)
+                Entities.dumpInfo(app);
+        }
+        assertTrue(started);
+    }
+
+    protected Status getApplicationStatus(URI uri) {
+        return client().resource(uri).get(ApplicationSummary.class).getStatus();
+    }
+
+    protected void waitForPageFoundResponse(final String resource, final Class<?> clazz) {
+        boolean found = Repeater.create("Wait for page found")
+                .until(new Callable<Boolean>() {
+                    @Override
+                    public Boolean call() throws Exception {
+                        try {
+                            client().resource(resource).get(clazz);
+                            return true;
+                        } catch (UniformInterfaceException e) {
+                            return false;
+                        }
+                    }
+                })
+                .every(1, TimeUnit.SECONDS)
+                .limitTimeTo(30, TimeUnit.SECONDS)
+                .run();
+        assertTrue(found);
+    }
+    
+    protected void waitForPageNotFoundResponse(final String resource, final Class<?> clazz) {
+        boolean success = Repeater.create("Wait for page not found")
+                .until(new Callable<Boolean>() {
+                    @Override
+                    public Boolean call() throws Exception {
+                        try {
+                            client().resource(resource).get(clazz);
+                            return false;
+                        } catch (UniformInterfaceException e) {
+                            return e.getResponse().getStatus() == 404;
+                        }
+                    }
+                })
+                .every(1, TimeUnit.SECONDS)
+                .limitTimeTo(30, TimeUnit.SECONDS)
+                .run();
+        assertTrue(success);
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/testing/mocks/CapitalizePolicy.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/testing/mocks/CapitalizePolicy.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/testing/mocks/CapitalizePolicy.java
new file mode 100644
index 0000000..7d80a6f
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/testing/mocks/CapitalizePolicy.java
@@ -0,0 +1,33 @@
+/*
+ * 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.testing.mocks;
+
+import org.apache.brooklyn.api.entity.EntityLocal;
+import org.apache.brooklyn.core.policy.AbstractPolicy;
+
+@SuppressWarnings("deprecation")
+public class CapitalizePolicy extends AbstractPolicy {
+
+    @Override
+    public void setEntity(EntityLocal entity) {
+        super.setEntity(entity);
+        // TODO subscribe to foo and emit an enriched sensor on different channel which is capitalized
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/testing/mocks/EverythingGroup.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/testing/mocks/EverythingGroup.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/testing/mocks/EverythingGroup.java
new file mode 100644
index 0000000..707c4a3
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/testing/mocks/EverythingGroup.java
@@ -0,0 +1,27 @@
+/*
+ * 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.testing.mocks;
+
+import org.apache.brooklyn.api.entity.Group;
+import org.apache.brooklyn.api.entity.ImplementedBy;
+
+@ImplementedBy(EverythingGroupImpl.class)
+public interface EverythingGroup extends Group {
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/testing/mocks/EverythingGroupImpl.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/testing/mocks/EverythingGroupImpl.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/testing/mocks/EverythingGroupImpl.java
new file mode 100644
index 0000000..8b2c98b
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/testing/mocks/EverythingGroupImpl.java
@@ -0,0 +1,32 @@
+/*
+ * 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.testing.mocks;
+
+import org.apache.brooklyn.entity.group.DynamicGroupImpl;
+
+import com.google.common.base.Predicates;
+
+public class EverythingGroupImpl extends DynamicGroupImpl implements EverythingGroup {
+
+    public EverythingGroupImpl() {
+        super();
+        config().set(ENTITY_FILTER, Predicates.alwaysTrue());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/testing/mocks/NameMatcherGroup.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/testing/mocks/NameMatcherGroup.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/testing/mocks/NameMatcherGroup.java
new file mode 100644
index 0000000..f9a2e21
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/testing/mocks/NameMatcherGroup.java
@@ -0,0 +1,30 @@
+/*
+ * 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.testing.mocks;
+
+import org.apache.brooklyn.api.entity.Group;
+import org.apache.brooklyn.api.entity.ImplementedBy;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+
+@ImplementedBy(NameMatcherGroupImpl.class)
+public interface NameMatcherGroup extends Group {
+
+    public static final ConfigKey<String> NAME_REGEX = ConfigKeys.newStringConfigKey("namematchergroup.regex");
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/testing/mocks/NameMatcherGroupImpl.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/testing/mocks/NameMatcherGroupImpl.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/testing/mocks/NameMatcherGroupImpl.java
new file mode 100644
index 0000000..bec416f
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/testing/mocks/NameMatcherGroupImpl.java
@@ -0,0 +1,33 @@
+/*
+ * 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.testing.mocks;
+
+import org.apache.brooklyn.core.entity.EntityPredicates;
+import org.apache.brooklyn.entity.group.DynamicGroupImpl;
+import org.apache.brooklyn.util.text.StringPredicates;
+
+public class NameMatcherGroupImpl extends DynamicGroupImpl implements NameMatcherGroup {
+
+    @Override
+    public void init() {
+        super.init();
+        config().set(ENTITY_FILTER, EntityPredicates.displayNameSatisfies(StringPredicates.matchesRegex(getConfig(NAME_REGEX))));
+        rescanEntities();
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockApp.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockApp.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockApp.java
new file mode 100644
index 0000000..6d92e65
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockApp.java
@@ -0,0 +1,24 @@
+/*
+ * 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.testing.mocks;
+
+import org.apache.brooklyn.core.entity.AbstractApplication;
+
+public class RestMockApp extends AbstractApplication {
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockAppBuilder.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockAppBuilder.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockAppBuilder.java
new file mode 100644
index 0000000..1ca10bd
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockAppBuilder.java
@@ -0,0 +1,39 @@
+/*
+ * 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.testing.mocks;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.core.entity.StartableApplication;
+import org.apache.brooklyn.core.entity.factory.ApplicationBuilder;
+import org.apache.brooklyn.util.javalang.Reflections;
+
+public class RestMockAppBuilder extends ApplicationBuilder {
+
+    public RestMockAppBuilder() {
+        super(EntitySpec.create(StartableApplication.class).impl(RestMockApp.class));
+    }
+    
+    @Override
+    protected void doBuild() {
+        addChild(EntitySpec.create(Entity.class).impl(RestMockSimpleEntity.class)
+            .additionalInterfaces(Reflections.getAllInterfaces(RestMockSimpleEntity.class))
+            .displayName("child1"));
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockSimpleEntity.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockSimpleEntity.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockSimpleEntity.java
new file mode 100644
index 0000000..58d24aa
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockSimpleEntity.java
@@ -0,0 +1,103 @@
+/*
+ * 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.testing.mocks;
+
+import java.util.Map;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.EntityLocal;
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.annotation.Effector;
+import org.apache.brooklyn.core.annotation.EffectorParam;
+import org.apache.brooklyn.core.config.BasicConfigKey;
+import org.apache.brooklyn.core.effector.MethodEffector;
+import org.apache.brooklyn.core.sensor.BasicAttributeSensor;
+import org.apache.brooklyn.entity.software.base.AbstractSoftwareProcessSshDriver;
+import org.apache.brooklyn.entity.software.base.SoftwareProcessImpl;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.brooklyn.location.ssh.SshMachineLocation;
+import org.apache.brooklyn.util.core.flags.SetFromFlag;
+
+public class RestMockSimpleEntity extends SoftwareProcessImpl {
+
+    private static final Logger log = LoggerFactory.getLogger(RestMockSimpleEntity.class);
+    
+    public RestMockSimpleEntity() {
+        super();
+    }
+
+    public RestMockSimpleEntity(Entity parent) {
+        super(parent);
+    }
+
+    public RestMockSimpleEntity(@SuppressWarnings("rawtypes") Map flags, Entity parent) {
+        super(flags, parent);
+    }
+
+    public RestMockSimpleEntity(@SuppressWarnings("rawtypes") Map flags) {
+        super(flags);
+    }
+    
+    @Override
+    protected void connectSensors() {
+        super.connectSensors();
+        connectServiceUpIsRunning();
+    }
+
+    @SetFromFlag("sampleConfig")
+    public static final ConfigKey<String> SAMPLE_CONFIG = new BasicConfigKey<String>(
+            String.class, "brooklyn.rest.mock.sample.config", "Mock sample config", "DEFAULT_VALUE");
+
+    public static final AttributeSensor<String> SAMPLE_SENSOR = new BasicAttributeSensor<String>(
+            String.class, "brooklyn.rest.mock.sample.sensor", "Mock sample sensor");
+
+    public static final MethodEffector<String> SAMPLE_EFFECTOR = new MethodEffector<String>(RestMockSimpleEntity.class, "sampleEffector");
+    
+    @Effector
+    public String sampleEffector(@EffectorParam(name="param1", description="param one") String param1, 
+            @EffectorParam(name="param2") Integer param2) {
+        log.info("Invoked sampleEffector("+param1+","+param2+")");
+        String result = ""+param1+param2;
+        sensors().set(SAMPLE_SENSOR, result);
+        return result;
+    }
+
+    @SuppressWarnings("rawtypes")
+    @Override
+    public Class getDriverInterface() {
+        return MockSshDriver.class;
+    }
+    
+    public static class MockSshDriver extends AbstractSoftwareProcessSshDriver {
+        public MockSshDriver(EntityLocal entity, SshMachineLocation machine) {
+            super(entity, machine);
+        }
+        public boolean isRunning() { return true; }
+        public void stop() {}
+        public void kill() {}
+        public void install() {}
+        public void customize() {}
+        public void launch() {}
+        public void setup() { }
+        public void copyInstallResources() { }
+        public void copyRuntimeResources() { }
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockSimplePolicy.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockSimplePolicy.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockSimplePolicy.java
new file mode 100644
index 0000000..e15cdd1
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockSimplePolicy.java
@@ -0,0 +1,64 @@
+/*
+ * 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.testing.mocks;
+
+import java.util.Map;
+
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.BasicConfigKey;
+import org.apache.brooklyn.core.policy.AbstractPolicy;
+import org.apache.brooklyn.util.core.flags.SetFromFlag;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class RestMockSimplePolicy extends AbstractPolicy {
+
+    @SuppressWarnings("unused")
+    private static final Logger log = LoggerFactory.getLogger(RestMockSimplePolicy.class);
+
+    public RestMockSimplePolicy() {
+        super();
+    }
+
+    @SuppressWarnings("rawtypes")
+    public RestMockSimplePolicy(Map flags) {
+        super(flags);
+    }
+
+    @SetFromFlag("sampleConfig")
+    public static final ConfigKey<String> SAMPLE_CONFIG = BasicConfigKey.builder(String.class)
+            .name("brooklyn.rest.mock.sample.config")
+            .description("Mock sample config")
+            .defaultValue("DEFAULT_VALUE")
+            .reconfigurable(true)
+            .build();
+
+    @SetFromFlag
+    public static final ConfigKey<Integer> INTEGER_CONFIG = BasicConfigKey.builder(Integer.class)
+            .name("brooklyn.rest.mock.sample.integer")
+            .description("Mock integer config")
+            .defaultValue(1)
+            .reconfigurable(true)
+            .build();
+
+    @Override
+    protected <T> void doReconfigureConfig(ConfigKey<T> key, T val) {
+        // no-op
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/util/BrooklynRestResourceUtilsTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/util/BrooklynRestResourceUtilsTest.java b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/util/BrooklynRestResourceUtilsTest.java
new file mode 100644
index 0000000..48908e3
--- /dev/null
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/util/BrooklynRestResourceUtilsTest.java
@@ -0,0 +1,213 @@
+/*
+ * 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.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Map;
+
+import org.apache.brooklyn.api.catalog.Catalog;
+import org.apache.brooklyn.api.entity.Application;
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.policy.Policy;
+import org.apache.brooklyn.core.catalog.internal.CatalogItemBuilder;
+import org.apache.brooklyn.core.catalog.internal.CatalogTemplateItemDto;
+import org.apache.brooklyn.core.catalog.internal.CatalogUtils;
+import org.apache.brooklyn.core.entity.AbstractApplication;
+import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
+import org.apache.brooklyn.core.objs.proxy.EntityProxy;
+import org.apache.brooklyn.core.policy.AbstractPolicy;
+import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
+import org.apache.brooklyn.core.test.entity.TestApplication;
+import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.apache.brooklyn.entity.stock.BasicEntity;
+import org.apache.brooklyn.rest.domain.ApplicationSpec;
+import org.apache.brooklyn.rest.domain.EntitySpec;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+
+public class BrooklynRestResourceUtilsTest {
+
+    private LocalManagementContext managementContext;
+    private BrooklynRestResourceUtils util;
+
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        managementContext = LocalManagementContextForTests.newInstance();
+        util = new BrooklynRestResourceUtils(managementContext);
+    }
+    
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws Exception {
+        if (managementContext != null) managementContext.terminate();
+    }
+
+    @Test
+    public void testCreateAppFromImplClass() {
+        ApplicationSpec spec = ApplicationSpec.builder()
+                .name("myname")
+                .type(SampleNoOpApplication.class.getName())
+                .locations(ImmutableSet.of("localhost"))
+                .build();
+        Application app = util.create(spec);
+        
+        assertEquals(ImmutableList.copyOf(managementContext.getApplications()), ImmutableList.of(app));
+        assertEquals(app.getDisplayName(), "myname");
+        assertTrue(app instanceof EntityProxy);
+        assertTrue(app instanceof MyInterface);
+        assertFalse(app instanceof SampleNoOpApplication);
+    }
+
+    @Test
+    public void testCreateAppFromCatalogByType() {
+        createAppFromCatalog(SampleNoOpApplication.class.getName());
+    }
+
+    @Test
+    public void testCreateAppFromCatalogByName() {
+        createAppFromCatalog("app.noop");
+    }
+
+    @Test
+    public void testCreateAppFromCatalogById() {
+        createAppFromCatalog("app.noop:0.0.1");
+    }
+
+    @Test
+    @SuppressWarnings("deprecation")
+    public void testCreateAppFromCatalogByTypeMultipleItems() {
+        CatalogTemplateItemDto item = CatalogItemBuilder.newTemplate("app.noop", "0.0.2-SNAPSHOT")
+                .javaType(SampleNoOpApplication.class.getName())
+                .build();
+        managementContext.getCatalog().addItem(item);
+        createAppFromCatalog(SampleNoOpApplication.class.getName());
+    }
+
+    @SuppressWarnings("deprecation")
+    private void createAppFromCatalog(String type) {
+        CatalogTemplateItemDto item = CatalogItemBuilder.newTemplate("app.noop", "0.0.1")
+            .javaType(SampleNoOpApplication.class.getName())
+            .build();
+        managementContext.getCatalog().addItem(item);
+        
+        ApplicationSpec spec = ApplicationSpec.builder()
+                .name("myname")
+                .type(type)
+                .locations(ImmutableSet.of("localhost"))
+                .build();
+        Application app = util.create(spec);
+
+        assertEquals(app.getCatalogItemId(), "app.noop:0.0.1");
+    }
+
+    @Test
+    public void testEntityAppFromCatalogByType() {
+        createEntityFromCatalog(BasicEntity.class.getName());
+    }
+
+    @Test
+    public void testEntityAppFromCatalogByName() {
+        createEntityFromCatalog("app.basic");
+    }
+
+    @Test
+    public void testEntityAppFromCatalogById() {
+        createEntityFromCatalog("app.basic:0.0.1");
+    }
+
+    @Test
+    @SuppressWarnings("deprecation")
+    public void testEntityAppFromCatalogByTypeMultipleItems() {
+        CatalogTemplateItemDto item = CatalogItemBuilder.newTemplate("app.basic", "0.0.2-SNAPSHOT")
+                .javaType(SampleNoOpApplication.class.getName())
+                .build();
+        managementContext.getCatalog().addItem(item);
+        createEntityFromCatalog(BasicEntity.class.getName());
+    }
+
+    @SuppressWarnings("deprecation")
+    private void createEntityFromCatalog(String type) {
+        String symbolicName = "app.basic";
+        String version = "0.0.1";
+        CatalogTemplateItemDto item = CatalogItemBuilder.newTemplate(symbolicName, version)
+            .javaType(BasicEntity.class.getName())
+            .build();
+        managementContext.getCatalog().addItem(item);
+
+        ApplicationSpec spec = ApplicationSpec.builder()
+                .name("myname")
+                .entities(ImmutableSet.of(new EntitySpec(type)))
+                .locations(ImmutableSet.of("localhost"))
+                .build();
+        Application app = util.create(spec);
+
+        Entity entity = Iterables.getOnlyElement(app.getChildren());
+        assertEquals(entity.getCatalogItemId(), CatalogUtils.getVersionedId(symbolicName, version));
+    }
+
+    @Test
+    public void testNestedApplications() {
+        // hierarchy is: app -> subapp -> subentity (where subentity has a policy)
+        
+        Application app = managementContext.getEntityManager().createEntity(org.apache.brooklyn.api.entity.EntitySpec.create(TestApplication.class)
+                .displayName("app")
+                .child(org.apache.brooklyn.api.entity.EntitySpec.create(TestApplication.class)
+                        .displayName("subapp")
+                        .child(org.apache.brooklyn.api.entity.EntitySpec.create(TestEntity.class)
+                                .displayName("subentity")
+                                .policy(org.apache.brooklyn.api.policy.PolicySpec.create(MyPolicy.class)
+                                        .displayName("mypolicy")))));
+
+        Application subapp = (Application) Iterables.getOnlyElement(app.getChildren());
+        TestEntity subentity = (TestEntity) Iterables.getOnlyElement(subapp.getChildren());
+        
+        Entity subappRetrieved = util.getEntity(app.getId(), subapp.getId());
+        assertEquals(subappRetrieved.getDisplayName(), "subapp");
+        
+        Entity subentityRetrieved = util.getEntity(app.getId(), subentity.getId());
+        assertEquals(subentityRetrieved.getDisplayName(), "subentity");
+        
+        Policy subappPolicy = util.getPolicy(app.getId(), subentity.getId(), "mypolicy");
+        assertEquals(subappPolicy.getDisplayName(), "mypolicy");
+    }
+
+    public interface MyInterface {
+    }
+
+    @Catalog(name="Sample No-Op Application",
+            description="Application which does nothing, included only as part of the test cases.",
+            iconUrl="")
+    public static class SampleNoOpApplication extends AbstractApplication implements MyInterface {
+    }
+    
+    public static class MyPolicy extends AbstractPolicy {
+        public MyPolicy() {
+        }
+        public MyPolicy(Map<String, ?> flags) {
+            super(flags);
+        }
+    }
+}