changeset 292:1738c834713c

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
author Simon Tooke <stooke@redhat.com>
date Tue, 24 Oct 2017 14:46:18 -0400
parents dd3f50e39a56
children bf5be7d61823
files common/core/src/main/java/com/redhat/thermostat/gateway/common/core/servlet/ServiceVersionFilter.java common/core/src/test/java/com/redhat/thermostat/gateway/common/core/servlet/ServiceVersionFilterTest.java distribution/pom.xml distribution/src/etc/services.properties server/pom.xml services/commands/src/main/webapp/WEB-INF/web.xml services/jcmd/src/main/webapp/WEB-INF/web.xml services/jvm-byteman/src/main/resources/jvm-byteman-swagger.yaml services/jvm-byteman/src/main/webapp/WEB-INF/web.xml services/jvm-compiler/src/main/webapp/WEB-INF/web.xml services/jvm-cpu/src/main/webapp/WEB-INF/web.xml services/jvm-gc/src/main/webapp/WEB-INF/web.xml services/jvm-io/src/main/webapp/WEB-INF/web.xml services/jvm-memory/src/main/webapp/WEB-INF/web.xml services/jvms/src/main/webapp/WEB-INF/web.xml services/pom.xml services/schema/pom.xml services/schema/src/main/java/com/redhat/thermostat/gateway/service/schema/SchemaHttpHandler.java services/schema/src/main/java/com/redhat/thermostat/gateway/service/schema/SwaggerBuilder.java services/schema/src/main/java/com/redhat/thermostat/gateway/service/schema/SwaggerSpecResourceHandler.java services/schema/src/main/resources/schema-swagger.yaml services/schema/src/main/webapp/WEB-INF/swagger-template.json services/schema/src/main/webapp/WEB-INF/web.xml services/system-cpu/src/main/webapp/WEB-INF/web.xml services/system-memory/src/main/webapp/WEB-INF/web.xml services/system-network/src/main/webapp/WEB-INF/web.xml services/systems/src/main/webapp/WEB-INF/web.xml tests/integration-tests/pom.xml tests/integration-tests/src/test/java/com/redhat/thermostat/gateway/service/schema/SchemaIntegrationTest.java tests/integration-tests/src/test/java/com/redhat/thermostat/gateway/tests/integration/ServiceIntegrationTest.java
diffstat 30 files changed, 1078 insertions(+), 17 deletions(-) [+]
line wrap: on
line diff
--- 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);
--- 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);
--- 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 @@
                             <type>war</type>
                             <overWrite>false</overWrite>
                         </artifactItem>
+                        <artifactItem>
+                            <groupId>com.redhat.thermostat</groupId>
+                            <artifactId>thermostat-web-gateway-service-schema</artifactId>
+                            <version>${project.version}</version>
+                            <type>war</type>
+                            <overWrite>false</overWrite>
+                        </artifactItem>
                     </artifactItems>
                     <outputDirectory>${project.build.directory}/image/services</outputDirectory>
                 </configuration>
@@ -376,6 +383,13 @@
         </dependency>
         <dependency>
             <groupId>com.redhat.thermostat</groupId>
+            <artifactId>thermostat-web-gateway-service-schema</artifactId>
+            <version>${project.version}</version>
+            <type>war</type>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.redhat.thermostat</groupId>
             <artifactId>thermostat-web-gateway-service-jcmd</artifactId>
             <version>${project.version}</version>
             <type>war</type>
--- 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
--- 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 @@
         </dependency>
         <dependency>
             <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-security</artifactId>
+            <version>${jetty.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-util</artifactId>
+            <version>${jetty.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
             <artifactId>jetty-servlets</artifactId>
             <version>${jetty.version}</version>
         </dependency>
--- 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 @@
         <param-name>com.redhat.thermostat.gateway.SERVICE_VERSION</param-name>
         <param-value>v1</param-value>
     </context-param>
+
+    <!-- allow querying API version without authentication -->
+    <security-constraint>
+        <web-resource-collection>
+            <web-resource-name>Version</web-resource-name>
+            <url-pattern>/version</url-pattern>
+        </web-resource-collection>
+    </security-constraint>
+
     <!-- Allow viewing of API spec without authentication -->
     <security-constraint>
       <web-resource-collection>
--- 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 @@
     <listener>
         <listener-class>com.redhat.thermostat.gateway.common.mongodb.servlet.StorageConnectionSettingListener</listener-class>
     </listener>
+
+    <!-- allow querying API version without authentication -->
+    <security-constraint>
+        <web-resource-collection>
+            <web-resource-name>Version</web-resource-name>
+            <url-pattern>/version</url-pattern>
+        </web-resource-collection>
+    </security-constraint>
+
     <!-- Allow viewing of API spec without authentication -->
     <security-constraint>
       <web-resource-collection>
--- 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:
--- 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 @@
     <listener>
         <listener-class>com.redhat.thermostat.gateway.common.mongodb.servlet.StorageConnectionSettingListener</listener-class>
     </listener>
+
+    <!-- allow querying API version without authentication -->
+    <security-constraint>
+        <web-resource-collection>
+            <web-resource-name>Version</web-resource-name>
+            <url-pattern>/version</url-pattern>
+        </web-resource-collection>
+    </security-constraint>
+
     <!-- Allow viewing of API spec without authentication -->
     <security-constraint>
       <web-resource-collection>
--- 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 @@
     <listener>
         <listener-class>com.redhat.thermostat.gateway.common.mongodb.servlet.StorageConnectionSettingListener</listener-class>
     </listener>
+
+    <!-- allow querying API version without authentication -->
+    <security-constraint>
+        <web-resource-collection>
+            <web-resource-name>Version</web-resource-name>
+            <url-pattern>/version</url-pattern>
+        </web-resource-collection>
+    </security-constraint>
+
     <!-- Allow viewing of API spec without authentication -->
     <security-constraint>
         <web-resource-collection>
--- 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 @@
     <listener>
         <listener-class>com.redhat.thermostat.gateway.common.mongodb.servlet.StorageConnectionSettingListener</listener-class>
     </listener>
+
+    <!-- allow querying API version without authentication -->
+    <security-constraint>
+        <web-resource-collection>
+            <web-resource-name>Version</web-resource-name>
+            <url-pattern>/version</url-pattern>
+        </web-resource-collection>
+    </security-constraint>
+
     <!-- Allow viewing of API spec without authentication -->
     <security-constraint>
       <web-resource-collection>
--- 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 @@
         <param-name>com.redhat.thermostat.gateway.SERVICE_NAME</param-name>
         <param-value>@com.redhat.thermostat.gateway.SERVICE_NAME@</param-value>
     </context-param>
+
     <!-- Listener for setting up the storage connection -->
     <listener>
         <listener-class>com.redhat.thermostat.gateway.common.mongodb.servlet.StorageConnectionSettingListener</listener-class>
     </listener>
-    <!-- Allow viewing of API spec without authentication -->
+
+    <!-- allow querying API version without authentication -->
     <security-constraint>
-      <web-resource-collection>
-        <web-resource-name>Swagger API Spec File</web-resource-name>
-        <url-pattern>/0.0.3/doc/@com.redhat.thermostat.gateway.SERVICE_NAME@-swagger.yaml</url-pattern>
-      </web-resource-collection>
+        <web-resource-collection>
+            <web-resource-name>Version</web-resource-name>
+            <url-pattern>/version</url-pattern>
+        </web-resource-collection>
+    </security-constraint>
+
+    <security-constraint>
+        <!-- Allow viewing of API spec without authentication -->
+        <web-resource-collection>
+            <web-resource-name>Swagger API Spec File</web-resource-name>
+            <url-pattern>/0.0.3/doc/@com.redhat.thermostat.gateway.SERVICE_NAME@-swagger.yaml</url-pattern>
+        </web-resource-collection>
       <!-- Explicitly no auth constraint for this file -->
     </security-constraint>
 </web-app>
--- 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 @@
     <listener>
         <listener-class>com.redhat.thermostat.gateway.common.mongodb.servlet.StorageConnectionSettingListener</listener-class>
     </listener>
+
+    <!-- allow querying API version without authentication -->
+    <security-constraint>
+        <web-resource-collection>
+            <web-resource-name>Version</web-resource-name>
+            <url-pattern>/version</url-pattern>
+        </web-resource-collection>
+    </security-constraint>
+
     <!-- Allow viewing of API spec without authentication -->
     <security-constraint>
       <web-resource-collection>
--- 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 @@
     <listener>
         <listener-class>com.redhat.thermostat.gateway.common.mongodb.servlet.StorageConnectionSettingListener</listener-class>
     </listener>
+
+    <!-- allow querying API version without authentication -->
+    <security-constraint>
+        <web-resource-collection>
+            <web-resource-name>Version</web-resource-name>
+            <url-pattern>/version</url-pattern>
+        </web-resource-collection>
+    </security-constraint>
+
     <!-- Allow viewing of API spec without authentication -->
     <security-constraint>
       <web-resource-collection>
--- 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 @@
     <listener>
         <listener-class>com.redhat.thermostat.gateway.common.mongodb.servlet.StorageConnectionSettingListener</listener-class>
     </listener>
+
+    <!-- allow querying API version without authentication -->
+    <security-constraint>
+        <web-resource-collection>
+            <web-resource-name>Version</web-resource-name>
+            <url-pattern>/version</url-pattern>
+        </web-resource-collection>
+    </security-constraint>
+
     <!-- Allow viewing of API spec without authentication -->
     <security-constraint>
       <web-resource-collection>
--- 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 @@
 
     <modules>
         <module>commands</module>
+        <module>schema</module>
         <module>jvms</module>
         <module>jcmd</module>
         <module>jvm-byteman</module>
--- /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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ 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
+ <http://www.gnu.org/licenses/>.
+
+ 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.
+
+-->
+<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>
+
+    <parent>
+        <groupId>com.redhat.thermostat</groupId>
+        <artifactId>thermostat-web-gateway-services</artifactId>
+        <version>1.99.12-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>thermostat-web-gateway-service-schema</artifactId>
+    <packaging>war</packaging>
+
+    <name>Thermostat Web Gateway Schema service</name>
+
+    <build>
+        <plugins>
+            <!-- ensure war file includes docs directory -->
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-war-plugin</artifactId>
+                <version>3.2.0</version>
+                <configuration>
+                    <webResources>
+                        <resource>
+                            <directory>src/main/webapp</directory>
+                            <filtering>true</filtering>
+                        </resource>
+                    </webResources>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.redhat.thermostat</groupId>
+            <artifactId>thermostat-common</artifactId>
+            <version>${thermostat.common.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.redhat.thermostat</groupId>
+            <artifactId>thermostat-web-gateway-common-core</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <!-- Servlet API deps -->
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>javax.servlet-api</artifactId>
+            <version>${javax.servlet.version}</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <!-- JAX-RS Dependencies -->
+        <dependency>
+            <groupId>javax.ws.rs</groupId>
+            <artifactId>javax.ws.rs-api</artifactId>
+            <version>${javax-rs-api.version}</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-client</artifactId>
+            <version>${jetty.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-util</artifactId>
+            <version>${jetty.version}</version>
+        </dependency>
+    </dependencies>
+
+
+</project>
+
--- /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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * 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;
+        }
+    }
+}
--- /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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * 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<String, Object> 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<String, Object> ctxmap = ((Configuration)context.getAttribute(GlobalConstants.SERVICE_CONFIG_KEY)).asMap();
+        final Map<String, String> services = (Map<String, String>)(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);
+        }
+    }
+}
--- /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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * 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);
+    }
+
+}
--- /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
--- /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
--- /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 @@
+<!--
+
+ 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
+ <http://www.gnu.org/licenses/>.
+
+ 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.
+
+-->
+<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
+         http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
+         version="3.1">
+
+    <servlet>
+        <servlet-name>SchemaInfoServlet</servlet-name>
+        <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
+        <init-param>
+            <param-name>
+                jersey.config.server.provider.packages
+            </param-name>
+            <param-value>
+                com.redhat.thermostat.gateway.service.schema,
+            </param-value>
+        </init-param>
+    </servlet>
+
+    <servlet-mapping>
+        <servlet-name>default</servlet-name>
+        <url-pattern>/doc</url-pattern>
+    </servlet-mapping>
+
+    <servlet-mapping>
+        <servlet-name>SchemaInfoServlet</servlet-name>
+        <url-pattern>/*</url-pattern>
+    </servlet-mapping>
+
+    <!-- Service configuration -->
+    <context-param>
+        <param-name>com.redhat.thermostat.gateway.SERVICE_NAME</param-name>
+        <param-value>@com.redhat.thermostat.gateway.SERVICE_NAME@</param-value>
+    </context-param>
+
+    <!-- Listener for setting up the storage connection -->
+    <listener>
+        <listener-class>com.redhat.thermostat.gateway.common.mongodb.servlet.StorageConnectionSettingListener</listener-class>
+    </listener>
+
+    <!-- allow querying API version without authentication -->
+    <security-constraint>
+        <web-resource-collection>
+            <web-resource-name>Version</web-resource-name>
+            <url-pattern>/version</url-pattern>
+        </web-resource-collection>
+    </security-constraint>
+
+    <!-- Allow viewing of API spec without authentication -->
+    <security-constraint>
+        <web-resource-collection>
+            <web-resource-name>Schema API Spec File</web-resource-name>
+            <url-pattern>/doc/schema-swagger.yaml</url-pattern>
+        </web-resource-collection>
+        <web-resource-collection>
+            <web-resource-name>Combined Schema API Spec File - YAML</web-resource-name>
+            <url-pattern>/fullapi-swagger.yaml</url-pattern>
+        </web-resource-collection>
+        <web-resource-collection>
+            <web-resource-name>Combined Schema API Spec File - JSON</web-resource-name>
+            <url-pattern>/fullapi-swagger.json</url-pattern>
+        </web-resource-collection>
+        <!-- Explicitly no auth constraint for this file -->
+    </security-constraint>
+</web-app>
--- 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 @@
     <listener>
         <listener-class>com.redhat.thermostat.gateway.common.mongodb.servlet.StorageConnectionSettingListener</listener-class>
     </listener>
+
+    <!-- allow querying API version without authentication -->
+    <security-constraint>
+        <web-resource-collection>
+            <web-resource-name>Version</web-resource-name>
+            <url-pattern>/version</url-pattern>
+        </web-resource-collection>
+    </security-constraint>
+
     <!-- Allow viewing of API spec without authentication -->
     <security-constraint>
       <web-resource-collection>
+          <web-resource-name>Version</web-resource-name>
+          <url-pattern>/version</url-pattern>
+      </web-resource-collection>
+      <web-resource-collection>
         <web-resource-name>Swagger API Spec File</web-resource-name>
         <url-pattern>/0.0.1/doc/@com.redhat.thermostat.gateway.SERVICE_NAME@-swagger.yaml</url-pattern>
       </web-resource-collection>
--- 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 @@
     <listener>
         <listener-class>com.redhat.thermostat.gateway.common.mongodb.servlet.StorageConnectionSettingListener</listener-class>
     </listener>
+
+    <!-- allow querying API version without authentication -->
+    <security-constraint>
+        <web-resource-collection>
+            <web-resource-name>Version</web-resource-name>
+            <url-pattern>/version</url-pattern>
+        </web-resource-collection>
+    </security-constraint>
+
     <!-- Allow viewing of API spec without authentication -->
     <security-constraint>
       <web-resource-collection>
--- 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 @@
     <listener>
         <listener-class>com.redhat.thermostat.gateway.common.mongodb.servlet.StorageConnectionSettingListener</listener-class>
     </listener>
+
+    <!-- allow querying API version without authentication -->
+    <security-constraint>
+        <web-resource-collection>
+            <web-resource-name>Version</web-resource-name>
+            <url-pattern>/version</url-pattern>
+        </web-resource-collection>
+    </security-constraint>
+
     <!-- Allow viewing of API spec without authentication -->
     <security-constraint>
       <web-resource-collection>
--- 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 @@
     <listener>
         <listener-class>com.redhat.thermostat.gateway.common.mongodb.servlet.StorageConnectionSettingListener</listener-class>
     </listener>
+
+    <!-- allow querying API version without authentication -->
+    <security-constraint>
+        <web-resource-collection>
+            <web-resource-name>Version</web-resource-name>
+            <url-pattern>/version</url-pattern>
+        </web-resource-collection>
+    </security-constraint>
+
     <!-- Allow viewing of API spec without authentication -->
     <security-constraint>
       <web-resource-collection>
--- 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 @@
             <version>${junit.version}</version>
             <scope>test</scope>
         </dependency>
+
         <dependency>
             <groupId>org.eclipse.jetty</groupId>
             <artifactId>jetty-client</artifactId>
@@ -138,10 +139,22 @@
         </dependency>
 
         <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-util</artifactId>
+            <version>${jetty.version}</version>
+        </dependency>
+
+        <dependency>
             <groupId>com.google.code.gson</groupId>
             <artifactId>gson</artifactId>
             <version>${google-gson.version}</version>
-            <scope>provided</scope>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>com.redhat.thermostat</groupId>
+            <artifactId>thermostat-common</artifactId>
+            <version>${thermostat.common.version}</version>
         </dependency>
     </dependencies>
 
--- /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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * 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
--- 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";
     }
-
 }