From 881c32ec109f0fff6f5661940ad035c3dfc0c7d9 Mon Sep 17 00:00:00 2001 From: Jorge Hernandez Date: Thu, 25 May 2017 16:44:15 -0500 Subject: [POLICY-16] generic introduction of swagger On a per-server basis append /swagger.json or /swagger.yaml ie: HTTP GET :6969/swagger.json HTTP GET :9696/swagger.json Resulting specification can be used by swagger clients. Change-Id: I4b1a8b53d50b1528664150934b04e92447e4d4d7 Signed-off-by: Jorge Hernandez --- policy-endpoints/pom.xml | 12 +- .../drools/http/server/HttpServletServer.java | 16 +- .../http/server/HttpServletServerFactory.java | 75 +++++++- .../http/server/internal/JettyJerseyServer.java | 195 ++++++++++++++++----- .../drools/http/server/test/HttpClientTest.java | 6 +- .../drools/http/server/test/HttpServerTest.java | 44 ++++- .../drools/http/server/test/RestEchoService.java | 7 + 7 files changed, 286 insertions(+), 69 deletions(-) (limited to 'policy-endpoints') diff --git a/policy-endpoints/pom.xml b/policy-endpoints/pom.xml index cd79f6a7..dba7349f 100644 --- a/policy-endpoints/pom.xml +++ b/policy-endpoints/pom.xml @@ -89,7 +89,6 @@ org.glassfish.jersey.containers jersey-container-servlet-core - ${jersey.version} @@ -127,6 +126,17 @@ jackson-datatype-jsr310 ${jackson.version} + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + + + + io.swagger + swagger-jersey2-jaxrs + org.apache.httpcomponents diff --git a/policy-endpoints/src/main/java/org/openecomp/policy/drools/http/server/HttpServletServer.java b/policy-endpoints/src/main/java/org/openecomp/policy/drools/http/server/HttpServletServer.java index 5f5dd787..106602bc 100644 --- a/policy-endpoints/src/main/java/org/openecomp/policy/drools/http/server/HttpServletServer.java +++ b/policy-endpoints/src/main/java/org/openecomp/policy/drools/http/server/HttpServletServer.java @@ -44,10 +44,10 @@ public interface HttpServletServer extends Startable { /** * adds a JAX-RS servlet class to serve REST requests * - * @param servletPath - * @param restClass - * @throws IllegalArgumentException - * @throws IllegalStateException + * @param servletPath servlet path + * @param restClass JAX-RS API Class + * @throws IllegalArgumentException unable to process because of invalid input + * @throws IllegalStateException unable to process because of invalid state */ public void addServletClass(String servletPath, String restClass) throws IllegalArgumentException, IllegalStateException; @@ -55,10 +55,10 @@ public interface HttpServletServer extends Startable { /** * adds a package containing JAX-RS classes to serve REST requests * - * @param servletPath - * @param restPackage - * @throws IllegalArgumentException - * @throws IllegalStateException + * @param servletPath servlet path + * @param restPackage JAX-RS package to scan + * @throws IllegalArgumentException unable to process because of invalid input + * @throws IllegalStateException unable to process because of invalid state */ public void addServletPackage(String servletPath, String restPackage) throws IllegalArgumentException, IllegalStateException; diff --git a/policy-endpoints/src/main/java/org/openecomp/policy/drools/http/server/HttpServletServerFactory.java b/policy-endpoints/src/main/java/org/openecomp/policy/drools/http/server/HttpServletServerFactory.java index bd5ae242..40f5c9ad 100644 --- a/policy-endpoints/src/main/java/org/openecomp/policy/drools/http/server/HttpServletServerFactory.java +++ b/policy-endpoints/src/main/java/org/openecomp/policy/drools/http/server/HttpServletServerFactory.java @@ -31,36 +31,87 @@ import org.openecomp.policy.drools.http.server.internal.JettyJerseyServer; import org.openecomp.policy.drools.properties.PolicyProperties; /** - * Jetty Server Factory + * Factory of HTTP Servlet-Enabled Servlets */ public interface HttpServletServerFactory { - public HttpServletServer build(String name, String host, int port, String contextPath, boolean managed) - throws IllegalArgumentException; + /** + * builds an http server with support for servlets + * + * @param name name + * @param host binding host + * @param port port + * @param contextPath server base path + * @param swagger enable swagger documentation + * @param managed is it managed by infrastructure + * @return http server + * @throws IllegalArgumentException when invalid parameters are provided + */ + public HttpServletServer build(String name, String host, int port, String contextPath, + boolean swagger, boolean managed) + throws IllegalArgumentException; + /** + * list of http servers per properties + * + * @param properties properties based configuration + * @return list of http servers + * @throws IllegalArgumentException when invalid parameters are provided + */ public ArrayList build(Properties properties) throws IllegalArgumentException; + /** + * gets a server based on the port + * + * @param port port + * @return http server + */ public HttpServletServer get(int port); + + /** + * provides an inventory of servers + * + * @return inventory of servers + */ public List inventory(); + + /** + * destroys server bound to a port + * @param port + */ public void destroy(int port); + + /** + * destroys the factory and therefore all servers + */ public void destroy(); } +/** + * Indexed factory implementation + */ class IndexedHttpServletServerFactory implements HttpServletServerFactory { - protected static Logger logger = FlexLogger.getLogger(IndexedHttpServletServerFactory.class); + /** + * logger + */ + protected static Logger logger = FlexLogger.getLogger(IndexedHttpServletServerFactory.class); - protected HashMap servers = new HashMap(); + /** + * servers index + */ + protected HashMap servers = new HashMap(); @Override public synchronized HttpServletServer build(String name, String host, int port, - String contextPath, boolean managed) + String contextPath, boolean swagger, + boolean managed) throws IllegalArgumentException { if (servers.containsKey(port)) return servers.get(port); - JettyJerseyServer server = new JettyJerseyServer(name, host, port, contextPath); + JettyJerseyServer server = new JettyJerseyServer(name, host, port, contextPath, swagger); if (managed) servers.put(port, server); @@ -140,7 +191,15 @@ class IndexedHttpServletServerFactory implements HttpServletServerFactory { managed = Boolean.parseBoolean(managedString); } - HttpServletServer service = build(serviceName, hostName, servicePort, contextUriPath, managed); + String swaggerString = properties.getProperty(PolicyProperties.PROPERTY_HTTP_SERVER_SERVICES + "." + + serviceName + + PolicyProperties.PROPERTY_HTTP_SWAGGER_SUFFIX); + boolean swagger = false; + if (swaggerString != null && !swaggerString.isEmpty()) { + swagger = Boolean.parseBoolean(swaggerString); + } + + HttpServletServer service = build(serviceName, hostName, servicePort, contextUriPath, swagger, managed); if (userName != null && !userName.isEmpty() && password != null && !password.isEmpty()) { service.setBasicAuthentication(userName, password, authUriPath); } diff --git a/policy-endpoints/src/main/java/org/openecomp/policy/drools/http/server/internal/JettyJerseyServer.java b/policy-endpoints/src/main/java/org/openecomp/policy/drools/http/server/internal/JettyJerseyServer.java index 4914a4cb..5bc320e5 100644 --- a/policy-endpoints/src/main/java/org/openecomp/policy/drools/http/server/internal/JettyJerseyServer.java +++ b/policy-endpoints/src/main/java/org/openecomp/policy/drools/http/server/internal/JettyJerseyServer.java @@ -19,52 +19,132 @@ */ package org.openecomp.policy.drools.http.server.internal; -import java.util.ArrayList; +import java.net.InetAddress; +import java.net.UnknownHostException; import java.util.HashMap; import org.eclipse.jetty.servlet.ServletHolder; - import org.openecomp.policy.common.logging.flexlogger.FlexLogger; import org.openecomp.policy.common.logging.flexlogger.Logger; +import io.swagger.jersey.config.JerseyJaxrsConfig; + /** - * REST Jetty Server using Jersey + * REST Jetty Server that uses Jersey Servlets to support JAX-RS Web Services */ public class JettyJerseyServer extends JettyServletServer { - protected static final String JERSEY_PACKAGES_PARAM = "jersey.config.server.provider.packages"; - protected static final String JERSEY_CLASSNAMES_PARAM = "jersey.config.server.provider.classnames"; + /** + * Jersey Packages Init Param Name + */ + protected static final String JERSEY_INIT_PACKAGES_PARAM_NAME = "jersey.config.server.provider.packages"; + + /** + * Jersey Packages Init Param Value + */ + protected static final String JERSEY_INIT_PACKAGES_PARAM_VALUE = "com.fasterxml.jackson.jaxrs.json"; + + /** + * Swagger Packages + */ + protected static final String SWAGGER_INIT_PACKAGES_PARAM_VALUE = "io.swagger.jaxrs.listing"; + /** + * Jersey Classes Init Param Name + */ + protected static final String JERSEY_INIT_CLASSNAMES_PARAM_NAME = "jersey.config.server.provider.classnames"; + + /** + * Jersey Jackson Classes Init Param Value + */ + protected static final String JERSEY_JACKSON_INIT_CLASSNAMES_PARAM_VALUE = "com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider"; + + /** + * Jersey Swagger Classes Init Param Value + */ + protected static final String SWAGGER_INIT_CLASSNAMES_PARAM_VALUE = "io.swagger.jaxrs.listing.ApiListingResource," + + "io.swagger.jaxrs.listing.SwaggerSerializers"; + /** + * Logger + */ protected static Logger logger = FlexLogger.getLogger(JettyJerseyServer.class); - protected ArrayList packages = new ArrayList(); - protected HashMap servlets = - new HashMap(); + /** + * Container for servlets + */ + protected HashMap servlets = new HashMap(); + + /** + * Swagger ID + */ + protected String swaggerId = null; - public JettyJerseyServer(String name, String host, int port, String contextPath) - throws IllegalArgumentException { + /** + * Constructor + * + * @param name name + * @param host host server host + * @param port port server port + * @param swagger support swagger? + * @param contextPath context path + * + * @throws IllegalArgumentException in invalid arguments are provided + */ + public JettyJerseyServer(String name, String host, int port, String contextPath, boolean swagger) + throws IllegalArgumentException { + super(name, host, port, contextPath); + if (swagger) { + this.swaggerId = "swagger-" + this.port; + attachSwaggerServlet(); + } } - protected synchronized ServletHolder getServlet(String servletPath) - throws IllegalArgumentException { + /** + * attaches a swagger initialization servlet + */ + protected void attachSwaggerServlet() { + + ServletHolder swaggerServlet = context.addServlet(JerseyJaxrsConfig.class, "/"); + + String hostname = this.connector.getHost(); + if (hostname == null || hostname.isEmpty() || hostname.equals("0.0.0.0")) { + try { + hostname = InetAddress.getLocalHost().getHostName(); + } catch (UnknownHostException e) { + logger.warn("can't resolve connector's hostname: " + this); + hostname = "localhost"; + } + } + + swaggerServlet.setInitParameter("swagger.api.basepath", + "http://" + hostname + ":" + this.connector.getPort() + "/"); + swaggerServlet.setInitParameter("swagger.context.id", swaggerId); + swaggerServlet.setInitParameter("swagger.scanner.id", swaggerId); + swaggerServlet.setInitParameter("swagger.pretty.print", "true"); + swaggerServlet.setInitOrder(2); - if (servletPath == null || servletPath.isEmpty()) - servletPath = "/*"; + if (logger.isDebugEnabled()) + logger.debug(this + "Swagger Servlet has been attached: " + swaggerServlet.dump()); + } + + /** + * retrieves cached server based on servlet path + * + * @param servletPath servlet path + * @return the jetty servlet holder + * + * @throws IllegalArgumentException if invalid arguments are provided + */ + protected synchronized ServletHolder getServlet(String servletPath) + throws IllegalArgumentException { ServletHolder jerseyServlet = servlets.get(servletPath); if (jerseyServlet == null) { jerseyServlet = context.addServlet (org.glassfish.jersey.servlet.ServletContainer.class, servletPath); jerseyServlet.setInitOrder(0); - String initPackages = - jerseyServlet.getInitParameter(JERSEY_PACKAGES_PARAM); - if (initPackages == null) { - jerseyServlet.setInitParameter( - JERSEY_PACKAGES_PARAM, - "com.jersey.jaxb,com.fasterxml.jackson.jaxrs.json"); - } - this.servlets.put(servletPath, jerseyServlet); + servlets.put(servletPath, jerseyServlet); } return jerseyServlet; @@ -76,19 +156,36 @@ public class JettyJerseyServer extends JettyServletServer { if (restPackage == null || restPackage.isEmpty()) throw new IllegalArgumentException("No discoverable REST package provided"); + + if (servletPath == null || servletPath.isEmpty()) + servletPath = "/*"; ServletHolder jerseyServlet = this.getServlet(servletPath); - if (jerseyServlet == null) - throw new IllegalStateException("Unexpected, no Jersey Servlet class"); + + String initClasses = + jerseyServlet.getInitParameter(JERSEY_INIT_CLASSNAMES_PARAM_NAME); + if (initClasses != null && !initClasses.isEmpty()) + logger.warn("Both packages and classes are used in Jetty+Jersey Configuration: " + restPackage); String initPackages = - jerseyServlet.getInitParameter(JERSEY_PACKAGES_PARAM); - if (initPackages == null) - throw new IllegalStateException("Unexpected, no Init Parameters loaded"); + jerseyServlet.getInitParameter(JERSEY_INIT_PACKAGES_PARAM_NAME); + if (initPackages == null) { + if (this.swaggerId != null) { + initPackages = JERSEY_INIT_PACKAGES_PARAM_VALUE + "," + + SWAGGER_INIT_PACKAGES_PARAM_VALUE + "," + + restPackage; + + jerseyServlet.setInitParameter("swagger.context.id", swaggerId); + jerseyServlet.setInitParameter("swagger.scanner.id", swaggerId); + } else { + initPackages = JERSEY_INIT_PACKAGES_PARAM_VALUE + "," + + restPackage; + } + } else { + initPackages = initPackages + "," + restPackage; + } - jerseyServlet.setInitParameter( - JERSEY_PACKAGES_PARAM, - initPackages + "," + restPackage); + jerseyServlet.setInitParameter(JERSEY_INIT_PACKAGES_PARAM_NAME, initPackages); if (logger.isDebugEnabled()) logger.debug(this + "Added REST Package: " + jerseyServlet.dump()); @@ -100,31 +197,45 @@ public class JettyJerseyServer extends JettyServletServer { if (restClass == null || restClass.isEmpty()) throw new IllegalArgumentException("No discoverable REST class provided"); + + if (servletPath == null || servletPath.isEmpty()) + servletPath = "/*"; ServletHolder jerseyServlet = this.getServlet(servletPath); - if (jerseyServlet == null) - throw new IllegalStateException("Unexpected, no Jersey Servlet class"); + + String initPackages = + jerseyServlet.getInitParameter(JERSEY_INIT_PACKAGES_PARAM_NAME); + if (initPackages != null && !initPackages.isEmpty()) + logger.warn("Both classes and packages are used in Jetty+Jersey Configuration: " + restClass); String initClasses = - jerseyServlet.getInitParameter(JERSEY_CLASSNAMES_PARAM); - if (initClasses == null) - initClasses = restClass; - else + jerseyServlet.getInitParameter(JERSEY_INIT_CLASSNAMES_PARAM_NAME); + if (initClasses == null) { + if (this.swaggerId != null) { + initClasses = JERSEY_JACKSON_INIT_CLASSNAMES_PARAM_VALUE + "," + + SWAGGER_INIT_CLASSNAMES_PARAM_VALUE + "," + + restClass; + + jerseyServlet.setInitParameter("swagger.context.id", swaggerId); + jerseyServlet.setInitParameter("swagger.scanner.id", swaggerId); + } else { + initClasses = JERSEY_JACKSON_INIT_CLASSNAMES_PARAM_VALUE + "," + restClass; + } + } else { initClasses = initClasses + "," + restClass; + } - jerseyServlet.setInitParameter( - JERSEY_CLASSNAMES_PARAM, - initClasses); + jerseyServlet.setInitParameter(JERSEY_INIT_CLASSNAMES_PARAM_NAME, initClasses); if (logger.isDebugEnabled()) logger.debug(this + "Added REST Class: " + jerseyServlet.dump()); } - + @Override public String toString() { StringBuilder builder = new StringBuilder(); - builder.append("JerseyJettyServer [packages=").append(packages).append(", servlets=").append(servlets) - .append(", toString()=").append(super.toString()).append("]"); + builder.append("JettyJerseyServer [servlets=").append(servlets).append(", swaggerId=").append(swaggerId) + .append(", toString()=").append(super.toString()).append("]"); return builder.toString(); } } diff --git a/policy-endpoints/src/test/java/org/openecomp/policy/drools/http/server/test/HttpClientTest.java b/policy-endpoints/src/test/java/org/openecomp/policy/drools/http/server/test/HttpClientTest.java index 47774b1f..cd149f7a 100644 --- a/policy-endpoints/src/test/java/org/openecomp/policy/drools/http/server/test/HttpClientTest.java +++ b/policy-endpoints/src/test/java/org/openecomp/policy/drools/http/server/test/HttpClientTest.java @@ -38,7 +38,7 @@ public class HttpClientTest { public void testHttpNoAuthClient() throws Exception { System.out.println("-- testHttpNoAuthClient() --"); - HttpServletServer server = HttpServletServer.factory.build("echo", "localhost", 6666, "/", true); + HttpServletServer server = HttpServletServer.factory.build("echo", "localhost", 6666, "/", false, true); server.addServletPackage("/*", this.getClass().getPackage().getName()); server.waitedStart(5000); @@ -59,7 +59,7 @@ public class HttpClientTest { public void testHttpAuthClient() throws Exception { System.out.println("-- testHttpAuthClient() --"); - HttpServletServer server = HttpServletServer.factory.build("echo", "localhost", 6666, "/", true); + HttpServletServer server = HttpServletServer.factory.build("echo", "localhost", 6666, "/", false, true); server.setBasicAuthentication("x", "y", null); server.addServletPackage("/*", this.getClass().getPackage().getName()); server.waitedStart(5000); @@ -81,7 +81,7 @@ public class HttpClientTest { public void testHttpAuthClient401() throws Exception { System.out.println("-- testHttpAuthClient401() --"); - HttpServletServer server = HttpServletServer.factory.build("echo", "localhost", 6666, "/", true); + HttpServletServer server = HttpServletServer.factory.build("echo", "localhost", 6666, "/", false, true); server.setBasicAuthentication("x", "y", null); server.addServletPackage("/*", this.getClass().getPackage().getName()); server.waitedStart(5000); diff --git a/policy-endpoints/src/test/java/org/openecomp/policy/drools/http/server/test/HttpServerTest.java b/policy-endpoints/src/test/java/org/openecomp/policy/drools/http/server/test/HttpServerTest.java index 94f29804..6a369814 100644 --- a/policy-endpoints/src/test/java/org/openecomp/policy/drools/http/server/test/HttpServerTest.java +++ b/policy-endpoints/src/test/java/org/openecomp/policy/drools/http/server/test/HttpServerTest.java @@ -31,7 +31,7 @@ import org.junit.Test; import org.openecomp.policy.drools.http.server.HttpServletServer; /** - * + * HttpServletServer JUNIT tests */ public class HttpServerTest { @@ -39,7 +39,7 @@ public class HttpServerTest { public void testSingleServer() throws Exception { System.out.println("-- testSingleServer() --"); - HttpServletServer server = HttpServletServer.factory.build("echo", "localhost", 5678, "/", true); + HttpServletServer server = HttpServletServer.factory.build("echo", "localhost", 5678, "/", false, true); server.addServletPackage("/*", this.getClass().getPackage().getName()); server.waitedStart(5000); @@ -50,6 +50,16 @@ public class HttpServerTest { String response = response(url); System.out.println("Received .. " + response); assertTrue(response.equals(echo)); + + String responseSwagger = null; + try { + URL urlSwagger = new URL("http://localhost:5678/swagger.json" + echo); + responseSwagger = response(urlSwagger); + } catch(IOException ioe) { + // Expected + } + + assertTrue(responseSwagger == null); HttpServletServer.factory.destroy(); assertTrue(HttpServletServer.factory.inventory().size() == 0); @@ -59,11 +69,11 @@ public class HttpServerTest { public void testMultipleServers() throws Exception { System.out.println("-- testMultipleServers() --"); - HttpServletServer server1 = HttpServletServer.factory.build("echo-1", "localhost", 5678, "/", true); + HttpServletServer server1 = HttpServletServer.factory.build("echo-1", "localhost", 5678, "/", true, true); server1.addServletPackage("/*", this.getClass().getPackage().getName()); server1.waitedStart(5000); - HttpServletServer server2 = HttpServletServer.factory.build("echo-2", "localhost", 5679, "/", true); + HttpServletServer server2 = HttpServletServer.factory.build("echo-2", "localhost", 5679, "/", false, true); server2.addServletPackage("/*", this.getClass().getPackage().getName()); server2.waitedStart(5000); @@ -77,11 +87,28 @@ public class HttpServerTest { System.out.println("Received .. " + response1); assertTrue(response1.equals(echo)); + URL urlSwagger = new URL("http://localhost:5678/swagger.json"); + String responseSwagger = response(urlSwagger); + + System.out.println("Received .. " + responseSwagger); + assertTrue(responseSwagger != null); + URL url2 = new URL("http://localhost:5679/junit/echo/" + echo); String response2 = response(url2); System.out.println("Received .. " + response2); assertTrue(response2.equals(echo)); + String responseSwagger2 = null; + try { + URL urlSwagger2 = new URL("http://localhost:5679/swagger.json"); + responseSwagger2 = response(urlSwagger2); + } catch(IOException ioe) { + // Expected + } + + System.out.println("Received .. " + responseSwagger2); + assertTrue(responseSwagger2 == null); + HttpServletServer.factory.destroy(); assertTrue(HttpServletServer.factory.inventory().size() == 0); } @@ -92,7 +119,7 @@ public class HttpServerTest { String randomName = UUID.randomUUID().toString(); - HttpServletServer server = HttpServletServer.factory.build(randomName, "localhost", 5678, "/", true); + HttpServletServer server = HttpServletServer.factory.build(randomName, "localhost", 5678, "/", false, true); server.addServletPackage("/*", this.getClass().getPackage().getName()); server.waitedStart(5000); @@ -118,7 +145,7 @@ public class HttpServerTest { System.out.println("-- testServiceClass() --"); String randomName = UUID.randomUUID().toString(); - HttpServletServer server = HttpServletServer.factory.build(randomName, "localhost", 5678, "/", true); + HttpServletServer server = HttpServletServer.factory.build(randomName, "localhost", 5678, "/", false, true); server.addServletClass("/*", RestEchoService.class.getCanonicalName()); server.waitedStart(5000); @@ -140,7 +167,7 @@ public class HttpServerTest { String randomName = UUID.randomUUID().toString(); - HttpServletServer server = HttpServletServer.factory.build(randomName, "localhost", 5678, "/", true); + HttpServletServer server = HttpServletServer.factory.build(randomName, "localhost", 5678, "/", false, true); server.addServletClass("/*", RestEchoService.class.getCanonicalName()); server.addServletClass("/*", RestEndpoints.class.getCanonicalName()); server.waitedStart(5000); @@ -173,6 +200,9 @@ public class HttpServerTest { while ((line = ioReader.readLine()) != null) { response += line; } + + System.out.println("R is " + response); + ioReader.close(); return response; } diff --git a/policy-endpoints/src/test/java/org/openecomp/policy/drools/http/server/test/RestEchoService.java b/policy-endpoints/src/test/java/org/openecomp/policy/drools/http/server/test/RestEchoService.java index a0320a09..b714925f 100644 --- a/policy-endpoints/src/test/java/org/openecomp/policy/drools/http/server/test/RestEchoService.java +++ b/policy-endpoints/src/test/java/org/openecomp/policy/drools/http/server/test/RestEchoService.java @@ -6,12 +6,19 @@ import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; + +@Api(value="echo") @Path("/junit/echo") public class RestEchoService { @GET @Path("{word}") @Produces(MediaType.TEXT_PLAIN) + @ApiOperation( + value="echoes back whatever received" + ) public String echo(@PathParam("word") String word) { return word; } -- cgit 1.2.3-korg