changeset 161:16c078be30b4

Add jvm information service PR3375 Reviewed-by: jerboaa Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2017-May/023328.html Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2017-May/023295.html
author Jie Kang <jkang@redhat.com>
date Tue, 30 May 2017 09:16:06 -0400
parents bd282001a37d
children 8a351ae62467
files common/mongodb/src/main/java/com/redhat/thermostat/gateway/common/mongodb/executor/MongoExecutor.java distribution/pom.xml distribution/src/etc/jvms/service-config.properties distribution/src/etc/services.properties services/jvms/pom.xml services/jvms/src/main/java/com/redhat/thermostat/service/jvms/http/JvmsHttpHandler.java services/jvms/src/main/java/com/redhat/thermostat/service/jvms/http/Parameters.java services/jvms/src/main/java/com/redhat/thermostat/service/jvms/http/SwaggerSpecResourceHandler.java services/jvms/src/main/java/com/redhat/thermostat/service/jvms/mongo/Fields.java services/jvms/src/main/java/com/redhat/thermostat/service/jvms/mongo/MongoStorageHandler.java services/jvms/src/main/resources/jvms-swagger.yaml services/jvms/src/main/webapp/WEB-INF/web.xml services/pom.xml tests/integration-tests/src/test/java/com/redhat/thermostat/gateway/service/jvms/JvmsServiceIntegrationTest.java
diffstat 14 files changed, 1810 insertions(+), 45 deletions(-) [+]
line wrap: on
line diff
--- a/common/mongodb/src/main/java/com/redhat/thermostat/gateway/common/mongodb/executor/MongoExecutor.java	Fri May 19 16:03:47 2017 +0200
+++ b/common/mongodb/src/main/java/com/redhat/thermostat/gateway/common/mongodb/executor/MongoExecutor.java	Tue May 30 09:16:06 2017 -0400
@@ -70,9 +70,8 @@
             documents = collection.find();
         }
 
-        List<String> projectionsList;
         if (projections != null) {
-            projectionsList = Arrays.asList(projections.split(","));
+            List<String> projectionsList = Arrays.asList(projections.split(","));
             documents = documents.projection(fields(include(projectionsList), excludeId()));
         } else {
             documents = documents.projection(excludeId());
@@ -84,7 +83,6 @@
         return mongoResponseBuilder.buildGetResponseString(documents);
     }
 
-
     public void buildPutResponse(MongoCollection<Document> collection, String body, String queries) {
         BasicDBObject inputObject = (BasicDBObject) JSON.parse(body);
 
--- a/distribution/pom.xml	Fri May 19 16:03:47 2017 +0200
+++ b/distribution/pom.xml	Tue May 30 09:16:06 2017 -0400
@@ -51,43 +51,43 @@
 
     <packaging>pom</packaging>
 
-	<profiles>
-	
-	<profile>
-	  <id>windows</id>
-	  <activation>
-		<os><family>Windows</family></os>
-	  </activation>
-	  <build>
-		<plugins>
-		  <plugin>
-			<artifactId>maven-resources-plugin</artifactId>
-			<executions>
-			  <execution>
-				<id>copy-windows-scripts</id>
-				<phase>prepare-package</phase>
-				<goals>
-				  <goal>copy-resources</goal>
-				</goals>
-				<configuration>
-				  <outputDirectory>${project.build.directory}</outputDirectory>
-				  <resources>
-					<resource>
-					  <directory>src/windows/bin</directory>
-					  <targetPath>image/bin</targetPath>
-					  <filtering>true</filtering>
-					</resource>
-				  </resources>
-				</configuration>
-			  </execution>
-			</executions>
-		  </plugin>
-		</plugins>
-	  </build>
-	</profile>
+    <profiles>
 
-	</profiles>
-	
+        <profile>
+            <id>windows</id>
+            <activation>
+                <os><family>Windows</family></os>
+            </activation>
+            <build>
+                <plugins>
+                    <plugin>
+                        <artifactId>maven-resources-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <id>copy-windows-scripts</id>
+                                <phase>prepare-package</phase>
+                                <goals>
+                                    <goal>copy-resources</goal>
+                                </goals>
+                                <configuration>
+                                    <outputDirectory>${project.build.directory}</outputDirectory>
+                                    <resources>
+                                        <resource>
+                                            <directory>src/windows/bin</directory>
+                                            <targetPath>image/bin</targetPath>
+                                            <filtering>true</filtering>
+                                        </resource>
+                                    </resources>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+
+    </profiles>
+
     <build>
         <plugins>
             <plugin>
@@ -192,14 +192,21 @@
                         </artifactItem>
                         <artifactItem>
                             <groupId>com.redhat.thermostat</groupId>
-                            <artifactId>thermostat-web-gateway-service-jvm-memory</artifactId>
+                            <artifactId>thermostat-web-gateway-service-jvm-gc</artifactId>
                             <version>${project.version}</version>
                             <type>war</type>
                             <overWrite>false</overWrite>
                         </artifactItem>
                         <artifactItem>
                             <groupId>com.redhat.thermostat</groupId>
-                            <artifactId>thermostat-web-gateway-service-jvm-gc</artifactId>
+                            <artifactId>thermostat-web-gateway-service-jvms</artifactId>
+                            <version>${project.version}</version>
+                            <type>war</type>
+                            <overWrite>false</overWrite>
+                        </artifactItem>
+                        <artifactItem>
+                            <groupId>com.redhat.thermostat</groupId>
+                            <artifactId>thermostat-web-gateway-service-jvm-memory</artifactId>
                             <version>${project.version}</version>
                             <type>war</type>
                             <overWrite>false</overWrite>
@@ -232,6 +239,13 @@
         </dependency>
         <dependency>
             <groupId>com.redhat.thermostat</groupId>
+            <artifactId>thermostat-web-gateway-service-jvms</artifactId>
+            <version>${project.version}</version>
+            <type>war</type>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.redhat.thermostat</groupId>
             <artifactId>thermostat-web-gateway-service-jvm-memory</artifactId>
             <version>${project.version}</version>
             <type>war</type>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/distribution/src/etc/jvms/service-config.properties	Tue May 30 09:16:06 2017 -0400
@@ -0,0 +1,4 @@
+MONGO_URL=mongodb://127.0.0.1:27518
+MONGO_DB=thermostat
+MONGO_USERNAME=mongodevuser
+MONGO_PASSWORD=mongodevpassword
\ No newline at end of file
--- a/distribution/src/etc/services.properties	Fri May 19 16:03:47 2017 +0200
+++ b/distribution/src/etc/services.properties	Tue May 30 09:16:06 2017 -0400
@@ -1,4 +1,5 @@
-/jvm-memory = thermostat-web-gateway-service-jvm-memory-@project.version@.war
+/commands = thermostat-web-gateway-service-commands-@project.version@.war
 /jvm-gc = thermostat-web-gateway-service-jvm-gc-@project.version@.war
-/commands = thermostat-web-gateway-service-commands-@project.version@.war
+/jvm-memory = thermostat-web-gateway-service-jvm-memory-@project.version@.war
+/jvms = thermostat-web-gateway-service-jvms-@project.version@.war
 /white-pages = thermostat-web-gateway-service-white-pages-@project.version@.war
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/services/jvms/pom.xml	Tue May 30 09:16:06 2017 -0400
@@ -0,0 +1,97 @@
+<!--
+
+ 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/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <artifactId>thermostat-web-gateway-services</artifactId>
+    <groupId>com.redhat.thermostat</groupId>
+    <version>1.99.12-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>thermostat-web-gateway-service-jvms</artifactId>
+
+  <packaging>war</packaging>
+
+  <name>Thermostat Web Gateway JVMs Service</name>
+
+  <properties>
+    <com.redhat.thermostat.gateway.SERVICE_NAME>jvms</com.redhat.thermostat.gateway.SERVICE_NAME>
+  </properties>
+
+  <build>
+    <plugins>
+      <plugin>
+        <artifactId>maven-war-plugin</artifactId>
+          <configuration>
+          <webResources>
+            <resource>
+              <directory>src/main/webapp</directory>
+              <filtering>true</filtering>
+            </resource>
+          </webResources>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+  <dependencies>
+    <!-- Thermostat Web Gateway Dependencies -->
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-web-gateway-common-mongodb</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+
+    <!-- Servlet API Dependencies-->
+    <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>
+  </dependencies>
+</project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/services/jvms/src/main/java/com/redhat/thermostat/service/jvms/http/JvmsHttpHandler.java	Tue May 30 09:16:06 2017 -0400
@@ -0,0 +1,218 @@
+/*
+ * 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.service.jvms.http;
+
+import javax.servlet.ServletContext;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+
+import com.mongodb.DBObject;
+import com.redhat.thermostat.gateway.common.mongodb.ThermostatMongoStorage;
+import com.redhat.thermostat.gateway.common.mongodb.servlet.ServletContextConstants;
+import com.redhat.thermostat.service.jvms.mongo.MongoStorageHandler;
+
+@Path("/")
+public class JvmsHttpHandler {
+    private final MongoStorageHandler mongoStorageHandler = new MongoStorageHandler();
+    private final String collectionName = "jvm-info";
+
+    @GET
+    @Path("/systems/{" + Parameters.SYSTEM_ID +"}")
+    @Consumes({ "application/json" })
+    @Produces({ "application/json", "text/html; charset=utf-8" })
+    public Response getJvmInfos(@PathParam(Parameters.SYSTEM_ID) String systemId,
+                                @QueryParam(Parameters.LIMIT) @DefaultValue("1") Integer limit,
+                                @QueryParam(Parameters.OFFSET) @DefaultValue("0") Integer offset,
+                                @QueryParam(Parameters.SORT) String sort,
+                                @QueryParam(Parameters.QUERY) String queries,
+                                @QueryParam(Parameters.INCLUDE) String includes,
+                                @QueryParam(Parameters.EXCLUDE) String excludes,
+                                @Context ServletContext context
+    ) {
+        try {
+            ThermostatMongoStorage storage = (ThermostatMongoStorage) context.getAttribute(ServletContextConstants.MONGODB_CLIENT_ATTRIBUTE);
+
+            String message = mongoStorageHandler.getJvmInfos(storage.getDatabase().getCollection(collectionName), systemId, limit, offset, sort, queries, includes, excludes);
+
+            return Response.status(Response.Status.OK).entity(message).build();
+        } catch (Exception e) {
+            return Response.status(Response.Status.BAD_REQUEST).build();
+        }
+    }
+
+    @POST
+    @Path("/systems/{" + Parameters.SYSTEM_ID +"}")
+    @Consumes({ "application/json" })
+    @Produces({ "application/json", "text/html; charset=utf-8" })
+    public Response postJvmInfos(String body,
+                                 @PathParam(Parameters.SYSTEM_ID) String systemId,
+                                 @Context ServletContext context) {
+        try {
+            ThermostatMongoStorage storage = (ThermostatMongoStorage) context.getAttribute(ServletContextConstants.MONGODB_CLIENT_ATTRIBUTE);
+
+            mongoStorageHandler.addJvmInfos(storage.getDatabase().getCollection(collectionName, DBObject.class), body, systemId);
+            return Response.status(Response.Status.OK).build();
+        } catch (Exception e) {
+            return Response.status(Response.Status.BAD_REQUEST).build();
+        }
+    }
+
+    @DELETE
+    @Path("/systems/{" + Parameters.SYSTEM_ID +"}")
+    @Consumes({ "application/json" })
+    @Produces({ "application/json", "text/html; charset=utf-8" })
+    public Response deleteJvmInfos(@PathParam(Parameters.SYSTEM_ID) String systemId,
+                                   @Context ServletContext context) {
+        try {
+            ThermostatMongoStorage storage = (ThermostatMongoStorage) context.getAttribute(ServletContextConstants.MONGODB_CLIENT_ATTRIBUTE);
+
+            mongoStorageHandler.deleteJvmInfos(storage.getDatabase().getCollection(collectionName), systemId);
+
+            return Response.status(Response.Status.OK).build();
+        } catch (Exception e) {
+            return Response.status(Response.Status.BAD_REQUEST).build();
+        }
+    }
+
+    @GET
+    @Path("/systems/{" + Parameters.SYSTEM_ID +"}/jvms/{" + Parameters.JVM_ID +"}")
+    @Consumes({ "application/json" })
+    @Produces({ "application/json", "text/html; charset=utf-8" })
+    public Response getJvmInfo(@PathParam(Parameters.SYSTEM_ID) String systemId,
+                               @PathParam(Parameters.JVM_ID) String jvmId,
+                               @QueryParam(Parameters.INCLUDE) String includes,
+                               @QueryParam(Parameters.EXCLUDE) String excludes,
+                               @Context ServletContext context
+    ) {
+        try {
+            ThermostatMongoStorage storage = (ThermostatMongoStorage) context.getAttribute(ServletContextConstants.MONGODB_CLIENT_ATTRIBUTE);
+
+            String message = mongoStorageHandler.getJvmInfo(storage.getDatabase().getCollection(collectionName), systemId, jvmId, includes, excludes);
+
+            return Response.status(Response.Status.OK).entity(message).build();
+        } catch (Exception e) {
+            return Response.status(Response.Status.BAD_REQUEST).build();
+        }
+    }
+    @PUT
+    @Path("/systems/{" + Parameters.SYSTEM_ID +"}/jvms/{" + Parameters.JVM_ID +"}")
+    @Consumes({ "application/json" })
+    @Produces({ "application/json", "text/html; charset=utf-8" })
+    public Response putJvmInfo(String body,
+                               @PathParam(Parameters.SYSTEM_ID) String systemId,
+                               @PathParam(Parameters.JVM_ID) String jvmId,
+                               @QueryParam(Parameters.QUERY) String queries,
+                               @Context ServletContext context) {
+        try {
+            ThermostatMongoStorage storage = (ThermostatMongoStorage) context.getAttribute(ServletContextConstants.MONGODB_CLIENT_ATTRIBUTE);
+
+            mongoStorageHandler.updateJvmInfo(storage.getDatabase().getCollection(collectionName), body, systemId, jvmId, queries);
+
+            return Response.status(Response.Status.OK).build();
+        } catch (Exception e) {
+            return Response.status(Response.Status.BAD_REQUEST).build();
+        }
+    }
+
+    @DELETE
+    @Path("/systems/{" + Parameters.SYSTEM_ID +"}/jvms/{" + Parameters.JVM_ID +"}")
+    @Consumes({ "application/json" })
+    @Produces({ "application/json", "text/html; charset=utf-8" })
+    public Response deleteJvmInfo(@PathParam(Parameters.SYSTEM_ID) String systemId,
+                                  @PathParam(Parameters.JVM_ID) String jvmId,
+                                  @Context ServletContext context) {
+        try {
+            ThermostatMongoStorage storage = (ThermostatMongoStorage) context.getAttribute(ServletContextConstants.MONGODB_CLIENT_ATTRIBUTE);
+
+            mongoStorageHandler.deleteJvmInfo(storage.getDatabase().getCollection(collectionName), systemId, jvmId);
+
+            return Response.status(Response.Status.OK).build();
+        } catch (Exception e) {
+            return Response.status(Response.Status.BAD_REQUEST).build();
+        }
+    }
+
+    @PUT
+    @Path("/update/systems/{" + Parameters.SYSTEM_ID + "}/ts/{" + Parameters.TIMESTAMP +"}")
+    public Response putUpdateTimestamp(String body,
+                                       @PathParam(Parameters.SYSTEM_ID) String systemId,
+                                       @PathParam(Parameters.TIMESTAMP) Long timeStamp,
+                                       @Context ServletContext context) {
+        try {
+            ThermostatMongoStorage storage = (ThermostatMongoStorage) context.getAttribute(ServletContextConstants.MONGODB_CLIENT_ATTRIBUTE);
+
+            mongoStorageHandler.updateTimestamps(storage.getDatabase().getCollection(collectionName), body, systemId, timeStamp);
+
+            return Response.status(Response.Status.OK).build();
+        } catch (Exception e) {
+            e.printStackTrace();
+            return Response.status(Response.Status.BAD_REQUEST).build();
+        }
+    }
+
+    @GET
+    @Path("/tree")
+    @Consumes({ "application/json" })
+    @Produces({ "application/json", "text/html; charset=utf-8" })
+    public Response getJvmInfoTree(@QueryParam(Parameters.LIMIT) @DefaultValue("1") Integer limit,
+                                   @QueryParam(Parameters.OFFSET) @DefaultValue("0") Integer offset,
+                                   @QueryParam(Parameters.ALIVE_ONLY) @DefaultValue("true") Boolean aliveOnly,
+                                   @QueryParam(Parameters.INCLUDE) String includes,
+                                   @QueryParam(Parameters.EXCLUDE) String excludes,
+                                   @Context ServletContext context) {
+        try {
+            ThermostatMongoStorage storage = (ThermostatMongoStorage) context.getAttribute(ServletContextConstants.MONGODB_CLIENT_ATTRIBUTE);
+
+            String message = mongoStorageHandler.getJvmsTree(storage.getDatabase().getCollection(collectionName), aliveOnly, excludes, includes, limit, offset);
+
+            return Response.status(Response.Status.OK).entity(message).build();
+        } catch (Exception e) {
+            e.printStackTrace();
+            return Response.status(Response.Status.BAD_REQUEST).build();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/services/jvms/src/main/java/com/redhat/thermostat/service/jvms/http/Parameters.java	Tue May 30 09:16:06 2017 -0400
@@ -0,0 +1,52 @@
+/*
+ * 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.service.jvms.http;
+
+class Parameters {
+    static final String SYSTEM_ID = "systemId";
+    static final String JVM_ID = "jvmId";
+
+    static final String SORT = "sort";
+    static final String QUERY = "query";
+    static final String OFFSET = "offset";
+    static final String LIMIT = "limit";
+    static final String INCLUDE = "include";
+    static final String EXCLUDE = "exclude";
+
+    static final String TIMESTAMP = "timeStamp";
+    static final String ALIVE_ONLY = "aliveOnly";
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/services/jvms/src/main/java/com/redhat/thermostat/service/jvms/http/SwaggerSpecResourceHandler.java	Tue May 30 09:16:06 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.service.jvms.http;
+
+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/jvms/src/main/java/com/redhat/thermostat/service/jvms/mongo/Fields.java	Tue May 30 09:16:06 2017 -0400
@@ -0,0 +1,49 @@
+/*
+ * 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.service.jvms.mongo;
+
+class Fields {
+    static final String SYSTEM_ID = "systemId";
+    static final String JVM_ID = "jvmId";
+
+    static final String SET = "set";
+    static final String LAST_UPDATED = "lastUpdated";
+    static final String STOP_TIME = "stopTime";
+
+    static final String RESPONSE = "response";
+    static final String JVMS = "jvms";
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/services/jvms/src/main/java/com/redhat/thermostat/service/jvms/mongo/MongoStorageHandler.java	Tue May 30 09:16:06 2017 -0400
@@ -0,0 +1,244 @@
+/*
+ * 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.service.jvms.mongo;
+
+import static com.mongodb.client.model.Filters.and;
+import static com.mongodb.client.model.Filters.eq;
+import static com.mongodb.client.model.Filters.lt;
+import static com.mongodb.client.model.Filters.or;
+import static com.mongodb.client.model.Projections.exclude;
+import static com.mongodb.client.model.Projections.excludeId;
+import static com.mongodb.client.model.Projections.fields;
+import static com.mongodb.client.model.Projections.include;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.bson.Document;
+import org.bson.conversions.Bson;
+
+import com.mongodb.BasicDBObject;
+import com.mongodb.Block;
+import com.mongodb.CursorType;
+import com.mongodb.DBObject;
+import com.mongodb.client.FindIterable;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.util.JSON;
+import com.redhat.thermostat.gateway.common.mongodb.filters.MongoRequestFilters;
+import com.redhat.thermostat.gateway.common.mongodb.filters.MongoSortFilters;
+import com.redhat.thermostat.gateway.common.mongodb.response.MongoResponseBuilder;
+
+public class MongoStorageHandler {
+
+    private final MongoResponseBuilder mongoResponseBuilder = new MongoResponseBuilder();
+
+    public String getJvmInfos(MongoCollection<Document> collection, String systemId, Integer limit, Integer offset, String sort, String queries, String includes, String excludes) {
+        Bson baseQuery = eq(Fields.SYSTEM_ID, systemId);
+        FindIterable<Document> documents;
+        if (queries != null) {
+            List<String> queriesList = Arrays.asList(queries.split(","));
+            final Bson query = MongoRequestFilters.buildQueriesFilter(queriesList);
+            documents = collection.find(and(baseQuery, query));
+        } else {
+            documents = collection.find(baseQuery);
+        }
+
+        documents = buildProjection(documents, includes, excludes);
+
+        final Bson sortObject = MongoSortFilters.createSortObject(sort);
+        documents = documents.sort(sortObject).limit(limit).skip(offset).batchSize(limit).cursorType(CursorType.NonTailable);
+
+        return mongoResponseBuilder.buildGetResponseString(documents);
+    }
+
+    public String getJvmInfo(MongoCollection<Document> collection, String systemId, String jvmId, String includes, String excludes) {
+        Bson query = and(eq(Fields.JVM_ID, jvmId), eq(Fields.SYSTEM_ID, systemId));
+        FindIterable<Document> documents = collection.find(query);
+
+        documents = buildProjection(documents, includes, excludes);
+
+        documents = documents.limit(1).skip(0).batchSize(1).cursorType(CursorType.NonTailable);
+
+        return mongoResponseBuilder.buildGetResponseString(documents);
+    }
+
+    private FindIterable<Document> buildProjection(FindIterable<Document> documents, String includes, String excludes) {
+        if (excludes != null) {
+            List<String> excludesList = Arrays.asList(excludes.split(","));
+            documents = documents.projection(fields(exclude(excludesList), excludeId()));
+        } else if (includes != null) {
+            List<String> includesList = Arrays.asList(includes.split(","));
+            documents = documents.projection(fields(include(includesList), excludeId()));
+        } else {
+            documents = documents.projection(excludeId());
+        }
+
+        return documents;
+    }
+
+    public void addJvmInfos(MongoCollection<DBObject> collection, String body, String systemId) {
+        if (body.length() > 0) {
+            List<DBObject> inputList = (List<DBObject>) JSON.parse(body);
+            for (DBObject o : inputList) {
+                o.put(Fields.SYSTEM_ID, systemId);
+            }
+            collection.insertMany(inputList);
+        }
+    }
+
+    public void deleteJvmInfos(MongoCollection<Document> collection, String systemId) {
+        Bson query = eq(Fields.SYSTEM_ID, systemId);
+        deleteDocuments(collection, query);
+    }
+    public void deleteJvmInfo(MongoCollection<Document> collection, String systemId, String jvmId) {
+        Bson query = and(eq(Fields.JVM_ID, jvmId), eq(Fields.SYSTEM_ID, systemId));
+        deleteDocuments(collection, query);
+    }
+
+    private void deleteDocuments(MongoCollection<Document> collection, Bson query) {
+        collection.deleteMany(query);
+    }
+
+    public void updateJvmInfo(MongoCollection<Document> collection, String body, String systemId, String jvmId, String queries) {
+        Bson baseQuery = and(eq(Fields.JVM_ID, jvmId), eq(Fields.SYSTEM_ID, systemId));
+
+        BasicDBObject inputObject = (BasicDBObject) JSON.parse(body);
+        BasicDBObject setObject = (BasicDBObject) inputObject.get(Fields.SET);
+        if (setObject.containsField(Fields.JVM_ID) || setObject.containsField(Fields.SYSTEM_ID)) {
+            throw new UnsupportedOperationException("Updating " +  Fields.SYSTEM_ID + " or " + Fields.JVM_ID + " fields is not allowed");
+        }
+
+        final List<String> queriesList;
+        if (queries != null) {
+            queriesList = Arrays.asList(queries.split(","));
+        } else {
+            queriesList = Collections.emptyList();
+        }
+
+        final Bson fields = new Document("$set", setObject);
+
+        collection.updateMany(and(baseQuery, MongoRequestFilters.buildQueriesFilter(queriesList)), fields);
+    }
+
+    public void updateTimestamps(MongoCollection<Document> collection, String body, String systemId, Long timeStamp) {
+        final Bson filter;
+        if (body != null && body.length() > 0) {
+            List<String> jvms = (List<String>) JSON.parse(body);
+            List<Bson> jvmFilters = new ArrayList<>();
+            for (String id : jvms) {
+                jvmFilters.add(eq(Fields.JVM_ID, id));
+            }
+            filter = and(eq(Fields.SYSTEM_ID, systemId), or(jvmFilters));
+        } else {
+            filter = eq(Fields.SYSTEM_ID, systemId);
+        }
+
+        String setDocument = "{ \"$set\" : { \"" + Fields.LAST_UPDATED + "\":" + timeStamp + " } }";
+        final Bson update = Document.parse(setDocument);
+        collection.updateMany(filter, update);
+    }
+
+    public String getJvmsTree(MongoCollection<Document> collection, boolean aliveOnly, String excludes, String includes, int limit, int offset) {
+        FindIterable<Document> documents;
+
+        if (aliveOnly) {
+            documents = collection.find(lt(Fields.STOP_TIME, 0));
+        } else {
+            documents = collection.find();
+        }
+
+        documents = documents.limit(limit).skip(offset);
+
+        boolean includeSystemId = true;
+        if (excludes != null) {
+            List<String> excludesList = new ArrayList<>(Arrays.asList(excludes.split(",")));
+            if (excludesList.contains(Fields.SYSTEM_ID)) {
+                excludesList.remove(Fields.SYSTEM_ID);
+                includeSystemId = false;
+            }
+            if (excludesList.size() > 0) {
+                documents = documents.projection(fields(exclude(excludesList), excludeId()));
+            } else {
+                documents = documents.projection(excludeId());
+            }
+        } else if (includes != null) {
+            List<String> includesList = new ArrayList<>(Arrays.asList(includes.split(",")));
+            if (!includesList.contains(Fields.SYSTEM_ID)) {
+                includesList.add(Fields.SYSTEM_ID);
+                includeSystemId = false;
+            }
+            documents = documents.projection(fields(include(includesList), excludeId()));
+        } else {
+            documents = documents.projection(excludeId());
+        }
+
+        final Map<String, StringBuilder> map = new HashMap<>();
+
+        final boolean finalIncludeSystemId = includeSystemId;
+        documents.forEach(new Block<Document>() {
+            @Override
+            public void apply(Document document) {
+                String systemId = document.getString(Fields.SYSTEM_ID);
+                if (!finalIncludeSystemId) {
+                    document.remove(Fields.SYSTEM_ID);
+                }
+
+                if (!map.containsKey(systemId)) {
+                    map.put(systemId, new StringBuilder().append("{\"" + Fields.SYSTEM_ID + "\":\"" + systemId + "\", \"" + Fields.JVMS + "\":["));
+                }
+
+                map.get(systemId).append(document.toJson()).append(",");
+            }
+        });
+
+        StringBuilder responseBuilder = new StringBuilder().append("{ \"" + Fields.RESPONSE + "\" : [");
+        if (map.size() > 0) {
+            for (StringBuilder systemBuilder : map.values()) {
+                responseBuilder.append(systemBuilder.deleteCharAt(systemBuilder.length() - 1).toString());
+                responseBuilder.append("]},");
+            }
+            responseBuilder.deleteCharAt(responseBuilder.length() - 1);
+        }
+        responseBuilder.append("]}");
+
+        return responseBuilder.toString();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/services/jvms/src/main/resources/jvms-swagger.yaml	Tue May 30 09:16:06 2017 -0400
@@ -0,0 +1,339 @@
+swagger: '2.0'
+info:
+  version: 0.0.1
+  title: Thermostat Web Gateway JVM Information 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: /jvms/0.0.1
+paths:
+  '/systems/{systemId}':
+    parameters:
+      - $ref: '#/parameters/system-id'
+    get:
+      description: 'Get jvms for system {systemId}'
+      parameters:
+        - $ref: '#/parameters/limit'
+        - $ref: '#/parameters/offset'
+        - $ref: '#/parameters/sort'
+        - $ref: '#/parameters/include'
+        - $ref: '#/parameters/exclude'
+        - $ref: '#/parameters/query'
+      responses:
+        '200':
+          description: OK
+          schema:
+            $ref: '#/definitions/jvms-get-response'
+    post:
+      description: 'Add jvms for system {systemId}'
+      parameters:
+        - $ref: '#/parameters/jvms-post-body'
+      responses:
+        '200':
+          description: OK
+    delete:
+      description: 'Delete all jvms on system {systemId}'
+      responses:
+        '200':
+          description: OK
+  '/systems/{systemId}/jvms/{jvmId}':
+    parameters:
+    - $ref: '#/parameters/system-id'
+    - $ref: '#/parameters/jvm-id'
+    get:
+      description: 'Get information for the JVM with id {jvmId} running on system {systemId}'
+      parameters:
+        - $ref: '#/parameters/include'
+        - $ref: '#/parameters/exclude'
+      responses:
+        '200':
+          description: OK
+          schema:
+            $ref: '#/definitions/jvms-get-response'
+    put:
+      description: 'Update the JVM with id {jvmId} running on system {systemId}'
+      responses:
+        '200':
+          description: OK
+      parameters:
+      - $ref: "#/parameters/jvms-put-body"
+    delete:
+      description: 'Delete the JVM with id {jvmId} running on system {systemId}'
+      responses:
+        '200':
+          description: OK
+  '/tree':
+    get:
+      description: Get jvm information organized by systemId
+      parameters:
+        - $ref: '#/parameters/alive-only'
+        - $ref: '#/parameters/include'
+        - $ref: '#/parameters/exclude'
+        - $ref: '#/parameters/limit'
+        - $ref: '#/parameters/offset'
+      responses:
+        '200':
+          description: OK
+          schema:
+            $ref: '#/definitions/tree-get-response'
+  '/update/systems/{systemId}/ts/{timeStamp}':
+    parameters:
+      - $ref: '#/parameters/system-id'
+      - $ref: '#/parameters/timestamp'
+    put:
+      description: 'Set last updated to {timeStamp} for jvm information on system {systemId}'
+      parameters:
+      - $ref: "#/parameters/update-put-body"
+      responses:
+        '200':
+          description: OK
+
+definitions:
+  jvm-get-info:
+    type: object
+    properties:
+      systemId:
+        type: string
+      agentId:
+        type: string
+      jvmId:
+        type: string
+      mainClass:
+        type: string
+      startTime:
+        type: integer
+        format: int64
+      endTime:
+        type: integer
+        format: int64
+      jvmPid:
+        type: integer
+      javaVersion:
+        type: string
+      javaHome:
+        type: string
+      javaCommandLine:
+        type: string
+      jvmArguments:
+        type: string
+      jvmName:
+        type: string
+      jvmInfo:
+        type: string
+      jvmVersion:
+        type: string
+      classpath:
+        type: string
+      environment:
+        type: array
+        items:
+          $ref: '#/definitions/environment-items'
+      uid:
+        type: integer
+        format: int64
+      username:
+        type: string
+      lastUpdated:
+        type: integer
+        format: int64
+
+  jvm-post-info:
+    type: object
+    properties:
+      agentId:
+        type: string
+      jvmId:
+        type: string
+      mainClass:
+        type: string
+      startTime:
+        type: integer
+        format: int64
+      stopTime:
+        type: integer
+        format: int64
+      jvmPid:
+        type: integer
+      javaVersion:
+        type: string
+      javaHome:
+        type: string
+      javaCommandLine:
+        type: string
+      jvmArguments:
+        type: string
+      jvmName:
+        type: string
+      jvmInfo:
+        type: string
+      jvmVersion:
+        type: string
+      classpath:
+        type: string
+      environment:
+        type: array
+        items:
+          $ref: '#/definitions/environment-items'
+      uid:
+        type: integer
+        format: int64
+      username:
+        type: string
+      lastUpdated:
+        type: integer
+        format: int64
+  environment-items:
+    type: object
+    properties:
+      key:
+        type: string
+      value:
+        type: string
+
+  jvms-get-response:
+    type: object
+    properties:
+      response:
+        type: array
+        items:
+          $ref: '#/definitions/jvm-get-info'
+  jvms-put-body:
+    type: object
+    properties:
+      set:
+        type: object
+  jvms-post-body:
+    type: array
+    items:
+      $ref: '#/definitions/jvm-post-info'
+
+  update-put-body:
+    type: array
+    description: 'An array of jvm ID strings'
+    items:
+      type: string
+
+  tree-get-response:
+    type: object
+    properties:
+      systemId:
+        type: string
+      jvms:
+        type: array
+        items:
+          $ref: '#/definitions/jvm-get-info'
+parameters:
+  system-id:
+    name: systemId
+    in: path
+    required: true
+    type: string
+    description: The system ID for the jvms
+  jvm-id:
+    name: jvmId
+    in: path
+    required: true
+    type: string
+    description: The ID of the jvm
+  timestamp:
+    name: timeStamp
+    in: path
+    required: true
+    type: integer
+    format: int64
+    description: The timestamp to set the last_updated field for.
+  jvms-post-body:
+    name: body
+    in: body
+    description: The jvm information
+    required: true
+    schema:
+      $ref: '#/definitions/jvms-post-body'
+  jvms-put-body:
+    name: body
+    in: body
+    description: >-
+      The JSON object containing a 'set' object. This contains single item JSON
+      objects that specify the field to replace and the JSON value to replace with.
+      Must not include 'systemId' or 'jvmId' fields. Example { "set" : {
+      "field" : "value", "field2":{"object":"item"} }
+    required: true
+    schema:
+      $ref: '#/definitions/jvms-put-body'
+  update-put-body:
+    name: body
+    in: body
+    description: >-
+      An array of jvmIds for which to update the last_updated field.
+    required: true
+    schema:
+      $ref: '#/definitions/update-put-body'
+  limit:
+    name: limit
+    in: query
+    description: Limit of items to return. Example '1'
+    type: integer
+    required: false
+    default: 1
+  offset:
+    name: offset
+    in: query
+    description: Offset of items 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
+  alive-only:
+    name: aliveOnly
+    in: query
+    description: Whether or not to return only JVMs that are live
+    type: boolean
+    default: true
+    required: false
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/services/jvms/src/main/webapp/WEB-INF/web.xml	Tue May 30 09:16:06 2017 -0400
@@ -0,0 +1,68 @@
+<!--
+
+ 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>JvmsServlet</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.service.jvms.http,
+            </param-value>
+        </init-param>
+    </servlet>
+    <servlet-mapping>
+        <servlet-name>JvmsServlet</servlet-name>
+        <url-pattern>/0.0.1/*</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>
+</web-app>
\ No newline at end of file
--- a/services/pom.xml	Fri May 19 16:03:47 2017 +0200
+++ b/services/pom.xml	Tue May 30 09:16:06 2017 -0400
@@ -51,9 +51,10 @@
     <name>Thermostat Services</name>
 
     <modules>
-        <module>jvm-memory</module>
+        <module>commands</module>
         <module>jvm-gc</module>
-        <module>commands</module>
+        <module>jvms</module>
+        <module>jvm-memory</module>
         <module>white-pages</module>
     </modules>
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/integration-tests/src/test/java/com/redhat/thermostat/gateway/service/jvms/JvmsServiceIntegrationTest.java	Tue May 30 09:16:06 2017 -0400
@@ -0,0 +1,621 @@
+/*
+ * 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.jvms;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.util.StringContentProvider;
+import org.eclipse.jetty.http.HttpMethod;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.redhat.thermostat.gateway.tests.integration.IntegrationTest;
+
+public class JvmsServiceIntegrationTest extends IntegrationTest {
+    private final String collectionName = "jvm-info";
+    private final String jvmsUrl = baseUrl + "/jvms/0.0.1";
+
+    private final String postData = "[{ \"agentId\" : \"aid\", \"jvmId\" : \"jid1\", \"jvmPid\" : 1, \"startTime\" :" +
+            "{ \"$numberLong\" : \"1495727607481\" }, \"stopTime\" : { \"$numberLong\" : \"-9223372036854775808\" }," +
+            " \"javaVersion\" : \"1.8.0_131\", \"javaHome\" : \"/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.131-1.b12.fc24.x86_64/jre\"," +
+            " \"mainClass\" : \"mc\", \"javaCommandLine\" : \"j cl\", \"jvmName\" : \"vm\", \"vmArguments\" :" +
+            " \"-Djline.log.jul=true\", \"jvmInfo\" : \"mixed mode\", \"lastUpdated\" : 0, \"jvmVersion\" :" +
+            " \"25.131-b12\", \"environment\" : [{ \"key\" : \"PATH\", \"value\" : \"/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin\"" +
+            " }, { \"key\" : \"XAUTHORITY\", \"value\" : \"/run/user/1000/gdm/Xauthority\" }, { \"key\" : \"GDMSESSION\"," +
+            " \"value\" : \"i3\" }, { \"key\" : \"fish_greeting\", \"value\" : \"\" }, { \"key\" : \"TERM\", \"value\" :" +
+            " \"xterm-256color\" }, { \"key\" : \"DARWIN_MODE\", \"value\" : \"0\" }, { \"key\" : \"LANG\", \"value\" :" +
+            " \"en_US.UTF-8\" }, { \"key\" : \"DBUS_SESSION_BUS_ADDRESS\", \"value\" : \"unix:path=/run/user/1000/bus\" }," +
+            " { \"key\" : \"XDG_SESSION_ID\", \"value\" : \"2\" }, { \"key\" : \"XDG_SESSION_TYPE\", \"value\" : \"x11\" }," +
+            " { \"key\" : \"XDG_CURRENT_DESKTOP\", \"value\" : \"i3\" }, { \"key\" : \"DISPLAY\", \"value\" : \":0\" }," +
+            " { \"key\" : \"CYGWIN_MODE\", \"value\" : \"0\" }, { \"key\" : \"COLORTERM\", \"value\" : \"truecolor\" }, " +
+            "{ \"key\" : \"_\", \"value\" : \"/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.131-1.b12.fc24.x86_64/jre/../bin/java\" }]," +
+            " \"uid\" : 1000, \"username\" : \"user\"},{ \"agentId\" : \"aid\", \"jvmId\" : \"jid2\", \"jvmPid\" : 2," +
+            " \"startTime\" : { \"$numberLong\" : \"1495727607481\" }, \"stopTime\" : { \"$numberLong\" : \"1495727607482\" }," +
+            " \"javaVersion\" : \"1.8.0_131\", \"javaHome\" : \"/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.131-1.b12.fc24.x86_64/jre\"," +
+            " \"mainClass\" : \"mc\", \"javaCommandLine\" : \"j cl\", \"jvmName\" : \"vm\", \"vmArguments\" :" +
+            " \"-Djline.log.jul=true\", \"jvmInfo\" : \"mixed mode\", \"lastUpdated\" : 0, \"jvmVersion\" : \"25.131-b12\"," +
+            " \"environment\" : [{ \"key\" : \"PATH\", \"value\" : \"/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin\" }," +
+            " { \"key\" : \"XAUTHORITY\", \"value\" : \"/run/user/1000/gdm/Xauthority\" }, { \"key\" : \"GDMSESSION\"," +
+            " \"value\" : \"i3\" }, { \"key\" : \"fish_greeting\", \"value\" : \"\" }, { \"key\" : \"TERM\", \"value\" :" +
+            " \"xterm-256color\" }, { \"key\" : \"DARWIN_MODE\", \"value\" : \"0\" }, { \"key\" : \"LANG\", \"value\" :" +
+            " \"en_US.UTF-8\" }, { \"key\" : \"DBUS_SESSION_BUS_ADDRESS\", \"value\" : \"unix:path=/run/user/1000/bus\" }," +
+            " { \"key\" : \"XDG_SESSION_ID\", \"value\" : \"2\" }, { \"key\" : \"XDG_SESSION_TYPE\", \"value\" : \"x11\" }," +
+            " { \"key\" : \"XDG_CURRENT_DESKTOP\", \"value\" : \"i3\" }, { \"key\" : \"DISPLAY\", \"value\" : \":0\" }," +
+            " { \"key\" : \"CYGWIN_MODE\", \"value\" : \"0\" }, { \"key\" : \"COLORTERM\", \"value\" : \"truecolor\" }," +
+            " { \"key\" : \"_\", \"value\" : \"/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.131-1.b12.fc24.x86_64/jre/../bin/java\" }]," +
+            " \"uid\" : 1000, \"username\" : \"user\"}]";
+
+        private final String postDataWithSystemId = postData.substring(0, postData.length() - 2) +
+                ", \"systemId\" : \"invalid\"}]";
+
+    @Before
+    public void beforeIntegrationTest() {
+        mongodTestUtil.dropCollection(collectionName);
+    }
+
+    @Test
+    public void testGetEmpty() throws InterruptedException, TimeoutException, ExecutionException {
+        String url = jvmsUrl + "/systems/1";
+        ContentResponse response = client.newRequest(url).method(HttpMethod.GET).send();
+        assertEquals(200, response.getStatus());
+        String expected = "{ \"response\" : [] }";
+        assertEquals(expected, response.getContentAsString());
+    }
+
+    @Test
+    public void testPost() throws InterruptedException, ExecutionException, TimeoutException {
+        String url = jvmsUrl + "/systems/1";
+
+        ContentResponse postResponse = client.newRequest(url).method(HttpMethod.POST)
+                .content(new StringContentProvider(postData), "application/json").send();
+        assertEquals(200, postResponse.getStatus());
+
+        ContentResponse getResponse = client.newRequest(url).method(HttpMethod.GET).send();
+        assertEquals(200, getResponse.getStatus());
+        String expected = "{ \"response\" : [{ \"agentId\" : \"aid\", \"jvmId\" : \"jid1\", \"jvmPid\" : 1, " +
+                "\"startTime\" : { \"$numberLong\" : \"1495727607481\" }, \"stopTime\" : { \"$numberLong\" : " +
+                "\"-9223372036854775808\" }, \"javaVersion\" : \"1.8.0_131\", \"javaHome\" : " +
+                "\"/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.131-1.b12.fc24.x86_64/jre\", \"mainClass\" : \"mc\", " +
+                "\"javaCommandLine\" : \"j cl\", \"jvmName\" : \"vm\", \"vmArguments\" : \"-Djline.log.jul=true\"," +
+                " \"jvmInfo\" : \"mixed mode\", \"lastUpdated\" : 0, \"jvmVersion\" : \"25.131-b12\", \"environment\"" +
+                " : [{ \"key\" : \"PATH\", \"value\" : \"/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin\" }," +
+                " { \"key\" : \"XAUTHORITY\", \"value\" : \"/run/user/1000/gdm/Xauthority\" }, { \"key\" :" +
+                " \"GDMSESSION\", \"value\" : \"i3\" }, { \"key\" : \"fish_greeting\", \"value\" : \"\" }, { \"key\" :" +
+                " \"TERM\", \"value\" : \"xterm-256color\" }, { \"key\" : \"DARWIN_MODE\", \"value\" : \"0\" }," +
+                " { \"key\" : \"LANG\", \"value\" : \"en_US.UTF-8\" }, { \"key\" : \"DBUS_SESSION_BUS_ADDRESS\"," +
+                " \"value\" : \"unix:path=/run/user/1000/bus\" }, { \"key\" : \"XDG_SESSION_ID\", \"value\" : \"2\" }," +
+                " { \"key\" : \"XDG_SESSION_TYPE\", \"value\" : \"x11\" }, { \"key\" : \"XDG_CURRENT_DESKTOP\"," +
+                " \"value\" : \"i3\" }, { \"key\" : \"DISPLAY\", \"value\" : \":0\" }, { \"key\" : \"CYGWIN_MODE\"," +
+                " \"value\" : \"0\" }, { \"key\" : \"COLORTERM\", \"value\" : \"truecolor\" }, { \"key\" : \"_\", " +
+                "\"value\" : \"/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.131-1.b12.fc24.x86_64/jre/../bin/java\" }], " +
+                "\"uid\" : 1000, \"username\" : \"user\", \"systemId\" : \"1\" }] }";
+        assertEquals(expected, getResponse.getContentAsString());
+    }
+
+    /*
+     * Verify POST does not override path parameter systemId with body systemId
+     * POST /systems/1 [{"systemId":2 ... }] should result in systemId of 1, not 2
+     */
+    @Test
+    public void testPostWithSystemId() throws InterruptedException, ExecutionException, TimeoutException {
+        String url = jvmsUrl + "/systems/1";
+
+        ContentResponse postResponse = client.newRequest(url).method(HttpMethod.POST)
+                .content(new StringContentProvider(postDataWithSystemId), "application/json").send();
+        assertEquals(200, postResponse.getStatus());
+
+        ContentResponse getResponse = client.newRequest(url).method(HttpMethod.GET).send();
+        assertEquals(200, getResponse.getStatus());
+        String expected = "{ \"response\" : [{ \"agentId\" : \"aid\", \"jvmId\" : \"jid1\", \"jvmPid\" : 1," +
+                " \"startTime\" : { \"$numberLong\" : \"1495727607481\" }, \"stopTime\" : { \"$numberLong\" :" +
+                " \"-9223372036854775808\" }, \"javaVersion\" : \"1.8.0_131\", \"javaHome\" : \"/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.131-1.b12.fc24.x86_64/jre\", \"mainClass\" : \"mc\", " +
+                "\"javaCommandLine\" : \"j cl\", \"jvmName\" : \"vm\", \"vmArguments\" : \"-Djline.log.jul=true\"," +
+                " \"jvmInfo\" : \"mixed mode\", \"lastUpdated\" : 0, \"jvmVersion\" : \"25.131-b12\", \"environment\"" +
+                " : [{ \"key\" : \"PATH\", \"value\" : \"/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin\" }, { " +
+                "\"key\" : \"XAUTHORITY\", \"value\" : \"/run/user/1000/gdm/Xauthority\" }, { \"key\" : \"GDMSESSION\"," +
+                " \"value\" : \"i3\" }, { \"key\" : \"fish_greeting\", \"value\" : \"\" }, { \"key\" : \"TERM\", " +
+                "\"value\" : \"xterm-256color\" }, { \"key\" : \"DARWIN_MODE\", \"value\" : \"0\" }, { \"key\" : " +
+                "\"LANG\", \"value\" : \"en_US.UTF-8\" }, { \"key\" : \"DBUS_SESSION_BUS_ADDRESS\", \"value\" : \"unix:path=/run/user/1000/bus\" }, { \"key\" : \"XDG_SESSION_ID\", \"value\" : \"2\" }, { \"key\"" +
+                " : \"XDG_SESSION_TYPE\", \"value\" : \"x11\" }, { \"key\" : \"XDG_CURRENT_DESKTOP\", \"value\" : " +
+                "\"i3\" }, { \"key\" : \"DISPLAY\", \"value\" : \":0\" }, { \"key\" : \"CYGWIN_MODE\", \"value\" :" +
+                " \"0\" }, { \"key\" : \"COLORTERM\", \"value\" : \"truecolor\" }, { \"key\" : \"_\", \"value\"" +
+                " : \"/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.131-1.b12.fc24.x86_64/jre/../bin/java\" }], \"uid\" : 1000," +
+                " \"username\" : \"user\", \"systemId\" : \"1\" }] }";
+        assertEquals(expected, getResponse.getContentAsString());
+    }
+
+    @Test
+    public void testGetLimit() throws InterruptedException, ExecutionException, TimeoutException {
+        String url = jvmsUrl + "/systems/1";
+
+        ContentResponse postResponse = client.newRequest(url).method(HttpMethod.POST)
+                .content(new StringContentProvider(postData), "application/json").send();
+        assertEquals(200, postResponse.getStatus());
+
+        String query = "?limit=2";
+        ContentResponse getResponse = client.newRequest(url + query).method(HttpMethod.GET).send();
+        assertEquals(200, getResponse.getStatus());
+        String expected = "{ \"response\" : [{ \"agentId\" : \"aid\", \"jvmId\" : \"jid1\", \"jvmPid\" : 1," +
+                " \"startTime\" : { \"$numberLong\" : \"1495727607481\" }, \"stopTime\" : { \"$numberLong\" :" +
+                " \"-9223372036854775808\" }, \"javaVersion\" : \"1.8.0_131\", \"javaHome\" : " +
+                "\"/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.131-1.b12.fc24.x86_64/jre\", \"mainClass\" : \"mc\"," +
+                " \"javaCommandLine\" : \"j cl\", \"jvmName\" : \"vm\", \"vmArguments\" : \"-Djline.log.jul=true\"," +
+                " \"jvmInfo\" : \"mixed mode\", \"lastUpdated\" : 0, \"jvmVersion\" : \"25.131-b12\", \"environment\" " +
+                ": [{ \"key\" : \"PATH\", \"value\" : \"/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin\" }, {" +
+                " \"key\" : \"XAUTHORITY\", \"value\" : \"/run/user/1000/gdm/Xauthority\" }, { \"key\" : " +
+                "\"GDMSESSION\", \"value\" : \"i3\" }, { \"key\" : \"fish_greeting\", \"value\" : \"\" }, { \"key\" :" +
+                " \"TERM\", \"value\" : \"xterm-256color\" }, { \"key\" : \"DARWIN_MODE\", \"value\" : \"0\" }, { " +
+                "\"key\" : \"LANG\", \"value\" : \"en_US.UTF-8\" }, { \"key\" : \"DBUS_SESSION_BUS_ADDRESS\", \"value\"" +
+                " : \"unix:path=/run/user/1000/bus\" }, { \"key\" : \"XDG_SESSION_ID\", \"value\" : \"2\" }, { " +
+                "\"key\" : \"XDG_SESSION_TYPE\", \"value\" : \"x11\" }, { \"key\" : \"XDG_CURRENT_DESKTOP\", \"value\"" +
+                " : \"i3\" }, { \"key\" : \"DISPLAY\", \"value\" : \":0\" }, { \"key\" : \"CYGWIN_MODE\", \"value\" :" +
+                " \"0\" }, { \"key\" : \"COLORTERM\", \"value\" : \"truecolor\" }, { \"key\" : \"_\", \"value\" : " +
+                "\"/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.131-1.b12.fc24.x86_64/jre/../bin/java\" }], \"uid\" : 1000, " +
+                "\"username\" : \"user\", \"systemId\" : \"1\" },{ \"agentId\" : \"aid\", \"jvmId\" : \"jid2\", " +
+                "\"jvmPid\" : 2, \"startTime\" : { \"$numberLong\" : \"1495727607481\" }, \"stopTime\" : { " +
+                "\"$numberLong\" : \"1495727607482\" }, \"javaVersion\" : \"1.8.0_131\", \"javaHome\" : " +
+                "\"/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.131-1.b12.fc24.x86_64/jre\", \"mainClass\" : \"mc\", " +
+                "\"javaCommandLine\" : \"j cl\", \"jvmName\" : \"vm\", \"vmArguments\" : \"-Djline.log.jul=true\", " +
+                "\"jvmInfo\" : \"mixed mode\", \"lastUpdated\" : 0, \"jvmVersion\" : \"25.131-b12\", \"environment\" :" +
+                " [{ \"key\" : \"PATH\", \"value\" : \"/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin\" }, { " +
+                "\"key\" : \"XAUTHORITY\", \"value\" : \"/run/user/1000/gdm/Xauthority\" }, { \"key\" : \"GDMSESSION\"," +
+                " \"value\" : \"i3\" }, { \"key\" : \"fish_greeting\", \"value\" : \"\" }, { \"key\" : \"TERM\"," +
+                " \"value\" : \"xterm-256color\" }, { \"key\" : \"DARWIN_MODE\", \"value\" : \"0\" }, { \"key\" :" +
+                " \"LANG\", \"value\" : \"en_US.UTF-8\" }, { \"key\" : \"DBUS_SESSION_BUS_ADDRESS\", \"value\" : " +
+                "\"unix:path=/run/user/1000/bus\" }, { \"key\" : \"XDG_SESSION_ID\", \"value\" : \"2\" }, { \"key\" : " +
+                "\"XDG_SESSION_TYPE\", \"value\" : \"x11\" }, { \"key\" : \"XDG_CURRENT_DESKTOP\", \"value\" : \"i3\"" +
+                " }, { \"key\" : \"DISPLAY\", \"value\" : \":0\" }, { \"key\" : \"CYGWIN_MODE\", \"value\" : \"0\"" +
+                " }, { \"key\" : \"COLORTERM\", \"value\" : \"truecolor\" }, { \"key\" : \"_\", \"value\" : " +
+                "\"/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.131-1.b12.fc24.x86_64/jre/../bin/java\" }], \"uid\" : 1000, " +
+                "\"username\" : \"user\", \"systemId\" : \"1\" }] }";
+        assertEquals(expected, getResponse.getContentAsString());
+    }
+
+    @Test
+    public void testGetOffset() throws InterruptedException, ExecutionException, TimeoutException {
+        String url = jvmsUrl + "/systems/1";
+
+        ContentResponse postResponse = client.newRequest(url).method(HttpMethod.POST)
+                .content(new StringContentProvider(postData), "application/json").send();
+        assertEquals(200, postResponse.getStatus());
+
+        String query = "?offset=1";
+        ContentResponse getResponse = client.newRequest(url + query).method(HttpMethod.GET).send();
+        assertEquals(200, getResponse.getStatus());
+        String expected = "{ \"response\" : [{ \"agentId\" : \"aid\", \"jvmId\" : \"jid2\", \"jvmPid\" : 2, " +
+                "\"startTime\" : { \"$numberLong\" : \"1495727607481\" }, \"stopTime\" : { \"$numberLong\" : " +
+                "\"1495727607482\" }, \"javaVersion\" : \"1.8.0_131\", \"javaHome\" : \"/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.131-1.b12.fc24.x86_64/jre\", \"mainClass\" : \"mc\"," +
+                " \"javaCommandLine\" : \"j cl\", \"jvmName\" : \"vm\", \"vmArguments\" : \"-Djline.log.jul=true\"," +
+                " \"jvmInfo\" : \"mixed mode\", \"lastUpdated\" : 0, \"jvmVersion\" : \"25.131-b12\", \"environment\" " +
+                ": [{ \"key\" : \"PATH\", \"value\" : \"/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin\" }, { " +
+                "\"key\" : \"XAUTHORITY\", \"value\" : \"/run/user/1000/gdm/Xauthority\" }, { \"key\" : \"GDMSESSION\"," +
+                " \"value\" : \"i3\" }, { \"key\" : \"fish_greeting\", \"value\" : \"\" }, { \"key\" : \"TERM\"," +
+                " \"value\" : \"xterm-256color\" }, { \"key\" : \"DARWIN_MODE\", \"value\" : \"0\" }, { \"key\" :" +
+                " \"LANG\", \"value\" : \"en_US.UTF-8\" }, { \"key\" : \"DBUS_SESSION_BUS_ADDRESS\", \"value\" : \"unix:path=/run/user/1000/bus\" }, { \"key\" : \"XDG_SESSION_ID\", \"value\" : \"2\" }, { \"key\" " +
+                ": \"XDG_SESSION_TYPE\", \"value\" : \"x11\" }, { \"key\" : \"XDG_CURRENT_DESKTOP\", \"value\" :" +
+                " \"i3\" }, { \"key\" : \"DISPLAY\", \"value\" : \":0\" }, { \"key\" : \"CYGWIN_MODE\", \"value\" : " +
+                "\"0\" }, { \"key\" : \"COLORTERM\", \"value\" : \"truecolor\" }, { \"key\" : \"_\", \"value\" :" +
+                " \"/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.131-1.b12.fc24.x86_64/jre/../bin/java\" }], \"uid\" : 1000," +
+                " \"username\" : \"user\", \"systemId\" : \"1\" }] }";
+        assertEquals(expected, getResponse.getContentAsString());
+    }
+
+    @Test
+    public void testGetQuery() throws InterruptedException, ExecutionException, TimeoutException {
+        String url = jvmsUrl + "/systems/1";
+
+        ContentResponse postResponse = client.newRequest(url).method(HttpMethod.POST)
+                .content(new StringContentProvider(postData), "application/json").send();
+        assertEquals(200, postResponse.getStatus());
+
+        String query = "?query=jvmId==jid2";
+        ContentResponse getResponse = client.newRequest(url + query).method(HttpMethod.GET).send();
+        assertEquals(200, getResponse.getStatus());
+        String expected = "{ \"response\" : [{ \"agentId\" : \"aid\", \"jvmId\" : \"jid2\", \"jvmPid\" : 2, " +
+                "\"startTime\" : { \"$numberLong\" : \"1495727607481\" }, \"stopTime\" : { \"$numberLong\" : " +
+                "\"1495727607482\" }, \"javaVersion\" : \"1.8.0_131\", \"javaHome\" : \"/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.131-1.b12.fc24.x86_64/jre\", \"mainClass\" : \"mc\"," +
+                " \"javaCommandLine\" : \"j cl\", \"jvmName\" : \"vm\", \"vmArguments\" : \"-Djline.log.jul=true\"," +
+                " \"jvmInfo\" : \"mixed mode\", \"lastUpdated\" : 0, \"jvmVersion\" : \"25.131-b12\", \"environment\" " +
+                ": [{ \"key\" : \"PATH\", \"value\" : \"/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin\" }, { " +
+                "\"key\" : \"XAUTHORITY\", \"value\" : \"/run/user/1000/gdm/Xauthority\" }, { \"key\" : \"GDMSESSION\"," +
+                " \"value\" : \"i3\" }, { \"key\" : \"fish_greeting\", \"value\" : \"\" }, { \"key\" : \"TERM\"," +
+                " \"value\" : \"xterm-256color\" }, { \"key\" : \"DARWIN_MODE\", \"value\" : \"0\" }, { \"key\" :" +
+                " \"LANG\", \"value\" : \"en_US.UTF-8\" }, { \"key\" : \"DBUS_SESSION_BUS_ADDRESS\", \"value\" : \"unix:path=/run/user/1000/bus\" }, { \"key\" : \"XDG_SESSION_ID\", \"value\" : \"2\" }, { \"key\" " +
+                ": \"XDG_SESSION_TYPE\", \"value\" : \"x11\" }, { \"key\" : \"XDG_CURRENT_DESKTOP\", \"value\" :" +
+                " \"i3\" }, { \"key\" : \"DISPLAY\", \"value\" : \":0\" }, { \"key\" : \"CYGWIN_MODE\", \"value\" : " +
+                "\"0\" }, { \"key\" : \"COLORTERM\", \"value\" : \"truecolor\" }, { \"key\" : \"_\", \"value\" :" +
+                " \"/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.131-1.b12.fc24.x86_64/jre/../bin/java\" }], \"uid\" : 1000," +
+                " \"username\" : \"user\", \"systemId\" : \"1\" }] }";
+        assertEquals(expected, getResponse.getContentAsString());
+    }
+
+    @Test
+    public void testGetInclude() throws InterruptedException, ExecutionException, TimeoutException {
+        String url = jvmsUrl + "/systems/1";
+
+        ContentResponse postResponse = client.newRequest(url).method(HttpMethod.POST)
+                .content(new StringContentProvider(postData), "application/json").send();
+        assertEquals(200, postResponse.getStatus());
+
+        String query = "?include=agentId,jvmId";
+        ContentResponse getResponse = client.newRequest(url + query).method(HttpMethod.GET).send();
+        assertEquals(200, getResponse.getStatus());
+        String expected = "{ \"response\" : [{ \"agentId\" : \"aid\", \"jvmId\" : \"jid1\" }] }";
+        assertEquals(expected, getResponse.getContentAsString());
+    }
+
+    @Test
+    public void testGetExclude() throws InterruptedException, ExecutionException, TimeoutException {
+        String url = jvmsUrl + "/systems/1";
+
+        ContentResponse postResponse = client.newRequest(url).method(HttpMethod.POST)
+                .content(new StringContentProvider(postData), "application/json").send();
+        assertEquals(200, postResponse.getStatus());
+
+        String query = "?exclude=agentId";
+        ContentResponse getResponse = client.newRequest(url + query).method(HttpMethod.GET).send();
+        assertEquals(200, getResponse.getStatus());
+        String expected = "{ \"response\" : [{ \"jvmId\" : \"jid1\", \"jvmPid\" : 1, \"startTime\" : { \"$numberLong\"" +
+                " : \"1495727607481\" }, \"stopTime\" : { \"$numberLong\" : \"-9223372036854775808\" }, \"javaVersion\"" +
+                " : \"1.8.0_131\", \"javaHome\" : \"/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.131-1.b12.fc24.x86_64/jre\"," +
+                " \"mainClass\" : \"mc\", \"javaCommandLine\" : \"j cl\", \"jvmName\" : \"vm\", \"vmArguments\" :" +
+                " \"-Djline.log.jul=true\", \"jvmInfo\" : \"mixed mode\", \"lastUpdated\" : 0, \"jvmVersion\" : " +
+                "\"25.131-b12\", \"environment\" : [{ \"key\" : \"PATH\", \"value\" : " +
+                "\"/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin\" }, { \"key\" : \"XAUTHORITY\", \"value\" : " +
+                "\"/run/user/1000/gdm/Xauthority\" }, { \"key\" : \"GDMSESSION\", \"value\" : \"i3\" }, { \"key\" :" +
+                " \"fish_greeting\", \"value\" : \"\" }, { \"key\" : \"TERM\", \"value\" : \"xterm-256color\" }, { " +
+                "\"key\" : \"DARWIN_MODE\", \"value\" : \"0\" }, { \"key\" : \"LANG\", \"value\" : \"en_US.UTF-8\" }, {" +
+                " \"key\" : \"DBUS_SESSION_BUS_ADDRESS\", \"value\" : \"unix:path=/run/user/1000/bus\" }, { \"key\" :" +
+                " \"XDG_SESSION_ID\", \"value\" : \"2\" }, { \"key\" : \"XDG_SESSION_TYPE\", \"value\" : \"x11\" }," +
+                " { \"key\" : \"XDG_CURRENT_DESKTOP\", \"value\" : \"i3\" }, { \"key\" : \"DISPLAY\", \"value\" :" +
+                " \":0\" }, { \"key\" : \"CYGWIN_MODE\", \"value\" : \"0\" }, { \"key\" : \"COLORTERM\", \"value\" :" +
+                " \"truecolor\" }, { \"key\" : \"_\", \"value\" : " +
+                "\"/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.131-1.b12.fc24.x86_64/jre/../bin/java\" }], \"uid\" : 1000, " +
+                "\"username\" : \"user\", \"systemId\" : \"1\" }] }";
+        assertEquals(expected, getResponse.getContentAsString());
+    }
+
+    @Test
+    public void testDeleteMany() throws InterruptedException, TimeoutException, ExecutionException {
+        String url = jvmsUrl + "/systems/1";
+
+        ContentResponse postResponse = client.newRequest(url).method(HttpMethod.POST)
+                .content(new StringContentProvider(postData), "application/json").send();
+        assertEquals(200, postResponse.getStatus());
+
+        ContentResponse deleteResponse = client.newRequest(url).method(HttpMethod.DELETE).send();
+        assertEquals(200, deleteResponse.getStatus());
+
+        ContentResponse getResponse = client.newRequest(url).method(HttpMethod.GET).send();
+        assertEquals(200, getResponse.getStatus());
+        String expected = "{ \"response\" : [] }";
+        assertEquals(expected, getResponse.getContentAsString());
+    }
+
+    @Test
+    public void testPut() throws InterruptedException, TimeoutException, ExecutionException {
+        String url = jvmsUrl + "/systems/1";
+        String putUrl = jvmsUrl + "/systems/1/jvms/jid1";
+
+        ContentResponse postResponse = client.newRequest(url).method(HttpMethod.POST)
+                .content(new StringContentProvider(postData), "application/json").send();
+        assertEquals(200, postResponse.getStatus());
+
+        String setContent = "{\"set\":{\"mainClass\":\"hello\",\"javaVersion\":\"1.7.0\"}}";
+        ContentResponse putResponse = client.newRequest(putUrl).method(HttpMethod.PUT).
+                content(new StringContentProvider(setContent), "application/json").send();
+        assertEquals(200, putResponse.getStatus());
+
+        ContentResponse getResponse = client.newRequest(url).method(HttpMethod.GET).send();
+        assertEquals(200, getResponse.getStatus());
+        String expected = "{ \"response\" : [{ \"agentId\" : \"aid\", \"jvmId\" : \"jid1\", \"jvmPid\" : 1, " +
+                "\"startTime\" : { \"$numberLong\" : \"1495727607481\" }, \"stopTime\" : { \"$numberLong\" : " +
+                "\"-9223372036854775808\" }, \"javaVersion\" : \"1.7.0\", \"javaHome\" : " +
+                "\"/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.131-1.b12.fc24.x86_64/jre\", \"mainClass\" : \"hello\", " +
+                "\"javaCommandLine\" : \"j cl\", \"jvmName\" : \"vm\", \"vmArguments\" : \"-Djline.log.jul=true\"," +
+                " \"jvmInfo\" : \"mixed mode\", \"lastUpdated\" : 0, \"jvmVersion\" : \"25.131-b12\", \"environment\"" +
+                " : [{ \"key\" : \"PATH\", \"value\" : \"/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin\" }, " +
+                "{ \"key\" : \"XAUTHORITY\", \"value\" : \"/run/user/1000/gdm/Xauthority\" }, { \"key\" : " +
+                "\"GDMSESSION\", \"value\" : \"i3\" }, { \"key\" : \"fish_greeting\", \"value\" : \"\" }, { \"key\" : " +
+                "\"TERM\", \"value\" : \"xterm-256color\" }, { \"key\" : \"DARWIN_MODE\", \"value\" : \"0\" }, { " +
+                "\"key\" : \"LANG\", \"value\" : \"en_US.UTF-8\" }, { \"key\" : \"DBUS_SESSION_BUS_ADDRESS\", " +
+                "\"value\" : \"unix:path=/run/user/1000/bus\" }, { \"key\" : \"XDG_SESSION_ID\", \"value\" : \"2\" }, " +
+                "{ \"key\" : \"XDG_SESSION_TYPE\", \"value\" : \"x11\" }, { \"key\" : \"XDG_CURRENT_DESKTOP\", " +
+                "\"value\" : \"i3\" }, { \"key\" : \"DISPLAY\", \"value\" : \":0\" }, { \"key\" : \"CYGWIN_MODE\"," +
+                " \"value\" : \"0\" }, { \"key\" : \"COLORTERM\", \"value\" : \"truecolor\" }, { \"key\" : \"_\", " +
+                "\"value\" : \"/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.131-1.b12.fc24.x86_64/jre/../bin/java\" }], " +
+                "\"uid\" : 1000, \"username\" : \"user\", \"systemId\" : \"1\" }] }";
+        assertEquals(expected, getResponse.getContentAsString());
+    }
+
+    /*
+     * Verify that PUT cannot be used to update systemId or jvmId fields
+     */
+    @Test
+    public void testPutInvalidField() throws InterruptedException, TimeoutException, ExecutionException {
+        //
+        String url = jvmsUrl + "/systems/1";
+        String putUrl = jvmsUrl + "/systems/1/jvms/jid1";
+
+        ContentResponse postResponse = client.newRequest(url).method(HttpMethod.POST)
+                .content(new StringContentProvider(postData), "application/json").send();
+        assertEquals(200, postResponse.getStatus());
+
+        String setContent = "{\"set\":{\"systemId\":\"hello\"}}";
+        ContentResponse putResponse = client.newRequest(putUrl).method(HttpMethod.PUT)
+                .content(new StringContentProvider(setContent), "application/json").send();
+        assertEquals(400, putResponse.getStatus());
+
+        setContent = "{\"set\":{\"jvmId\":\"hello\"}}";
+        putResponse = client.newRequest(putUrl).method(HttpMethod.PUT)
+                .content(new StringContentProvider(setContent), "application/json").send();
+        assertEquals(400, putResponse.getStatus());
+
+        ContentResponse getResponse = client.newRequest(url).method(HttpMethod.GET).send();
+        assertEquals(200, getResponse.getStatus());
+        String expected = "{ \"response\" : [{ \"agentId\" : \"aid\", \"jvmId\" : \"jid1\", \"jvmPid\" : 1," +
+                " \"startTime\" : { \"$numberLong\" : \"1495727607481\" }, \"stopTime\" : { \"$numberLong\" : " +
+                "\"-9223372036854775808\" }, \"javaVersion\" : \"1.8.0_131\", \"javaHome\" : \"/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.131-1.b12.fc24.x86_64/jre\", \"mainClass\" : \"mc\", " +
+                "\"javaCommandLine\" : \"j cl\", \"jvmName\" : \"vm\", \"vmArguments\" : \"-Djline.log.jul=true\", " +
+                "\"jvmInfo\" : \"mixed mode\", \"lastUpdated\" : 0, \"jvmVersion\" : \"25.131-b12\", \"environment\" :" +
+                " [{ \"key\" : \"PATH\", \"value\" : \"/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin\" }, { \"key\"" +
+                " : \"XAUTHORITY\", \"value\" : \"/run/user/1000/gdm/Xauthority\" }, { \"key\" : \"GDMSESSION\"," +
+                " \"value\" : \"i3\" }, { \"key\" : \"fish_greeting\", \"value\" : \"\" }, { \"key\" : \"TERM\", " +
+                "\"value\" : \"xterm-256color\" }, { \"key\" : \"DARWIN_MODE\", \"value\" : \"0\" }, { \"key\" :" +
+                " \"LANG\", \"value\" : \"en_US.UTF-8\" }, { \"key\" : \"DBUS_SESSION_BUS_ADDRESS\", \"value\" :" +
+                " \"unix:path=/run/user/1000/bus\" }, { \"key\" : \"XDG_SESSION_ID\", \"value\" : \"2\" }, { \"key\"" +
+                " : \"XDG_SESSION_TYPE\", \"value\" : \"x11\" }, { \"key\" : \"XDG_CURRENT_DESKTOP\", \"value\" :" +
+                " \"i3\" }, { \"key\" : \"DISPLAY\", \"value\" : \":0\" }, { \"key\" : \"CYGWIN_MODE\", \"value\" :" +
+                " \"0\" }, { \"key\" : \"COLORTERM\", \"value\" : \"truecolor\" }, { \"key\" : \"_\", \"value\" :" +
+                " \"/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.131-1.b12.fc24.x86_64/jre/../bin/java\" }], \"uid\" : 1000," +
+                " \"username\" : \"user\", \"systemId\" : \"1\" }] }";
+        assertEquals(expected, getResponse.getContentAsString());
+    }
+
+    @Test
+    public void testDeleteOne() throws InterruptedException, TimeoutException, ExecutionException {
+        String url = jvmsUrl + "/systems/1";
+        String deleteUrl = jvmsUrl + "/systems/1/jvms/jid1";
+
+        ContentResponse postResponse = client.newRequest(url).method(HttpMethod.POST)
+                .content(new StringContentProvider(postData), "application/json").send();
+        assertEquals(200, postResponse.getStatus());
+
+        ContentResponse deleteResponse = client.newRequest(deleteUrl).method(HttpMethod.DELETE).send();
+        assertEquals(200, deleteResponse.getStatus());
+
+        ContentResponse getResponse = client.newRequest(url).method(HttpMethod.GET).send();
+        assertEquals(200, getResponse.getStatus());
+        String expected = "{ \"response\" : [{ \"agentId\" : \"aid\", \"jvmId\" : \"jid2\", \"jvmPid\" : 2, " +
+                "\"startTime\" : { \"$numberLong\" : \"1495727607481\" }, \"stopTime\" : { \"$numberLong\" : " +
+                "\"1495727607482\" }, \"javaVersion\" : \"1.8.0_131\", \"javaHome\" : \"/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.131-1.b12.fc24.x86_64/jre\", \"mainClass\" : \"mc\"," +
+                " \"javaCommandLine\" : \"j cl\", \"jvmName\" : \"vm\", \"vmArguments\" : \"-Djline.log.jul=true\"," +
+                " \"jvmInfo\" : \"mixed mode\", \"lastUpdated\" : 0, \"jvmVersion\" : \"25.131-b12\", \"environment\" " +
+                ": [{ \"key\" : \"PATH\", \"value\" : \"/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin\" }, { " +
+                "\"key\" : \"XAUTHORITY\", \"value\" : \"/run/user/1000/gdm/Xauthority\" }, { \"key\" : \"GDMSESSION\"," +
+                " \"value\" : \"i3\" }, { \"key\" : \"fish_greeting\", \"value\" : \"\" }, { \"key\" : \"TERM\"," +
+                " \"value\" : \"xterm-256color\" }, { \"key\" : \"DARWIN_MODE\", \"value\" : \"0\" }, { \"key\" :" +
+                " \"LANG\", \"value\" : \"en_US.UTF-8\" }, { \"key\" : \"DBUS_SESSION_BUS_ADDRESS\", \"value\" : \"unix:path=/run/user/1000/bus\" }, { \"key\" : \"XDG_SESSION_ID\", \"value\" : \"2\" }, { \"key\" " +
+                ": \"XDG_SESSION_TYPE\", \"value\" : \"x11\" }, { \"key\" : \"XDG_CURRENT_DESKTOP\", \"value\" :" +
+                " \"i3\" }, { \"key\" : \"DISPLAY\", \"value\" : \":0\" }, { \"key\" : \"CYGWIN_MODE\", \"value\" : " +
+                "\"0\" }, { \"key\" : \"COLORTERM\", \"value\" : \"truecolor\" }, { \"key\" : \"_\", \"value\" :" +
+                " \"/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.131-1.b12.fc24.x86_64/jre/../bin/java\" }], \"uid\" : 1000," +
+                " \"username\" : \"user\", \"systemId\" : \"1\" }] }";
+        assertEquals(expected, getResponse.getContentAsString());
+    }
+
+    @Test
+    public void testUpdateTimestamp() throws InterruptedException, TimeoutException, ExecutionException {
+        String updateUrl = jvmsUrl + "/update/systems/1/ts/2000";
+        String url = jvmsUrl + "/systems/1";
+
+        ContentResponse postResponse = client.newRequest(url).method(HttpMethod.POST)
+                .content(new StringContentProvider(postData), "application/json").send();
+        assertEquals(200, postResponse.getStatus());
+
+
+        ContentResponse updateResponse = client.newRequest(updateUrl).method(HttpMethod.PUT).send();
+        assertEquals(200, updateResponse.getStatus());
+
+        ContentResponse response = client.newRequest(url).method(HttpMethod.GET)
+                .param("include", "lastUpdated").send();
+        assertEquals(200, response.getStatus());
+        String expected = "{ \"response\" : [{ \"lastUpdated\" : 2000 }] }";
+        assertEquals(expected, response.getContentAsString());
+
+    }
+
+    @Test
+    public void testTree() throws InterruptedException, TimeoutException, ExecutionException {
+        String postUrl = jvmsUrl + "/systems/1";
+        String treeUrl = jvmsUrl + "/tree";
+
+        ContentResponse postResponse = client.newRequest(postUrl).method(HttpMethod.POST)
+                .content(new StringContentProvider(postData), "application/json").send();
+        assertEquals(200, postResponse.getStatus());
+
+        ContentResponse response = client.newRequest(treeUrl).method(HttpMethod.GET).send();
+        assertEquals(200, response.getStatus());
+        String expected = "{ \"response\" : [{\"systemId\":\"1\", \"jvms\":[{ \"agentId\" : \"aid\", \"jvmId\" : " +
+                "\"jid1\", \"jvmPid\" : 1, \"startTime\" : { \"$numberLong\" : \"1495727607481\" }, \"stopTime\" : {" +
+                " \"$numberLong\" : \"-9223372036854775808\" }, \"javaVersion\" : \"1.8.0_131\", \"javaHome\" : \"/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.131-1.b12.fc24.x86_64/jre\", \"mainClass\" : \"mc\", " +
+                "\"javaCommandLine\" : \"j cl\", \"jvmName\" : \"vm\", \"vmArguments\" : \"-Djline.log.jul=true\", " +
+                "\"jvmInfo\" : \"mixed mode\", \"lastUpdated\" : 0, \"jvmVersion\" : \"25.131-b12\", \"environment\" :" +
+                " [{ \"key\" : \"PATH\", \"value\" : \"/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin\" }, { \"key\"" +
+                " : \"XAUTHORITY\", \"value\" : \"/run/user/1000/gdm/Xauthority\" }, { \"key\" : \"GDMSESSION\", " +
+                "\"value\" : \"i3\" }, { \"key\" : \"fish_greeting\", \"value\" : \"\" }, { \"key\" : \"TERM\", " +
+                "\"value\" : \"xterm-256color\" }, { \"key\" : \"DARWIN_MODE\", \"value\" : \"0\" }, { \"key\" : " +
+                "\"LANG\", \"value\" : \"en_US.UTF-8\" }, { \"key\" : \"DBUS_SESSION_BUS_ADDRESS\", \"value\" : \"unix:path=/run/user/1000/bus\" }, { \"key\" : \"XDG_SESSION_ID\", \"value\" : \"2\" }, { \"key\"" +
+                " : \"XDG_SESSION_TYPE\", \"value\" : \"x11\" }, { \"key\" : \"XDG_CURRENT_DESKTOP\", \"value\" : " +
+                "\"i3\" }, { \"key\" : \"DISPLAY\", \"value\" : \":0\" }, { \"key\" : \"CYGWIN_MODE\", \"value\" : " +
+                "\"0\" }, { \"key\" : \"COLORTERM\", \"value\" : \"truecolor\" }, { \"key\" : \"_\", \"value\" :" +
+                " \"/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.131-1.b12.fc24.x86_64/jre/../bin/java\" }], \"uid\" : 1000," +
+                " \"username\" : \"user\", \"systemId\" : \"1\" }]}]}";
+        assertEquals(expected, response.getContentAsString());
+    }
+
+    @Test
+    public void testTreeInclude() throws InterruptedException, TimeoutException, ExecutionException {
+        String postUrl = jvmsUrl + "/systems/1";
+        String treeUrl = jvmsUrl + "/tree";
+
+        ContentResponse postResponse = client.newRequest(postUrl).method(HttpMethod.POST)
+                .content(new StringContentProvider(postData), "application/json").send();
+        assertEquals(200, postResponse.getStatus());
+
+        String query = "?include=jvmId";
+        ContentResponse response = client.newRequest(treeUrl + query).method(HttpMethod.GET).send();
+        assertEquals(200, response.getStatus());
+        String expected = "{ \"response\" : [{\"systemId\":\"1\", \"jvms\":[{ \"jvmId\" : \"jid1\" }]}]}";
+        assertEquals(expected, response.getContentAsString());
+    }
+
+    @Test
+    public void testTreeExclude() throws InterruptedException, TimeoutException, ExecutionException {
+        String postUrl = jvmsUrl + "/systems/1";
+        String treeUrl = jvmsUrl + "/tree";
+
+        ContentResponse postResponse = client.newRequest(postUrl).method(HttpMethod.POST)
+                .content(new StringContentProvider(postData), "application/json").send();
+        assertEquals(200, postResponse.getStatus());
+
+        String query = "?exclude=systemId";
+        ContentResponse response = client.newRequest(treeUrl + query).method(HttpMethod.GET).send();
+        assertEquals(200, response.getStatus());
+        String expected = "{ \"response\" : [{\"systemId\":\"1\", \"jvms\":[{ \"agentId\" : \"aid\", \"jvmId\" : " +
+                "\"jid1\", \"jvmPid\" : 1, \"startTime\" : { \"$numberLong\" : \"1495727607481\" }, \"stopTime\" : {" +
+                " \"$numberLong\" : \"-9223372036854775808\" }, \"javaVersion\" : \"1.8.0_131\", \"javaHome\" : \"/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.131-1.b12.fc24.x86_64/jre\", \"mainClass\" : \"mc\", " +
+                "\"javaCommandLine\" : \"j cl\", \"jvmName\" : \"vm\", \"vmArguments\" : \"-Djline.log.jul=true\", " +
+                "\"jvmInfo\" : \"mixed mode\", \"lastUpdated\" : 0, \"jvmVersion\" : \"25.131-b12\", \"environment\" : " +
+                "[{ \"key\" : \"PATH\", \"value\" : \"/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin\" }, { \"key\" :" +
+                " \"XAUTHORITY\", \"value\" : \"/run/user/1000/gdm/Xauthority\" }, { \"key\" : \"GDMSESSION\", \"value\"" +
+                " : \"i3\" }, { \"key\" : \"fish_greeting\", \"value\" : \"\" }, { \"key\" : \"TERM\", \"value\" : " +
+                "\"xterm-256color\" }, { \"key\" : \"DARWIN_MODE\", \"value\" : \"0\" }, { \"key\" : \"LANG\", " +
+                "\"value\" : \"en_US.UTF-8\" }, { \"key\" : \"DBUS_SESSION_BUS_ADDRESS\", \"value\" : " +
+                "\"unix:path=/run/user/1000/bus\" }, { \"key\" : \"XDG_SESSION_ID\", \"value\" : \"2\" }, { \"key\" :" +
+                " \"XDG_SESSION_TYPE\", \"value\" : \"x11\" }, { \"key\" : \"XDG_CURRENT_DESKTOP\", \"value\" : \"i3\"" +
+                " }, { \"key\" : \"DISPLAY\", \"value\" : \":0\" }, { \"key\" : \"CYGWIN_MODE\", \"value\" : \"0\" }, " +
+                "{ \"key\" : \"COLORTERM\", \"value\" : \"truecolor\" }, { \"key\" : \"_\", \"value\" : " +
+                "\"/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.131-1.b12.fc24.x86_64/jre/../bin/java\" }], \"uid\" : 1000, " +
+                "\"username\" : \"user\" }]}]}";
+        assertEquals(expected, response.getContentAsString());
+    }
+
+    @Test
+    public void testTreeAllLimit() throws InterruptedException, TimeoutException, ExecutionException {
+        String postUrl = jvmsUrl + "/systems/1";
+        String treeUrl = jvmsUrl + "/tree";
+
+        ContentResponse postResponse = client.newRequest(postUrl).method(HttpMethod.POST)
+                .content(new StringContentProvider(postData), "application/json").send();
+        assertEquals(200, postResponse.getStatus());
+
+        String query = "?aliveOnly=false&limit=2";
+        ContentResponse response = client.newRequest(treeUrl + query).method(HttpMethod.GET).send();
+        assertEquals(200, response.getStatus());
+        String expected = "{ \"response\" : [{\"systemId\":\"1\", \"jvms\":[{ \"agentId\" : \"aid\", \"jvmId\" : " +
+                "\"jid1\", \"jvmPid\" : 1, \"startTime\" : { \"$numberLong\" : \"1495727607481\" }, \"stopTime\" : {" +
+                " \"$numberLong\" : \"-9223372036854775808\" }, \"javaVersion\" : \"1.8.0_131\", \"javaHome\" :" +
+                " \"/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.131-1.b12.fc24.x86_64/jre\", \"mainClass\" : \"mc\", " +
+                "\"javaCommandLine\" : \"j cl\", \"jvmName\" : \"vm\", \"vmArguments\" : \"-Djline.log.jul=true\"," +
+                " \"jvmInfo\" : \"mixed mode\", \"lastUpdated\" : 0, \"jvmVersion\" : \"25.131-b12\", \"environment\"" +
+                " : [{ \"key\" : \"PATH\", \"value\" : \"/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin\" }," +
+                " { \"key\" : \"XAUTHORITY\", \"value\" : \"/run/user/1000/gdm/Xauthority\" }, { \"key\" : " +
+                "\"GDMSESSION\", \"value\" : \"i3\" }, { \"key\" : \"fish_greeting\", \"value\" : \"\" }, { \"key\" " +
+                ": \"TERM\", \"value\" : \"xterm-256color\" }, { \"key\" : \"DARWIN_MODE\", \"value\" : \"0\" }, {" +
+                " \"key\" : \"LANG\", \"value\" : \"en_US.UTF-8\" }, { \"key\" : \"DBUS_SESSION_BUS_ADDRESS\", " +
+                "\"value\" : \"unix:path=/run/user/1000/bus\" }, { \"key\" : \"XDG_SESSION_ID\", \"value\" : \"2\" }, " +
+                "{ \"key\" : \"XDG_SESSION_TYPE\", \"value\" : \"x11\" }, { \"key\" : \"XDG_CURRENT_DESKTOP\", " +
+                "\"value\" : \"i3\" }, { \"key\" : \"DISPLAY\", \"value\" : \":0\" }, { \"key\" : \"CYGWIN_MODE\"," +
+                " \"value\" : \"0\" }, { \"key\" : \"COLORTERM\", \"value\" : \"truecolor\" }, { \"key\" : \"_\", " +
+                "\"value\" : \"/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.131-1.b12.fc24.x86_64/jre/../bin/java\" }], " +
+                "\"uid\" : 1000, \"username\" : \"user\", \"systemId\" : \"1\" },{ \"agentId\" : \"aid\", \"jvmId\" " +
+                ": \"jid2\", \"jvmPid\" : 2, \"startTime\" : { \"$numberLong\" : \"1495727607481\" }, \"stopTime\" " +
+                ": { \"$numberLong\" : \"1495727607482\" }, \"javaVersion\" : \"1.8.0_131\", \"javaHome\" : " +
+                "\"/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.131-1.b12.fc24.x86_64/jre\", \"mainClass\" : \"mc\", " +
+                "\"javaCommandLine\" : \"j cl\", \"jvmName\" : \"vm\", \"vmArguments\" : \"-Djline.log.jul=true\"," +
+                " \"jvmInfo\" : \"mixed mode\", \"lastUpdated\" : 0, \"jvmVersion\" : \"25.131-b12\", \"environment\"" +
+                " : [{ \"key\" : \"PATH\", \"value\" : \"/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin\" }," +
+                " { \"key\" : \"XAUTHORITY\", \"value\" : \"/run/user/1000/gdm/Xauthority\" }, { \"key\" : " +
+                "\"GDMSESSION\", \"value\" : \"i3\" }, { \"key\" : \"fish_greeting\", \"value\" : \"\" }, { \"key\" " +
+                ": \"TERM\", \"value\" : \"xterm-256color\" }, { \"key\" : \"DARWIN_MODE\", \"value\" : \"0\" }," +
+                " { \"key\" : \"LANG\", \"value\" : \"en_US.UTF-8\" }, { \"key\" : \"DBUS_SESSION_BUS_ADDRESS\", " +
+                "\"value\" : \"unix:path=/run/user/1000/bus\" }, { \"key\" : \"XDG_SESSION_ID\", \"value\" : \"2\"" +
+                " }, { \"key\" : \"XDG_SESSION_TYPE\", \"value\" : \"x11\" }, { \"key\" : \"XDG_CURRENT_DESKTOP\"," +
+                " \"value\" : \"i3\" }, { \"key\" : \"DISPLAY\", \"value\" : \":0\" }, { \"key\" : \"CYGWIN_MODE\"," +
+                " \"value\" : \"0\" }, { \"key\" : \"COLORTERM\", \"value\" : \"truecolor\" }, { \"key\" : \"_\"," +
+                " \"value\" : \"/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.131-1.b12.fc24.x86_64/jre/../bin/java\" }]," +
+                " \"uid\" : 1000, \"username\" : \"user\", \"systemId\" : \"1\" }]}]}";
+        assertEquals(expected, response.getContentAsString());
+    }
+
+    @Test
+    public void testTreeAllOffset() throws InterruptedException, TimeoutException, ExecutionException {
+        String postUrl = jvmsUrl + "/systems/1";
+        String treeUrl = jvmsUrl + "/tree";
+
+        ContentResponse postResponse = client.newRequest(postUrl).method(HttpMethod.POST)
+                .content(new StringContentProvider(postData), "application/json").send();
+        assertEquals(200, postResponse.getStatus());
+
+        String query = "?aliveOnly=false&offset=1";
+        ContentResponse response = client.newRequest(treeUrl + query).method(HttpMethod.GET).send();
+        assertEquals(200, response.getStatus());
+        String expected = "{ \"response\" : [{\"systemId\":\"1\", \"jvms\":[{ \"agentId\" : \"aid\", \"jvmId\" :" +
+                " \"jid2\", \"jvmPid\" : 2, \"startTime\" : { \"$numberLong\" : \"1495727607481\" }, \"stopTime\" :" +
+                " { \"$numberLong\" : \"1495727607482\" }, \"javaVersion\" : \"1.8.0_131\", \"javaHome\" : " +
+                "\"/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.131-1.b12.fc24.x86_64/jre\", \"mainClass\" : \"mc\", " +
+                "\"javaCommandLine\" : \"j cl\", \"jvmName\" : \"vm\", \"vmArguments\" : \"-Djline.log.jul=true\", " +
+                "\"jvmInfo\" : \"mixed mode\", \"lastUpdated\" : 0, \"jvmVersion\" : \"25.131-b12\", \"environment\"" +
+                " : [{ \"key\" : \"PATH\", \"value\" : \"/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin\" }, { " +
+                "\"key\" : \"XAUTHORITY\", \"value\" : \"/run/user/1000/gdm/Xauthority\" }, { \"key\" : " +
+                "\"GDMSESSION\", \"value\" : \"i3\" }, { \"key\" : \"fish_greeting\", \"value\" : \"\" }, { \"key\"" +
+                " : \"TERM\", \"value\" : \"xterm-256color\" }, { \"key\" : \"DARWIN_MODE\", \"value\" : \"0\" }, {" +
+                " \"key\" : \"LANG\", \"value\" : \"en_US.UTF-8\" }, { \"key\" : \"DBUS_SESSION_BUS_ADDRESS\", " +
+                "\"value\" : \"unix:path=/run/user/1000/bus\" }, { \"key\" : \"XDG_SESSION_ID\", \"value\" : \"2\" }," +
+                " { \"key\" : \"XDG_SESSION_TYPE\", \"value\" : \"x11\" }, { \"key\" : \"XDG_CURRENT_DESKTOP\", " +
+                "\"value\" : \"i3\" }, { \"key\" : \"DISPLAY\", \"value\" : \":0\" }, { \"key\" : \"CYGWIN_MODE\", " +
+                "\"value\" : \"0\" }, { \"key\" : \"COLORTERM\", \"value\" : \"truecolor\" }, { \"key\" : \"_\", " +
+                "\"value\" : \"/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.131-1.b12.fc24.x86_64/jre/../bin/java\" }], " +
+                "\"uid\" : 1000, \"username\" : \"user\", \"systemId\" : \"1\" }]}]}";
+        assertEquals(expected, response.getContentAsString());
+    }
+}