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:44 UTC
[26/34] brooklyn-server git commit: REST API optional Jersey
compatibility
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/filter/HaHotCheckResourceFilter.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/filter/HaHotCheckResourceFilter.java b/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/filter/HaHotCheckResourceFilter.java
new file mode 100644
index 0000000..c65c78f
--- /dev/null
+++ b/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/filter/HaHotCheckResourceFilter.java
@@ -0,0 +1,163 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.filter;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ContextResolver;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.api.mgmt.ha.ManagementNodeState;
+import org.apache.brooklyn.rest.domain.ApiError;
+import org.apache.brooklyn.util.text.Strings;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableSet;
+import com.sun.jersey.api.model.AbstractMethod;
+import com.sun.jersey.spi.container.ContainerRequest;
+import com.sun.jersey.spi.container.ContainerRequestFilter;
+import com.sun.jersey.spi.container.ContainerResponseFilter;
+import com.sun.jersey.spi.container.ResourceFilter;
+import com.sun.jersey.spi.container.ResourceFilterFactory;
+
+/**
+ * Checks that if the method or resource class corresponding to a request
+ * has a {@link HaHotStateRequired} annotation,
+ * that the server is in that state (and up).
+ * Requests with {@link #SKIP_CHECK_HEADER} set as a header skip this check.
+ * <p>
+ * This follows a different pattern to {@link HaMasterCheckFilter}
+ * as this needs to know the method being invoked.
+ */
+public class HaHotCheckResourceFilter implements ResourceFilterFactory {
+ public static final String SKIP_CHECK_HEADER = "Brooklyn-Allow-Non-Master-Access";
+
+ private static final Logger log = LoggerFactory.getLogger(HaHotCheckResourceFilter.class);
+
+ private static final Set<ManagementNodeState> HOT_STATES = ImmutableSet.of(
+ ManagementNodeState.MASTER, ManagementNodeState.HOT_STANDBY, ManagementNodeState.HOT_BACKUP);
+
+ @Context
+ private ContextResolver<ManagementContext> mgmt;
+
+ public HaHotCheckResourceFilter() {}
+
+ @VisibleForTesting
+ public HaHotCheckResourceFilter(ContextResolver<ManagementContext> mgmt) {
+ this.mgmt = mgmt;
+ }
+
+ private ManagementContext mgmt() {
+ return mgmt.getContext(ManagementContext.class);
+ }
+
+ private static class MethodFilter implements ResourceFilter, ContainerRequestFilter {
+
+ private AbstractMethod am;
+ private ManagementContext mgmt;
+
+ public MethodFilter(AbstractMethod am, ManagementContext mgmt) {
+ this.am = am;
+ this.mgmt = mgmt;
+ }
+
+ @Override
+ public ContainerRequestFilter getRequestFilter() {
+ return this;
+ }
+
+ @Override
+ public ContainerResponseFilter getResponseFilter() {
+ return null;
+ }
+
+ private String lookForProblem(ContainerRequest request) {
+ if (isSkipCheckHeaderSet(request))
+ return null;
+
+ if (!isHaHotStateRequired(request))
+ return null;
+
+ String problem = lookForProblemIfServerNotRunning(mgmt);
+ if (Strings.isNonBlank(problem))
+ return problem;
+
+ if (!isHaHotStatus())
+ return "server not in required HA hot state";
+ if (isStateNotYetValid())
+ return "server not yet completed loading data for required HA hot state";
+
+ return null;
+ }
+
+ @Override
+ public ContainerRequest filter(ContainerRequest request) {
+ String problem = lookForProblem(request);
+ if (Strings.isNonBlank(problem)) {
+ log.warn("Disallowing web request as "+problem+": "+request+"/"+am+" (caller should set '"+SKIP_CHECK_HEADER+"' to force)");
+ throw new WebApplicationException(ApiError.builder()
+ .message("This request is only permitted against an active hot Brooklyn server")
+ .errorCode(Response.Status.FORBIDDEN).build().asJsonResponse());
+ }
+ return request;
+ }
+
+ // Maybe there should be a separate state to indicate that we have switched state
+ // but still haven't finished rebinding. (Previously there was a time delay and an
+ // isRebinding check, but introducing RebindManager#isAwaitingInitialRebind() seems cleaner.)
+ private boolean isStateNotYetValid() {
+ return mgmt.getRebindManager().isAwaitingInitialRebind();
+ }
+
+ private boolean isHaHotStateRequired(ContainerRequest request) {
+ return (am.getAnnotation(HaHotStateRequired.class) != null ||
+ am.getResource().getAnnotation(HaHotStateRequired.class) != null);
+ }
+
+ private boolean isSkipCheckHeaderSet(ContainerRequest request) {
+ return "true".equalsIgnoreCase(request.getHeaderValue(SKIP_CHECK_HEADER));
+ }
+
+ private boolean isHaHotStatus() {
+ ManagementNodeState state = mgmt.getHighAvailabilityManager().getNodeState();
+ return HOT_STATES.contains(state);
+ }
+
+ }
+
+ public static String lookForProblemIfServerNotRunning(ManagementContext mgmt) {
+ if (mgmt==null) return "no management context available";
+ if (!mgmt.isRunning()) return "server no longer running";
+ if (!mgmt.isStartupComplete()) return "server not in required startup-completed state";
+ return null;
+ }
+
+ @Override
+ public List<ResourceFilter> create(AbstractMethod am) {
+ return Collections.<ResourceFilter>singletonList(new MethodFilter(am, mgmt()));
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/filter/NoCacheFilter.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/filter/NoCacheFilter.java b/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/filter/NoCacheFilter.java
new file mode 100644
index 0000000..8a3c1c6
--- /dev/null
+++ b/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/filter/NoCacheFilter.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.filter;
+
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MultivaluedMap;
+
+import com.sun.jersey.spi.container.ContainerRequest;
+import com.sun.jersey.spi.container.ContainerResponse;
+import com.sun.jersey.spi.container.ContainerResponseFilter;
+
+public class NoCacheFilter implements ContainerResponseFilter {
+
+ @Override
+ public ContainerResponse filter(ContainerRequest request, ContainerResponse response) {
+ //https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching_FAQ
+ MultivaluedMap<String, Object> headers = response.getHttpHeaders();
+ headers.putSingle(HttpHeaders.CACHE_CONTROL, "no-cache, no-store");
+ headers.putSingle("Pragma", "no-cache");
+ headers.putSingle(HttpHeaders.EXPIRES, "0");
+ return response;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/filter/SwaggerFilter.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/filter/SwaggerFilter.java b/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/filter/SwaggerFilter.java
new file mode 100644
index 0000000..d9013f0
--- /dev/null
+++ b/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/filter/SwaggerFilter.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.filter;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URISyntaxException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.ws.rs.core.UriBuilder;
+
+import org.apache.brooklyn.rest.apidoc.RestApiResourceScanner;
+
+import io.swagger.config.ScannerFactory;
+import io.swagger.models.Info;
+import io.swagger.models.License;
+import io.swagger.models.Swagger;
+
+/**
+ * Bootstraps swagger.
+ * <p>
+ * Swagger was intended to run as a servlet.
+ *
+ * @author Ciprian Ciubotariu <ch...@gmx.net>
+ */
+public class SwaggerFilter implements Filter {
+
+ static Info info = new Info()
+ .title("Brooklyn API Documentation")
+ .version("v1") // API version, not BROOKLYN_VERSION
+ .license(new License()
+ .name("Apache 2.0")
+ .url("http://www.apache.org/licenses/LICENSE-2.0.html"));
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+// ReflectiveJaxrsScanner scanner = new ReflectiveJaxrsScanner();
+// scanner.setResourcePackage("org.apache.brooklyn.rest.api,org.apache.brooklyn.rest.apidoc,org.apache.brooklyn.rest.resources");
+// ScannerFactory.setScanner(scanner);
+ ScannerFactory.setScanner(new RestApiResourceScanner());
+
+ ServletContext context = filterConfig.getServletContext();
+ Swagger swagger = new Swagger()
+ .info(info);
+ context.setAttribute("swagger", swagger);
+ }
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
+ chain.doFilter(request, response);
+ }
+
+ @Override
+ public void destroy() {
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/resources/ApiListingResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/resources/ApiListingResource.java b/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/resources/ApiListingResource.java
new file mode 100644
index 0000000..74f8426
--- /dev/null
+++ b/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/resources/ApiListingResource.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright 2015 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import com.sun.jersey.spi.container.servlet.WebConfig;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.config.FilterFactory;
+import io.swagger.config.Scanner;
+import io.swagger.config.ScannerFactory;
+import io.swagger.config.SwaggerConfig;
+import io.swagger.core.filter.SpecFilter;
+import io.swagger.core.filter.SwaggerSpecFilter;
+import io.swagger.jaxrs.Reader;
+import io.swagger.jaxrs.config.JaxrsScanner;
+import io.swagger.jaxrs.config.ReaderConfigUtils;
+import io.swagger.jaxrs.listing.SwaggerSerializers;
+import io.swagger.models.Swagger;
+import io.swagger.util.Yaml;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Cookie;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.brooklyn.util.text.Strings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * ApiListingResource usable within a jersey servlet filter.
+ *
+ * Taken from io.swagger:swagger-jaxrs, class
+ * io.swagger.jaxrs.listing.ApiListingResource, which can only be used within a
+ * servlet context. We are here using a filter, but jersey has a WebConfig class
+ * that can substitute ServletConfig and FilterConfig.
+ *
+ * @todo Remove when the rest-server is no longer running within a filter (e.g.
+ * as a standalone OSGi http service)
+ *
+ * @author Ciprian Ciubotariu <ch...@gmx.net>
+ */
+public class ApiListingResource {
+
+ static Logger LOGGER = LoggerFactory.getLogger(ApiListingResource.class);
+
+ @Context
+ ServletContext context;
+
+ boolean initialized = false;
+
+ private static class ServletConfigAdapter implements ServletConfig {
+
+ private final WebConfig webConfig;
+
+ private ServletConfigAdapter(WebConfig webConfig) {
+ this.webConfig = webConfig;
+ }
+
+ @Override
+ public String getServletName() {
+ return webConfig.getName();
+ }
+
+ @Override
+ public ServletContext getServletContext() {
+ return webConfig.getServletContext();
+ }
+
+ @Override
+ public String getInitParameter(String name) {
+ return webConfig.getInitParameter(name);
+ }
+
+ @Override
+ public Enumeration<String> getInitParameterNames() {
+ return webConfig.getInitParameterNames();
+ }
+
+ }
+
+ protected synchronized Swagger scan(Application app, WebConfig sc) {
+ Swagger swagger = null;
+ Scanner scanner = ScannerFactory.getScanner();
+ LOGGER.debug("using scanner " + scanner);
+
+ if (scanner != null) {
+ SwaggerSerializers.setPrettyPrint(scanner.getPrettyPrint());
+ swagger = (Swagger) context.getAttribute("swagger");
+
+ Set<Class<?>> classes;
+ if (scanner instanceof JaxrsScanner) {
+ JaxrsScanner jaxrsScanner = (JaxrsScanner) scanner;
+ classes = jaxrsScanner.classesFromContext(app, new ServletConfigAdapter(sc));
+ } else {
+ classes = scanner.classes();
+ }
+ if (classes != null) {
+ Reader reader = new Reader(swagger, ReaderConfigUtils.getReaderConfig(context));
+ swagger = reader.read(classes);
+ if (scanner instanceof SwaggerConfig) {
+ swagger = ((SwaggerConfig) scanner).configure(swagger);
+ } else {
+ SwaggerConfig configurator = (SwaggerConfig) context.getAttribute("reader");
+ if (configurator != null) {
+ LOGGER.debug("configuring swagger with " + configurator);
+ configurator.configure(swagger);
+ } else {
+ LOGGER.debug("no configurator");
+ }
+ }
+ context.setAttribute("swagger", swagger);
+ }
+ }
+ initialized = true;
+ return swagger;
+ }
+
+ private Swagger process(
+ Application app,
+ WebConfig sc,
+ HttpHeaders headers,
+ UriInfo uriInfo) {
+ Swagger swagger = (Swagger) context.getAttribute("swagger");
+ if (!initialized) {
+ swagger = scan(app, sc);
+ }
+ if (swagger != null) {
+ SwaggerSpecFilter filterImpl = FilterFactory.getFilter();
+ if (filterImpl != null) {
+ SpecFilter f = new SpecFilter();
+ swagger = f.filter(swagger, filterImpl, getQueryParams(uriInfo.getQueryParameters()), getCookies(headers),
+ getHeaders(headers));
+ }
+ }
+ return swagger;
+ }
+
+ @GET
+ @Produces({MediaType.APPLICATION_JSON, "application/yaml"})
+ @ApiOperation(value = "The swagger definition in either JSON or YAML", hidden = true)
+ @Path("/swagger.{type:json|yaml}")
+ public Response getListing(
+ @Context Application app,
+ @Context WebConfig sc,
+ @Context HttpHeaders headers,
+ @Context UriInfo uriInfo,
+ @PathParam("type") String type) {
+ if (Strings.isNonBlank(type) && type.trim().equalsIgnoreCase("yaml")) {
+ return getListingYaml(app, sc, headers, uriInfo);
+ } else {
+ return getListingJson(app, sc, headers, uriInfo);
+ }
+ }
+
+ @GET
+ @Produces({MediaType.APPLICATION_JSON})
+ @Path("/swagger")
+ @ApiOperation(value = "The swagger definition in JSON", hidden = true)
+ public Response getListingJson(
+ @Context Application app,
+ @Context WebConfig sc,
+ @Context HttpHeaders headers,
+ @Context UriInfo uriInfo) {
+ Swagger swagger = process(app, sc, headers, uriInfo);
+
+ if (swagger != null) {
+ return Response.ok().entity(swagger).build();
+ } else {
+ return Response.status(404).build();
+ }
+ }
+
+ @GET
+ @Produces("application/yaml")
+ @Path("/swagger")
+ @ApiOperation(value = "The swagger definition in YAML", hidden = true)
+ public Response getListingYaml(
+ @Context Application app,
+ @Context WebConfig sc,
+ @Context HttpHeaders headers,
+ @Context UriInfo uriInfo) {
+ Swagger swagger = process(app, sc, headers, uriInfo);
+ try {
+ if (swagger != null) {
+ String yaml = Yaml.mapper().writeValueAsString(swagger);
+ StringBuilder b = new StringBuilder();
+ String[] parts = yaml.split("\n");
+ for (String part : parts) {
+ b.append(part);
+ b.append("\n");
+ }
+ return Response.ok().entity(b.toString()).type("application/yaml").build();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return Response.status(404).build();
+ }
+
+ protected Map<String, List<String>> getQueryParams(MultivaluedMap<String, String> params) {
+ Map<String, List<String>> output = new HashMap<>();
+ if (params != null) {
+ for (String key : params.keySet()) {
+ List<String> values = params.get(key);
+ output.put(key, values);
+ }
+ }
+ return output;
+ }
+
+ protected Map<String, String> getCookies(HttpHeaders headers) {
+ Map<String, String> output = new HashMap<>();
+ if (headers != null) {
+ for (String key : headers.getCookies().keySet()) {
+ Cookie cookie = headers.getCookies().get(key);
+ output.put(key, cookie.getValue());
+ }
+ }
+ return output;
+ }
+
+ protected Map<String, List<String>> getHeaders(HttpHeaders headers) {
+ Map<String, List<String>> output = new HashMap<>();
+ if (headers != null) {
+ for (String key : headers.getRequestHeaders().keySet()) {
+ List<String> values = headers.getRequestHeaders().get(key);
+ output.put(key, values);
+ }
+ }
+ return output;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/resources/ApidocResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/resources/ApidocResource.java b/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/resources/ApidocResource.java
new file mode 100644
index 0000000..1cf6523
--- /dev/null
+++ b/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/resources/ApidocResource.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+
+import javax.ws.rs.Path;
+
+import io.swagger.annotations.Api;
+
+/**
+ * @author Ciprian Ciubotariu <ch...@gmx.net>
+ */
+@Api("API Documentation")
+@Path("/apidoc")
+public class ApidocResource extends ApiListingResource {
+
+}
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/abd2d5f3/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/util/FormMapProvider.java
----------------------------------------------------------------------
diff --git a/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/util/FormMapProvider.java b/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/util/FormMapProvider.java
new file mode 100644
index 0000000..2b5c19b
--- /dev/null
+++ b/rest/rest-server-jersey/src/main/java/org/apache/brooklyn/rest/util/FormMapProvider.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.List;
+import java.util.Map;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyReader;
+import javax.ws.rs.ext.Provider;
+
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.sun.jersey.core.impl.provider.entity.FormMultivaluedMapProvider;
+import com.sun.jersey.core.util.MultivaluedMapImpl;
+
+/**
+ * A MessageBodyReader producing a <code>Map<String, Object></code>, where Object
+ * is either a <code>String</code>, a <code>List<String></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);
+ }
+
+}