# HG changeset patch # User Simon Tooke # Date 1508870778 14400 # Node ID 1738c834713c65d8ef4177c60edcc947118875de # Parent dd3f50e39a564dc9e9e96a6ad855184e0eb99927 Add schema service to serve combined API swagger file This patch serves an uber-api Swagger file from URL https://127.0.0.1:30000/schema/fullapi-swagger.yaml (or ... .json) Rviewed-by: sgehwolf Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2017-October/025369.html diff -r dd3f50e39a56 -r 1738c834713c common/core/src/main/java/com/redhat/thermostat/gateway/common/core/servlet/ServiceVersionFilter.java --- a/common/core/src/main/java/com/redhat/thermostat/gateway/common/core/servlet/ServiceVersionFilter.java Tue Oct 24 12:58:31 2017 -0400 +++ b/common/core/src/main/java/com/redhat/thermostat/gateway/common/core/servlet/ServiceVersionFilter.java Tue Oct 24 14:46:18 2017 -0400 @@ -88,12 +88,14 @@ private int[] implVersion; private String outVersion; + private String version; private static final Pattern versionRegex = Pattern.compile("^(/[^/]+)/(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|\\d{1,3}\\.\\d{1,3})(/.*)*$"); @Override public void init(FilterConfig config) throws ServletException { - outVersion = '/' + config.getInitParameter("version"); - implVersion = extractVersion(config.getInitParameter("version")); + version = config.getInitParameter("version"); + outVersion = '/' + version; + implVersion = extractVersion(version); } // separated out for testing purposes @@ -106,6 +108,7 @@ final HttpServletRequest request = (HttpServletRequest) req; final String requestURI = request.getRequestURI(); final Matcher matcher = getMatcher(requestURI); + final String subPath = request.getRequestURI().substring(request.getContextPath().length()); if (matcher.find()) { @@ -117,7 +120,7 @@ try { inVersion = extractVersion(requestedVersionStr); } catch (NumberFormatException ignored) { - HttpServletResponse response = (HttpServletResponse)(res); + HttpServletResponse response = (HttpServletResponse) (res); response.sendError(404, "API version " + requestedVersionStr + " is invalid"); return; } @@ -127,7 +130,7 @@ // version is an integer, optionally followed with sub version and sub-subversion ('4.8', '5.3', or '0.0.2') if (!matchingVersion(inVersion, implVersion)) { - HttpServletResponse response = (HttpServletResponse)(res); + HttpServletResponse response = (HttpServletResponse) (res); response.sendError(404, "API version " + requestedVersionStr + " is not implemented"); return; } @@ -141,6 +144,9 @@ } else { req.getRequestDispatcher(filteredURI).forward(req, res); } + } else if (subPath.startsWith("/version")) { + HttpServletResponse response = (HttpServletResponse)(res); + response.getWriter().write("{ \"version\": \"" + version + "\" }"); } else { // if the incoming URL doesn't match the regext patterm, it has no version number. HttpServletResponse response = (HttpServletResponse)(res); diff -r dd3f50e39a56 -r 1738c834713c common/core/src/test/java/com/redhat/thermostat/gateway/common/core/servlet/ServiceVersionFilterTest.java --- a/common/core/src/test/java/com/redhat/thermostat/gateway/common/core/servlet/ServiceVersionFilterTest.java Tue Oct 24 12:58:31 2017 -0400 +++ b/common/core/src/test/java/com/redhat/thermostat/gateway/common/core/servlet/ServiceVersionFilterTest.java Tue Oct 24 14:46:18 2017 -0400 @@ -243,6 +243,7 @@ final ServiceVersionFilter filter = createFilter(implVersion); final HttpServletRequest req = mock(HttpServletRequest.class); when(req.getRequestURI()).thenReturn(uri + path); + when(req.getContextPath()).thenReturn(""); final RequestDispatcher rd = mock(RequestDispatcher.class); when(req.getRequestDispatcher(anyString())).thenReturn(rd); final HttpServletResponse resp = mock(HttpServletResponse.class); @@ -263,6 +264,7 @@ final ServiceVersionFilter filter = createFilter(implVersion); HttpServletRequest req = mock(HttpServletRequest.class); when(req.getRequestURI()).thenReturn(uri); + when(req.getContextPath()).thenReturn(""); HttpServletResponse resp = mock(HttpServletResponse.class); FilterChain chain = mock(FilterChain.class); filter.doFilter(req, resp, chain); diff -r dd3f50e39a56 -r 1738c834713c distribution/pom.xml --- a/distribution/pom.xml Tue Oct 24 12:58:31 2017 -0400 +++ b/distribution/pom.xml Tue Oct 24 14:46:18 2017 -0400 @@ -312,6 +312,13 @@ war false + + com.redhat.thermostat + thermostat-web-gateway-service-schema + ${project.version} + war + false + ${project.build.directory}/image/services @@ -376,6 +383,13 @@ com.redhat.thermostat + thermostat-web-gateway-service-schema + ${project.version} + war + provided + + + com.redhat.thermostat thermostat-web-gateway-service-jcmd ${project.version} war diff -r dd3f50e39a56 -r 1738c834713c distribution/src/etc/services.properties --- a/distribution/src/etc/services.properties Tue Oct 24 12:58:31 2017 -0400 +++ b/distribution/src/etc/services.properties Tue Oct 24 14:46:18 2017 -0400 @@ -8,6 +8,7 @@ /jvm-io = thermostat-web-gateway-service-jvm-io-@project.version@.war /jvm-memory = thermostat-web-gateway-service-jvm-memory-@project.version@.war /jvms = thermostat-web-gateway-service-jvms-@project.version@.war +/schema = thermostat-web-gateway-service-schema-@project.version@.war /systems = thermostat-web-gateway-service-systems-@project.version@.war /system-cpu = thermostat-web-gateway-service-system-cpu-@project.version@.war /system-memory = thermostat-web-gateway-service-system-memory-@project.version@.war diff -r dd3f50e39a56 -r 1738c834713c server/pom.xml --- a/server/pom.xml Tue Oct 24 12:58:31 2017 -0400 +++ b/server/pom.xml Tue Oct 24 14:46:18 2017 -0400 @@ -66,6 +66,16 @@ org.eclipse.jetty + jetty-security + ${jetty.version} + + + org.eclipse.jetty + jetty-util + ${jetty.version} + + + org.eclipse.jetty jetty-servlets ${jetty.version} diff -r dd3f50e39a56 -r 1738c834713c services/commands/src/main/webapp/WEB-INF/web.xml --- a/services/commands/src/main/webapp/WEB-INF/web.xml Tue Oct 24 12:58:31 2017 -0400 +++ b/services/commands/src/main/webapp/WEB-INF/web.xml Tue Oct 24 14:46:18 2017 -0400 @@ -69,6 +69,15 @@ com.redhat.thermostat.gateway.SERVICE_VERSION v1 + + + + + Version + /version + + + diff -r dd3f50e39a56 -r 1738c834713c services/jcmd/src/main/webapp/WEB-INF/web.xml --- a/services/jcmd/src/main/webapp/WEB-INF/web.xml Tue Oct 24 12:58:31 2017 -0400 +++ b/services/jcmd/src/main/webapp/WEB-INF/web.xml Tue Oct 24 14:46:18 2017 -0400 @@ -77,6 +77,15 @@ com.redhat.thermostat.gateway.common.mongodb.servlet.StorageConnectionSettingListener + + + + + Version + /version + + + diff -r dd3f50e39a56 -r 1738c834713c services/jvm-byteman/src/main/resources/jvm-byteman-swagger.yaml --- a/services/jvm-byteman/src/main/resources/jvm-byteman-swagger.yaml Tue Oct 24 12:58:31 2017 -0400 +++ b/services/jvm-byteman/src/main/resources/jvm-byteman-swagger.yaml Tue Oct 24 14:46:18 2017 -0400 @@ -138,8 +138,8 @@ $ref: '#/definitions/map-value' map-value: enum: [ - $ref: '#/definitions/map-value-string', - $ref: '#/definitions/map-value-boolean', + $ref: '#/definitions/map-value-string' + $ref: '#/definitions/map-value-boolean' $ref: '#/definitions/map-value-float' ] map-value-string: diff -r dd3f50e39a56 -r 1738c834713c services/jvm-byteman/src/main/webapp/WEB-INF/web.xml --- a/services/jvm-byteman/src/main/webapp/WEB-INF/web.xml Tue Oct 24 12:58:31 2017 -0400 +++ b/services/jvm-byteman/src/main/webapp/WEB-INF/web.xml Tue Oct 24 14:46:18 2017 -0400 @@ -77,6 +77,15 @@ com.redhat.thermostat.gateway.common.mongodb.servlet.StorageConnectionSettingListener + + + + + Version + /version + + + diff -r dd3f50e39a56 -r 1738c834713c services/jvm-compiler/src/main/webapp/WEB-INF/web.xml --- a/services/jvm-compiler/src/main/webapp/WEB-INF/web.xml Tue Oct 24 12:58:31 2017 -0400 +++ b/services/jvm-compiler/src/main/webapp/WEB-INF/web.xml Tue Oct 24 14:46:18 2017 -0400 @@ -77,6 +77,15 @@ com.redhat.thermostat.gateway.common.mongodb.servlet.StorageConnectionSettingListener + + + + + Version + /version + + + diff -r dd3f50e39a56 -r 1738c834713c services/jvm-cpu/src/main/webapp/WEB-INF/web.xml --- a/services/jvm-cpu/src/main/webapp/WEB-INF/web.xml Tue Oct 24 12:58:31 2017 -0400 +++ b/services/jvm-cpu/src/main/webapp/WEB-INF/web.xml Tue Oct 24 14:46:18 2017 -0400 @@ -77,6 +77,15 @@ com.redhat.thermostat.gateway.common.mongodb.servlet.StorageConnectionSettingListener + + + + + Version + /version + + + diff -r dd3f50e39a56 -r 1738c834713c services/jvm-gc/src/main/webapp/WEB-INF/web.xml --- a/services/jvm-gc/src/main/webapp/WEB-INF/web.xml Tue Oct 24 12:58:31 2017 -0400 +++ b/services/jvm-gc/src/main/webapp/WEB-INF/web.xml Tue Oct 24 14:46:18 2017 -0400 @@ -73,16 +73,26 @@ com.redhat.thermostat.gateway.SERVICE_NAME @com.redhat.thermostat.gateway.SERVICE_NAME@ + com.redhat.thermostat.gateway.common.mongodb.servlet.StorageConnectionSettingListener - + + - - Swagger API Spec File - /0.0.3/doc/@com.redhat.thermostat.gateway.SERVICE_NAME@-swagger.yaml - + + Version + /version + + + + + + + Swagger API Spec File + /0.0.3/doc/@com.redhat.thermostat.gateway.SERVICE_NAME@-swagger.yaml + diff -r dd3f50e39a56 -r 1738c834713c services/jvm-io/src/main/webapp/WEB-INF/web.xml --- a/services/jvm-io/src/main/webapp/WEB-INF/web.xml Tue Oct 24 12:58:31 2017 -0400 +++ b/services/jvm-io/src/main/webapp/WEB-INF/web.xml Tue Oct 24 14:46:18 2017 -0400 @@ -77,6 +77,15 @@ com.redhat.thermostat.gateway.common.mongodb.servlet.StorageConnectionSettingListener + + + + + Version + /version + + + diff -r dd3f50e39a56 -r 1738c834713c services/jvm-memory/src/main/webapp/WEB-INF/web.xml --- a/services/jvm-memory/src/main/webapp/WEB-INF/web.xml Tue Oct 24 12:58:31 2017 -0400 +++ b/services/jvm-memory/src/main/webapp/WEB-INF/web.xml Tue Oct 24 14:46:18 2017 -0400 @@ -77,6 +77,15 @@ com.redhat.thermostat.gateway.common.mongodb.servlet.StorageConnectionSettingListener + + + + + Version + /version + + + diff -r dd3f50e39a56 -r 1738c834713c services/jvms/src/main/webapp/WEB-INF/web.xml --- a/services/jvms/src/main/webapp/WEB-INF/web.xml Tue Oct 24 12:58:31 2017 -0400 +++ b/services/jvms/src/main/webapp/WEB-INF/web.xml Tue Oct 24 14:46:18 2017 -0400 @@ -77,6 +77,15 @@ com.redhat.thermostat.gateway.common.mongodb.servlet.StorageConnectionSettingListener + + + + + Version + /version + + + diff -r dd3f50e39a56 -r 1738c834713c services/pom.xml --- a/services/pom.xml Tue Oct 24 12:58:31 2017 -0400 +++ b/services/pom.xml Tue Oct 24 14:46:18 2017 -0400 @@ -52,6 +52,7 @@ commands + schema jvms jcmd jvm-byteman diff -r dd3f50e39a56 -r 1738c834713c services/schema/pom.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/services/schema/pom.xml Tue Oct 24 14:46:18 2017 -0400 @@ -0,0 +1,117 @@ + + + + 4.0.0 + + + com.redhat.thermostat + thermostat-web-gateway-services + 1.99.12-SNAPSHOT + + + thermostat-web-gateway-service-schema + war + + Thermostat Web Gateway Schema service + + + + + + org.apache.maven.plugins + maven-war-plugin + 3.2.0 + + + + src/main/webapp + true + + + + + + + + + + com.redhat.thermostat + thermostat-common + ${thermostat.common.version} + + + + com.redhat.thermostat + thermostat-web-gateway-common-core + ${project.version} + provided + + + + + javax.servlet + javax.servlet-api + ${javax.servlet.version} + provided + + + + + javax.ws.rs + javax.ws.rs-api + ${javax-rs-api.version} + provided + + + + org.eclipse.jetty + jetty-client + ${jetty.version} + + + + org.eclipse.jetty + jetty-util + ${jetty.version} + + + + + + diff -r dd3f50e39a56 -r 1738c834713c services/schema/src/main/java/com/redhat/thermostat/gateway/service/schema/SchemaHttpHandler.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/services/schema/src/main/java/com/redhat/thermostat/gateway/service/schema/SchemaHttpHandler.java Tue Oct 24 14:46:18 2017 -0400 @@ -0,0 +1,144 @@ +/* + * Copyright 2012-2017 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * . + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.gateway.service.schema; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; + +import com.redhat.thermostat.common.yaml.JsonToYaml; +import com.redhat.thermostat.gateway.common.util.LoggingUtil; + +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.DefaultValue; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import java.io.IOException; +import java.io.StringWriter; + +import java.util.logging.Logger; + +@Path("/") +public class SchemaHttpHandler { + + private static final Logger logger = LoggingUtil.getLogger(SchemaHttpHandler.class); + + private static final Object combineLock = new Object(); + private static JsonObject combinedSwagger = null; + private static boolean haveTriedToCombine = false; + private Gson gson = null; + private Gson prettyGson = null; + + @GET + @Path("/fullapi-swagger.yaml") + @Produces({ MediaType.TEXT_PLAIN }) + public Response getCombinedAPI(@Context ServletContext context) throws Exception { + return getCombinedAPI(context, true, false); + } + + @GET + @Path("/fullapi-swagger.json") + @Produces({ MediaType.APPLICATION_JSON }) + public Response getCombinedAPI(@QueryParam("pretty") @DefaultValue("false") Boolean prettyPrint, + @Context ServletContext context) throws Exception { + + return getCombinedAPI(context, false, prettyPrint); + } + + @GET + @Path("/version") + @Produces({ MediaType.APPLICATION_JSON }) + public Response getVersion() throws Exception { + + final String versionStr = "{ \"version\" : \"0.0.0\" }"; + return Response.status(Response.Status.OK).type(MediaType.APPLICATION_JSON_TYPE).entity(versionStr).build(); + } + + private Response getCombinedAPI(ServletContext context, boolean toYaml, boolean prettyPrint) throws IOException { + if (!haveTriedToCombine) { + synchronized (combineLock) { + if (!haveTriedToCombine) { + try { + // this is a long running operation + combinedSwagger = new SwaggerBuilder().createCombinedSwagger(context); + } catch (Exception e) { + logger.warning("error creating combined Swagger API: " + e.getLocalizedMessage()); + } + haveTriedToCombine = true; + } + } + } + + if (combinedSwagger != null) { + final StringWriter out = new StringWriter(); + final MediaType mediaType; + if (toYaml) { + new JsonToYaml().jsonToYaml(combinedSwagger, out); + mediaType = MediaType.TEXT_PLAIN_TYPE; + } else { + getGsonInstance(prettyPrint).toJson(combinedSwagger, out); + mediaType = MediaType.APPLICATION_JSON_TYPE; + } + return Response.status(Response.Status.OK).type(mediaType).entity(out.toString()).build(); + } else { + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } + } + + private Gson getGsonInstance(boolean isPretty) { + if (isPretty) { + if (prettyGson == null) { + final GsonBuilder gsonBuilder = new GsonBuilder(); + gsonBuilder.setPrettyPrinting(); + prettyGson = gsonBuilder.create(); + } + return prettyGson; + } else { + if (gson == null) { + gson = new Gson(); + } + return gson; + } + } +} diff -r dd3f50e39a56 -r 1738c834713c services/schema/src/main/java/com/redhat/thermostat/gateway/service/schema/SwaggerBuilder.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/services/schema/src/main/java/com/redhat/thermostat/gateway/service/schema/SwaggerBuilder.java Tue Oct 24 14:46:18 2017 -0400 @@ -0,0 +1,185 @@ +/* + * Copyright 2012-2017 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * . + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.gateway.service.schema; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import com.redhat.thermostat.common.swaggercombine.SwaggerCombine; +import com.redhat.thermostat.common.swaggercombine.SwaggerCombineContext; + +import com.redhat.thermostat.gateway.common.core.config.Configuration; +import com.redhat.thermostat.gateway.common.core.config.ConfigurationFactory; +import com.redhat.thermostat.gateway.common.core.config.GlobalConfiguration; +import com.redhat.thermostat.gateway.common.core.servlet.GlobalConstants; +import com.redhat.thermostat.gateway.common.util.LoggingUtil; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.util.ssl.SslContextFactory; + +import javax.servlet.ServletContext; + +import javax.ws.rs.HttpMethod; + +import java.io.ByteArrayOutputStream; +import java.io.DataInput; +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import java.util.logging.Logger; + +class SwaggerBuilder { + + private static final int HTTP_OKAY = 200; + private static final int HTTP_UNAUTHORIZED = 401; + private static final int HTTP_NOT_FOUND = 404; + + private static final Logger logger = LoggingUtil.getLogger(SwaggerBuilder.class); + + private final String baseUrl; + private final boolean tlsIsEnabled; + + SwaggerBuilder() { + final ConfigurationFactory configurationFactory = new ConfigurationFactory(); + final Map cfgmap = configurationFactory.createGlobalConfiguration().asMap(); + tlsIsEnabled = Boolean.parseBoolean((String)cfgmap.get(GlobalConfiguration.ConfigurationKey.WITH_TLS.name())); + final String scheme = tlsIsEnabled ? "https" : "http"; + final String port = (String)cfgmap.get(GlobalConfiguration.ConfigurationKey.PORT.name()); + String ip = (String)cfgmap.get(GlobalConfiguration.ConfigurationKey.IP.name()); + baseUrl = scheme + "://" + ip + ':' + port; + } + + JsonObject createCombinedSwagger(ServletContext context) throws Exception { + + final SwaggerCombineContext swaggerContext = new SwaggerCombineContext(); + + // add a template to define all common definitions + final String template = readFully(context.getResourceAsStream("/WEB-INF/swagger-template.json"), "UTF-8"); + swaggerContext.addMicroAPI(template, false); + + // create an HTTP client to call other services + final HttpClient client = createAndStartHttpClient(); + + // grab the list of services from the context + final Map ctxmap = ((Configuration)context.getAttribute(GlobalConstants.SERVICE_CONFIG_KEY)).asMap(); + final Map services = (Map)(ctxmap.get(GlobalConfiguration.ConfigurationKey.SERVICES.name())); + + // fetch all swagger files from every service + for (final String servicePrefix : services.keySet()) { + + // get the version JSON + final String version = fetchVersion(client, servicePrefix); + + // some services have no version or no available YAML + if (version != null) { + // get the YAML + final String swaggerYaml = fetchYaml(client, servicePrefix, version); + if (swaggerYaml != null) { + swaggerContext.addMicroAPI(swaggerYaml, true); + } + } + } + + // perform the combine operation + final SwaggerCombine sc = new SwaggerCombine(); + return sc.run(swaggerContext); + } + + private String fetchVersion(HttpClient client, final String servicePrefix) throws InterruptedException, ExecutionException, TimeoutException { + final String urlStr = baseUrl + servicePrefix + "/version"; + final ContentResponse response = client.newRequest(urlStr).method(HttpMethod.GET).send(); + if (response.getStatus() == HTTP_OKAY) { + final String jsonStr = response.getContentAsString(); + final JsonParser parser = new JsonParser(); + final JsonElement json = parser.parse(jsonStr); + return json.getAsJsonObject().getAsJsonPrimitive("version").getAsString(); + } else if (response.getStatus() == HTTP_NOT_FOUND) { + logger.warning("Unexpected 404 (Not Found) from service " + urlStr); + return null; + } else if (response.getStatus() == HTTP_UNAUTHORIZED) { + logger.warning("Unexpected 401 (Unauthorized) from service " + urlStr); + return null; + } else { + return null; + } + } + + private String fetchYaml(HttpClient client, final String servicePrefix, final String version) throws InterruptedException, ExecutionException, TimeoutException { + final String urlStr = baseUrl + servicePrefix + '/' + version + "/doc" + servicePrefix + "-swagger.yaml"; + final ContentResponse response = client.newRequest(urlStr).method(HttpMethod.GET).send(); + if (response.getStatus() == HTTP_OKAY) { + return response.getContentAsString(); + } else if (response.getStatus() == HTTP_NOT_FOUND) { + logger.warning("Unexpected 404 (Not Found) from service " + urlStr); + return null; + } else if (response.getStatus() == HTTP_UNAUTHORIZED) { + logger.warning("Unexpected 401 (Unauthorized) from service " + urlStr); + return null; + } else { + return null; + } + } + + private HttpClient createAndStartHttpClient() throws Exception { + final HttpClient theclient; + if (tlsIsEnabled) { + SslContextFactory sslFactory = new SslContextFactory(); + sslFactory.setTrustAll(true); + theclient = new HttpClient(sslFactory); + } else { + theclient = new HttpClient(); + } + theclient.start(); + return theclient; + } + + private String readFully(InputStream in, final String encoding) throws IOException { + try (ByteArrayOutputStream result = new ByteArrayOutputStream()) { + final byte[] buffer = new byte[1024]; + int length; + while ((length = in.read(buffer)) != -1) { + result.write(buffer, 0, length); + } + in.close(); + return result.toString(encoding); + } + } +} diff -r dd3f50e39a56 -r 1738c834713c services/schema/src/main/java/com/redhat/thermostat/gateway/service/schema/SwaggerSpecResourceHandler.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/services/schema/src/main/java/com/redhat/thermostat/gateway/service/schema/SwaggerSpecResourceHandler.java Tue Oct 24 14:46:18 2017 -0400 @@ -0,0 +1,59 @@ +/* + * Copyright 2012-2017 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * . + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.gateway.service.schema; + +import java.io.IOException; + +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.MediaType; +import javax.ws.rs.core.Response; + +import com.redhat.thermostat.gateway.common.core.servlet.BasicResourceHandler; + +@Path("doc/{fileName: .+\\.yaml}") +@Produces(MediaType.TEXT_PLAIN) +public class SwaggerSpecResourceHandler extends BasicResourceHandler { + + @GET + public Response getFileAsPlainText(@PathParam("fileName") String fileName) throws IOException { + return getFileAsResponse(SwaggerSpecResourceHandler.class.getClassLoader(), fileName); + } + +} diff -r dd3f50e39a56 -r 1738c834713c services/schema/src/main/resources/schema-swagger.yaml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/services/schema/src/main/resources/schema-swagger.yaml Tue Oct 24 14:46:18 2017 -0400 @@ -0,0 +1,52 @@ +swagger: '2.0' +info: + version: 0.0.1 + title: Thermostat Web Gateway Schema Service + license: + name: GPL v2 with Classpath Exception + url: 'http://www.gnu.org/licenses' +produces: + - application/json + - application/yaml + - text/html; charset=utf-8 +basePath: /schema +paths: + /fullapi-swagger.yaml: + get: + description: Get YAML description of combined Swagger API for Thermostat + responses: + '200': + description: OK + schema: + $ref: '#/definitions/swagger-response-yaml' + /fullapi-swagger.json: + get: + description: Get JSON description of combined Swagger API for Thermostat + parameters: + - $ref: '#/parameters/pretty' + responses: + '200': + description: OK + schema: + $ref: '#/definitions/swagger-response-json' + /doc/schema-swagger.yaml: + get: + description: Get YAML description of Swagger API for schma service + responses: + '200': + description: OK + schema: + $ref: '#/definitions/swagger-response' +definitions: + swagger-response-json: + type: object + swagger-response-yaml: + type: object +parameters: + pretty: + name: pretty + in: query + description: true if JSON output should be pretty-printed + type: boolean + required: false + default: false \ No newline at end of file diff -r dd3f50e39a56 -r 1738c834713c services/schema/src/main/webapp/WEB-INF/swagger-template.json --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/services/schema/src/main/webapp/WEB-INF/swagger-template.json Tue Oct 24 14:46:18 2017 -0400 @@ -0,0 +1,110 @@ +{ + "swagger": "2.0", + "info": { + "version": "0.0.1", + "title": "Thermostat Combined API", + "license": { + "name": "GPL v2 with Classpath Exception", + "url": "http://www.gnu.org/licenses" + } + }, + "consumes": [ + "application/json" + ], + "produces": [ + "application/json", + "text/html; charset=utf-8" + ], + "basePath": "/", + "paths": { + }, + "definitions": { + "environment-items": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "metric": { + "type": "object", + "properties": { + "$numberLong": { + "type": "string" + } + } + } + }, + "parameters": { + "system-id": { + "name": "systemId", + "in": "path", + "required": true, + "type": "string", + "description": "The system ID for the current request." + }, + "jvm-id": { + "name": "jvmId", + "in": "path", + "required": true, + "type": "string", + "description": "The JVM ID for the current request." + }, + "timestamp": { + "name": "timeStamp", + "in": "path", + "required": true, + "type": "integer", + "format": "int64", + "description": "The UNIX timestamp in milliseconds to set the last_updated field for." + }, + "limit": { + "name": "limit", + "in": "query", + "description": "Maximum number of items to return. Example '1'", + "type": "integer", + "required": false, + "default": 1 + }, + "offset": { + "name": "offset", + "in": "query", + "description": "Offset of first item to return. Example '0'", + "type": "integer", + "required": false, + "default": 0 + }, + "sort": { + "name": "sort", + "in": "query", + "description": "Sort string. Comma separated list of fields prefixed with '+' for ascending or '-' for descending. Example '?sort=+a,-b' Fields use dot notation for embedded documents. Example 'outer.inner' refers to field inner contained in field outer.", + "type": "string", + "required": false + }, + "query": { + "name": "query", + "in": "query", + "description": "Query string. Comma separated list of key, comparator, value pairs. Comparator supports '==', '<=', '>=', '<', '>', '!='. Example '?query=a==b,c!=d'. Keys are fields in documents and use dot notation for embedded documents. Example 'outer.inner' refers to field inner contained in field outer.", + "type": "string", + "required": false + }, + "include": { + "name": "include", + "in": "query", + "description": "Inclusion string. Comma separated list of fields to include in the response. Example '?include=a,b' Fields use dot notation for embedded documents. Example 'outer.inner' refers to field inner contained in field outer. Cannot be used in combination with 'exclude' parameter Overriden by 'exclude' parameter", + "type": "string", + "required": false + }, + "exclude": { + "name": "exclude", + "in": "query", + "description": "Exclusion string. Comma separated list of fields to exclude in the response. Example '?exclude=a,b' Fields use dot notation for embedded documents. Example 'outer.inner' refers to field inner contained in field outer. Cannot be used in combination with 'include' parameter; takes precedence over 'include' parameter", + "type": "string", + "required": false + } + } +} \ No newline at end of file diff -r dd3f50e39a56 -r 1738c834713c services/schema/src/main/webapp/WEB-INF/web.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/services/schema/src/main/webapp/WEB-INF/web.xml Tue Oct 24 14:46:18 2017 -0400 @@ -0,0 +1,102 @@ + + + + + SchemaInfoServlet + org.glassfish.jersey.servlet.ServletContainer + + + jersey.config.server.provider.packages + + + com.redhat.thermostat.gateway.service.schema, + + + + + + default + /doc + + + + SchemaInfoServlet + /* + + + + + com.redhat.thermostat.gateway.SERVICE_NAME + @com.redhat.thermostat.gateway.SERVICE_NAME@ + + + + + com.redhat.thermostat.gateway.common.mongodb.servlet.StorageConnectionSettingListener + + + + + + Version + /version + + + + + + + Schema API Spec File + /doc/schema-swagger.yaml + + + Combined Schema API Spec File - YAML + /fullapi-swagger.yaml + + + Combined Schema API Spec File - JSON + /fullapi-swagger.json + + + + diff -r dd3f50e39a56 -r 1738c834713c services/system-cpu/src/main/webapp/WEB-INF/web.xml --- a/services/system-cpu/src/main/webapp/WEB-INF/web.xml Tue Oct 24 12:58:31 2017 -0400 +++ b/services/system-cpu/src/main/webapp/WEB-INF/web.xml Tue Oct 24 14:46:18 2017 -0400 @@ -77,9 +77,22 @@ com.redhat.thermostat.gateway.common.mongodb.servlet.StorageConnectionSettingListener + + + + + Version + /version + + + + Version + /version + + Swagger API Spec File /0.0.1/doc/@com.redhat.thermostat.gateway.SERVICE_NAME@-swagger.yaml diff -r dd3f50e39a56 -r 1738c834713c services/system-memory/src/main/webapp/WEB-INF/web.xml --- a/services/system-memory/src/main/webapp/WEB-INF/web.xml Tue Oct 24 12:58:31 2017 -0400 +++ b/services/system-memory/src/main/webapp/WEB-INF/web.xml Tue Oct 24 14:46:18 2017 -0400 @@ -77,6 +77,15 @@ com.redhat.thermostat.gateway.common.mongodb.servlet.StorageConnectionSettingListener + + + + + Version + /version + + + diff -r dd3f50e39a56 -r 1738c834713c services/system-network/src/main/webapp/WEB-INF/web.xml --- a/services/system-network/src/main/webapp/WEB-INF/web.xml Tue Oct 24 12:58:31 2017 -0400 +++ b/services/system-network/src/main/webapp/WEB-INF/web.xml Tue Oct 24 14:46:18 2017 -0400 @@ -77,6 +77,15 @@ com.redhat.thermostat.gateway.common.mongodb.servlet.StorageConnectionSettingListener + + + + + Version + /version + + + diff -r dd3f50e39a56 -r 1738c834713c services/systems/src/main/webapp/WEB-INF/web.xml --- a/services/systems/src/main/webapp/WEB-INF/web.xml Tue Oct 24 12:58:31 2017 -0400 +++ b/services/systems/src/main/webapp/WEB-INF/web.xml Tue Oct 24 14:46:18 2017 -0400 @@ -77,6 +77,15 @@ com.redhat.thermostat.gateway.common.mongodb.servlet.StorageConnectionSettingListener + + + + + Version + /version + + + diff -r dd3f50e39a56 -r 1738c834713c tests/integration-tests/pom.xml --- a/tests/integration-tests/pom.xml Tue Oct 24 12:58:31 2017 -0400 +++ b/tests/integration-tests/pom.xml Tue Oct 24 14:46:18 2017 -0400 @@ -130,6 +130,7 @@ ${junit.version} test + org.eclipse.jetty jetty-client @@ -138,10 +139,22 @@ + org.eclipse.jetty + jetty-util + ${jetty.version} + + + com.google.code.gson gson ${google-gson.version} - provided + test + + + + com.redhat.thermostat + thermostat-common + ${thermostat.common.version} diff -r dd3f50e39a56 -r 1738c834713c tests/integration-tests/src/test/java/com/redhat/thermostat/gateway/service/schema/SchemaIntegrationTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/integration-tests/src/test/java/com/redhat/thermostat/gateway/service/schema/SchemaIntegrationTest.java Tue Oct 24 14:46:18 2017 -0400 @@ -0,0 +1,124 @@ +/* + * Copyright 2012-2017 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * . + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.gateway.service.schema; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.redhat.thermostat.common.json.JsonUtil; +import com.redhat.thermostat.common.yaml.YamlToJson; +import com.redhat.thermostat.gateway.tests.integration.ServiceIntegrationTest; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.client.api.ContentResponse; +import org.junit.Assert; +import org.junit.Test; + +import java.io.IOException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class SchemaIntegrationTest extends ServiceIntegrationTest { + + private static final String serviceName = "schema"; + private static final String versionString = ""; + private static final int HTTP_200_OK = 200; + private static final int HTTP_404_NOTFOUND = 404; + + public SchemaIntegrationTest() { + super(serviceName); + } + + @Test + public void testGoodYaml() throws InterruptedException, ExecutionException, TimeoutException, IOException { + final ContentResponse resp = get("/fullapi-swagger.yaml"); + final YamlToJson y2j = new YamlToJson(); + final JsonObject json = y2j.yamlToJsonObject(resp.getContentAsString()); + verifyApi(json); + } + + @Test + public void testGoodJson() throws InterruptedException, ExecutionException, TimeoutException { + final ContentResponse resp = get("/fullapi-swagger.json"); + final JsonObject json = new JsonParser().parse(resp.getContentAsString()).getAsJsonObject(); + verifyApi(json); + } + + private void verifyApi(JsonObject obj) { + final String title = JsonUtil.fetch(obj, "info.title").getAsString(); + assertNotNull(title); + assertTrue(title.contains("Thermostat" )); + final JsonObject paths = obj.get("paths").getAsJsonObject(); + assertNotNull(paths); + assertFalse(paths.entrySet().isEmpty()); + } + + @Test + public void testBad() throws InterruptedException, ExecutionException, TimeoutException { + final ContentResponse resp = getBad("/fullapi-swagge.yaml"); + } + + protected ContentResponse get(final String path) throws InterruptedException, ExecutionException, TimeoutException { + ContentResponse response = client.newRequest(resourceUrl + path).method(HttpMethod.GET).send(); + assertEquals(HTTP_200_OK, response.getStatus()); + return response; + } + + protected ContentResponse getBad(final String path) throws InterruptedException, ExecutionException, TimeoutException { + ContentResponse response = client.newRequest(resourceUrl + path).method(HttpMethod.GET).send(); + assertEquals(HTTP_404_NOTFOUND, response.getStatus()); + return response; + } + + @Override + public String getServiceVersion() { + return versionString; + } + + @Override + public String getServiceName() { + return serviceName; + } + + @Override + protected String getDocUrl() { + return baseUrl + "/" + getServiceName() + "/doc/" + getServiceName() + "-swagger.yaml"; + } +} \ No newline at end of file diff -r dd3f50e39a56 -r 1738c834713c tests/integration-tests/src/test/java/com/redhat/thermostat/gateway/tests/integration/ServiceIntegrationTest.java --- a/tests/integration-tests/src/test/java/com/redhat/thermostat/gateway/tests/integration/ServiceIntegrationTest.java Tue Oct 24 12:58:31 2017 -0400 +++ b/tests/integration-tests/src/test/java/com/redhat/thermostat/gateway/tests/integration/ServiceIntegrationTest.java Tue Oct 24 14:46:18 2017 -0400 @@ -68,11 +68,11 @@ public void verifySwaggerYamlAccessible() throws ExecutionException, InterruptedException, TimeoutException { try { removeAuthentication(client); // be sure to perform get with no authentication - String swaggerDocsUrl = getDocUrl(); + final String swaggerDocsUrl = getDocUrl(); final Request request = client.newRequest(swaggerDocsUrl); - ContentResponse response = request.method(HttpMethod.GET).send(); + final ContentResponse response = request.method(HttpMethod.GET).send(); assertEquals("Expected OK response (no auth should be required). URL was: " + swaggerDocsUrl, HttpStatus.OK_200, response.getStatus()); - String actual = response.getContentAsString(); + final String actual = response.getContentAsString(); assertNotNull(actual); assertFalse(actual.isEmpty()); assertTrue("service name expected to be part of swagger spec", actual.contains(getServiceName())); @@ -82,8 +82,7 @@ } } - private String getDocUrl() { + protected String getDocUrl() { return baseUrl + "/" + getServiceName() + "/" + getServiceVersion() + "/doc/" + getServiceName() + "-swagger.yaml"; } - }