You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by ha...@apache.org on 2015/11/19 00:42:51 UTC

[07/24] incubator-brooklyn git commit: [BROOKLYN-185] Move ApidocResource(s) to swagger 2.0 spec standard

[BROOKLYN-185] Move ApidocResource(s) to swagger 2.0 spec standard

Add SwaggerFilter to web.xml, initializing the appropriate context
parameters for new swagger resource scanners.

Use a custom ApiListingResource instead of swagger-provided resource so
we can use swagger within a jersey servlet filter context.


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

Branch: refs/heads/master
Commit: 5c62562640affb17d23eb87048933aa47b8230bd
Parents: 55c5904
Author: Ciprian Ciubotariu <ch...@gmx.net>
Authored: Tue Nov 10 01:09:54 2015 +0200
Committer: Ciprian Ciubotariu <ch...@gmx.net>
Committed: Wed Nov 11 17:42:43 2015 +0200

----------------------------------------------------------------------
 .../camp/server/rest/CampRestResources.java     |  10 +-
 .../rest/resource/ApidocRestResource.java       |   2 +-
 usage/rest-api/src/main/webapp/WEB-INF/web.xml  |  10 +-
 .../apache/brooklyn/rest/BrooklynRestApi.java   |   2 -
 .../brooklyn/rest/filter/SwaggerFilter.java     |  94 ++++++
 .../brooklyn/rest/resources/ApidocResource.java |   7 +-
 .../rest-server/src/main/webapp/WEB-INF/web.xml |  13 +-
 .../brooklyn/rest/BrooklynRestApiLauncher.java  |   3 +-
 .../rest/resources/ApiDocResourceTest.java      | 138 ---------
 .../rest/resources/ApidocResourceTest.java      | 135 +++++++++
 .../rest/testing/BrooklynRestApiTest.java       |   8 +
 .../rest/util/NullServletConfigProvider.java    |   5 +
 utils/rest-swagger/pom.xml                      |  16 +-
 .../rest/apidoc/ApiListingResource.java         | 259 ++++++++++++++++
 .../brooklyn/rest/apidoc/ApidocEndpoint.java    |  54 ----
 .../apidoc/ApidocHelpMessageBodyWriter.java     |  28 --
 .../brooklyn/rest/apidoc/ApidocResource.java    | 294 -------------------
 .../apache/brooklyn/rest/apidoc/ApidocRoot.java |  47 ---
 .../rest/apidoc/RestApiResourceScanner.java     |  79 +++++
 utils/swagger-annotations/pom.xml               |  44 +++
 .../brooklyn/swagger/annotations/Apidoc.java    |  33 +++
 21 files changed, 697 insertions(+), 584 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5c625626/camp/camp-server/src/main/java/org/apache/brooklyn/camp/server/rest/CampRestResources.java
----------------------------------------------------------------------
diff --git a/camp/camp-server/src/main/java/org/apache/brooklyn/camp/server/rest/CampRestResources.java b/camp/camp-server/src/main/java/org/apache/brooklyn/camp/server/rest/CampRestResources.java
index 2d3030c..0c99377 100644
--- a/camp/camp-server/src/main/java/org/apache/brooklyn/camp/server/rest/CampRestResources.java
+++ b/camp/camp-server/src/main/java/org/apache/brooklyn/camp/server/rest/CampRestResources.java
@@ -30,15 +30,15 @@ import org.apache.brooklyn.camp.server.rest.resource.AssemblyTemplateRestResourc
 import org.apache.brooklyn.camp.server.rest.resource.PlatformComponentRestResource;
 import org.apache.brooklyn.camp.server.rest.resource.PlatformComponentTemplateRestResource;
 import org.apache.brooklyn.camp.server.rest.resource.PlatformRestResource;
-import org.apache.brooklyn.rest.apidoc.ApidocHelpMessageBodyWriter;
 
 import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
 import com.google.common.collect.Iterables;
+import io.swagger.jaxrs.listing.SwaggerSerializers;
 
 public class CampRestResources {
 
     public static Iterable<AbstractCampRestResource> getCampRestResources() {
-        List<AbstractCampRestResource> resources = new ArrayList<AbstractCampRestResource>();
+        List<AbstractCampRestResource> resources = new ArrayList<>();
         resources.add(new PlatformRestResource());
         resources.add(new AssemblyTemplateRestResource());
         resources.add(new PlatformComponentTemplateRestResource());
@@ -50,14 +50,14 @@ public class CampRestResources {
     }
 
     public static Iterable<Object> getApidocResources() {
-        List<Object> resources = new ArrayList<Object>();
-        resources.add(new ApidocHelpMessageBodyWriter());
+        List<Object> resources = new ArrayList<>();
         resources.add(new ApidocRestResource());
         return resources;
     }
 
     public static Iterable<Object> getMiscResources() {
-        List<Object> resources = new ArrayList<Object>();
+        List<Object> resources = new ArrayList<>();
+        resources.add(new SwaggerSerializers());
         resources.add(new JacksonJsonProvider());
         return resources;
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5c625626/camp/camp-server/src/main/java/org/apache/brooklyn/camp/server/rest/resource/ApidocRestResource.java
----------------------------------------------------------------------
diff --git a/camp/camp-server/src/main/java/org/apache/brooklyn/camp/server/rest/resource/ApidocRestResource.java b/camp/camp-server/src/main/java/org/apache/brooklyn/camp/server/rest/resource/ApidocRestResource.java
index e410f4c..850ae22 100644
--- a/camp/camp-server/src/main/java/org/apache/brooklyn/camp/server/rest/resource/ApidocRestResource.java
+++ b/camp/camp-server/src/main/java/org/apache/brooklyn/camp/server/rest/resource/ApidocRestResource.java
@@ -24,7 +24,7 @@ import javax.ws.rs.Path;
 
 @Path(ApidocRestResource.API_URI_PATH)
 @Api(value = "org.apache.brooklyn.camp.server.rest.resource.ApidocRestResource", description = "Web API Documentation")
-public class ApidocRestResource extends org.apache.brooklyn.rest.apidoc.ApidocResource {
+public class ApidocRestResource extends org.apache.brooklyn.rest.apidoc.ApiListingResource {
 
     public static final String API_URI_PATH = PlatformRestResource.CAMP_URI_PATH + "/apidoc";
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5c625626/usage/rest-api/src/main/webapp/WEB-INF/web.xml
----------------------------------------------------------------------
diff --git a/usage/rest-api/src/main/webapp/WEB-INF/web.xml b/usage/rest-api/src/main/webapp/WEB-INF/web.xml
index 672785e..06331bd 100644
--- a/usage/rest-api/src/main/webapp/WEB-INF/web.xml
+++ b/usage/rest-api/src/main/webapp/WEB-INF/web.xml
@@ -59,6 +59,14 @@
         <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 
@@ -75,7 +83,7 @@
         <!-- load our REST API jersey resources -->
         <init-param>
             <param-name>com.sun.jersey.config.property.packages</param-name>
-            <param-value>org.apache.brooklyn.rest.resources;org.apache.brooklyn.rest.apidoc</param-value>
+            <param-value>io.swagger.jaxrs.listing;org.codehaus.jackson.jaxrs;org.apache.brooklyn.rest.resources;org.apache.brooklyn.rest.util</param-value>
         </init-param>
 
         <!-- install Jackson and turn on pojo/json serialization (could add org.codehaus.jackson.jaxrs 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5c625626/usage/rest-server/src/main/java/org/apache/brooklyn/rest/BrooklynRestApi.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/BrooklynRestApi.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/BrooklynRestApi.java
index 3973e52..209eb94 100644
--- a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/BrooklynRestApi.java
+++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/BrooklynRestApi.java
@@ -21,7 +21,6 @@ package org.apache.brooklyn.rest;
 import java.util.ArrayList;
 import java.util.List;
 
-import org.apache.brooklyn.rest.apidoc.ApidocHelpMessageBodyWriter;
 import org.apache.brooklyn.rest.resources.AbstractBrooklynRestResource;
 import org.apache.brooklyn.rest.resources.AccessResource;
 import org.apache.brooklyn.rest.resources.ActivityResource;
@@ -70,7 +69,6 @@ public class BrooklynRestApi {
 
     public static Iterable<Object> getApidocResources() {
         List<Object> resources = new ArrayList<Object>();
-        resources.add(new ApidocHelpMessageBodyWriter());
         resources.add(new ApidocResource());
         return resources;
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5c625626/usage/rest-server/src/main/java/org/apache/brooklyn/rest/filter/SwaggerFilter.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/filter/SwaggerFilter.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/filter/SwaggerFilter.java
new file mode 100644
index 0000000..5159b6e
--- /dev/null
+++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/filter/SwaggerFilter.java
@@ -0,0 +1,94 @@
+/*
+ * 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.filter;
+
+import io.swagger.config.ScannerFactory;
+import io.swagger.models.Info;
+import io.swagger.models.License;
+import io.swagger.models.Swagger;
+import java.io.IOException;
+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 org.apache.brooklyn.rest.apidoc.RestApiResourceScanner;
+
+/**
+ * Bootstraps swagger.
+ *
+ * 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 ApiDoc")
+            .version("TODO") // API version, not BROOKLYN_VERSION
+            //            .description("This is a sample server Petstore server.  You can find out more about Swagger " +
+            //              "at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/).  For this sample, " +
+            //              "you can use the api key `special-key` to test the authorization filters.")
+            //            .termsOfService("http://swagger.io/terms/")
+            //            .contact(new Contact()
+            //              .email("apiteam@swagger.io"))
+            .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);
+//        swagger.externalDocs(new ExternalDocs("Find out more about Swagger", "http://swagger.io"));
+//        swagger.securityDefinition("api_key", new ApiKeyAuthDefinition("api_key", In.HEADER));
+//        swagger.securityDefinition("petstore_auth",
+//                new OAuth2Definition()
+//                .implicit("http://petstore.swagger.io/api/oauth/dialog")
+//                .scope("read:pets", "read your pets")
+//                .scope("write:pets", "modify pets in your account"));
+//        swagger.tag(new Tag()
+//                .name("pet")
+//                .description("Everything about your Pets")
+//                .externalDocs(new ExternalDocs("Find out more", "http://swagger.io")));
+//        swagger.tag(new Tag()
+//                .name("store")
+//                .description("Access to Petstore orders"));
+//        swagger.tag(new Tag()
+//                .name("user")
+//                .description("Operations about user")
+//                .externalDocs(new ExternalDocs("Find out more about our store", "http://swagger.io")));
+
+        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/incubator-brooklyn/blob/5c625626/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ApidocResource.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ApidocResource.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ApidocResource.java
index e15ce80..e748516 100644
--- a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ApidocResource.java
+++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ApidocResource.java
@@ -21,8 +21,11 @@ package org.apache.brooklyn.rest.resources;
 import io.swagger.annotations.Api;
 import javax.ws.rs.Path;
 
-@Api(value = "org.apache.brooklyn.rest.resources.ApidocRestResource", description = "API Documentation")
+/**
+ * @author Ciprian Ciubotariu <ch...@gmx.net>
+ */
+@Api(value = "org.apache.brooklyn.rest.resources.ApidocResource", description = "API Documentation")
 @Path("/v1/apidoc")
-public class ApidocResource extends org.apache.brooklyn.rest.apidoc.ApidocResource {
+public class ApidocResource extends org.apache.brooklyn.rest.apidoc.ApiListingResource {
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5c625626/usage/rest-server/src/main/webapp/WEB-INF/web.xml
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/webapp/WEB-INF/web.xml b/usage/rest-server/src/main/webapp/WEB-INF/web.xml
index 045eaab..1c44b02 100644
--- a/usage/rest-server/src/main/webapp/WEB-INF/web.xml
+++ b/usage/rest-server/src/main/webapp/WEB-INF/web.xml
@@ -58,7 +58,16 @@
         <url-pattern>/*</url-pattern>
     </filter-mapping>
 
-    <!-- Brooklyn REST is usu run as a filter so static content can be placed in a webapp
+    <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)
@@ -80,7 +89,7 @@
         <init-param>
             <param-name>com.sun.jersey.config.property.classnames</param-name>
             <param-value>
-                org.apache.brooklyn.rest.apidoc.ApidocHelpMessageBodyWriter;
+                io.swagger.jaxrs.listing.SwaggerSerializers;
                 org.apache.brooklyn.rest.util.FormMapProvider;
                 org.codehaus.jackson.jaxrs.JacksonJsonProvider;
                 org.apache.brooklyn.rest.resources.ActivityResource;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5c625626/usage/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncher.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncher.java b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncher.java
index 221d1ad..a2d9113 100644
--- a/usage/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncher.java
+++ b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncher.java
@@ -71,6 +71,7 @@ import com.sun.jersey.api.core.DefaultResourceConfig;
 import com.sun.jersey.api.core.ResourceConfig;
 import com.sun.jersey.spi.container.servlet.ServletContainer;
 import org.eclipse.jetty.server.NetworkConnector;
+import org.apache.brooklyn.rest.filter.SwaggerFilter;
 
 /** Convenience and demo for launching programmatically. Also used for automated tests.
  * <p>
@@ -308,7 +309,7 @@ public class BrooklynRestApiLauncher {
     }
 
     public static void main(String[] args) throws Exception {
-        startRestResourcesViaFilter();
+        startRestResourcesViaWebXml();
         log.info("Press Ctrl-C to quit.");
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5c625626/usage/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ApiDocResourceTest.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ApiDocResourceTest.java b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ApiDocResourceTest.java
deleted file mode 100644
index e5c1f23..0000000
--- a/usage/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ApiDocResourceTest.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.rest.resources;
-
-import static org.testng.Assert.assertEquals;
-
-import java.util.List;
-import java.util.Map;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.Assert;
-import org.testng.annotations.Test;
-
-import org.apache.brooklyn.rest.BrooklynRestApi;
-import org.apache.brooklyn.rest.apidoc.ApidocRoot;
-import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
-
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-import com.wordnik.swagger.core.DocumentationEndPoint;
-import com.wordnik.swagger.core.DocumentationOperation;
-
-/**
- * @author Adam Lowe
- */
-@Test(singleThreaded = true)
-public class ApiDocResourceTest extends BrooklynRestResourceTest {
-
-    private static final Logger log = LoggerFactory.getLogger(ApiDocResourceTest.class);
-
-    @Override
-    protected void addBrooklynResources() {
-        for (Object o : BrooklynRestApi.getApidocResources()) {
-            addResource(o);
-        }
-        super.addBrooklynResources();
-    }
-    
-    @Test
-    public void testRootSerializesSensibly() throws Exception {
-        String data = client().resource("/v1/apidoc/").get(String.class);
-        log.info("apidoc gives: "+data);
-        // make sure no scala gets in
-        Assert.assertFalse(data.contains("$"));
-        Assert.assertFalse(data.contains("scala"));
-    }
-    
-    @Test
-    public void testCountRestResources() throws Exception {
-        ApidocRoot response = client().resource("/v1/apidoc/").get(ApidocRoot.class);
-        assertEquals(response.getApis().size(), 1 + Iterables.size(BrooklynRestApi.getBrooklynRestResources()));
-    }
-
-    @Test
-    public void testEndpointSerializesSensibly() throws Exception {
-        String data = client().resource("/v1/apidoc/org.apache.brooklyn.rest.resources.ApidocResource").get(String.class);
-        log.info("apidoc endpoint resource gives: "+data);
-        // make sure no scala gets in
-        Assert.assertFalse(data.contains("$"));
-        Assert.assertFalse(data.contains("scala"));
-    }
-    
-    @Test
-    public void testApiDocDetails() throws Exception {
-        ApidocRoot response = client().resource("/v1/apidoc/org.apache.brooklyn.rest.resources.ApidocResource").get(ApidocRoot.class);
-        assertEquals(countOperations(response), 2);
-    }
-
-    @Test
-    public void testEffectorDetails() throws Exception {
-        ApidocRoot response = client().resource("/v1/apidoc/org.apache.brooklyn.rest.resources.EffectorResource").get(ApidocRoot.class);
-        assertEquals(countOperations(response), 2);
-    }
-
-    @Test
-    public void testEntityDetails() throws Exception {
-        ApidocRoot response = client().resource("/v1/apidoc/org.apache.brooklyn.rest.resources.EntityResource").get(ApidocRoot.class);
-        assertEquals(countOperations(response), 14);
-    }
-
-    @Test
-    public void testCatalogDetails() throws Exception {
-        ApidocRoot response = client().resource("/v1/apidoc/org.apache.brooklyn.rest.resources.CatalogResource").get(ApidocRoot.class);
-        assertEquals(countOperations(response), 22, "ops="+getOperations(response));
-    }
-
-    @SuppressWarnings("rawtypes")
-    @Test
-    public void testAllAreLoadable() throws Exception {
-        // sometimes -- e.g. if an annotation refers to a class name with the wrong case -- the call returns a 500 and breaks apidoc; ensure we don't trigger that.  
-        Map response = client().resource("/v1/apidoc/").get(Map.class);
-        // "Documenation" object does not include the links :( so traverse via map
-        log.debug("root doc response is: "+response);
-        List apis = (List)response.get("apis");
-        for (Object api: apis) {
-            String link = (String) ((Map)api).get("link");
-            try {
-                Map r2 = client().resource(link).get(Map.class);
-                log.debug("doc for "+link+" is: "+r2);
-            } catch (Exception e) {
-                log.error("Error in swagger/apidoc annotations, unparseable, at "+link+": "+e, e);
-                Assert.fail("Error in swagger/apidoc annotations, unparseable, at "+link+": "+e, e);
-            }
-        }
-    }
-
-    /* Note in some cases we might have more than one Resource method per 'endpoint'
-     */
-    private int countOperations(ApidocRoot doc) throws Exception {
-        return getOperations(doc).size();
-    }
-    
-    private List<DocumentationOperation> getOperations(ApidocRoot doc) throws Exception {
-        List<DocumentationOperation> result = Lists.newArrayList();
-        for (DocumentationEndPoint endpoint : doc.getApis()) {
-            result.addAll(endpoint.getOperations());
-        }
-        return result;
-    }
-}
-

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5c625626/usage/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ApidocResourceTest.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ApidocResourceTest.java b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ApidocResourceTest.java
new file mode 100644
index 0000000..c98117f
--- /dev/null
+++ b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ApidocResourceTest.java
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import static org.testng.Assert.assertEquals;
+
+import java.util.List;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import org.apache.brooklyn.rest.BrooklynRestApi;
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+
+/**
+ * @author Adam Lowe
+ */
+@Test(singleThreaded = true)
+public class ApidocResourceTest extends BrooklynRestResourceTest {
+
+    private static final Logger log = LoggerFactory.getLogger(ApidocResourceTest.class);
+
+    @Override
+    protected void addBrooklynResources() {
+        for (Object o : BrooklynRestApi.getApidocResources()) {
+            addResource(o);
+        }
+        super.addBrooklynResources();
+    }
+    
+    @Test
+    public void testRootSerializesSensibly() throws Exception {
+        String data = client().resource("/v1/apidoc/swagger.json").get(String.class);
+        log.info("apidoc gives: "+data);
+        // make sure no scala gets in
+        Assert.assertFalse(data.contains("$"));
+        Assert.assertFalse(data.contains("scala"));
+    }
+    
+//    @Test
+//    public void testCountRestResources() throws Exception {
+//        ApidocRoot response = client().resource("/v1/apidoc/").get(ApidocRoot.class);
+//        assertEquals(response.getApis().size(), 1 + Iterables.size(BrooklynRestApi.getBrooklynRestResources()));
+//    }
+//
+//    @Test
+//    public void testEndpointSerializesSensibly() throws Exception {
+//        String data = client().resource("/v1/apidoc/org.apache.brooklyn.rest.resources.ApidocResource").get(String.class);
+//        log.info("apidoc endpoint resource gives: "+data);
+//        // make sure no scala gets in
+//        Assert.assertFalse(data.contains("$"));
+//        Assert.assertFalse(data.contains("scala"));
+//    }
+//
+//    @Test
+//    public void testApiDocDetails() throws Exception {
+//        ApidocRoot response = client().resource("/v1/apidoc/org.apache.brooklyn.rest.resources.ApidocResource").get(ApidocRoot.class);
+//        assertEquals(countOperations(response), 2);
+//    }
+//
+//    @Test
+//    public void testEffectorDetails() throws Exception {
+//        ApidocRoot response = client().resource("/v1/apidoc/org.apache.brooklyn.rest.resources.EffectorResource").get(ApidocRoot.class);
+//        assertEquals(countOperations(response), 2);
+//    }
+//
+//    @Test
+//    public void testEntityDetails() throws Exception {
+//        ApidocRoot response = client().resource("/v1/apidoc/org.apache.brooklyn.rest.resources.EntityResource").get(ApidocRoot.class);
+//        assertEquals(countOperations(response), 14);
+//    }
+//
+//    @Test
+//    public void testCatalogDetails() throws Exception {
+//        ApidocRoot response = client().resource("/v1/apidoc/org.apache.brooklyn.rest.resources.CatalogResource").get(ApidocRoot.class);
+//        assertEquals(countOperations(response), 22, "ops="+getOperations(response));
+//    }
+
+    @SuppressWarnings("rawtypes")
+    @Test
+    public void testAllAreLoadable() throws Exception {
+        // sometimes -- e.g. if an annotation refers to a class name with the wrong case -- the call returns a 500 and breaks apidoc; ensure we don't trigger that.  
+        Map response = client().resource("/v1/apidoc/swagger.json").get(Map.class);
+        // "Documenation" object does not include the links :( so traverse via map
+        log.debug("root doc response is: "+response);
+        List apis = (List)response.get("apis");
+        for (Object api: apis) {
+            String link = (String) ((Map)api).get("link");
+            try {
+                Map r2 = client().resource(link).get(Map.class);
+                log.debug("doc for "+link+" is: "+r2);
+            } catch (Exception e) {
+                log.error("Error in swagger/apidoc annotations, unparseable, at "+link+": "+e, e);
+                Assert.fail("Error in swagger/apidoc annotations, unparseable, at "+link+": "+e, e);
+            }
+        }
+    }
+
+//    /* Note in some cases we might have more than one Resource method per 'endpoint'
+//     */
+//    private int countOperations(ApidocRoot doc) throws Exception {
+//        return getOperations(doc).size();
+//    }
+//
+//    private List<DocumentationOperation> getOperations(ApidocRoot doc) throws Exception {
+//        List<DocumentationOperation> result = Lists.newArrayList();
+//        for (DocumentationEndPoint endpoint : doc.getApis()) {
+//            result.addAll(endpoint.getOperations());
+//        }
+//        return result;
+//    }
+}
+

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5c625626/usage/rest-server/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestApiTest.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestApiTest.java b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestApiTest.java
index 2f50cfc..27115ad 100644
--- a/usage/rest-server/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestApiTest.java
+++ b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestApiTest.java
@@ -36,9 +36,11 @@ import com.sun.jersey.api.core.DefaultResourceConfig;
 import com.sun.jersey.test.framework.AppDescriptor;
 import com.sun.jersey.test.framework.JerseyTest;
 import com.sun.jersey.test.framework.LowLevelAppDescriptor;
+import com.sun.jersey.test.framework.WebAppDescriptor;
 
 import org.apache.brooklyn.rest.BrooklynRestApi;
 import org.apache.brooklyn.rest.BrooklynRestApiLauncherTest;
+import org.apache.brooklyn.rest.filter.SwaggerFilter;
 import org.apache.brooklyn.rest.util.BrooklynRestResourceUtils;
 import org.apache.brooklyn.rest.util.NullHttpServletRequestProvider;
 import org.apache.brooklyn.rest.util.NullServletConfigProvider;
@@ -157,6 +159,12 @@ public abstract class BrooklynRestApiTest {
         jerseyTest = new JerseyTest() {
             @Override
             protected AppDescriptor configure() {
+//                return new WebAppDescriptor.Builder(
+//                        "io.swagger.jaxrs.listing",
+//                        "org.apache.brooklyn.rest.util",
+//                        "org.codehaus.jackson.jaxrs",
+//                        "org.apache.brooklyn.rest.resources")
+//                        .filterClass(SwaggerFilter.class).build();
                 return new LowLevelAppDescriptor.Builder(config).build();
             }
         };

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5c625626/usage/rest-server/src/test/java/org/apache/brooklyn/rest/util/NullServletConfigProvider.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/test/java/org/apache/brooklyn/rest/util/NullServletConfigProvider.java b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/util/NullServletConfigProvider.java
index 06c60ea..106780d 100644
--- a/usage/rest-server/src/test/java/org/apache/brooklyn/rest/util/NullServletConfigProvider.java
+++ b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/util/NullServletConfigProvider.java
@@ -26,6 +26,7 @@ import javax.ws.rs.ext.Provider;
 
 import com.sun.jersey.core.spi.component.ComponentContext;
 import com.sun.jersey.core.spi.component.ComponentScope;
+import com.sun.jersey.spi.container.servlet.WebConfig;
 import com.sun.jersey.spi.inject.Injectable;
 import com.sun.jersey.spi.inject.InjectableProvider;
 
@@ -37,6 +38,10 @@ public class NullServletConfigProvider implements InjectableProvider<Context, Ty
             return new Injectable<ServletContext>() {
                 public ServletContext getValue() { return null; }
             }; 
+        } else if (WebConfig.class == c) {
+            return new Injectable<ServletContext>() {
+                public ServletContext getValue() { return null; }
+            }; 
         } else 
             return null; 
     } 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5c625626/utils/rest-swagger/pom.xml
----------------------------------------------------------------------
diff --git a/utils/rest-swagger/pom.xml b/utils/rest-swagger/pom.xml
index 42edc4b..42e9767 100644
--- a/utils/rest-swagger/pom.xml
+++ b/utils/rest-swagger/pom.xml
@@ -40,11 +40,7 @@
         <!-- ATTN: this moves jersey-server from 1.7 to 1.12 -->
         <dependency>
             <groupId>com.sun.jersey</groupId>
-            <artifactId>jersey-server</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>com.sun.jersey</groupId>
-            <artifactId>jersey-core</artifactId>
+            <artifactId>jersey-servlet</artifactId>
         </dependency>
         <dependency>
             <groupId>com.google.guava</groupId>
@@ -83,16 +79,18 @@
             <artifactId>swagger-jaxrs</artifactId>
             <exclusions>
                 <exclusion>
-                    <groupId>javax.servlet</groupId>
-                    <artifactId>servlet-api</artifactId>
-                </exclusion>
-                <exclusion>
                     <groupId>com.sun.jersey</groupId>
                     <artifactId>jersey-client</artifactId>
                 </exclusion>
             </exclusions>
         </dependency>
 
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>javax.servlet-api</artifactId>
+        </dependency>
+
+
     </dependencies>
     
 </project>

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5c625626/utils/rest-swagger/src/main/java/org/apache/brooklyn/rest/apidoc/ApiListingResource.java
----------------------------------------------------------------------
diff --git a/utils/rest-swagger/src/main/java/org/apache/brooklyn/rest/apidoc/ApiListingResource.java b/utils/rest-swagger/src/main/java/org/apache/brooklyn/rest/apidoc/ApiListingResource.java
new file mode 100644
index 0000000..3edffe9
--- /dev/null
+++ b/utils/rest-swagger/src/main/java/org/apache/brooklyn/rest/apidoc/ApiListingResource.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright 2015 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.brooklyn.rest.apidoc;
+
+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.commons.lang3.StringUtils;
+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 (StringUtils.isNotBlank(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/incubator-brooklyn/blob/5c625626/utils/rest-swagger/src/main/java/org/apache/brooklyn/rest/apidoc/ApidocEndpoint.java
----------------------------------------------------------------------
diff --git a/utils/rest-swagger/src/main/java/org/apache/brooklyn/rest/apidoc/ApidocEndpoint.java b/utils/rest-swagger/src/main/java/org/apache/brooklyn/rest/apidoc/ApidocEndpoint.java
deleted file mode 100644
index 9260c26..0000000
--- a/utils/rest-swagger/src/main/java/org/apache/brooklyn/rest/apidoc/ApidocEndpoint.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.rest.apidoc;
-
-import java.util.Comparator;
-
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-import com.fasterxml.jackson.annotation.JsonProperty;
-
-import com.wordnik.swagger.core.DocumentationEndPoint;
-
-@JsonIgnoreProperties({
-    "com$wordnik$swagger$core$DocumentationEndPoint$$ops"
-})
-public class ApidocEndpoint extends DocumentationEndPoint {
-
-    public static final Comparator<ApidocEndpoint> COMPARATOR = new Comparator<ApidocEndpoint>() {
-        @Override
-        public int compare(ApidocEndpoint o1, ApidocEndpoint o2) {
-            if (o1.name==o2.name) return 0;
-            if (o1.name==null) return -1;
-            if (o2.name==null) return 1;
-            return o1.name.compareTo(o2.name);
-        }
-    };
-    
-    public final String name;
-    public final String link;
-    
-    @JsonCreator
-    public ApidocEndpoint(@JsonProperty("name") String name, @JsonProperty("path") String path, @JsonProperty("description") String description, @JsonProperty("link") String link) {
-        super(path, description);
-        this.name = name;
-        this.link = link;
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5c625626/utils/rest-swagger/src/main/java/org/apache/brooklyn/rest/apidoc/ApidocHelpMessageBodyWriter.java
----------------------------------------------------------------------
diff --git a/utils/rest-swagger/src/main/java/org/apache/brooklyn/rest/apidoc/ApidocHelpMessageBodyWriter.java b/utils/rest-swagger/src/main/java/org/apache/brooklyn/rest/apidoc/ApidocHelpMessageBodyWriter.java
deleted file mode 100644
index 12114e6..0000000
--- a/utils/rest-swagger/src/main/java/org/apache/brooklyn/rest/apidoc/ApidocHelpMessageBodyWriter.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.rest.apidoc;
-
-import javax.ws.rs.ext.Provider;
-
-import com.wordnik.swagger.jaxrs.ApiHelpMessageBodyWriter;
-
-/** subclassed for convenience */
-@Provider
-public class ApidocHelpMessageBodyWriter extends ApiHelpMessageBodyWriter {
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5c625626/utils/rest-swagger/src/main/java/org/apache/brooklyn/rest/apidoc/ApidocResource.java
----------------------------------------------------------------------
diff --git a/utils/rest-swagger/src/main/java/org/apache/brooklyn/rest/apidoc/ApidocResource.java b/utils/rest-swagger/src/main/java/org/apache/brooklyn/rest/apidoc/ApidocResource.java
deleted file mode 100644
index 2c0330e..0000000
--- a/utils/rest-swagger/src/main/java/org/apache/brooklyn/rest/apidoc/ApidocResource.java
+++ /dev/null
@@ -1,294 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.rest.apidoc;
-
-import org.apache.brooklyn.swagger.annotations.Apidoc;
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-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.Context;
-import javax.ws.rs.core.HttpHeaders;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.UriInfo;
-
-import com.google.common.collect.ImmutableList;
-import com.sun.jersey.api.core.ResourceConfig;
-import io.swagger.annotations.Api;
-import io.swagger.annotations.ApiOperation;
-import io.swagger.core.Documentation;
-import io.swagger.core.DocumentationEndPoint;
-import io.swagger.jaxrs.ConfigReader;
-import io.swagger.jaxrs.HelpApi;
-import io.swagger.jaxrs.JaxrsApiReader;
-import io.swagger.jaxrs.JaxrsApiSpecParser;
-
-@Produces({"application/json"})
-/** create a concrete subclass for this annotated with the Path where
- * this resource should live 
- * <p>
- * like Swagger ApiListing (and based on that) but:
- * supports singletons as well as classes;
- * supports simpler Apidoc annotation (doesn't repeat path, in common case);
- * doesn't support listingPath/Class that swagger does (but describes in under /apidoc/name.of.Class
- * does not support auth filters
- */
-abstract public class ApidocResource {
-
-    static ConfigReader configReader;
-    static {
-        JaxrsApiReader.setFormatString("");
-    }
-    
-    protected boolean isSupportedMediaType(String type) {
-        return "application/json".equals(type) || "application/xml".equals(type);
-    }
-    
-    protected boolean isIncludedForDocumentation(Class<?> resource) {
-        // TODO currently only support @Produces, not Contenty-type header, or Accept header (which original ApiListing does support)
-        Produces produces = getAnnotation(resource, Produces.class);
-        if (produces == null) return false;
-        for (String type: produces.value()) 
-            if (isSupportedMediaType(type))
-                return true;
-        return false;
-    }
-
-    protected <A extends Annotation> A getAnnotation(Class<?> r, Class<A> annotationClass) {
-        A result = r.getAnnotation(annotationClass);
-        if (result == null) {
-            // first look at things directly on superclass (not inherited)
-            if (r.getSuperclass()!=null)
-                result = r.getSuperclass().getAnnotation(annotationClass);
-        }
-        if (result == null) {
-            // then look at interfaces here (not inherited)
-            // we look at superclasses next so don't have to here
-            for(Class<?> parentInterface :  r.getInterfaces()) {
-                result = parentInterface.getAnnotation(annotationClass);
-                if (result != null) break;
-            }
-        }
-        if (result == null) {
-            // lastly take annotations on superclass and interfaces on superclass, recursively
-            // (so in short we prefer things lower down)
-            if (r.getSuperclass()!=null)
-                result = getAnnotation(r.getSuperclass(), annotationClass);
-        }
-        return result;
-    }
-    
-    protected String getLinkFor(String path, Class<?> resource) {
-        return getClass().getAnnotation(Path.class).value()+"/"+getLinkWordFor(resource);
-    }
-    
-    protected String getLinkWordFor(Class<?> resource) {
-        if (resource.getCanonicalName()!=null) 
-            return resource.getCanonicalName();        
-        else 
-            return Integer.toHexString(resource.hashCode());
-    }
-
-    protected Class<?> getResourceOfLink(ResourceConfig rc, String link) {
-        for (Class<?> r: getResourceClasses(rc)) {
-            if (getLinkWordFor(r).equals(link))
-                return r;
-        }
-        return null;
-    }
-    
-    @GET
-    @ApiOperation(value = "Returns list of all available API resource endpoints", 
-        response = ApidocRoot.class,
-        responseContainer = "List")
-    public Response getAllApis(
-            @Context ResourceConfig rc,
-            @Context HttpHeaders headers,
-            @Context UriInfo uriInfo) {
-
-        String apiVersion = getConfigReader().getApiVersion();
-        String swaggerVersion = getConfigReader().getSwaggerVersion();
-        String basePath = getConfigReader().getBasePath();
-
-        Set<Class<?>> resources = getResourceClasses(rc);
-
-        ApidocRoot allApiDoc = new ApidocRoot();
-
-        List<ApidocEndpoint> endpoints = new ArrayList<>();
-        for (Class<?> resource : resources) {
-            if (!isIncludedForDocumentation(resource))
-                continue;
-            
-            Apidoc apidoc = getAnnotation(resource, Apidoc.class);
-            Api apidocX = getAnnotation(resource, Api.class);
-            Path rsPath = getAnnotation(resource, Path.class);
-
-            if (apidoc==null && apidocX == null) continue;
-            String path = rsPath.value();
-            String name = null;
-            String description;
-
-            if (apidoc!=null) {
-                name = apidoc.value();
-                description = apidoc.description();
-            } else {
-                path = apidocX.value();
-                description = apidocX.description();
-            }
-
-            endpoints.add(new ApidocEndpoint(name, path, description, getLinkFor(path, resource)));
-        }
-        
-        Collections.sort(endpoints, ApidocEndpoint.COMPARATOR);
-        
-        for (ApidocEndpoint api: endpoints) {
-            if (!isApiAdded(allApiDoc, api)) {
-                allApiDoc.addApi(api);
-            }
-        }
-        allApiDoc.setSwaggerVersion(swaggerVersion);
-        allApiDoc.setBasePath(basePath);
-        allApiDoc.setApiVersion(apiVersion);
-
-        return Response.ok().entity(allApiDoc).build();
-    }
-
-    protected Set<Class<?>> getResourceClasses(ResourceConfig rc) {
-        Set<Class<?>> resourceClasses = rc.getRootResourceClasses();
-        Set<Object> resourceObjects = rc.getRootResourceSingletons();
-        
-        Set<Class<?>> resources = new LinkedHashSet<Class<?>>();
-        // @Path should always be set, right? unless something is oddd
-        for (Class<?> r: resourceClasses)
-            if (r.getAnnotation(Path.class)!=null) resources.add(r);
-        for (Object r: resourceObjects) {
-            if (getAnnotation(r.getClass(), Path.class)!=null) {
-                resources.add(r.getClass());
-            }            
-        }
-        return resources;
-    }
-
-    private boolean isApiAdded(Documentation allApiDoc, DocumentationEndPoint endpoint) {
-        boolean isAdded = false;
-        if (allApiDoc.getApis() != null) {
-            for (DocumentationEndPoint addedApi : allApiDoc.getApis()) {
-                if (endpoint.getPath().equals(addedApi.getPath())) isAdded = true;
-            }
-        }
-        return isAdded;
-    }
-
-    @GET
-    @Path("/{resource}")
-    @ApiOperation(value = "Returns detail on the given API resource endpoint", 
-        response  = DocumentationEndPoint.class,
-        responseContainer = "List")
-    public Response details(
-            @Context ResourceConfig rc, 
-            @Context HttpHeaders headers, 
-            @Context UriInfo uriInfo,
-            @PathParam("resource") String resource) {
-        Class<?> target = getResourceOfLink(rc, resource);
-        if (target==null) return Response.status(Response.Status.NOT_FOUND).build();
-        
-        // roughly duplicates JavaHelp
-        String apiVersion = getConfigReader().getApiVersion();
-        String swaggerVersion = getConfigReader().getSwaggerVersion();
-        String basePath = getConfigReader().getBasePath();
-
-        String apiFilterClassName = getConfigReader().getApiFilterClassName();
-
-        Apidoc apidoc = getAnnotation(target, Apidoc.class);
-        Api apidocX = getAnnotation(target, Api.class);
-        Path rsPath = getAnnotation(target, Path.class);
-
-        if ((apidoc==null && apidocX==null) || rsPath==null)
-            return Response.status(Response.Status.NOT_FOUND).build();
-
-        String apiPath = apidoc!=null ? rsPath.value() : apidocX.value();
-
-        HelpApi helpApi = new HelpApi(apiFilterClassName);
-        Documentation doc = read(target, apiVersion, swaggerVersion, basePath, apiPath);
-        Documentation docs = helpApi.filterDocs(doc, headers, uriInfo, apiPath, apiPath);
-        return Response.ok().entity(docs).build();
-    }
-
-
-    
-    
-    // items below here simply override the swagger Jaxrs* classes/behaviour so we can use @Path/@Apidoc instead of @Api
-    
-    protected ConfigReader getConfigReader() {
-        if (configReader==null) configReader = new ConfigReader(null);
-        return configReader;
-    }
-
-    static protected Map<Class<?>,Documentation> endpointsCache = new LinkedHashMap<Class<?>, Documentation>();
-            
-    protected Documentation read(Class<?> target, String apiVersion, String swaggerVersion, String basePath, String apiPath) {
-        Documentation result = endpointsCache.get(target);
-        if (result!=null) return result;
-        JaxrsApiSpecParser parser = new ApidocJaxrsSpecParser(target, apiVersion, swaggerVersion, basePath, apiPath);
-        result = parser.parse();
-        endpointsCache.put(target, result);
-        return result;
-    }
-
-    @Api("ignored")
-    static class ApidocJaxrsSpecParser extends JaxrsApiSpecParser {
-        public ApidocJaxrsSpecParser(Class<?> target, String apiVersion, String swaggerVersion, String basePath, String apiPath) {
-            super(target, apiVersion, swaggerVersion, basePath, apiPath);
-        }
-        @Override
-        public Api apiEndpoint() {
-            // return an ignored item; all clients do is check it isn't null
-            return ApidocJaxrsSpecParser.class.getAnnotation(Api.class);
-        }
-        @Override
-        public Class<?> hostClass() {
-            // Overriding to make sure we have a look at the interfaces (Jersey jaxrs implementation doesn't bother)
-            // Note this means we require the @Path class annotation on the same class as all the method annotations
-            for (Class<?> tryMe : ImmutableList.<Class<?>>builder().add(super.hostClass()).add(super.hostClass().getInterfaces()).build()) {
-                if (tryMe.getAnnotation(Path.class) != null) {
-                    return tryMe;                
-                }
-            }
-            return super.hostClass();
-        }
-        
-        public String getPath(Method method) {
-            Path cwsPath = hostClass().getAnnotation(Path.class);
-            Path mwsPath = method.getAnnotation(Path.class);
-            if (cwsPath==null) return null;
-            return cwsPath.value() + (mwsPath!=null ? mwsPath.value() : "");
-        }
-    }
-    
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5c625626/utils/rest-swagger/src/main/java/org/apache/brooklyn/rest/apidoc/ApidocRoot.java
----------------------------------------------------------------------
diff --git a/utils/rest-swagger/src/main/java/org/apache/brooklyn/rest/apidoc/ApidocRoot.java b/utils/rest-swagger/src/main/java/org/apache/brooklyn/rest/apidoc/ApidocRoot.java
deleted file mode 100644
index e88f1ed..0000000
--- a/utils/rest-swagger/src/main/java/org/apache/brooklyn/rest/apidoc/ApidocRoot.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.rest.apidoc;
-
-import java.util.List;
-
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.annotation.JsonSetter;
-
-import io.swagger.core.Documentation;
-
-@JsonIgnoreProperties({
-    "com$wordnik$swagger$core$Documentation$$apis",
-    "com$wordnik$swagger$core$Documentation$$models"
-})
-public class ApidocRoot extends Documentation {
-
-    @SuppressWarnings({ "unchecked", "rawtypes" })
-    @JsonProperty("apis")
-    public List<ApidocEndpoint> getApidocApis() {
-        return (List) getApis();
-    }
-    
-    @SuppressWarnings({ "unchecked", "rawtypes" })
-    @JsonSetter("apis")
-    public void setApidocApis(List<ApidocEndpoint> ep) {
-        super.setApis((List)ep);
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5c625626/utils/rest-swagger/src/main/java/org/apache/brooklyn/rest/apidoc/RestApiResourceScanner.java
----------------------------------------------------------------------
diff --git a/utils/rest-swagger/src/main/java/org/apache/brooklyn/rest/apidoc/RestApiResourceScanner.java b/utils/rest-swagger/src/main/java/org/apache/brooklyn/rest/apidoc/RestApiResourceScanner.java
new file mode 100644
index 0000000..2f3c8c7
--- /dev/null
+++ b/utils/rest-swagger/src/main/java/org/apache/brooklyn/rest/apidoc/RestApiResourceScanner.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2015 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.brooklyn.rest.apidoc;
+
+import com.google.common.collect.Sets;
+import io.swagger.annotations.Api;
+import io.swagger.jaxrs.config.AbstractScanner;
+import io.swagger.jaxrs.config.JaxrsScanner;
+import java.util.HashSet;
+import java.util.Set;
+import javax.servlet.ServletConfig;
+import javax.ws.rs.core.Application;
+import org.apache.brooklyn.util.collections.MutableSet;
+
+/**
+ * Much like DefaultJaxrsScanner, but looks at annotations of ancestors as well.
+ *
+ * For instance, if a resource implementation exposes an annotated interface,
+ * that interface will be added as well.
+ *
+ * @author Ciprian Ciubotariu <ch...@gmx.net>
+ */
+public class RestApiResourceScanner extends AbstractScanner implements JaxrsScanner {
+
+    private Set<Class<?>> apiClasses = null;
+
+
+    private void addAnnotatedClasses(Set<Class<?>> output, Set<Class<?>> classes) {
+        for (Class<?> clz : classes) {
+            if (clz.getAnnotation(Api.class) != null) {
+                output.add(clz);
+            }
+            addAnnotatedClasses(output, Sets.newHashSet(clz.getInterfaces()));
+        }
+    }
+
+    private synchronized void buildApiClasses(Application app) {
+        if (apiClasses == null) {
+            apiClasses = new HashSet<>();
+            if (app != null) {
+                Set<Class<?>> classes = app.getClasses();
+                if (classes != null) {
+                    addAnnotatedClasses(apiClasses, classes);
+                }
+                Set<Object> singletons = app.getSingletons();
+                if (singletons != null) {
+                    for (Object o : singletons) {
+                        addAnnotatedClasses(apiClasses, (MutableSet<Class<?>>) MutableSet.of(o.getClass()));
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public Set<Class<?>> classesFromContext(Application app, ServletConfig sc) {
+        buildApiClasses(app);
+        return apiClasses;
+    }
+
+    @Override
+    public Set<Class<?>> classes() {
+        return new HashSet<>();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5c625626/utils/swagger-annotations/pom.xml
----------------------------------------------------------------------
diff --git a/utils/swagger-annotations/pom.xml b/utils/swagger-annotations/pom.xml
new file mode 100644
index 0000000..36e5959
--- /dev/null
+++ b/utils/swagger-annotations/pom.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>brooklyn-swagger-annotations</artifactId>
+    <name>Brooklyn Swagger Annotations</name>
+
+    <description>
+        Swagger-like annotations developed for Brooklyn but not dependendent on Brooklyn
+    </description>
+
+    <parent>
+        <groupId>org.apache.brooklyn</groupId>
+        <artifactId>brooklyn-parent</artifactId>
+        <version>0.9.0-SNAPSHOT</version>  <!-- BROOKLYN_VERSION -->
+        <relativePath>../../parent/pom.xml</relativePath>
+    </parent>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.wordnik</groupId>
+            <artifactId>swagger-core_2.9.1</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5c625626/utils/swagger-annotations/src/main/java/org/apache/brooklyn/swagger/annotations/Apidoc.java
----------------------------------------------------------------------
diff --git a/utils/swagger-annotations/src/main/java/org/apache/brooklyn/swagger/annotations/Apidoc.java b/utils/swagger-annotations/src/main/java/org/apache/brooklyn/swagger/annotations/Apidoc.java
new file mode 100644
index 0000000..a9263c2
--- /dev/null
+++ b/utils/swagger-annotations/src/main/java/org/apache/brooklyn/swagger/annotations/Apidoc.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.swagger.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/** like Swagger Api annotation (and treated similarly) but doesn't require path to be repeated, and supports a name */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Apidoc {
+    String value();
+    String description() default "";
+    // ? what is 'open' in @Api
+}