http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/filter/HaHotCheckResourceFilter.java
----------------------------------------------------------------------
diff --git 
a/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/filter/HaHotCheckResourceFilter.java
 
b/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/filter/HaHotCheckResourceFilter.java
new file mode 100644
index 0000000..c65c78f
--- /dev/null
+++ 
b/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/filter/HaHotCheckResourceFilter.java
@@ -0,0 +1,163 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.filter;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ContextResolver;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.api.mgmt.ha.ManagementNodeState;
+import org.apache.brooklyn.rest.domain.ApiError;
+import org.apache.brooklyn.util.text.Strings;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableSet;
+import com.sun.jersey.api.model.AbstractMethod;
+import com.sun.jersey.spi.container.ContainerRequest;
+import com.sun.jersey.spi.container.ContainerRequestFilter;
+import com.sun.jersey.spi.container.ContainerResponseFilter;
+import com.sun.jersey.spi.container.ResourceFilter;
+import com.sun.jersey.spi.container.ResourceFilterFactory;
+
+/** 
+ * Checks that if the method or resource class corresponding to a request
+ * has a {@link HaHotStateRequired} annotation,
+ * that the server is in that state (and up). 
+ * Requests with {@link #SKIP_CHECK_HEADER} set as a header skip this check.
+ * <p>
+ * This follows a different pattern to {@link HaMasterCheckFilter} 
+ * as this needs to know the method being invoked. 
+ */
+public class HaHotCheckResourceFilter implements ResourceFilterFactory {
+    public static final String SKIP_CHECK_HEADER = 
"Brooklyn-Allow-Non-Master-Access";
+    
+    private static final Logger log = 
LoggerFactory.getLogger(HaHotCheckResourceFilter.class);
+    
+    private static final Set<ManagementNodeState> HOT_STATES = ImmutableSet.of(
+            ManagementNodeState.MASTER, ManagementNodeState.HOT_STANDBY, 
ManagementNodeState.HOT_BACKUP);
+
+    @Context
+    private ContextResolver<ManagementContext> mgmt;
+
+    public HaHotCheckResourceFilter() {}
+    
+    @VisibleForTesting
+    public HaHotCheckResourceFilter(ContextResolver<ManagementContext> mgmt) {
+        this.mgmt = mgmt;
+    }
+
+    private ManagementContext mgmt() {
+        return mgmt.getContext(ManagementContext.class);
+    }
+
+    private static class MethodFilter implements ResourceFilter, 
ContainerRequestFilter {
+
+        private AbstractMethod am;
+        private ManagementContext mgmt;
+
+        public MethodFilter(AbstractMethod am, ManagementContext mgmt) {
+            this.am = am;
+            this.mgmt = mgmt;
+        }
+
+        @Override
+        public ContainerRequestFilter getRequestFilter() {
+            return this;
+        }
+
+        @Override
+        public ContainerResponseFilter getResponseFilter() {
+            return null;
+        }
+
+        private String lookForProblem(ContainerRequest request) {
+            if (isSkipCheckHeaderSet(request)) 
+                return null;
+            
+            if (!isHaHotStateRequired(request))
+                return null;
+            
+            String problem = lookForProblemIfServerNotRunning(mgmt);
+            if (Strings.isNonBlank(problem)) 
+                return problem;
+            
+            if (!isHaHotStatus())
+                return "server not in required HA hot state";
+            if (isStateNotYetValid())
+                return "server not yet completed loading data for required HA 
hot state";
+            
+            return null;
+        }
+        
+        @Override
+        public ContainerRequest filter(ContainerRequest request) {
+            String problem = lookForProblem(request);
+            if (Strings.isNonBlank(problem)) {
+                log.warn("Disallowing web request as "+problem+": 
"+request+"/"+am+" (caller should set '"+SKIP_CHECK_HEADER+"' to force)");
+                throw new WebApplicationException(ApiError.builder()
+                    .message("This request is only permitted against an active 
hot Brooklyn server")
+                    
.errorCode(Response.Status.FORBIDDEN).build().asJsonResponse());
+            }
+            return request;
+        }
+
+        // Maybe there should be a separate state to indicate that we have 
switched state
+        // but still haven't finished rebinding. (Previously there was a time 
delay and an
+        // isRebinding check, but introducing 
RebindManager#isAwaitingInitialRebind() seems cleaner.)
+        private boolean isStateNotYetValid() {
+            return mgmt.getRebindManager().isAwaitingInitialRebind();
+        }
+
+        private boolean isHaHotStateRequired(ContainerRequest request) {
+            return (am.getAnnotation(HaHotStateRequired.class) != null ||
+                    am.getResource().getAnnotation(HaHotStateRequired.class) 
!= null);
+        }
+
+        private boolean isSkipCheckHeaderSet(ContainerRequest request) {
+            return 
"true".equalsIgnoreCase(request.getHeaderValue(SKIP_CHECK_HEADER));
+        }
+
+        private boolean isHaHotStatus() {
+            ManagementNodeState state = 
mgmt.getHighAvailabilityManager().getNodeState();
+            return HOT_STATES.contains(state);
+        }
+
+    }
+
+    public static String lookForProblemIfServerNotRunning(ManagementContext 
mgmt) {
+        if (mgmt==null) return "no management context available";
+        if (!mgmt.isRunning()) return "server no longer running";
+        if (!mgmt.isStartupComplete()) return "server not in required 
startup-completed state";
+        return null;
+    }
+    
+    @Override
+    public List<ResourceFilter> create(AbstractMethod am) {
+        return Collections.<ResourceFilter>singletonList(new MethodFilter(am, 
mgmt()));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/filter/NoCacheFilter.java
----------------------------------------------------------------------
diff --git 
a/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/filter/NoCacheFilter.java
 
b/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/filter/NoCacheFilter.java
new file mode 100644
index 0000000..8a3c1c6
--- /dev/null
+++ 
b/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/filter/NoCacheFilter.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.filter;
+
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MultivaluedMap;
+
+import com.sun.jersey.spi.container.ContainerRequest;
+import com.sun.jersey.spi.container.ContainerResponse;
+import com.sun.jersey.spi.container.ContainerResponseFilter;
+
+public class NoCacheFilter implements ContainerResponseFilter {
+
+    @Override
+    public ContainerResponse filter(ContainerRequest request, 
ContainerResponse response) {
+        //https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching_FAQ
+        MultivaluedMap<String, Object> headers = response.getHttpHeaders();
+        headers.putSingle(HttpHeaders.CACHE_CONTROL, "no-cache, no-store");
+        headers.putSingle("Pragma", "no-cache");
+        headers.putSingle(HttpHeaders.EXPIRES, "0");
+        return response;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/filter/SwaggerFilter.java
----------------------------------------------------------------------
diff --git 
a/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/filter/SwaggerFilter.java
 
b/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/filter/SwaggerFilter.java
new file mode 100644
index 0000000..d9013f0
--- /dev/null
+++ 
b/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/filter/SwaggerFilter.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.filter;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URISyntaxException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.ws.rs.core.UriBuilder;
+
+import org.apache.brooklyn.rest.apidoc.RestApiResourceScanner;
+
+import io.swagger.config.ScannerFactory;
+import io.swagger.models.Info;
+import io.swagger.models.License;
+import io.swagger.models.Swagger;
+
+/**
+ * Bootstraps swagger.
+ * <p>
+ * Swagger was intended to run as a servlet.
+ *
+ * @author Ciprian Ciubotariu <cheepe...@gmx.net>
+ */
+public class SwaggerFilter implements Filter {
+
+    static Info info = new Info()
+            .title("Brooklyn API Documentation")
+            .version("v1") // API version, not BROOKLYN_VERSION
+            .license(new License()
+                    .name("Apache 2.0")
+                    .url("http://www.apache.org/licenses/LICENSE-2.0.html";));
+
+    @Override
+    public void init(FilterConfig filterConfig) throws ServletException {
+//        ReflectiveJaxrsScanner scanner = new ReflectiveJaxrsScanner();
+//        
scanner.setResourcePackage("org.apache.brooklyn.rest.api,org.apache.brooklyn.rest.apidoc,org.apache.brooklyn.rest.resources");
+//        ScannerFactory.setScanner(scanner);
+        ScannerFactory.setScanner(new RestApiResourceScanner());
+
+        ServletContext context = filterConfig.getServletContext();
+        Swagger swagger = new Swagger()
+                .info(info);
+        context.setAttribute("swagger", swagger);
+    }
+
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response, 
FilterChain chain) throws IOException, ServletException {
+        chain.doFilter(request, response);
+    }
+
+    @Override
+    public void destroy() {
+    }
+
+}

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

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

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/util/FormMapProvider.java
----------------------------------------------------------------------
diff --git 
a/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/util/FormMapProvider.java
 
b/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/util/FormMapProvider.java
new file mode 100644
index 0000000..2b5c19b
--- /dev/null
+++ 
b/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/util/FormMapProvider.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.List;
+import java.util.Map;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyReader;
+import javax.ws.rs.ext.Provider;
+
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.sun.jersey.core.impl.provider.entity.FormMultivaluedMapProvider;
+import com.sun.jersey.core.util.MultivaluedMapImpl;
+
+/**
+ * A MessageBodyReader producing a <code>Map&lt;String, Object&gt;</code>, 
where Object
+ * is either a <code>String</code>, a <code>List&lt;String&gt;</code> or null.
+ */
+@Provider
+@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+public class FormMapProvider implements MessageBodyReader<Map<String, Object>> 
{
+
+    @Override
+    public boolean isReadable(Class<?> type, Type genericType, Annotation[] 
annotations, MediaType mediaType) {
+        if (!Map.class.equals(type) || !(genericType instanceof 
ParameterizedType)) {
+            return false;
+        }
+        ParameterizedType parameterized = (ParameterizedType) genericType;
+        return parameterized.getActualTypeArguments().length == 2 &&
+                parameterized.getActualTypeArguments()[0] == String.class &&
+                parameterized.getActualTypeArguments()[1] == Object.class;
+    }
+
+    @Override
+    public Map<String, Object> readFrom(Class<Map<String, Object>> type, Type 
genericType, Annotation[] annotations,
+            MediaType mediaType, MultivaluedMap<String, String> httpHeaders, 
InputStream entityStream)
+            throws IOException, WebApplicationException {
+        FormMultivaluedMapProvider delegate = new FormMultivaluedMapProvider();
+        MultivaluedMap<String, String> multi = new MultivaluedMapImpl();
+        multi = delegate.readFrom(multi, mediaType, entityStream);
+
+        Map<String, Object> map = 
Maps.newHashMapWithExpectedSize(multi.keySet().size());
+        for (String key : multi.keySet()) {
+            List<String> value = multi.get(key);
+            if (value.size() > 1) {
+                map.put(key, Lists.newArrayList(value));
+            } else if (value.size() == 1) {
+                map.put(key, Iterables.getOnlyElement(value));
+            } else {
+                map.put(key, null);
+            }
+        }
+        return map;
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/main/resources/build-metadata.properties
----------------------------------------------------------------------
diff --git 
a/rest/rest-server-jersey/src/main/resources/build-metadata.properties 
b/rest/rest-server-jersey/src/main/resources/build-metadata.properties
new file mode 100644
index 0000000..eab85ef
--- /dev/null
+++ b/rest/rest-server-jersey/src/main/resources/build-metadata.properties
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#  http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+git-sha-1 = ${buildNumber}
+git-branch-name = ${scmBranch}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/main/webapp/WEB-INF/web.xml
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/main/webapp/WEB-INF/web.xml 
b/rest/rest-server-jersey/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..a7f99f4
--- /dev/null
+++ b/rest/rest-server-jersey/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,144 @@
+<!DOCTYPE web-app PUBLIC
+        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
+        "http://java.sun.com/dtd/web-app_2_3.dtd"; >
+<!--
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+    
+     http://www.apache.org/licenses/LICENSE-2.0
+    
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+-->
+<web-app>
+    <display-name>Brooklyn REST API v1</display-name>
+
+    <filter>
+        <filter-name>Brooklyn Request Tagging Filter</filter-name>
+        
<filter-class>org.apache.brooklyn.rest.filter.RequestTaggingFilter</filter-class>
+    </filter>
+    <filter-mapping>
+        <filter-name>Brooklyn Request Tagging Filter</filter-name>
+        <url-pattern>/*</url-pattern>
+    </filter-mapping>
+
+    <filter>
+        <filter-name>Brooklyn Properties Authentication Filter</filter-name>
+        
<filter-class>org.apache.brooklyn.rest.filter.BrooklynPropertiesSecurityFilter</filter-class>
+    </filter>
+    <filter-mapping>
+        <filter-name>Brooklyn Properties Authentication Filter</filter-name>
+        <url-pattern>/*</url-pattern>
+    </filter-mapping>
+
+    <filter>
+        <filter-name>Brooklyn Logging Filter</filter-name>
+        
<filter-class>org.apache.brooklyn.rest.filter.LoggingFilter</filter-class>
+    </filter>
+    <filter-mapping>
+        <filter-name>Brooklyn Logging Filter</filter-name>
+        <url-pattern>/*</url-pattern>
+    </filter-mapping>
+
+    <filter>
+        <filter-name>Brooklyn HA Master Filter</filter-name>
+        
<filter-class>org.apache.brooklyn.rest.filter.HaMasterCheckFilter</filter-class>
+    </filter>
+    <filter-mapping>
+        <filter-name>Brooklyn HA Master Filter</filter-name>
+        <url-pattern>/*</url-pattern>
+    </filter-mapping>
+
+    <filter>
+        <filter-name>Brooklyn Swagger Bootstrap</filter-name>
+        
<filter-class>org.apache.brooklyn.rest.filter.SwaggerFilter</filter-class>
+    </filter>
+    <filter-mapping>
+        <filter-name>Brooklyn Swagger Bootstrap</filter-name>
+        <url-pattern>/*</url-pattern>
+    </filter-mapping>
+
+    <!-- Brooklyn REST is usually run as a filter so static content can be 
placed in a webapp
+         to which this is added; to run as a servlet directly, replace the 
filter tags 
+         below (after the comment) with the servlet tags (commented out 
immediately below),
+         (and do the same for the matching tags at the bottom)
+        <servlet>
+            <servlet-name>Brooklyn REST API v1 Servlet</servlet-name>
+            
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
+     -->
+    <filter>
+        <filter-name>Brooklyn REST API v1 Filter</filter-name>
+        
<filter-class>com.sun.jersey.spi.container.servlet.ServletContainer</filter-class>
+
+        <!-- load our REST API jersey resources explicitly 
+            (the package scanner will only pick up classes with @Path 
annotations - doesn't look at implemented interfaces) 
+        -->
+        <init-param>
+            
<param-name>com.sun.jersey.config.property.resourceConfigClass</param-name>
+            
<param-value>com.sun.jersey.api.core.ClassNamesResourceConfig</param-value>
+        </init-param>
+        <init-param>
+            <param-name>com.sun.jersey.config.property.classnames</param-name>
+            <param-value>
+                io.swagger.jaxrs.listing.SwaggerSerializers;
+                org.apache.brooklyn.rest.util.FormMapProvider;
+                com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
+                org.apache.brooklyn.rest.resources.AccessResource;
+                org.apache.brooklyn.rest.resources.ActivityResource;
+                org.apache.brooklyn.rest.resources.ApidocResource;
+                org.apache.brooklyn.rest.resources.ApplicationResource;
+                org.apache.brooklyn.rest.resources.CatalogResource;
+                org.apache.brooklyn.rest.resources.EffectorResource;
+                org.apache.brooklyn.rest.resources.EntityConfigResource;
+                org.apache.brooklyn.rest.resources.EntityResource;
+                org.apache.brooklyn.rest.resources.LocationResource;
+                org.apache.brooklyn.rest.resources.PolicyConfigResource;
+                org.apache.brooklyn.rest.resources.PolicyResource;
+                org.apache.brooklyn.rest.resources.ScriptResource;
+                org.apache.brooklyn.rest.resources.SensorResource;
+                org.apache.brooklyn.rest.resources.ServerResource;
+                org.apache.brooklyn.rest.resources.UsageResource;
+                org.apache.brooklyn.rest.resources.VersionResource;
+            </param-value>
+        </init-param>
+
+        <init-param>
+            <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
+            <param-value>true</param-value>
+        </init-param>
+
+        <!-- no need for WADL. of course you can turn it back on it you want. 
-->
+        <init-param>
+            <param-name>com.sun.jersey.config.feature.DisableWADL</param-name>
+            <param-value>true</param-value>
+        </init-param>
+
+        <init-param>
+            
<param-name>com.sun.jersey.config.feature.FilterContextPath</param-name>
+            <param-value>/v1</param-value>
+        </init-param>
+
+    </filter>
+    <filter-mapping>
+        <filter-name>Brooklyn REST API v1 Filter</filter-name>
+        <url-pattern>/v1/*</url-pattern>
+    </filter-mapping>
+    <!-- Brooklyn REST as a filter above; replace above 5 lines with those 
commented out below,
+         to run it as a servlet (see note above) 
+            <load-on-startup>1</load-on-startup>
+        </servlet>
+        <servlet-mapping>
+            <servlet-name>Brooklyn REST API v1 Servlet</servlet-name>
+            <url-pattern>/*</url-pattern>
+        </servlet-mapping>
+    -->
+</web-app>

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/BrooklynPropertiesSecurityFilterTest.java
----------------------------------------------------------------------
diff --git 
a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/BrooklynPropertiesSecurityFilterTest.java
 
b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/BrooklynPropertiesSecurityFilterTest.java
new file mode 100644
index 0000000..e855841
--- /dev/null
+++ 
b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/BrooklynPropertiesSecurityFilterTest.java
@@ -0,0 +1,151 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest;
+
+import static org.testng.Assert.assertTrue;
+
+import java.net.URI;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.http.HttpHeaders;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.utils.URLEncodedUtils;
+import org.apache.http.entity.ContentType;
+import org.apache.http.message.BasicNameValuePair;
+import org.eclipse.jetty.server.Server;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.Test;
+import org.apache.brooklyn.rest.security.provider.AnyoneSecurityProvider;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.http.HttpTool;
+import org.apache.brooklyn.util.http.HttpToolResponse;
+import org.apache.brooklyn.util.time.Time;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.base.Charsets;
+import com.google.common.base.Stopwatch;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+
+public class BrooklynPropertiesSecurityFilterTest extends 
BrooklynRestApiLauncherTestFixture {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(BrooklynPropertiesSecurityFilterTest.class);
+
+    /*
+        Exception java.lang.AssertionError
+        
+        Message: error creating app. response code=400 expected [true] but 
found [false]
+        Stacktrace:
+        
+        
+        at org.testng.Assert.fail(Assert.java:94)
+        at org.testng.Assert.failNotEquals(Assert.java:494)
+        at org.testng.Assert.assertTrue(Assert.java:42)
+        at 
org.apache.brooklyn.rest.BrooklynPropertiesSecurityFilterTest.startAppAtNode(BrooklynPropertiesSecurityFilterTest.java:94)
+        at 
org.apache.brooklyn.rest.BrooklynPropertiesSecurityFilterTest.testInteractionOfSecurityFilterAndFormMapProvider(BrooklynPropertiesSecurityFilterTest.java:64)
+        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
+        at 
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
+        at 
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
+        at java.lang.reflect.Method.invoke(Method.java:606)
+        at 
org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:84)
+        at org.testng.internal.Invoker.invokeMethod(Invoker.java:714)
+        at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:901)
+        at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1231)
+        at 
org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:127)
+        at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:111)
+        at org.testng.TestRunner.privateRun(TestRunner.java:767)
+        at org.testng.TestRunner.run(TestRunner.java:617)
+        at org.testng.SuiteRunner.runTest(SuiteRunner.java:348)
+        at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:343)
+        at org.testng.SuiteRunner.privateRun(SuiteRunner.java:305)
+        at org.testng.SuiteRunner.run(SuiteRunner.java:254)
+        at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
+        at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:86)
+        at org.testng.TestNG.runSuitesSequentially(TestNG.java:1224)
+        at org.testng.TestNG.runSuitesLocally(TestNG.java:1149)
+        at org.testng.TestNG.run(TestNG.java:1057)
+        at 
org.apache.maven.surefire.testng.TestNGExecutor.run(TestNGExecutor.java:115)
+        at 
org.apache.maven.surefire.testng.TestNGDirectoryTestSuite.executeMulti(TestNGDirectoryTestSuite.java:205)
+        at 
org.apache.maven.surefire.testng.TestNGDirectoryTestSuite.execute(TestNGDirectoryTestSuite.java:108)
+        at 
org.apache.maven.surefire.testng.TestNGProvider.invoke(TestNGProvider.java:111)
+        at 
org.apache.maven.surefire.booter.ForkedBooter.invokeProviderInSameClassLoader(ForkedBooter.java:203)
+        at 
org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:155)
+        at 
org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:103)
+     */
+    // Would be great for this to be a unit test but it takes almost ten 
seconds.
+    @Test(groups = {"Integration","Broken"})
+    public void testInteractionOfSecurityFilterAndFormMapProvider() throws 
Exception {
+        Stopwatch stopwatch = Stopwatch.createStarted();
+        try {
+            Server server = useServerForTest(BrooklynRestApiLauncher.launcher()
+                    .securityProvider(AnyoneSecurityProvider.class)
+                    .forceUseOfDefaultCatalogWithJavaClassPath(true)
+                    .withoutJsgui()
+                    .start());
+            String appId = startAppAtNode(server);
+            String entityId = getTestEntityInApp(server, appId);
+            HttpClient client = HttpTool.httpClientBuilder()
+                    .uri(getBaseUri(server))
+                    .build();
+            List<? extends NameValuePair> nvps = Lists.newArrayList(
+                    new BasicNameValuePair("arg", "bar"));
+            String effector = 
String.format("/v1/applications/%s/entities/%s/effectors/identityEffector", 
appId, entityId);
+            HttpToolResponse response = HttpTool.httpPost(client, 
URI.create(getBaseUri() + effector),
+                    ImmutableMap.of(HttpHeaders.CONTENT_TYPE, 
ContentType.APPLICATION_FORM_URLENCODED.getMimeType()),
+                    URLEncodedUtils.format(nvps, Charsets.UTF_8).getBytes());
+
+            LOG.info("Effector response: {}", response.getContentAsString());
+            
assertTrue(HttpTool.isStatusCodeHealthy(response.getResponseCode()), "response 
code=" + response.getResponseCode());
+        } finally {
+            LOG.info("testInteractionOfSecurityFilterAndFormMapProvider 
complete in " + Time.makeTimeStringRounded(stopwatch));
+        }
+    }
+
+    private String startAppAtNode(Server server) throws Exception {
+        String blueprint = "name: TestApp\n" +
+                "location: localhost\n" +
+                "services:\n" +
+                "- type: org.apache.brooklyn.test.entity.TestEntity";
+        HttpClient client = HttpTool.httpClientBuilder()
+                .uri(getBaseUri(server))
+                .build();
+        HttpToolResponse response = HttpTool.httpPost(client, 
URI.create(getBaseUri() + "/v1/applications"),
+                ImmutableMap.of(HttpHeaders.CONTENT_TYPE, 
"application/x-yaml"),
+                blueprint.getBytes());
+        assertTrue(HttpTool.isStatusCodeHealthy(response.getResponseCode()), 
"error creating app. response code=" + response.getResponseCode());
+        @SuppressWarnings("unchecked")
+        Map<String, Object> body = new 
ObjectMapper().readValue(response.getContent(), HashMap.class);
+        return (String) body.get("entityId");
+    }
+
+    @SuppressWarnings("rawtypes")
+    private String getTestEntityInApp(Server server, String appId) throws 
Exception {
+        HttpClient client = HttpTool.httpClientBuilder()
+                .uri(getBaseUri(server))
+                .build();
+        List entities = new ObjectMapper().readValue(
+                HttpTool.httpGet(client, URI.create(getBaseUri() + 
"/v1/applications/" + appId + "/entities"), MutableMap.<String, 
String>of()).getContent(), List.class);
+        LOG.info((String) ((Map) entities.get(0)).get("id"));
+        return (String) ((Map) entities.get(0)).get("id");
+    }
+}

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

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTest.java
----------------------------------------------------------------------
diff --git 
a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTest.java
 
b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTest.java
new file mode 100644
index 0000000..1bf756d
--- /dev/null
+++ 
b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTest.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest;
+
+import static 
org.apache.brooklyn.rest.BrooklynRestApiLauncher.StartMode.FILTER;
+import static 
org.apache.brooklyn.rest.BrooklynRestApiLauncher.StartMode.SERVLET;
+import static 
org.apache.brooklyn.rest.BrooklynRestApiLauncher.StartMode.WEB_XML;
+
+import java.util.concurrent.Callable;
+
+import org.apache.brooklyn.entity.brooklynnode.BrooklynNode;
+import org.apache.brooklyn.rest.security.provider.AnyoneSecurityProvider;
+import org.apache.brooklyn.test.Asserts;
+import org.apache.brooklyn.util.http.HttpAsserts;
+import org.apache.brooklyn.util.http.HttpTool;
+import org.apache.http.HttpStatus;
+import org.eclipse.jetty.server.NetworkConnector;
+import org.eclipse.jetty.server.Server;
+import org.testng.annotations.Test;
+
+public class BrooklynRestApiLauncherTest extends 
BrooklynRestApiLauncherTestFixture {
+
+    @Test
+    public void testFilterStart() throws Exception {
+        
checkRestCatalogEntities(useServerForTest(baseLauncher().mode(FILTER).start()));
+    }
+
+    @Test
+    public void testServletStart() throws Exception {
+        
checkRestCatalogEntities(useServerForTest(baseLauncher().mode(SERVLET).start()));
+    }
+
+    @Test
+    public void testWebAppStart() throws Exception {
+        
checkRestCatalogEntities(useServerForTest(baseLauncher().mode(WEB_XML).start()));
+    }
+
+    private BrooklynRestApiLauncher baseLauncher() {
+        return BrooklynRestApiLauncher.launcher()
+                .securityProvider(AnyoneSecurityProvider.class)
+                .forceUseOfDefaultCatalogWithJavaClassPath(true);
+    }
+    
+    private static void checkRestCatalogEntities(Server server) throws 
Exception {
+        final String rootUrl = 
"http://localhost:"+((NetworkConnector)server.getConnectors()[0]).getLocalPort();
+        int code = Asserts.succeedsEventually(new Callable<Integer>() {
+            @Override
+            public Integer call() throws Exception {
+                int code = 
HttpTool.getHttpStatusCode(rootUrl+"/v1/catalog/entities");
+                if (code == HttpStatus.SC_FORBIDDEN) {
+                    throw new RuntimeException("Retry request");
+                } else {
+                    return code;
+                }
+            }
+        });
+        HttpAsserts.assertHealthyStatusCode(code);
+        HttpAsserts.assertContentContainsText(rootUrl+"/v1/catalog/entities", 
BrooklynNode.class.getSimpleName());
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTestFixture.java
----------------------------------------------------------------------
diff --git 
a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTestFixture.java
 
b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTestFixture.java
new file mode 100644
index 0000000..c894f3e
--- /dev/null
+++ 
b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTestFixture.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest;
+
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.internal.BrooklynProperties;
+import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
+import org.apache.brooklyn.core.server.BrooklynServerConfig;
+import org.apache.brooklyn.rest.security.provider.AnyoneSecurityProvider;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.eclipse.jetty.server.NetworkConnector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.reflections.util.ClasspathHelper;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+
+public abstract class BrooklynRestApiLauncherTestFixture {
+
+    Server server = null;
+    
+    @AfterMethod(alwaysRun=true)
+    public void stopServer() throws Exception {
+        if (server!=null) {
+            ManagementContext mgmt = 
getManagementContextFromJettyServerAttributes(server);
+            server.stop();
+            if (mgmt!=null) Entities.destroyAll(mgmt);
+            server = null;
+        }
+    }
+    
+    protected Server newServer() {
+        try {
+            Server server = BrooklynRestApiLauncher.launcher()
+                    .forceUseOfDefaultCatalogWithJavaClassPath(true)
+                    .securityProvider(AnyoneSecurityProvider.class)
+                    .start();
+            return server;
+        } catch (Exception e) {
+            throw Exceptions.propagate(e);
+        }
+    }
+    
+    protected Server useServerForTest(Server server) {
+        if (this.server!=null) {
+            Assert.fail("Test only meant for single server; already have 
"+this.server+" when checking "+server);
+        } else {
+            this.server = server;
+        }
+        return server;
+    }
+    
+    protected String getBaseUri() {
+        return getBaseUri(server);
+    }
+    public static String getBaseUri(Server server) {
+        return 
"http://localhost:"+((NetworkConnector)server.getConnectors()[0]).getLocalPort();
+    }
+    
+    public static void forceUseOfDefaultCatalogWithJavaClassPath(Server 
server) {
+        ManagementContext mgmt = 
getManagementContextFromJettyServerAttributes(server);
+        forceUseOfDefaultCatalogWithJavaClassPath(mgmt);
+    }
+
+    public static void 
forceUseOfDefaultCatalogWithJavaClassPath(ManagementContext manager) {
+        // TODO duplication with BrooklynRestApiLauncher ?
+        
+        // don't use any catalog.xml which is set
+        
((BrooklynProperties)manager.getConfig()).put(BrooklynServerConfig.BROOKLYN_CATALOG_URL,
 BrooklynRestApiLauncher.SCANNING_CATALOG_BOM_URL);
+        // sets URLs for a surefire
+        
((LocalManagementContext)manager).setBaseClassPathForScanning(ClasspathHelper.forJavaClassPath());
+        // this also works
+//        
((LocalManagementContext)manager).setBaseClassPathForScanning(ClasspathHelper.forPackage("brooklyn"));
+        // but this (near-default behaviour) does not
+//        
((LocalManagementContext)manager).setBaseClassLoader(getClass().getClassLoader());
+    }
+
+    public static void enableAnyoneLogin(Server server) {
+        ManagementContext mgmt = 
getManagementContextFromJettyServerAttributes(server);
+        enableAnyoneLogin(mgmt);
+    }
+
+    public static void enableAnyoneLogin(ManagementContext mgmt) {
+        
((BrooklynProperties)mgmt.getConfig()).put(BrooklynWebConfig.SECURITY_PROVIDER_CLASSNAME,
 
+                AnyoneSecurityProvider.class.getName());
+    }
+
+    public static ManagementContext 
getManagementContextFromJettyServerAttributes(Server server) {
+        return BrooklynRestApiLauncher.getManagementContext((ContextHandler) 
server.getHandler());
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/HaHotCheckTest.java
----------------------------------------------------------------------
diff --git 
a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/HaHotCheckTest.java
 
b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/HaHotCheckTest.java
new file mode 100644
index 0000000..73dfe5f
--- /dev/null
+++ 
b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/HaHotCheckTest.java
@@ -0,0 +1,130 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest;
+
+import static org.testng.Assert.assertEquals;
+
+import javax.ws.rs.core.MediaType;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import org.apache.brooklyn.api.mgmt.ha.HighAvailabilityManager;
+import org.apache.brooklyn.api.mgmt.ha.HighAvailabilityMode;
+import org.apache.brooklyn.api.mgmt.ha.ManagementNodeState;
+import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
+import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
+import org.apache.brooklyn.rest.filter.HaHotCheckResourceFilter;
+import org.apache.brooklyn.rest.filter.HaMasterCheckFilter;
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+import org.apache.brooklyn.rest.util.HaHotStateCheckClassResource;
+import org.apache.brooklyn.rest.util.HaHotStateCheckResource;
+import org.apache.brooklyn.rest.util.ManagementContextProvider;
+
+import com.sun.jersey.api.client.ClientResponse;
+import com.sun.jersey.api.client.WebResource.Builder;
+import com.sun.jersey.api.core.ResourceConfig;
+
+public class HaHotCheckTest extends BrooklynRestResourceTest {
+
+    // setup and teardown before/after each method
+    
+    @BeforeMethod(alwaysRun = true)
+    public void setUp() throws Exception { super.setUp(); }
+
+    @AfterMethod(alwaysRun = true)
+    public void tearDown() throws Exception { super.tearDown(); }
+    
+    @Override
+    protected void addBrooklynResources() {
+        
config.getProperties().put(ResourceConfig.PROPERTY_RESOURCE_FILTER_FACTORIES, 
+            new HaHotCheckResourceFilter(new 
ManagementContextProvider(getManagementContext())));
+        addResource(new HaHotStateCheckResource());
+        addResource(new HaHotStateCheckClassResource());
+        
+        ((LocalManagementContext)getManagementContext()).noteStartupComplete();
+    }
+
+    @Test
+    public void testHaCheck() {
+        HighAvailabilityManager ha = 
getManagementContext().getHighAvailabilityManager();
+        assertEquals(ha.getNodeState(), ManagementNodeState.MASTER);
+        testResourceFetch("/v1/ha/method/ok", 200);
+        testResourceFetch("/v1/ha/method/fail", 200);
+        testResourceFetch("/v1/ha/class/fail", 200);
+
+        
getManagementContext().getHighAvailabilityManager().changeMode(HighAvailabilityMode.STANDBY);
+        assertEquals(ha.getNodeState(), ManagementNodeState.STANDBY);
+
+        testResourceFetch("/v1/ha/method/ok", 200);
+        testResourceFetch("/v1/ha/method/fail", 403);
+        testResourceFetch("/v1/ha/class/fail", 403);
+
+        ((ManagementContextInternal)getManagementContext()).terminate();
+        assertEquals(ha.getNodeState(), ManagementNodeState.TERMINATED);
+
+        testResourceFetch("/v1/ha/method/ok", 200);
+        testResourceFetch("/v1/ha/method/fail", 403);
+        testResourceFetch("/v1/ha/class/fail", 403);
+    }
+
+    @Test
+    public void testHaCheckForce() {
+        HighAvailabilityManager ha = 
getManagementContext().getHighAvailabilityManager();
+        assertEquals(ha.getNodeState(), ManagementNodeState.MASTER);
+        testResourceForcedFetch("/v1/ha/method/ok", 200);
+        testResourceForcedFetch("/v1/ha/method/fail", 200);
+        testResourceForcedFetch("/v1/ha/class/fail", 200);
+
+        
getManagementContext().getHighAvailabilityManager().changeMode(HighAvailabilityMode.STANDBY);
+        assertEquals(ha.getNodeState(), ManagementNodeState.STANDBY);
+
+        testResourceForcedFetch("/v1/ha/method/ok", 200);
+        testResourceForcedFetch("/v1/ha/method/fail", 200);
+        testResourceForcedFetch("/v1/ha/class/fail", 200);
+
+        ((ManagementContextInternal)getManagementContext()).terminate();
+        assertEquals(ha.getNodeState(), ManagementNodeState.TERMINATED);
+
+        testResourceForcedFetch("/v1/ha/method/ok", 200);
+        testResourceForcedFetch("/v1/ha/method/fail", 200);
+        testResourceForcedFetch("/v1/ha/class/fail", 200);
+    }
+
+
+    private void testResourceFetch(String resourcePath, int code) {
+        testResourceFetch(resourcePath, false, code);
+    }
+
+    private void testResourceForcedFetch(String resourcePath, int code) {
+        testResourceFetch(resourcePath, true, code);
+    }
+
+    private void testResourceFetch(String resourcePath, boolean force, int 
code) {
+        Builder resource = client().resource(resourcePath)
+                .accept(MediaType.APPLICATION_JSON_TYPE);
+        if (force) {
+            resource.header(HaHotCheckResourceFilter.SKIP_CHECK_HEADER, 
"true");
+        }
+        ClientResponse response = resource
+                .get(ClientResponse.class);
+        assertEquals(response.getStatus(), code);
+    }
+
+}

Reply via email to