Repository: incubator-brooklyn
Updated Branches:
  refs/heads/master 2ed62d61b -> 2aac052fb


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/test/java/org/apache/brooklyn/rest/security/provider/TestSecurityProvider.java
----------------------------------------------------------------------
diff --git 
a/usage/rest-server/src/test/java/org/apache/brooklyn/rest/security/provider/TestSecurityProvider.java
 
b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/security/provider/TestSecurityProvider.java
new file mode 100644
index 0000000..cad251f
--- /dev/null
+++ 
b/usage/rest-server/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/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestApiTest.java
----------------------------------------------------------------------
diff --git 
a/usage/rest-server/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestApiTest.java
 
b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestApiTest.java
new file mode 100644
index 0000000..252c972
--- /dev/null
+++ 
b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestApiTest.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.testing;
+
+import org.apache.brooklyn.camp.brooklyn.BrooklynCampPlatformLauncherNoServer;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeMethod;
+
+import com.google.common.base.Preconditions;
+import com.sun.jersey.api.client.Client;
+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 brooklyn.entity.basic.Entities;
+import brooklyn.location.LocationRegistry;
+import brooklyn.location.basic.BasicLocationRegistry;
+import brooklyn.management.ManagementContext;
+import brooklyn.management.ManagementContextInjectable;
+import brooklyn.management.internal.LocalManagementContext;
+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.TestShutdownHandler;
+import org.apache.brooklyn.rest.util.json.BrooklynJacksonJsonProvider;
+import brooklyn.test.entity.LocalManagementContextForTests;
+import brooklyn.util.exceptions.Exceptions;
+
+public abstract class BrooklynRestApiTest {
+
+    protected ManagementContext manager;
+    
+    protected boolean useLocalScannedCatalog = false;
+    protected TestShutdownHandler 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 TestShutdownHandler createShutdownHandler() {
+        return new TestShutdownHandler();
+    }
+
+    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).injectManagementContext(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 = new JerseyTest() {
+            @Override
+            protected AppDescriptor configure() {
+                return new LowLevelAppDescriptor.Builder(config).build();
+            }
+        };
+        config = null;
+        try {
+            jerseyTest.setUp();
+        } catch (Exception e) {
+            throw Exceptions.propagate(e);
+        }
+    }
+    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();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestResourceTest.java
----------------------------------------------------------------------
diff --git 
a/usage/rest-server/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestResourceTest.java
 
b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestResourceTest.java
new file mode 100644
index 0000000..9db714d
--- /dev/null
+++ 
b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestResourceTest.java
@@ -0,0 +1,155 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.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.codehaus.jackson.map.ObjectMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+
+import brooklyn.entity.Application;
+import brooklyn.entity.basic.Entities;
+import brooklyn.rest.domain.ApplicationSpec;
+import brooklyn.rest.domain.ApplicationSummary;
+import brooklyn.rest.domain.Status;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.repeat.Repeater;
+import brooklyn.util.time.Duration;
+
+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/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/CapitalizePolicy.java
----------------------------------------------------------------------
diff --git 
a/usage/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/CapitalizePolicy.java
 
b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/CapitalizePolicy.java
new file mode 100644
index 0000000..c174891
--- /dev/null
+++ 
b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/CapitalizePolicy.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 brooklyn.entity.basic.EntityLocal;
+import brooklyn.policy.basic.AbstractPolicy;
+
+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/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/EverythingGroup.java
----------------------------------------------------------------------
diff --git 
a/usage/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/EverythingGroup.java
 
b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/EverythingGroup.java
new file mode 100644
index 0000000..3020f9e
--- /dev/null
+++ 
b/usage/rest-server/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 brooklyn.entity.Group;
+import brooklyn.entity.proxying.ImplementedBy;
+
+@ImplementedBy(EverythingGroupImpl.class)
+public interface EverythingGroup extends Group {
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/EverythingGroupImpl.java
----------------------------------------------------------------------
diff --git 
a/usage/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/EverythingGroupImpl.java
 
b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/EverythingGroupImpl.java
new file mode 100644
index 0000000..61c9f3b
--- /dev/null
+++ 
b/usage/rest-server/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 brooklyn.entity.basic.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/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/NameMatcherGroup.java
----------------------------------------------------------------------
diff --git 
a/usage/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/NameMatcherGroup.java
 
b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/NameMatcherGroup.java
new file mode 100644
index 0000000..06552a6
--- /dev/null
+++ 
b/usage/rest-server/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 brooklyn.config.ConfigKey;
+import brooklyn.entity.Group;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.proxying.ImplementedBy;
+
+@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/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/NameMatcherGroupImpl.java
----------------------------------------------------------------------
diff --git 
a/usage/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/NameMatcherGroupImpl.java
 
b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/NameMatcherGroupImpl.java
new file mode 100644
index 0000000..f0a307c
--- /dev/null
+++ 
b/usage/rest-server/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 brooklyn.entity.basic.DynamicGroupImpl;
+import brooklyn.entity.basic.EntityPredicates;
+import 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/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockApp.java
----------------------------------------------------------------------
diff --git 
a/usage/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockApp.java
 
b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockApp.java
new file mode 100644
index 0000000..33447fd
--- /dev/null
+++ 
b/usage/rest-server/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 brooklyn.entity.basic.AbstractApplication;
+
+public class RestMockApp extends AbstractApplication {
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockAppBuilder.java
----------------------------------------------------------------------
diff --git 
a/usage/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockAppBuilder.java
 
b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockAppBuilder.java
new file mode 100644
index 0000000..b9d7d87
--- /dev/null
+++ 
b/usage/rest-server/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 brooklyn.entity.Entity;
+import brooklyn.entity.basic.ApplicationBuilder;
+import brooklyn.entity.basic.StartableApplication;
+import brooklyn.entity.proxying.EntitySpec;
+import 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/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockSimpleEntity.java
----------------------------------------------------------------------
diff --git 
a/usage/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockSimpleEntity.java
 
b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockSimpleEntity.java
new file mode 100644
index 0000000..7fd13da
--- /dev/null
+++ 
b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockSimpleEntity.java
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.Entity;
+import brooklyn.entity.annotation.Effector;
+import brooklyn.entity.annotation.EffectorParam;
+import brooklyn.entity.basic.AbstractSoftwareProcessSshDriver;
+import brooklyn.entity.basic.EntityLocal;
+import brooklyn.entity.basic.MethodEffector;
+import brooklyn.entity.basic.SoftwareProcessImpl;
+import brooklyn.event.AttributeSensor;
+import brooklyn.event.basic.BasicAttributeSensor;
+import brooklyn.event.basic.BasicConfigKey;
+import brooklyn.location.basic.SshMachineLocation;
+import brooklyn.util.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;
+        setAttribute(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/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockSimplePolicy.java
----------------------------------------------------------------------
diff --git 
a/usage/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockSimplePolicy.java
 
b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockSimplePolicy.java
new file mode 100644
index 0000000..92d488f
--- /dev/null
+++ 
b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/testing/mocks/RestMockSimplePolicy.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.event.basic.BasicConfigKey;
+import brooklyn.policy.basic.AbstractPolicy;
+import brooklyn.util.flags.SetFromFlag;
+
+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/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/test/java/org/apache/brooklyn/rest/util/BrooklynRestResourceUtilsTest.java
----------------------------------------------------------------------
diff --git 
a/usage/rest-server/src/test/java/org/apache/brooklyn/rest/util/BrooklynRestResourceUtilsTest.java
 
b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/util/BrooklynRestResourceUtilsTest.java
new file mode 100644
index 0000000..07aefc9
--- /dev/null
+++ 
b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/util/BrooklynRestResourceUtilsTest.java
@@ -0,0 +1,215 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import org.apache.brooklyn.catalog.Catalog;
+import brooklyn.catalog.internal.CatalogItemBuilder;
+import brooklyn.catalog.internal.CatalogTemplateItemDto;
+import brooklyn.catalog.internal.CatalogUtils;
+import brooklyn.entity.Application;
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.AbstractApplication;
+import brooklyn.entity.basic.BasicEntity;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.basic.EntityLocal;
+import brooklyn.entity.proxying.EntityProxy;
+import brooklyn.management.internal.LocalManagementContext;
+import brooklyn.policy.Policy;
+import brooklyn.policy.basic.AbstractPolicy;
+import brooklyn.rest.domain.ApplicationSpec;
+import brooklyn.rest.domain.EntitySpec;
+import brooklyn.test.entity.LocalManagementContextForTests;
+import brooklyn.test.entity.TestEntityImpl;
+import brooklyn.util.collections.MutableMap;
+
+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)
+        
+        SampleNoOpApplication app = new SampleNoOpApplication();
+        app.setDisplayName("app");
+        
+        SampleNoOpApplication subapp = new SampleNoOpApplication();
+        subapp.setDisplayName("subapp");
+        
+        TestEntityImpl subentity = new 
TestEntityImpl(MutableMap.of("displayName", "subentity"), subapp);
+        subentity.addPolicy(new MyPolicy(MutableMap.of("name", "mypolicy")));
+        subentity.getApplication(); // force this to be cached
+        
+        app.addChild(subapp);
+        Entities.startManagement(app, managementContext);
+        
+        EntityLocal subappRetrieved = util.getEntity(app.getId(), 
subapp.getId());
+        assertEquals(subappRetrieved.getDisplayName(), "subapp");
+        
+        EntityLocal 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(Map<String, ?> flags) {
+            super(flags);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/test/java/org/apache/brooklyn/rest/util/EntityLocationUtilsTest.java
----------------------------------------------------------------------
diff --git 
a/usage/rest-server/src/test/java/org/apache/brooklyn/rest/util/EntityLocationUtilsTest.java
 
b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/util/EntityLocationUtilsTest.java
new file mode 100644
index 0000000..9840d84
--- /dev/null
+++ 
b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/util/EntityLocationUtilsTest.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.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 brooklyn.entity.BrooklynAppUnitTestSupport;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.basic.SoftwareProcess;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.location.Location;
+import brooklyn.location.basic.AbstractLocation;
+import brooklyn.location.basic.LocationInternal;
+import brooklyn.location.geo.HostGeoInfo;
+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/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/test/java/org/apache/brooklyn/rest/util/HaHotStateCheckClassResource.java
----------------------------------------------------------------------
diff --git 
a/usage/rest-server/src/test/java/org/apache/brooklyn/rest/util/HaHotStateCheckClassResource.java
 
b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/util/HaHotStateCheckClassResource.java
new file mode 100644
index 0000000..80e9c46
--- /dev/null
+++ 
b/usage/rest-server/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/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/test/java/org/apache/brooklyn/rest/util/HaHotStateCheckResource.java
----------------------------------------------------------------------
diff --git 
a/usage/rest-server/src/test/java/org/apache/brooklyn/rest/util/HaHotStateCheckResource.java
 
b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/util/HaHotStateCheckResource.java
new file mode 100644
index 0000000..5c9d4d1
--- /dev/null
+++ 
b/usage/rest-server/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/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/test/java/org/apache/brooklyn/rest/util/NullHttpServletRequestProvider.java
----------------------------------------------------------------------
diff --git 
a/usage/rest-server/src/test/java/org/apache/brooklyn/rest/util/NullHttpServletRequestProvider.java
 
b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/util/NullHttpServletRequestProvider.java
new file mode 100644
index 0000000..7a24f31
--- /dev/null
+++ 
b/usage/rest-server/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/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/test/java/org/apache/brooklyn/rest/util/NullServletConfigProvider.java
----------------------------------------------------------------------
diff --git 
a/usage/rest-server/src/test/java/org/apache/brooklyn/rest/util/NullServletConfigProvider.java
 
b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/util/NullServletConfigProvider.java
new file mode 100644
index 0000000..06c60ea
--- /dev/null
+++ 
b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/util/NullServletConfigProvider.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.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.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 
+            return null; 
+    } 
+    public ComponentScope getScope() { 
+        return ComponentScope.Singleton; 
+    } 
+} 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/test/java/org/apache/brooklyn/rest/util/TestShutdownHandler.java
----------------------------------------------------------------------
diff --git 
a/usage/rest-server/src/test/java/org/apache/brooklyn/rest/util/TestShutdownHandler.java
 
b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/util/TestShutdownHandler.java
new file mode 100644
index 0000000..87fbb24
--- /dev/null
+++ 
b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/util/TestShutdownHandler.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 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/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/test/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonSerializerTest.java
----------------------------------------------------------------------
diff --git 
a/usage/rest-server/src/test/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonSerializerTest.java
 
b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/util/json/BrooklynJacksonSerializerTest.java
new file mode 100644
index 0000000..8e16198
--- /dev/null
+++ 
b/usage/rest-server/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.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+import org.eclipse.jetty.server.Server;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.Attributes;
+import brooklyn.entity.basic.BrooklynTaskTags;
+import brooklyn.entity.basic.Entities;
+import brooklyn.management.ManagementContext;
+import org.apache.brooklyn.rest.BrooklynRestApiLauncher;
+import brooklyn.test.entity.LocalManagementContextForTests;
+import brooklyn.test.entity.TestApplication;
+import brooklyn.test.entity.TestEntity;
+import brooklyn.util.collections.MutableList;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.http.HttpTool;
+import brooklyn.util.stream.Streams;
+import brooklyn.util.text.Strings;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.MultimapBuilder;
+import com.google.gson.Gson;
+
+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:"+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:"+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();
+    }
+}

Reply via email to