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

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

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