changeset 238:1093c542f930

Add jvm-io service This patch adds a jvm-io service, with GET /jvm-io/0.0.1/jvms/{jvmid} and GET|POST|PUT|DELETE /jvm-io/systems/{sysid}/jvms/{jvmid} Reviewed-by: neugens Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2017-August/024771.html
author Simon Tooke <stooke@redhat.com>
date Thu, 31 Aug 2017 10:22:31 -0400
parents 9a08bbf30b04
children 4e58443cbb7f
files distribution/pom.xml distribution/src/etc/services.properties services/jvm-io/pom.xml services/jvm-io/src/main/java/com/redhat/thermostat/gateway/service/jvm/io/JvmIoHttpHandler.java services/jvm-io/src/main/java/com/redhat/thermostat/gateway/service/jvm/io/SwaggerSpecResourceHandler.java services/jvm-io/src/main/resources/jvm-io-swagger.yaml services/jvm-io/src/main/webapp/WEB-INF/web.xml services/pom.xml tests/integration-tests/src/test/java/com/redhat/thermostat/gateway/service/jvm/io/JvmIoServiceIntegrationTest.java
diffstat 9 files changed, 1310 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/distribution/pom.xml	Wed Aug 30 12:00:11 2017 -0400
+++ b/distribution/pom.xml	Thu Aug 31 10:22:31 2017 -0400
@@ -244,6 +244,13 @@
                         </artifactItem>
                         <artifactItem>
                             <groupId>com.redhat.thermostat</groupId>
+                            <artifactId>thermostat-web-gateway-service-jvm-io</artifactId>
+                            <version>${project.version}</version>
+                            <type>war</type>
+                            <overWrite>false</overWrite>
+                        </artifactItem>
+                        <artifactItem>
+                            <groupId>com.redhat.thermostat</groupId>
                             <artifactId>thermostat-web-gateway-service-systems</artifactId>
                             <version>${project.version}</version>
                             <type>war</type>
@@ -305,6 +312,13 @@
         </dependency>
         <dependency>
             <groupId>com.redhat.thermostat</groupId>
+            <artifactId>thermostat-web-gateway-service-jvm-io</artifactId>
+            <version>${project.version}</version>
+            <type>war</type>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.redhat.thermostat</groupId>
             <artifactId>thermostat-web-gateway-service-systems</artifactId>
             <version>${project.version}</version>
             <type>war</type>
--- a/distribution/src/etc/services.properties	Wed Aug 30 12:00:11 2017 -0400
+++ b/distribution/src/etc/services.properties	Thu Aug 31 10:22:31 2017 -0400
@@ -1,5 +1,6 @@
 /commands = thermostat-web-gateway-service-commands-@project.version@.war
 /jvm-gc = thermostat-web-gateway-service-jvm-gc-@project.version@.war
+/jvm-io = thermostat-web-gateway-service-jvm-io-@project.version@.war
 /jvm-memory = thermostat-web-gateway-service-jvm-memory-@project.version@.war
 /jvms = thermostat-web-gateway-service-jvms-@project.version@.war
 /systems = thermostat-web-gateway-service-systems-@project.version@.war
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/services/jvm-io/pom.xml	Thu Aug 31 10:22:31 2017 -0400
@@ -0,0 +1,96 @@
+<!--
+
+ 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-jvm-io</artifactId>
+
+  <packaging>war</packaging>
+
+  <name>Thermostat Web Gateway JVM IO Service</name>
+  
+  <properties>
+    <com.redhat.thermostat.gateway.SERVICE_NAME>jvm-io</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 deps -->
+    <dependency>
+      <groupId>javax.servlet</groupId>
+      <artifactId>javax.servlet-api</artifactId>
+      <version>${javax.servlet.version}</version>
+      <scope>provided</scope>
+    </dependency>
+
+    <!-- JAX-RS Dependencies -->
+    <dependency>
+      <groupId>javax.ws.rs</groupId>
+      <artifactId>javax.ws.rs-api</artifactId>
+      <version>${javax-rs-api.version}</version>
+      <scope>provided</scope>
+    </dependency>
+  </dependencies>
+</project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/services/jvm-io/src/main/java/com/redhat/thermostat/gateway/service/jvm/io/JvmIoHttpHandler.java	Thu Aug 31 10:22:31 2017 -0400
@@ -0,0 +1,137 @@
+/*
+ * 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.jvm.io;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+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.redhat.thermostat.gateway.common.core.model.OffsetParameter;
+import com.redhat.thermostat.gateway.common.mongodb.servlet.RequestParameters;
+import com.redhat.thermostat.gateway.common.mongodb.servlet.MongoHttpHandlerHelper;
+
+@Path("/")
+public class JvmIoHttpHandler {
+    private static final String collectionName = "jvm-io";
+    private final MongoHttpHandlerHelper serviceHelper = new MongoHttpHandlerHelper( collectionName );
+
+    @GET
+    @Path("/jvms/{" + RequestParameters.JVM_ID +"}")
+    @Consumes({ "application/json" })
+    @Produces({ "application/json", "text/html; charset=utf-8" })
+    public Response getJvmIo(@PathParam(RequestParameters.JVM_ID) String jvmId,
+                                 @QueryParam(RequestParameters.LIMIT) @DefaultValue("1") Integer limit,
+                                 @QueryParam(RequestParameters.OFFSET) @DefaultValue("0") OffsetParameter offsetParam,
+                                 @QueryParam(RequestParameters.SORT) String sort,
+                                 @QueryParam(RequestParameters.QUERY) String queries,
+                                 @QueryParam(RequestParameters.INCLUDE) String includes,
+                                 @QueryParam(RequestParameters.EXCLUDE) String excludes,
+                                 @QueryParam(RequestParameters.METADATA) @DefaultValue("false") String metadata,
+                                 @Context ServletContext context,
+                                 @Context HttpServletRequest httpServletRequest) {
+        return serviceHelper.handleGetWithJvmID(httpServletRequest, context, null, jvmId, limit, offsetParam.getValue(), sort, queries, includes, excludes, metadata);
+    }
+
+    @GET
+    @Path("/systems/{" + RequestParameters.SYSTEM_ID +"}/jvms/{" + RequestParameters.JVM_ID +"}")
+    @Consumes({ "application/json" })
+    @Produces({ "application/json", "text/html; charset=utf-8" })
+    public Response getJvmIo(@PathParam(RequestParameters.SYSTEM_ID) String systemId,
+                                 @PathParam(RequestParameters.JVM_ID) String jvmId,
+                                 @QueryParam(RequestParameters.LIMIT) @DefaultValue("1") Integer limit,
+                                 @QueryParam(RequestParameters.OFFSET) @DefaultValue("0") OffsetParameter offsetParam,
+                                 @QueryParam(RequestParameters.SORT) String sort,
+                                 @QueryParam(RequestParameters.QUERY) String queries,
+                                 @QueryParam(RequestParameters.INCLUDE) String includes,
+                                 @QueryParam(RequestParameters.EXCLUDE) String excludes,
+                                 @QueryParam(RequestParameters.METADATA) @DefaultValue("false") String metadata,
+                                 @Context ServletContext context,
+                                 @Context HttpServletRequest httpServletRequest) {
+        return serviceHelper.handleGetWithJvmID(httpServletRequest, context, systemId, jvmId, limit, offsetParam.getValue(), sort, queries, includes, excludes, metadata);
+    }
+
+    @PUT
+    @Path("/systems/{" + RequestParameters.SYSTEM_ID +"}/jvms/{" + RequestParameters.JVM_ID +"}")
+    @Consumes({ "application/json" })
+    @Produces({ "application/json", "text/html; charset=utf-8" })
+    public Response putJvmIo(String body,
+                                 @PathParam(RequestParameters.SYSTEM_ID) String systemId,
+                                 @PathParam(RequestParameters.JVM_ID) String jvmId,
+                                 @QueryParam(RequestParameters.QUERY) String queries,
+                                 @QueryParam(RequestParameters.METADATA) @DefaultValue("false") String metadata,
+                                 @Context ServletContext context,
+                                 @Context HttpServletRequest httpServletRequest) {
+        return serviceHelper.handlePutWithJvmId(httpServletRequest, context, systemId, jvmId, queries, metadata, body);
+    }
+
+    @POST
+    @Path("/systems/{" + RequestParameters.SYSTEM_ID +"}/jvms/{" + RequestParameters.JVM_ID +"}")
+    @Consumes({ "application/json" })
+    @Produces({ "application/json", "text/html; charset=utf-8" })
+    public Response postJvmIo(String body,
+                                  @PathParam(RequestParameters.SYSTEM_ID) String systemId,
+                                  @PathParam(RequestParameters.JVM_ID) String jvmId,
+                                  @QueryParam(RequestParameters.METADATA) @DefaultValue("false") String metadata,
+                                  @Context ServletContext context,
+                                  @Context HttpServletRequest httpServletRequest) {
+        return serviceHelper.handlePostWithJvmID(httpServletRequest, context, systemId, jvmId, metadata, body);
+    }
+
+    @DELETE
+    @Path("/systems/{" + RequestParameters.SYSTEM_ID +"}/jvms/{" + RequestParameters.JVM_ID +"}")
+    @Consumes({ "application/json" })
+    @Produces({ "application/json", "text/html; charset=utf-8" })
+    public Response deleteJvmIo(@PathParam(RequestParameters.SYSTEM_ID) String systemId,
+                                    @PathParam(RequestParameters.JVM_ID) String jvmId,
+                                    @QueryParam(RequestParameters.QUERY) String queries,
+                                    @QueryParam(RequestParameters.METADATA) @DefaultValue("false") String metadata,
+                                    @Context ServletContext context,
+                                    @Context HttpServletRequest httpServletRequest) {
+        return serviceHelper.handleDeleteWithJvmID(httpServletRequest, context, systemId, jvmId, queries, metadata);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/services/jvm-io/src/main/java/com/redhat/thermostat/gateway/service/jvm/io/SwaggerSpecResourceHandler.java	Thu Aug 31 10:22:31 2017 -0400
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2012-2017 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.gateway.service.jvm.io;
+
+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/jvm-io/src/main/resources/jvm-io-swagger.yaml	Thu Aug 31 10:22:31 2017 -0400
@@ -0,0 +1,226 @@
+swagger: '2.0'
+info:
+  version: 0.0.1
+  title: Thermostat Web Gateway JVM IO 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: /jvm-memory/0.0.1
+paths:
+  /jvms/{jvmId}:
+    parameters:
+      - $ref: '#/parameters/thermostat-realms'
+      - $ref: '#/parameters/jvm-id'
+    get:
+      description: Get jvm IO information.
+      parameters:
+        - $ref: '#/parameters/limit'
+        - $ref: '#/parameters/offset'
+        - $ref: '#/parameters/sort'
+        - $ref: '#/parameters/include'
+        - $ref: '#/parameters/exclude'
+        - $ref: '#/parameters/query'
+        - $ref: '#/parameters/metadata'
+      responses:
+        '200':
+          description: OK
+          schema:
+            $ref: '#/definitions/jvm-io-stats-response'
+  /systems/{systemId}/jvms/{jvmId}:
+    parameters:
+      - $ref: '#/parameters/thermostat-realms'
+      - $ref: '#/parameters/system-id'
+      - $ref: '#/parameters/jvm-id'
+    get:
+      description: Get jvm io information.
+      parameters:
+        - $ref: '#/parameters/limit'
+        - $ref: '#/parameters/offset'
+        - $ref: '#/parameters/sort'
+        - $ref: '#/parameters/include'
+        - $ref: '#/parameters/exclude'
+        - $ref: '#/parameters/query'
+        - $ref: '#/parameters/metadata'
+      responses:
+        '200':
+          description: OK
+          schema:
+            $ref: '#/definitions/jvm-io-stats-response'
+    put:
+      description: Update jvm io information.
+      parameters:
+        - $ref: '#/parameters/put-body'
+        - $ref: '#/parameters/query'
+        - $ref: '#/parameters/metadata'
+      responses:
+        '200':
+          description: OK
+          schema:
+            $ref: '#/definitions/metadata'
+    post:
+      description: Add jvm io information
+      parameters:
+        - $ref: '#/parameters/jvm-io-stats'
+      responses:
+        '200':
+          description: OK
+          schema:
+            $ref: '#/definitions/metadata'
+    delete:
+      description: Delete jvm io information.
+      parameters:
+        - $ref: '#/parameters/query'
+      responses:
+        '200':
+          description: OK
+          schema:
+            $ref: '#/definitions/metadata'
+definitions:
+  jvm-io-stats-response:
+    type: object
+    properties:
+      response:
+          $ref: '#/definitions/jvm-io-stats'
+      metadata:
+        $ref: '#/definitions/metadata'
+  jvm-io-stats:
+    type: array
+    items:
+      $ref: '#/definitions/jvm-io-stat'
+  jvm-io-stat:
+    type: object
+    properties:
+      agentId:
+        type: string
+      jvmId:
+        type: string
+      timeStamp:
+        $ref: '#/definitions/timestamp'
+      charactersRead:
+        ref: '#/definitions/metric'
+      charactersWritten:
+        ref: '#/definitions/metric'
+      readSyscalls:
+        ref: '#/definitions/metric'
+      writeSyscalls:
+       ref: '#/definitions/metric'
+  metadata:
+      type: object
+      properties:
+        payloadCount:
+          type: integer
+        count:
+          type: integer
+        prev:
+          type: string
+        next:
+          type: string
+        first:
+          type: string
+        last:
+          type: string
+        insertCount:
+          type: integer
+        matchCount:
+          type: integer
+        elapsed:
+          type: integer
+          format: int64
+  metric:
+    type: object
+    properties:
+      $numberLong:
+        type: string
+  timestamp:
+    description: UNIX timestamp in milliseconds
+    type: object
+    properties:
+      $numberLong:
+        type: string
+  put-body:
+    type: object
+    properties:
+      "set":
+        type: object
+parameters:
+  system-id:
+    name: systemId
+    in: path
+    required: true
+    type: string
+  jvm-id:
+    name: jvmId
+    in: path
+    required: true
+    type: string
+  jvm-io-stats:
+    name: jvm-io-stats
+    in: body
+    description: The jvm io statistics
+    required: true
+    schema:
+      $ref: '#/definitions/jvm-io-stats'
+  put-body:
+    name: putBody
+    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. Example { \"set\" : { \"field\" : \"value\", \"field2\":{\"object\":\"item\"} }"
+    required: true
+    schema:
+      $ref: '#/definitions/put-body'
+  limit:
+    name: limit
+    in: query
+    description: Limit of items to return. Example '1'
+    type: integer
+  offset:
+    name: offset
+    in: query
+    description: Offset of items to return. Example '0'
+    type: integer
+  sort:
+    name: sort
+    in: query
+    description: Sort string. Comma separated list of fields prefixed with '+' for ascending or '-' for descending. Example '?s=+a,-b' Fields use dot notation for embedded documents. Example 'outer.inner' refers to field inner contained in field outer.
+    type: string
+  query:
+    name: query
+    in: query
+    description: Query string. Comma separated list of key, comparator, value pairs. Comparator supports '==', '<=', '>=', '<', '>', '!='. Example '?q=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
+  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
+  metadata:
+    name: metadata
+    type: boolean
+    in: query
+    description: "Metadata flag. If set to 'true', the subsequent request response will return metadata information. If set to 'false', such metadata information will be omitted."
+  thermostat-realms:
+      name: X-Thermostat-Realms
+      type: string
+      in: header
+      description: "Realms Header used to specify a subset of roles to use for Keycloak authorization. Attempts to specify realms that the client does not have, or no valid realms at all will result in a 400 Bad Request response. Expects a space separated list of realms. Example 'X-Thermostat-Realms: realm-one realm-two'"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/services/jvm-io/src/main/webapp/WEB-INF/web.xml	Thu Aug 31 10:22:31 2017 -0400
@@ -0,0 +1,88 @@
+<!--
+
+ 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>JvmIoServlet</servlet-name>
+        <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
+        <init-param>
+            <param-name>
+                jersey.config.server.provider.packages
+            </param-name>
+            <param-value>
+                com.redhat.thermostat.gateway.service.jvm.io,
+            </param-value>
+        </init-param>
+    </servlet>
+    <filter>
+        <filter-name>ServiceVersionFilter</filter-name>
+        <filter-class>com.redhat.thermostat.gateway.common.core.servlet.ServiceVersionFilter</filter-class>
+        <init-param>
+            <param-name>version</param-name>
+            <param-value>0.0.1</param-value>
+        </init-param>
+    </filter>
+    <filter-mapping>
+        <filter-name>ServiceVersionFilter</filter-name>
+        <url-pattern>/*</url-pattern>
+    </filter-mapping>
+    <servlet-mapping>
+        <servlet-name>JvmIoServlet</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>
+    <!-- Allow viewing of API spec without authentication -->
+    <security-constraint>
+      <web-resource-collection>
+        <web-resource-name>Swagger API Spec File</web-resource-name>
+        <url-pattern>/0.0.1/doc/@com.redhat.thermostat.gateway.SERVICE_NAME@-swagger.yaml</url-pattern>
+      </web-resource-collection>
+      <!-- Explicitly no auth constraint for this file -->
+    </security-constraint>
+</web-app>
--- a/services/pom.xml	Wed Aug 30 12:00:11 2017 -0400
+++ b/services/pom.xml	Thu Aug 31 10:22:31 2017 -0400
@@ -55,6 +55,7 @@
         <module>jvm-gc</module>
         <module>jvms</module>
         <module>jvm-memory</module>
+        <module>jvm-io</module>
         <module>systems</module>
         <module>system-cpu</module>
         <module>system-memory</module>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/integration-tests/src/test/java/com/redhat/thermostat/gateway/service/jvm/io/JvmIoServiceIntegrationTest.java	Thu Aug 31 10:22:31 2017 -0400
@@ -0,0 +1,688 @@
+/*
+ * 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.jvm.io;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.redhat.thermostat.gateway.tests.integration.MongoIntegrationTest;
+import com.redhat.thermostat.gateway.tests.utils.HttpTestUtil;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.util.StringContentProvider;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class JvmIoServiceIntegrationTest extends MongoIntegrationTest {
+
+    private static final String serviceName = "jvm-io";
+    private static final String versionNumber = "0.0.1";
+
+    private static final int HTTP_200_OK = 200;
+    private static final int HTTP_404_NOTFOUND = 404;
+
+    private static final String QUERY_PREFIX = "query";
+    private static final String LIMIT_PREFIX = "limit";
+    private static final String SORT_PREFIX = "sort";
+    private static final String OFFSET_PREFIX = "offset";
+    private static final String METADATA_PREFIX = "metadata";
+    private static final String INCLUDE_PREFIX = "include";
+
+    private static final String TIMESTAMP_TOKEN = "\"$TIMESTAMP$\"";
+    private static final String JVMID_TOKEN = "\"$JVMID_TOKEN\"";
+
+    private final String AGENT_ID = getRandomSystemId();
+    private final String JVM_ID = getRandomJvmId();
+    private long timeStamp = java.lang.System.nanoTime();
+    private final String SYSTEM_JVM_FRAGMENT = ",\"systemId\":\"" + AGENT_ID + "\",\"jvmId\":\"" + JVM_ID + "\"";
+
+    private final String jsonData =
+            "{\n" +
+                    "   \"timeStamp\" : " + TIMESTAMP_TOKEN + ",\n" +
+                    "   \"jvmId\" : " + JVMID_TOKEN + ",\n" +
+                    "   \"metaspaceMaxCapacity\" : \"1000\",\n" +
+                    "   \"metaspaceMinCapacity\" : \"22\",\n" +
+                    "   \"metaspaceCapacity\" : \"777\",\n" +
+                    "   \"metaspaceUsed\" : \"77\"\n" +
+                    "}\n";
+
+    private final String simpleUrl = baseUrl + "/" + serviceName + "/" + versionNumber;
+    private final String serviceUrl = simpleUrl + "/systems/" + AGENT_ID + "/jvms/" + JVM_ID;
+
+    private final String returnedUrl;
+
+    public JvmIoServiceIntegrationTest() {
+        super(serviceName + "/" + versionNumber, serviceName);
+        this.returnedUrl = serviceUrl;
+    }
+
+    @Test
+    public void testGetUnknown() throws InterruptedException, TimeoutException, ExecutionException {
+        getUnknown(getRandomSystemId(), getRandomJvmId());
+    }
+
+    @Test
+    public void testCreateOne() throws InterruptedException, TimeoutException, ExecutionException {
+
+        final String systemid = getRandomSystemId();
+        final String jvmid = getRandomJvmId();
+        post(systemid, jvmid);
+        getKnown(systemid, jvmid);
+    }
+
+    @Test
+    public void testPut() throws InterruptedException, TimeoutException, ExecutionException {
+
+        final String systemid = getRandomSystemId();
+        final String jvmid = getRandomJvmId();
+
+        final long timestamp = getTimestamp();
+
+        // create it
+        post(systemid, jvmid);
+
+        // retrieve it
+        final ContentResponse response1 = getKnown(systemid, jvmid);
+        final List<TinyJvmIo> list1 = parse(response1, jvmid);
+        assertEquals(1, list1.size());
+
+        // modify it
+        put(systemid, jvmid, timestamp+1);
+
+        // ensure it was changed
+        final ContentResponse response2 = getKnown(systemid, jvmid);
+        final List<TinyJvmIo> list2 = parse(response2, jvmid);
+        assertEquals(1, list2.size());
+    }
+
+    @Test
+    public void testDeleteUnknown() throws InterruptedException, TimeoutException, ExecutionException {
+        final String systemid = getRandomSystemId();
+        final String jvmid = getRandomJvmId();
+        // delete it
+        delete(systemid, jvmid);
+    }
+
+    @Test
+    public void testDeleteOne() throws InterruptedException, ExecutionException, TimeoutException {
+        final String systemid = getRandomSystemId();
+        final String jvmid = getRandomJvmId();
+
+        // create the new record
+        post(systemid, jvmid);
+
+        // check that it's there
+        getKnown(systemid, jvmid);
+
+        // delete it
+        delete(systemid, jvmid);
+
+        // check that it's not there
+        getUnknown(systemid, jvmid);
+    }
+
+
+
+    @Test
+    public void testGetWithUnsupportedQuery() throws InterruptedException, TimeoutException, ExecutionException {
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, serviceUrl + "?xyz=5", 200, HttpTestUtil.EMPTY_RESPONSE);
+    }
+
+    @Test
+    public void testDefaultLimitOne() throws InterruptedException, TimeoutException, ExecutionException {
+        String expected = "{\"response\":[{\"a\":\"b\"" + SYSTEM_JVM_FRAGMENT + "}]}";
+        HttpTestUtil.addRecords(client, serviceUrl, "[{\"a\":\"b\"},{\"a\":\"d\"}]");
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, serviceUrl + "?s=+a&l=", 200, expected);
+    }
+
+    @Test
+    public void testGetWithCommaQuery() throws InterruptedException, TimeoutException, ExecutionException {
+        String expectedFirst = "{\"response\":[{\"a\":\"b\",\"c\":\"d\",\"e\":\"f\"" + SYSTEM_JVM_FRAGMENT + "}]}";
+        String expectedAll = "{\"response\":[{\"a\":\"b\",\"c\":\"d\",\"e\":\"f\"" + SYSTEM_JVM_FRAGMENT + "},{\"x\":\"y\"" + SYSTEM_JVM_FRAGMENT + "},{\"z\":\"z\"" + SYSTEM_JVM_FRAGMENT + "}]}";
+        HttpTestUtil.addRecords(client, serviceUrl, "[{\"a\":\"b\",\"c\":\"d\",\"e\":\"f\"},{\"x\":\"y\"},{\"z\":\"z\"}]");
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, serviceUrl + "?" + LIMIT_PREFIX + "=5", 200, expectedAll);
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, serviceUrl + "?" + QUERY_PREFIX + "=a==b", 200, expectedFirst);
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, serviceUrl + "?" + QUERY_PREFIX + "=c==d", 200, expectedFirst);
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, serviceUrl + "?" + QUERY_PREFIX + "=a==b,c==d", 200, expectedFirst);
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, serviceUrl + "?" + QUERY_PREFIX + "=a==b,c==none", 200, HttpTestUtil.EMPTY_RESPONSE);
+    }
+
+    @Test
+    public void testGetWithAmpersandQuery() throws InterruptedException, TimeoutException, ExecutionException {
+        String expectedAll = "{\"response\":[{\"a\":\"b\"" + SYSTEM_JVM_FRAGMENT + "},{\"c\":\"d\"" + SYSTEM_JVM_FRAGMENT + "}]}";
+        String expectedAmpersand = "{\"response\":[{\"a\":\"b\"" + SYSTEM_JVM_FRAGMENT + "}]}";
+        HttpTestUtil.addRecords(client, serviceUrl, "[{\"a\":\"b\"},{\"c\":\"d\"}]");
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, serviceUrl + "?" + LIMIT_PREFIX + "=5", 200, expectedAll);
+
+        // Since q=a==b&c==d means "q= 'a==b'" and "c= '=d'", we should only
+        // get 'a: b' back.
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, serviceUrl + "?" + QUERY_PREFIX + "=a==b&c==d", 200, expectedAmpersand);
+
+        // The following should find the one with multiple matches, not the
+        // single match. This means we will evaluate u==v only, and should get
+        // back the first one it finds since no limit query is specified.
+        String expectedSingleMatch = "{\"response\":[{\"u\":\"v\",\"x\":\"y\"" + SYSTEM_JVM_FRAGMENT + "}]}";
+        String expectedMultiMatch = "{\"response\":[{\"u\":\"v\",\"x\":\"y\"" + SYSTEM_JVM_FRAGMENT + "},{\"u\":\"v\"" + SYSTEM_JVM_FRAGMENT + "}]}";
+        HttpTestUtil.addRecords(client, serviceUrl, "[{\"x\":\"y\"},{\"u\":\"v\",\"x\":\"y\"},{\"u\":\"v\"}]");
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, serviceUrl + "?" + QUERY_PREFIX + "=u==v&" + QUERY_PREFIX + "=x==y", 200, expectedSingleMatch);
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, serviceUrl + "?" + QUERY_PREFIX + "=u==v&" + QUERY_PREFIX + "=x==y&" + LIMIT_PREFIX + "=5", 200, expectedMultiMatch);
+    }
+
+    @Test
+    public void testMultiplePosts() throws InterruptedException, TimeoutException, ExecutionException {
+        String expectedResponse = "{\"response\":[{\"fakedata\":\"test\"" + SYSTEM_JVM_FRAGMENT + "},{\"new\":\"data\"" + SYSTEM_JVM_FRAGMENT + "}]}";
+        HttpTestUtil.addRecords(client, serviceUrl, "[{\"fakedata\":\"test\"}]");
+        HttpTestUtil.addRecords(client, serviceUrl, "[{\"new\":\"data\"}]");
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, serviceUrl + "?" + LIMIT_PREFIX + "=5", 200, expectedResponse);
+    }
+
+    @Test
+    public void testPostPutAddsData() throws InterruptedException, TimeoutException, ExecutionException {
+        String expectedDataBeforePut = "{\"response\":[{\"a\":\"b\"" + SYSTEM_JVM_FRAGMENT + "}]}";
+        String expectedDataAfterPut = "{\"response\":[{\"a\":\"b\"" + SYSTEM_JVM_FRAGMENT + ",\"x\":\"y\"}]}";
+        HttpTestUtil.addRecords(client, serviceUrl, "[{\"a\":\"b\"},{\"a\":\"c\"}]");
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, serviceUrl, 200, expectedDataBeforePut);
+        HttpTestUtil.testContentResponse(client, HttpMethod.PUT, serviceUrl + "?" + QUERY_PREFIX + "=a==b", "{\"set\":{\"x\":\"y\"}}", 200);
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, serviceUrl, 200, expectedDataAfterPut);
+    }
+
+    @Test
+    public void testPostPutModifiesData() throws InterruptedException, TimeoutException, ExecutionException {
+        String expectedDataBeforePut = "{\"response\":[{\"a\":\"b\"" + SYSTEM_JVM_FRAGMENT + "}]}";
+        String expectedDataAfterPut = "{\"response\":[{\"a\":\"c\"" + SYSTEM_JVM_FRAGMENT + "}]}";
+        String expectedAllDataAfterPut = "{\"response\":[{\"a\":\"c\"" + SYSTEM_JVM_FRAGMENT + "},{\"x\":\"y\"" + SYSTEM_JVM_FRAGMENT + "}]}";
+        HttpTestUtil.addRecords(client, serviceUrl, "[{\"a\":\"b\"},{\"x\":\"y\"}]");
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, serviceUrl, 200, expectedDataBeforePut);
+        HttpTestUtil.testContentResponse(client, HttpMethod.PUT, serviceUrl + "?" + QUERY_PREFIX + "=a==b", "{\"set\":{\"a\":\"c\"}}", 200);
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, serviceUrl, 200, expectedDataAfterPut);
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, serviceUrl + "?" + LIMIT_PREFIX + "=5", 200, expectedAllDataAfterPut);
+    }
+
+    @Test
+    public void testDeleteProperlyDeletesData() throws InterruptedException, TimeoutException, ExecutionException {
+        HttpTestUtil.addRecords(client, serviceUrl, "[{\"fakedata\":\"test\"}]");
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.DELETE, serviceUrl + "?" + QUERY_PREFIX + "=fakedata==test", 200);
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, serviceUrl, 200, HttpTestUtil.EMPTY_RESPONSE);
+
+        String expectedAfterDeletion = "{\"response\":[{\"c\":\"d\"" + SYSTEM_JVM_FRAGMENT + "}]}";
+        HttpTestUtil.addRecords(client, serviceUrl, "[{\"fakedata\":\"test\",\"a\":\"b\"},{\"c\":\"d\"}]");
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.DELETE, serviceUrl + "?" + QUERY_PREFIX + "=a==b", 200);
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, serviceUrl, 200, expectedAfterDeletion);
+    }
+
+    @Test
+    public void testMalformedDeleteRequestDoesNotMutateData() throws InterruptedException, TimeoutException, ExecutionException {
+        String expectedDataResponse = "{\"response\":[{\"fakedata\":\"test\"" + SYSTEM_JVM_FRAGMENT + "}]}";
+        HttpTestUtil.addRecords(client, serviceUrl, "[{\"fakedata\":\"test\"}]");
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, serviceUrl, 200, expectedDataResponse);
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.DELETE, serviceUrl + "?" + QUERY_PREFIX + "=nosuchkey==", 200);
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, serviceUrl, 200, expectedDataResponse);
+    }
+
+    @Test
+    public void testPutDataWithoutUrlQuery() throws InterruptedException, TimeoutException, ExecutionException {
+        HttpTestUtil.addRecords(client, serviceUrl, "[{\"fakedata\":\"test\"}]");
+        HttpTestUtil.testContentResponse(client, HttpMethod.PUT, serviceUrl, "{\"set\":{\"fakedata\":\"test\"}}", 200);
+    }
+
+    @Test
+    public void testPostAndPutWithInvalidData() throws InterruptedException, TimeoutException, ExecutionException {
+        String expectedDataResponse = "{\"response\":[{\"fakedata\":\"test\"" + SYSTEM_JVM_FRAGMENT + "}]}";
+        String urlQuery = serviceUrl + "?" + QUERY_PREFIX + "=nosuchkey==nosuchvalue";
+        HttpTestUtil.addRecords(client, serviceUrl, "[{\"fakedata\":\"test\"}]");
+        HttpTestUtil.testContentResponse(client, HttpMethod.PUT, urlQuery, "{\"set\":{\"fakedata\":\"somethingnew\"}}", 200);
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, serviceUrl + "?" + LIMIT_PREFIX + "=5", 200, expectedDataResponse);
+    }
+
+    @Test
+    public void testPutWithIdenticalData() throws InterruptedException, TimeoutException, ExecutionException {
+        String expectedDataResponse = "{\"response\":[{\"fakedata\":\"test\"" + SYSTEM_JVM_FRAGMENT + "}]}";
+        HttpTestUtil.addRecords(client, serviceUrl, "[{\"fakedata\":\"test\"}]");
+        HttpTestUtil.testContentResponse(client, HttpMethod.PUT, serviceUrl, "{\"set\":{\"fakedata\":\"test\"}}", 200);
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, serviceUrl + "?" + LIMIT_PREFIX + "=5", 200, expectedDataResponse);
+    }
+
+    @Test
+    public void testPutDifferentData() throws InterruptedException, TimeoutException, ExecutionException {
+        String expectedDataResponse = "{\"response\":[{\"a\":\"b\"" + SYSTEM_JVM_FRAGMENT + ",\"c\":\"d\"}]}";
+        HttpTestUtil.addRecords(client, serviceUrl, "[{\"a\":\"b\"}]");
+        HttpTestUtil.testContentResponse(client, HttpMethod.PUT, serviceUrl + "?" + QUERY_PREFIX + "=a==b", "{\"set\":{\"c\":\"d\"}}", 200);
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, serviceUrl + "?" + LIMIT_PREFIX + "=5", 200, expectedDataResponse);
+    }
+
+    @Test
+    public void testChangeDataWithPutMultipleTimes() throws InterruptedException, TimeoutException, ExecutionException {
+        String expectedData = "{\"response\":[{\"a\":\"a2\"" + SYSTEM_JVM_FRAGMENT + ",\"b\":\"b2\",\"c\":\"c2\"}]}";
+        HttpTestUtil.addRecords(client, serviceUrl, "[{\"a\":\"a2\"}]");
+        HttpTestUtil.testContentResponse(client, HttpMethod.PUT, serviceUrl + "?" + QUERY_PREFIX + "=a==a2", "{\"set\":{\"b\":\"b2\"}}", 200);
+        HttpTestUtil.testContentResponse(client, HttpMethod.PUT, serviceUrl + "?" + QUERY_PREFIX + "=a==a2", "{\"set\":{\"c\":\"c2\"}}", 200);
+
+        // It won't find the target from the query, so the addition should not occur to any object.
+        HttpTestUtil.testContentResponse(client, HttpMethod.PUT, serviceUrl + "?" + QUERY_PREFIX + "=a==none", "{\"set\":{\"d\":\"d2\"}}", 200);
+
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, serviceUrl + "?" + LIMIT_PREFIX + "=5", 200, expectedData);
+    }
+
+    @Test
+    public void testGetWithBadUrlQuery() throws InterruptedException, TimeoutException, ExecutionException {
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, serviceUrl + "?" + QUERY_PREFIX + "=2", 400);
+    }
+
+    @Test
+    public void testGetLimitWithQuery() throws InterruptedException, TimeoutException, ExecutionException {
+        String expectedDataOne = "{\"response\":[{\"a\":\"a2\"" + SYSTEM_JVM_FRAGMENT + "}]}";
+        String expectedDataAll = "{\"response\":[{\"a\":\"a2\"" + SYSTEM_JVM_FRAGMENT + "},{\"b\":\"b2\"" + SYSTEM_JVM_FRAGMENT + "}]}";
+        HttpTestUtil.addRecords(client, serviceUrl, "[{\"a\":\"a2\"},{\"b\":\"b2\"}]");
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, serviceUrl, 200, expectedDataOne);
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, serviceUrl + "?" + LIMIT_PREFIX + "=2", 200, expectedDataAll);
+    }
+
+    @Test
+    public void testQueryOffset() throws InterruptedException, TimeoutException, ExecutionException {
+        String expectedOffsetRestOfData = "{\"response\":[{\"b\":\"2\"" + SYSTEM_JVM_FRAGMENT + "},{\"c\":\"3\"" + SYSTEM_JVM_FRAGMENT + "}]}";
+        HttpTestUtil.addRecords(client, serviceUrl, "[{\"a\":\"1\"},{\"b\":\"2\"},{\"c\":\"3\"}]");
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, serviceUrl + "?" + OFFSET_PREFIX + "=1", 200, "{\"response\":[{\"b\":\"2\"" + SYSTEM_JVM_FRAGMENT + "}]}");
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, serviceUrl + "?" + OFFSET_PREFIX + "=3", 200, HttpTestUtil.EMPTY_RESPONSE);
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, serviceUrl + "?" + LIMIT_PREFIX + "=5&" + OFFSET_PREFIX + "=1", 200, expectedOffsetRestOfData);
+    }
+
+    @Test
+    public void testNegativeOffsetQuery() throws InterruptedException, TimeoutException, ExecutionException {
+        HttpTestUtil.addRecords(client, serviceUrl, "[{\"a\":\"1\"}]");
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, serviceUrl + "?" + OFFSET_PREFIX + "=-1", 400);
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, serviceUrl + "?" + OFFSET_PREFIX + "=-582", 400);
+    }
+
+    @Test
+    public void testQueryOrdering() throws InterruptedException, TimeoutException, ExecutionException {
+        String expectedGet = "{\"response\":[{\"a\":1" + SYSTEM_JVM_FRAGMENT + "},{\"a\":2" + SYSTEM_JVM_FRAGMENT + "}]}";
+        String expectedGetReverse = "{\"response\":[{\"a\":2" + SYSTEM_JVM_FRAGMENT + "},{\"a\":1" + SYSTEM_JVM_FRAGMENT + "}]}";
+        HttpTestUtil.addRecords(client, serviceUrl, "[{\"a\":1},{\"a\":2}]");
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, serviceUrl + "?" + LIMIT_PREFIX + "=10&" + SORT_PREFIX + "=+a", 200, expectedGet);
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, serviceUrl + "?" + LIMIT_PREFIX + "=10&" + SORT_PREFIX + "=-a", 200, expectedGetReverse);
+    }
+
+    @Test
+    public void testQueryProjection() throws InterruptedException, TimeoutException, ExecutionException {
+        String expectedGet = "{\"response\":[{\"b\":\"2\",\"c\":\"3\"}]}";
+        HttpTestUtil.addRecords(client, serviceUrl, "[{\"a\":\"1\",\"b\":\"2\",\"c\":\"3\"}]");
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, serviceUrl + "?" + INCLUDE_PREFIX + "=b,c", 200, expectedGet);
+    }
+
+    @Test
+    public void testGetWithMetaDataAndLimit() throws InterruptedException, TimeoutException, ExecutionException {
+        String data = "[{\"a\":\"test\",\"b\":\"test1\",\"c\":\"test2\"}, {\"b\":\"test1\"}," +
+                "{\"e\":\"test4\",\"b\":\"test1\"}]";
+
+        // {"response":["{\"a\":\"test\",\"b\":\"test1\",\"c\":\"test2\"}"],
+        // "metaData":{"payloadCount":1,"count":3,
+        // "next":"http://127.0.0.1:30000/jvm-io/0.0.2?o=1&l=1&q===test1&m=true"}}
+        String expectedResponse = "{\"response\":[{\"a\":\"test\",\"b\":\"test1\"," +
+                "\"c\":\"test2\"" + SYSTEM_JVM_FRAGMENT + "}],\"metaData\":{\"payloadCount\":1,\"count\":3," +
+                "\"next\":\"" + returnedUrl + "?" + OFFSET_PREFIX + "\\u003d1\\u0026" + LIMIT_PREFIX + "\\u003d1\\u0026" + QUERY_PREFIX + "\\u003db\\u003d\\u003dtest1\\u0026" + METADATA_PREFIX + "\\u003dtrue\"}}";
+
+        HttpTestUtil.addRecords(client, serviceUrl, data);
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, serviceUrl + "?" + QUERY_PREFIX + "=b==test1&" + METADATA_PREFIX + "=true&" + LIMIT_PREFIX + "=1", 200, expectedResponse);
+    }
+
+    @Test
+    public void testGetWithVersions() throws InterruptedException, TimeoutException, ExecutionException {
+        String data = "[{\"a\":\"test\",\"b\":\"test1\",\"c\":\"test2\"}, {\"b\":\"test1\"}," +
+                "{\"e\":\"test4\",\"b\":\"test1\"}]";
+
+        String expectedResponse = "{\"response\":[{\"a\":\"test\",\"b\":\"test1\"," +
+                "\"c\":\"test2\"" + SYSTEM_JVM_FRAGMENT + "}],\"metaData\":{\"payloadCount\":1,\"count\":3," +
+                "\"next\":\"" + returnedUrl + "?" + OFFSET_PREFIX + "\\u003d1\\u0026" + LIMIT_PREFIX + "\\u003d1\\u0026" + QUERY_PREFIX + "\\u003db\\u003d\\u003dtest1\\u0026" + METADATA_PREFIX + "\\u003dtrue\"}}";
+
+        HttpTestUtil.addRecords(client, serviceUrl, data);
+        final String url1 =  baseUrl + "/" + serviceName + "/" + "0.0" + "/systems/" + AGENT_ID + "/jvms/" + JVM_ID;
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, url1 + "?" + QUERY_PREFIX + "=b==test1&" + METADATA_PREFIX + "=true&" + LIMIT_PREFIX + "=1", 200, expectedResponse);
+        final String url2 =  baseUrl + "/" + serviceName + "/" + "0.0.1" + "/systems/" + AGENT_ID + "/jvms/" + JVM_ID;
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, url2 + "?" + QUERY_PREFIX + "=b==test1&" + METADATA_PREFIX + "=true&" + LIMIT_PREFIX + "=1", 200, expectedResponse);
+        final String url3 =  baseUrl + "/" + serviceName + "/" + "0.0.3" + "/systems/" + AGENT_ID + "/jvms/" + JVM_ID;
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, url3 + "?" + QUERY_PREFIX + "=b==test1&" + METADATA_PREFIX + "=true&" + LIMIT_PREFIX + "=1", 404, null);
+    }
+
+    @Test
+    public void testGetWithMetaDataAndOffset() throws InterruptedException, TimeoutException, ExecutionException {
+        String data = "[{\"a\":\"test\",\"b\":\"test1\",\"c\":\"test2\"}, {\"b\":\"test1\"}," +
+                "{\"e\":\"test4\",\"b\":\"test1\"}]";
+
+        // {"response":["{\"b\":\"test1\"}"],"metaData":{"payloadCount":1,"count":3,
+        // "prev":"http://127.0.0.1:30000/jvm-io/0.0.2?q===test1&m=true&l=1",
+        // "next":"http://127.0.0.1:30000/jvm-io/0.0.2?o=2&l=1&q===test1&m=true"}}
+        String expectedResponse = "{\"response\":[{\"b\":\"test1\"" + SYSTEM_JVM_FRAGMENT + "}]," +
+                "\"metaData\":{\"payloadCount\":1,\"count\":3," +
+                "\"prev\":\"" + returnedUrl + "?" + QUERY_PREFIX + "\\u003db\\u003d\\u003dtest1\\u0026" + METADATA_PREFIX + "\\u003dtrue\\u0026" + LIMIT_PREFIX + "\\u003d1\"," +
+                "\"next\":\"" + returnedUrl + "?" + OFFSET_PREFIX + "\\u003d2\\u0026" + LIMIT_PREFIX + "\\u003d1\\u0026" + QUERY_PREFIX + "\\u003db\\u003d\\u003dtest1\\u0026" + METADATA_PREFIX + "\\u003dtrue\"}}";
+
+        HttpTestUtil.addRecords(client, serviceUrl, data);
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, serviceUrl + "?" + QUERY_PREFIX + "=b==test1&" + METADATA_PREFIX + "=true&" + OFFSET_PREFIX + "=1", 200, expectedResponse);
+    }
+
+    @Test
+    public void testGetMetDatPrevOffsetLessThanLimit() throws InterruptedException, TimeoutException, ExecutionException {
+        String data = "[{\"a\":\"test\",\"b\":\"test1\",\"c\":\"test2\"}, {\"b\":\"test1\"}," +
+                "{\"e\":\"test4\",\"b\":\"test1\"}, {\"b\":\"test1\"}, {\"b\":\"test1\"}]";
+
+        // {"response":["{\"b\":\"test1\"}","{\"e\":\"test4\",\"b\":\"test1\"}","{\"b\":\"test1\"}"],
+        // "metaData":{"payloadCount":1,"count":5,
+        // "prev":"http://127.0.0.1:30000/jvm-io/0.0.2?q===test1&m=true&l=1&o=0",
+        // "next":"http://127.0.0.1:30000/jvm-io/0.0.2?o=4&l=1&q===test1&m=true"}}
+        String expectedResponse = "{\"response\":[{\"b\":\"test1\"" + SYSTEM_JVM_FRAGMENT + "},{\"e\":\"test4\"," +
+                "\"b\":\"test1\"" + SYSTEM_JVM_FRAGMENT + "},{\"b\":\"test1\"" + SYSTEM_JVM_FRAGMENT + "}]," +
+                "\"metaData\":{\"payloadCount\":1,\"count\":5," +
+                "\"prev\":\"" + returnedUrl + "?" + QUERY_PREFIX + "\\u003db\\u003d\\u003dtest1\\u0026" + METADATA_PREFIX + "\\u003dtrue" +
+                "\\u0026" + LIMIT_PREFIX + "\\u003d1\\u0026" + OFFSET_PREFIX + "\\u003d0\",\"next\":\"" + returnedUrl + "?" + OFFSET_PREFIX + "\\u003d4" +
+                "\\u0026" + LIMIT_PREFIX + "\\u003d1\\u0026" + QUERY_PREFIX + "\\u003db\\u003d\\u003dtest1\\u0026" + METADATA_PREFIX + "\\u003dtrue\"}}";
+
+        HttpTestUtil.addRecords(client, serviceUrl, data);
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, serviceUrl + "?" + QUERY_PREFIX + "=b==test1&" + METADATA_PREFIX + "=true&" + OFFSET_PREFIX + "=1&" + LIMIT_PREFIX + "=3", 200, expectedResponse);
+    }
+
+    @Test
+    public void testGetMetDatPrevOffsetBiggerThanLimit() throws InterruptedException, TimeoutException, ExecutionException {
+        String data = "[{\"a\":\"test\",\"b\":\"test1\",\"c\":\"test2\"}, {\"b\":\"test1\"}," +
+                "{\"e\":\"test4\",\"b\":\"test1\"}, {\"b\":\"test1\"}, {\"b\":\"test1\"}, " +
+                "{\"b\":\"test1\"}]";
+
+        // {"response":["{\"b\":\"test1\"}","{\"b\":\"test1\"}"],
+        // "metaData":{"payloadCount":1,"count":6,
+        // "prev":"http://127.0.0.1:30000/jvm-io/0.0.2?q===test1&m=true&l=2&o=1",
+        // "next":"http://127.0.0.1:30000/jvm-io/0.0.2?o=5&l=1&q===test1&m=true"}}
+        String expectedResponse = "{\"response\":[{\"b\":\"test1\"" + SYSTEM_JVM_FRAGMENT + "},{\"b\":\"test1\"" + SYSTEM_JVM_FRAGMENT + "}]," +
+                "\"metaData\":{\"payloadCount\":1,\"count\":6," +
+                "\"prev\":\"" + returnedUrl + "?" + QUERY_PREFIX + "\\u003db\\u003d\\u003dtest1\\u0026" + METADATA_PREFIX + "\\u003dtrue\\u0026" + LIMIT_PREFIX + "\\u003d2\\u0026" + OFFSET_PREFIX + "\\u003d1\"," +
+                "\"next\":\"" + returnedUrl + "?" + OFFSET_PREFIX + "\\u003d5\\u0026" + LIMIT_PREFIX + "\\u003d1\\u0026" + QUERY_PREFIX + "\\u003db\\u003d\\u003dtest1\\u0026" + METADATA_PREFIX + "\\u003dtrue\"}}";
+
+        HttpTestUtil.addRecords(client, serviceUrl, data);
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, serviceUrl + "?" + QUERY_PREFIX + "=b==test1&" + METADATA_PREFIX + "=true&" + OFFSET_PREFIX + "=3&" + LIMIT_PREFIX + "=2", 200, expectedResponse);
+    }
+
+    @Test
+    public void testGetMetDatPrevOffsetEqualToLimit() throws InterruptedException, TimeoutException, ExecutionException {
+        String data = "[{\"a\":\"test\",\"b\":\"test1\",\"c\":\"test2\"}, {\"b\":\"test1\"}," +
+                "{\"e\":\"test4\",\"b\":\"test1\"}]";
+
+        // {"response":["{\"b\":\"test1\"}"],
+        // "metaData":{"payloadCount":1,"count":3,
+        // "prev":"http://127.0.0.1:30000/jvm-io/0.0.2?q===test1&m=true&l=1",
+        // "next":"http://127.0.0.1:30000/jvm-io/0.0.2?o=2&l=1&q===test1&m=true"}}
+        String expectedResponse = "{\"response\":[{\"b\":\"test1\"" + SYSTEM_JVM_FRAGMENT + "}]," +
+                "\"metaData\":{\"payloadCount\":1,\"count\":3," +
+                "\"prev\":\"" + returnedUrl + "?" + QUERY_PREFIX + "\\u003db\\u003d\\u003dtest1\\u0026" + METADATA_PREFIX + "\\u003dtrue\\u0026" + LIMIT_PREFIX + "\\u003d1\"," +
+                "\"next\":\"" + returnedUrl + "?" + OFFSET_PREFIX + "\\u003d2\\u0026" + LIMIT_PREFIX + "\\u003d1\\u0026" + QUERY_PREFIX + "\\u003db\\u003d\\u003dtest1\\u0026" + METADATA_PREFIX + "\\u003dtrue\"}}";
+
+        HttpTestUtil.addRecords(client, serviceUrl, data);
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, serviceUrl + "?" + QUERY_PREFIX + "=b==test1&" + METADATA_PREFIX + "=true&" + OFFSET_PREFIX + "=1&" + LIMIT_PREFIX + "=1", 200, expectedResponse);
+    }
+
+    @Test
+    public void testGetMetDatNextOffsetLessThanLimit() throws InterruptedException, TimeoutException, ExecutionException {
+        String data = "[{\"a\":\"test\",\"b\":\"test1\",\"c\":\"test2\"}, {\"b\":\"test1\"}," +
+                "{\"e\":\"test4\",\"b\":\"test1\"}, {\"b\":\"test1\"}, {\"b\":\"test1\"}, " +
+                "{\"b\":\"test1\"}]";
+
+        // {"response":["{\"b\":\"test1\"}","{\"e\":\"test4\",\"b\":\"test1\"}"],
+        // "metaData":{"payloadCount":2,"count":6,
+        // "prev":"http://127.0.0.1:30000/jvm-io/0.0.2?q===test1&m=true&l=1&o=0",
+        // "next":"http://127.0.0.1:30000/jvm-io/0.0.2?o=3&l=2&q===test1&m=true"}}
+        String expectedResponse = "{\"response\":[{\"b\":\"test1\"" + SYSTEM_JVM_FRAGMENT + "},{\"e\":\"test4\"," +
+                "\"b\":\"test1\"" + SYSTEM_JVM_FRAGMENT + "}],\"metaData\":{\"payloadCount\":2,\"count\":6," +
+                "\"prev\":\"" + returnedUrl + "?" + QUERY_PREFIX + "\\u003db\\u003d\\u003dtest1\\u0026" + METADATA_PREFIX + "\\u003dtrue\\u0026" + LIMIT_PREFIX + "\\u003d1\\u0026" + OFFSET_PREFIX + "\\u003d0\"," +
+                "\"next\":\"" + returnedUrl + "?" + OFFSET_PREFIX + "\\u003d3\\u0026" + LIMIT_PREFIX + "\\u003d2\\u0026" + QUERY_PREFIX + "\\u003db\\u003d\\u003dtest1\\u0026" + METADATA_PREFIX + "\\u003dtrue\"}}";
+
+        HttpTestUtil.addRecords(client, serviceUrl, data);
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, serviceUrl + "?" + QUERY_PREFIX + "=b==test1&" + METADATA_PREFIX + "=true&" + OFFSET_PREFIX + "=1&" + LIMIT_PREFIX + "=2", 200, expectedResponse);
+    }
+
+    @Test
+    public void testGetMetDatNextOffsetBiggerThanLimit() throws InterruptedException, TimeoutException, ExecutionException {
+        String data = "[{\"a\":\"test\",\"b\":\"test1\",\"c\":\"test2\"}, {\"b\":\"test1\"}," +
+                "{\"e\":\"test4\",\"b\":\"test1\"}, {\"b\":\"test1\"}, {\"b\":\"test1\"}, " +
+                "{\"b\":\"test1\"}]";
+
+        // {"response":["{\"b\":\"test1\"}","{\"b\":\"test1\"}"],
+        // "metaData":{"payloadCount":1,"count":6,
+        // "prev":"http://127.0.0.1:30000/jvm-io/0.0.2?q===test1&m=true&l=2&o=1",
+        // "next":"http://127.0.0.1:30000/jvm-io/0.0.2?o=5&l=1&q===test1&m=true"}}
+        String expectedResponse = "{\"response\":[{\"b\":\"test1\"" + SYSTEM_JVM_FRAGMENT + "},{\"b\":\"test1\"" + SYSTEM_JVM_FRAGMENT + "}]," +
+                "\"metaData\":{\"payloadCount\":1,\"count\":6," +
+                "\"prev\":\"" + returnedUrl + "?" + QUERY_PREFIX + "\\u003db\\u003d\\u003dtest1\\u0026" + METADATA_PREFIX + "\\u003dtrue\\u0026" + LIMIT_PREFIX + "\\u003d2\\u0026" + OFFSET_PREFIX + "\\u003d1\"," +
+                "\"next\":\"" + returnedUrl + "?" + OFFSET_PREFIX + "\\u003d5\\u0026" + LIMIT_PREFIX + "\\u003d1\\u0026" + QUERY_PREFIX + "\\u003db\\u003d\\u003dtest1\\u0026" + METADATA_PREFIX + "\\u003dtrue\"}}";
+
+        HttpTestUtil.addRecords(client, serviceUrl, data);
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, serviceUrl + "?" + QUERY_PREFIX + "=b==test1&" + METADATA_PREFIX + "=true&" + OFFSET_PREFIX + "=3&" +LIMIT_PREFIX + "=2", 200, expectedResponse);
+    }
+
+    @Test
+    public void testGetMetDatNextOffsetEqualToLimit() throws InterruptedException, TimeoutException, ExecutionException {
+        String data = "[{\"a\":\"test\",\"b\":\"test1\",\"c\":\"test2\"}, {\"b\":\"test1\"}," +
+                "{\"e\":\"test4\",\"b\":\"test1\"}, {\"b\":\"test1\"}, {\"b\":\"test1\"}, " +
+                "{\"b\":\"test1\"}]";
+        // {"response":["{\"e\":\"test4\",\"b\":\"test1\"}","{\"b\":\"test1\"}"],
+        // "metaData":{"payloadCount":2,"count":6,
+        // "prev":"http://127.0.0.1:30000/jvm-io/0.0.2?q===test1&m=true&l=2&o=0",
+        // "next":"http://127.0.0.1:30000/jvm-io/0.0.2?o=4&l=2&q===test1&m=true"}}
+        String expectedResponse = "{\"response\":[{\"e\":\"test4\",\"b\":\"test1\"" + SYSTEM_JVM_FRAGMENT + "},{\"b\":\"test1\"" + SYSTEM_JVM_FRAGMENT + "}]," +
+                "\"metaData\":{\"payloadCount\":2,\"count\":6," +
+                "\"prev\":\"" + returnedUrl + "?" + QUERY_PREFIX + "\\u003db\\u003d\\u003dtest1\\u0026" + METADATA_PREFIX + "\\u003dtrue\\u0026" + LIMIT_PREFIX + "\\u003d2\\u0026" + OFFSET_PREFIX + "\\u003d0\"," +
+                "\"next\":\"" + returnedUrl + "?" + OFFSET_PREFIX + "\\u003d4\\u0026" + LIMIT_PREFIX + "\\u003d2\\u0026" + QUERY_PREFIX + "\\u003db\\u003d\\u003dtest1\\u0026" + METADATA_PREFIX + "\\u003dtrue\"}}";
+
+        HttpTestUtil.addRecords(client, serviceUrl, data);
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, serviceUrl + "?" + QUERY_PREFIX + "=b==test1&" + METADATA_PREFIX + "=true&" + OFFSET_PREFIX + "=2&" + LIMIT_PREFIX + "=2", 200, expectedResponse);
+    }
+
+    @Test
+    public void testGetMetDatNextExtremity() throws InterruptedException, TimeoutException, ExecutionException {
+        String data = "[{\"a\":\"test\",\"b\":\"test1\",\"c\":\"test2\"}, {\"b\":\"test1\"}," +
+                "{\"e\":\"test4\",\"b\":\"test1\"}, {\"b\":\"test1\"}, {\"b\":\"test1\"}, " +
+                "{\"b\":\"test1\"}]";
+
+        // {"response":["{\"e\":\"test4\",\"b\":\"test1\"}","{\"b\":\"test1\"}"],
+        // "metaData":{"payloadCount":2,"count":6,
+        // "prev":"http://127.0.0.1:30000/jvm-io/0.0.2?q===test1&m=true&l=2&o=0",
+        // "next":"http://127.0.0.1:30000/jvm-io/0.0.2?o=4&l=2&q===test1&m=true"}}
+        String expectedResponse = "{\"response\":[{\"e\":\"test4\",\"b\":\"test1\"" + SYSTEM_JVM_FRAGMENT + "},{\"b\":\"test1\"" + SYSTEM_JVM_FRAGMENT + "}]," +
+                "\"metaData\":{\"payloadCount\":2,\"count\":6," +
+                "\"prev\":\"" + returnedUrl + "?" + QUERY_PREFIX + "\\u003db\\u003d\\u003dtest1\\u0026" + METADATA_PREFIX + "\\u003dtrue\\u0026" + LIMIT_PREFIX + "\\u003d2\\u0026" + OFFSET_PREFIX + "\\u003d0\"," +
+                "\"next\":\"" + returnedUrl + "?" + OFFSET_PREFIX + "\\u003d4\\u0026" + LIMIT_PREFIX + "\\u003d2\\u0026" + QUERY_PREFIX + "\\u003db\\u003d\\u003dtest1\\u0026" + METADATA_PREFIX + "\\u003dtrue\"}}";
+
+        HttpTestUtil.addRecords(client, serviceUrl, data);
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, serviceUrl + "?" + QUERY_PREFIX + "=b==test1&" + METADATA_PREFIX + "=true&" + OFFSET_PREFIX + "=2&" + LIMIT_PREFIX + "=2", 200, expectedResponse);
+    }
+
+    @Test
+    public void testGetMetDatPrevExtremity() throws InterruptedException, TimeoutException, ExecutionException {
+        String data = "[{\"a\":\"test\",\"b\":\"test1\",\"c\":\"test2\"}, {\"b\":\"test1\"}," +
+                "{\"e\":\"test4\",\"b\":\"test1\"}, {\"b\":\"test1\"}, {\"b\":\"test1\"}, " +
+                "{\"b\":\"test1\"}]";
+
+        // {"response":["{\"e\":\"test4\",\"b\":\"test1\"}","{\"b\":\"test1\"}","{\"b\":\"test1\"}"],"
+        // metaData":{"payloadCount":1,"count":6,
+        // "prev":"http://127.0.0.1:30000/jvm-io/0.0.2?q=b==test1&m=true&l=2&o=0",
+        // "next":"http://127.0.0.1:30000/jvm-io/0.0.2?o=5&l=1&q=b==test1&m=true"}}
+        String expectedResponse = "{\"response\":[{\"e\":\"test4\",\"b\":\"test1\"" + SYSTEM_JVM_FRAGMENT + "}," +
+                "{\"b\":\"test1\"" + SYSTEM_JVM_FRAGMENT + "},{\"b\":\"test1\"" + SYSTEM_JVM_FRAGMENT + "}]," +
+                "\"metaData\":{\"payloadCount\":1,\"count\":6," +
+                "\"prev\":\"" + returnedUrl + "?" + QUERY_PREFIX + "\\u003db\\u003d\\u003dtest1\\u0026" + METADATA_PREFIX + "\\u003dtrue\\u0026" + LIMIT_PREFIX + "\\u003d2\\u0026" + OFFSET_PREFIX + "\\u003d0\"," +
+                "\"next\":\"" + returnedUrl + "?" + OFFSET_PREFIX + "\\u003d5\\u0026" + LIMIT_PREFIX + "\\u003d1\\u0026" + QUERY_PREFIX + "\\u003db\\u003d\\u003dtest1\\u0026" + METADATA_PREFIX + "\\u003dtrue\"}}";
+
+        HttpTestUtil.addRecords(client, serviceUrl, data);
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, serviceUrl + "?" + QUERY_PREFIX + "=b==test1&" + METADATA_PREFIX + "=true&" + OFFSET_PREFIX + "=2&" + LIMIT_PREFIX + "=3", 200, expectedResponse);
+    }
+
+
+    private static String getRandomSystemId() {
+        return UUID.randomUUID().toString();
+    }
+
+    private static String getRandomJvmId() {
+        return UUID.randomUUID().toString();
+    }
+
+    private String createJSON() {
+        return createJSON(getTimestamp());
+    }
+
+    private long getTimestamp() {
+        timeStamp += 1;
+        return timeStamp;
+    }
+
+    private String createJSON(final long ts) {
+        return jsonData.replace(TIMESTAMP_TOKEN, Long.toString(ts));
+    }
+
+    private ContentResponse put(final String systemid, final String jvmid, final long ts) throws InterruptedException, ExecutionException, TimeoutException {
+        final Request request = client.newRequest(simpleUrl + "/systems/" + systemid + "/jvms/" + jvmid);
+        request.header(HttpHeader.CONTENT_TYPE, "application/json");
+        final String contentStr = createJSON(ts);
+        request.content(new StringContentProvider("{ \"set\" : " +contentStr + "}"));
+        ContentResponse response = request.method(HttpMethod.PUT).send();
+        assertEquals(HTTP_200_OK, response.getStatus());
+        final String expected = "";
+        assertEquals(expected, response.getContentAsString());
+        return response;
+    }
+
+    private ContentResponse post(final String systemid, final String jvmid) throws InterruptedException, ExecutionException, TimeoutException {
+        final Request request = client.newRequest(simpleUrl + "/systems/" + systemid + "/jvms/" + jvmid);
+        request.header(HttpHeader.CONTENT_TYPE, "application/json");
+        request.content(new StringContentProvider( '[' + createJSON() + ']'));
+        ContentResponse response = request.method(HttpMethod.POST).send();
+        assertEquals(HTTP_200_OK, response.getStatus());
+        final String expected = "";
+        assertEquals(expected, response.getContentAsString());
+        return response;
+    }
+
+    private ContentResponse getUnknown(final String systemid, final String jvmid) throws InterruptedException, ExecutionException, TimeoutException {
+        ContentResponse response = get(systemid, jvmid);
+        assertTrue(parse(response, jvmid).isEmpty());
+        return response;
+    }
+
+    private ContentResponse getKnown(final String systemid, final String jvmid) throws InterruptedException, ExecutionException, TimeoutException {
+        ContentResponse response = get(systemid, jvmid);
+        assertEquals(1, parse(response, jvmid).size());
+        return response;
+    }
+
+    private ContentResponse get(final String systemid, final String jvmid) throws InterruptedException, ExecutionException, TimeoutException {
+        ContentResponse response = client.newRequest(simpleUrl + "/systems/" + systemid + "/jvms/" + jvmid).method(HttpMethod.GET).send();
+        assertEquals(HTTP_200_OK, response.getStatus());
+        return response;
+    }
+
+    private ContentResponse get(final String systemid, final String jvmid, final String query) throws InterruptedException, ExecutionException, TimeoutException {
+        final Request rq = client.newRequest(simpleUrl + "/systems/" + systemid + "/jvms/" + jvmid + query);
+        rq.method(HttpMethod.GET);
+        ContentResponse response = rq.send();
+        assertEquals(HTTP_200_OK, response.getStatus());
+        return response;
+    }
+
+    private ContentResponse delete(final String systemid, final String jvmid) throws InterruptedException, ExecutionException, TimeoutException {
+        ContentResponse response = client.newRequest(simpleUrl + "/systems/" + systemid + "/jvms/" + jvmid).method(HttpMethod.DELETE).send();
+        assertEquals(HTTP_200_OK, response.getStatus());
+        final String expected = "";
+        assertEquals(expected, response.getContentAsString());
+        return response;
+    }
+
+    class TinyJvmIo {
+        String jvmId;
+        long timeStamp;
+        public TinyJvmIo(String jvmId, long ts) {
+            this.jvmId = jvmId;
+            this.timeStamp = ts;
+        }
+    }
+    private List<TinyJvmIo> parse(ContentResponse contentResponse, final String expectedJvmId) {
+
+        JsonParser parser = new JsonParser();
+        JsonObject json = (JsonObject) parser.parse(contentResponse.getContentAsString());
+        JsonElement response = json.get("response");
+
+        JsonArray allData = response.getAsJsonArray();
+        List<TinyJvmIo> result = new ArrayList<>();
+
+        for (JsonElement entry : allData) {
+
+            json = (JsonObject) parser.parse(entry.toString());
+
+            assertTrue(json.has("jvmId"));
+            //assertTrue(json.has("agentId"));
+            assertTrue(json.has("timeStamp"));
+
+            final String jvmId = json.get("jvmId").getAsString();
+            //final String agentId = json.get("agentId").getAsString();
+            //final long timeStamp = getLong(json, "timeStamp");
+
+            if (expectedJvmId != null) {
+                assertEquals(expectedJvmId, jvmId);
+            }
+
+            TinyJvmIo hi = new TinyJvmIo(jvmId, 0);
+
+            result.add(hi);
+        }
+        return result;
+    }
+
+    private static long getLong(JsonObject json, final String id) {
+        JsonElement el = json.get(id);
+        if (el.isJsonObject()) {
+            final JsonObject o = el.getAsJsonObject();
+            return o.get("$numberLong").getAsLong();
+        } else {
+            return el.getAsLong();
+        }
+    }
+}