changeset 23:ab2706b9b1e3

Add common YAML utilities (The previous commit was missing the new files for this functionality.) This patch adds YAML reading and writing utilities to the base common packages. The intent is we'll be able to compare (at build time) our Swagger API definitions to our Java models (or even build them), or convert the YAML to Java schema for use with the schema validation utilities. Reviewed-by: sgehwolf, neugens Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2017-September/024988.html
author stooke@redhat.com
date Fri, 15 Sep 2017 11:41:22 -0400
parents 0a4ed473faab
children e7a2adb465d1
files common/json/pom.xml common/json/src/main/java/com/redhat/thermostat/common/json/JsonUtil.java common/json/src/main/java/com/redhat/thermostat/common/json/models/ApiDef.java common/json/src/main/java/com/redhat/thermostat/common/json/models/SchemaDef.java common/json/src/main/java/com/redhat/thermostat/common/json/models/SchemaType.java common/json/src/main/java/com/redhat/thermostat/common/json/models/SwaggerDef.java common/json/src/main/java/com/redhat/thermostat/common/yaml/JsonToYaml.java common/json/src/main/java/com/redhat/thermostat/common/yaml/RefResolver.java common/json/src/main/java/com/redhat/thermostat/common/yaml/YamlToJson.java common/json/src/test/java/com/redhat/thermostat/common/json/JsonUtilTest.java common/json/src/test/java/com/redhat/thermostat/common/json/models/SchemaDefTest.java common/json/src/test/java/com/redhat/thermostat/common/yaml/JsonToYamlTest.java common/json/src/test/java/com/redhat/thermostat/common/yaml/RefResolverTest.java common/json/src/test/java/com/redhat/thermostat/common/yaml/YamlToJsonTest.java common/json/src/test/resources/jvms-swagger.json common/json/src/test/resources/jvms-swagger.yaml common/json/src/test/resources/systems-swagger.json common/json/src/test/resources/systems-swagger.yaml
diffstat 18 files changed, 2984 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/json/pom.xml	Fri Sep 15 11:41:22 2017 -0400
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright 2012-2017 Red Hat, Inc.
+
+ This file is part of Thermostat.
+
+ Thermostat is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 2, or (at your
+ option) any later version.
+
+ Thermostat is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Thermostat; see the file COPYING.  If not see
+ <http://www.gnu.org/licenses/>.
+
+ Linking this code with other modules is making a combined work
+ based on this code.  Thus, the terms and conditions of the GNU
+ General Public License cover the whole combination.
+
+ As a special exception, the copyright holders of this code give
+ you permission to link this code with independent modules to
+ produce an executable, regardless of the license terms of these
+ independent modules, and to copy and distribute the resulting
+ executable under terms of your choice, provided that you also
+ meet, for each linked independent module, the terms and conditions
+ of the license of that module.  An independent module is a module
+ which is not derived from or based on this code.  If you modify
+ this code, you may extend this exception to your version of the
+ library, but you are not obligated to do so.  If you do not wish
+ to do so, delete this exception statement from your version.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>com.redhat.thermostat</groupId>
+        <artifactId>base-common</artifactId>
+        <version>1.99.12-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>base-common-json</artifactId>
+    <packaging>jar</packaging>
+
+    <name>Thermostat Common JSON / YAML Libraries</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.google.code.gson</groupId>
+            <artifactId>gson</artifactId>
+            <version>2.6.2</version>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.12</version>
+        </dependency>
+        <dependency>
+            <groupId>com.redhat.thermostat.lang.schema</groupId>
+            <artifactId>thermostat-lang-schema</artifactId>
+            <version>1.99.12-SNAPSHOT</version>
+        </dependency>
+    </dependencies>
+
+</project>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/json/src/main/java/com/redhat/thermostat/common/json/JsonUtil.java	Fri Sep 15 11:41:22 2017 -0400
@@ -0,0 +1,149 @@
+/*
+ * 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.common.json;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+
+import java.util.Map;
+import java.util.NoSuchElementException;
+
+/**
+ * misc GSON utility classes
+ */
+public class JsonUtil {
+
+    static String capitalize(final String s) {
+        return s.substring(0, 1).toUpperCase() + s.substring(1);
+    }
+
+    /**
+     * access a nested JSON object via a path string
+     * "foo.bar.2.fred" is equivalent to
+     * root.getAsJsonObject().get("foo").getAsJsonObject().get("bar").getAsJsonArray().get(2).getAsJsonObject().get("fred")
+     *
+     * @param root object to access
+     * @param path path into object
+     * @return element within root
+     */
+    public static JsonElement fetch(final JsonElement root, final String path) {
+        String[] pathelements = path.split("[.]");
+        // NOTE - it would be nice to parse foo[N] to foo.N to allow normal subscripting
+        return fetch(root, pathelements, 0);
+    }
+
+    /**
+     * access a nexted JSON object via a path string
+     * "foo.bar.2.fred" is equivalent to
+     * root.getAsJsonObject().get("foo").getAsJsonObject().get("bar").getAsJsonArray().get(2).getAsJsonObject().get("fred")
+     *
+     * @param root      object to access
+     * @param path      path into object
+     * @param delimiter delimiter regex for path antries (default '.')
+     * @return element within root
+     */
+    public static JsonElement fetch(final JsonElement root, final String path, final String delimiter) {
+        String[] pathelements = path.split(delimiter);
+        return fetch(root, pathelements, 0);
+    }
+
+    private static JsonElement fetch(final JsonElement e, final String[] pathelements, int idx) {
+        if (idx == pathelements.length) {
+            // we've fallen off the end of the input path - now it's time to return the current object.
+            return e;
+        } else if (idx > pathelements.length) {
+            throw new IndexOutOfBoundsException("Internal error in JsonUtil.fetch() - index = " + idx);
+        } else {
+            if (e.isJsonObject()) {
+                JsonObject obj = e.getAsJsonObject();
+                JsonElement next = obj.get(pathelements[idx]);
+                if (next == null) {
+                    throw new NoSuchElementException(pathelements[idx]);
+                } else {
+                    return fetch(next, pathelements, idx + 1);
+                }
+            } else if (e.isJsonArray()) {
+                JsonArray arr = e.getAsJsonArray();
+                int n = Integer.parseInt(pathelements[idx]);
+                if (arr.size() <= n) {
+                    throw new ArrayIndexOutOfBoundsException(n);
+                } else {
+                    return fetch(arr.get(n), pathelements, idx + 1);
+                }
+            } else {
+                throw new NoSuchElementException(pathelements[idx]);
+            }
+        }
+    }
+
+    /**
+     * perform a deep copy of a JsonElement
+     *
+     * @param jsonElement element to copy
+     * @return deep copy of the element tree
+     */
+    // why isn't JsonElement.deepCopy() public?
+    public static JsonElement deepCopy(JsonElement jsonElement) {
+        if (jsonElement.isJsonPrimitive() || jsonElement.isJsonNull()) {
+            return jsonElement;       // these are immutables anyway
+        } else if (jsonElement.isJsonObject()) {
+            return deepCopy(jsonElement.getAsJsonObject());
+        } else if (jsonElement.isJsonArray()) {
+            return deepCopy(jsonElement.getAsJsonArray());
+        } else {
+            throw new UnsupportedOperationException("Unsupported element: " + jsonElement);
+        }
+    }
+
+    private static JsonObject deepCopy(JsonObject jsonObject) {
+        JsonObject result = new JsonObject();
+        for (Map.Entry<String, JsonElement> entry : jsonObject.entrySet()) {
+            result.add(entry.getKey(), deepCopy(entry.getValue()));
+        }
+        return result;
+    }
+
+    private static JsonArray deepCopy(JsonArray jsonArray) {
+        JsonArray result = new JsonArray();
+        for (JsonElement e : jsonArray) {
+            result.add(deepCopy(e));
+        }
+        return result;
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/json/src/main/java/com/redhat/thermostat/common/json/models/ApiDef.java	Fri Sep 15 11:41:22 2017 -0400
@@ -0,0 +1,105 @@
+/*
+ * 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.common.json.models;
+
+import com.google.gson.JsonObject;
+import com.google.gson.annotations.SerializedName;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Java representation of a Swagger-defined web API endpoint
+ */
+public class ApiDef {
+
+    String path;
+    String description;
+    JsonObject root;
+    List<ParameterDef>  parameters;
+    MethodDef get;
+    MethodDef put;
+    MethodDef post;
+    MethodDef delete;
+
+    public static class MethodDef {
+        List<ParameterDef> parameters;
+        String description;
+        Map<Integer, ResponseDef> responses;
+
+        public String getDescription() {
+            return this.description;
+        }
+    }
+
+    static class ResponseDef {
+        String description;
+        JsonObject schema;
+    }
+
+    static class ParameterDef {
+        enum In { query, path, body };
+        enum Type { integer, string, bool };
+        String name;
+        String description;
+        In in;
+        Type type;
+        boolean required;
+        @SerializedName("default")
+        String deflt;
+    }
+
+    public void setPath(String path) {
+        this.path = path;
+    }
+
+    public MethodDef getGETAPI() {
+        return get;
+    }
+
+    public MethodDef getPOSTAPI() {
+        return post;
+    }
+
+    public MethodDef getDELETEAPI() {
+        return delete;
+    }
+
+    public MethodDef getPUTAPI() {
+        return put;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/json/src/main/java/com/redhat/thermostat/common/json/models/SchemaDef.java	Fri Sep 15 11:41:22 2017 -0400
@@ -0,0 +1,208 @@
+/*
+ * 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.common.json.models;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.annotations.SerializedName;
+
+import com.redhat.thermostat.common.yaml.JsonToYaml;
+import com.redhat.thermostat.lang.schema.annotations.Schema;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Partiual Java representation of a JSON Schema (see json-schema.org)
+ */
+public class SchemaDef implements SchemaType {
+
+    private static final Gson gson;
+    private static final int DEFAULT_MAXDEPTH = 1;
+
+    static {
+        final boolean ENABLE_PRETTY_PRINT = false;
+        if (ENABLE_PRETTY_PRINT) {
+            GsonBuilder gbuilder = new GsonBuilder();
+            gbuilder.setPrettyPrinting();
+            gson = gbuilder.create();
+        } else {
+            gson = new Gson();
+        }
+    }
+
+    @SerializedName("$schema")
+    private String schema;
+    @SerializedName("$id")
+    private String id;
+    private String title;
+    private String description;
+    private Map<String, SchemaType> properties;
+    private List<String> required;
+
+    /** standard JSON schema properties that are not yet implemented:
+     private int multipleOf;
+     private long maximum;
+     private long exclusiveMaximum;
+     private long minimum;
+     private long exclusiveMinimum;
+     private int maxLength;
+     private int minLength;
+     private String pattern;
+     private int maxItems;
+     private int minItems;
+     private boolean uniqueItems;
+     private int maxProperties;
+     private int minProperties;
+     (and more, see json-schema.org)
+     ***/
+
+    // for GSon
+    public SchemaDef() {}
+
+    public SchemaDef(Object target) {
+        this(target.getClass(), DEFAULT_MAXDEPTH);
+    }
+
+    public SchemaDef(Object target, int maxDepth) {
+        this(target.getClass(), maxDepth);
+    }
+
+    public SchemaDef(Class target) {
+        this(target, true, DEFAULT_MAXDEPTH);
+    }
+
+    public SchemaDef(Class target, int maxDepth) {
+        this(target, true, maxDepth);
+    }
+
+    private SchemaDef(Class target, boolean rootLevel, int maxDepth) {
+        if (rootLevel) {
+            schema = "http://json-schema.org/draft-04/schema#";
+        }
+        Field[] fields = target.getDeclaredFields();
+        if (fields.length > 0) {
+            properties = new HashMap<>(fields.length);
+            required = new ArrayList<>();
+            for (Field field : fields) {
+                final boolean isTransient =  Modifier.isTransient(field.getModifiers());
+                if (!isTransient) {
+                    String fname = field.getAnnotation(SerializedName.class) != null ? field.getAnnotation(SerializedName.class).value() : field.getName();
+                    Schema schema = field.getAnnotation(Schema.class);
+                    String descr = null;
+                    if (schema != null) {
+                        if (!schema.name().isEmpty()) {
+                            fname = schema.name();
+                        }
+                        descr = schema.description();
+                        if (schema.required()) {
+                            required.add(fname);
+                        }
+                    }
+                    if (field.getType().isPrimitive()) {
+                        String tt = field.getType().getSimpleName();
+                        properties.put(fname, new SimpleType(tt, descr));
+                    } else if (maxDepth > 1){
+                        properties.put(fname, new SchemaDef(field.getType(), false, maxDepth - 1));
+                    } else {
+                        properties.put(fname, new SimpleType(field.getType().getSimpleName(), descr));
+                    }
+                }
+            }
+            if (required.isEmpty()) {
+                required = null;
+            }
+        }
+    }
+
+    public Map<String, SchemaType> getProperties() {
+        return this.properties;
+    }
+
+    public List<String> getRequired() {
+        return this.required;
+    }
+
+    public JsonObject toJsonObject() {
+        JsonElement el = gson.toJsonTree(this);
+        return el.isJsonObject() ? el.getAsJsonObject() : null;
+    }
+
+    public String toString() {
+        return gson.toJson(this);
+    }
+
+    @Override
+    public TypeClass getTypeClass() {
+        return TypeClass.OBJECT;
+    }
+
+    public static class SimpleType implements SchemaType {
+        transient TypeClass type;
+        @SerializedName("type")
+        String typeStr;
+        String description;
+        SimpleType(String tname, String descr) {
+            if ("string".equals(tname)) {
+                type = TypeClass.STRING;
+            } else if ("int".equals(tname) || "float".equals(tname) || "double".equals(tname) || "long".equals(tname) || "number".equals(tname)) {
+                type = TypeClass.NUMBER;
+            } else if ("Integer".equals(tname) || "Float".equals(tname) || "Double".equals(tname) || "Long".equals(tname)) {
+                type = TypeClass.NUMBER;
+            } else if ("boolean".equals(tname) || "Boolean".equals(tname)) {
+                type = TypeClass.BOOLEAN;
+            } else {
+                type = TypeClass.OBJECT;
+            }
+            this.typeStr = type == TypeClass.OBJECT ? tname : type.toString();
+            this.description = descr;
+        }
+        @Override
+        public TypeClass getTypeClass() {
+            return type;
+        }
+        public String getTypeName() {
+            return type == TypeClass.OBJECT ? typeStr : type.toString();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/json/src/main/java/com/redhat/thermostat/common/json/models/SchemaType.java	Fri Sep 15 11:41:22 2017 -0400
@@ -0,0 +1,65 @@
+/*
+ * 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.common.json.models;
+
+/**
+ * the type of a JSON schema element
+ */
+public interface SchemaType {
+    TypeClass getTypeClass();
+
+    enum TypeClass {
+
+        OBJECT("object"),
+        ARRAY("array"),
+        NUMBER("number"),
+        STRING("string"),
+        BOOLEAN("boolean"),
+        NULL("null");
+
+        private final String typeString;
+
+        private TypeClass(String s) {
+            typeString = s;
+        }
+
+        @Override
+        public String toString() {
+            return typeString;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/json/src/main/java/com/redhat/thermostat/common/json/models/SwaggerDef.java	Fri Sep 15 11:41:22 2017 -0400
@@ -0,0 +1,76 @@
+/*
+ * 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.common.json.models;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Java representation of a Swagger file
+ */
+public class SwaggerDef {
+
+    String swagger;
+    InfoDef info;
+    List<String> consumes;
+    List<String> produces;
+    String basePath;
+    Map<String, ApiDef> paths;
+
+    static class InfoDef {
+        String version;
+        String title;
+        LicenseDef license;
+    }
+
+    static class LicenseDef {
+        String name;
+        String url;
+    }
+
+    static class SwaggerSchema extends SchemaDef {
+        private String type;
+    }
+
+    public String getBasePath() {
+        return basePath;
+    }
+
+    public Map<String, ApiDef> getPaths() {
+        return paths;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/json/src/main/java/com/redhat/thermostat/common/yaml/JsonToYaml.java	Fri Sep 15 11:41:22 2017 -0400
@@ -0,0 +1,119 @@
+/*
+ * 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.common.yaml;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.Map;
+
+/**
+ * convert JSON object to YAML string
+ */
+public class JsonToYaml {
+
+    private Writer out;
+    private int indent = 2;
+
+    public void setIndentation(int i) {
+        this.indent = i;
+    }
+
+    String jsonToYamlString(JsonObject root) throws IOException {
+        this.out = new StringWriter();
+        jsonToYaml(root, out);
+        return out.toString();
+    }
+
+    public void jsonToYaml(JsonObject root, Writer out) throws IOException {
+        this.out = out;
+        jsonToYaml(root, 0);
+    }
+
+    private void dispatch(JsonElement el, int currentIndent) throws IOException {
+        if (el.isJsonPrimitive()) {
+            final JsonPrimitive pr = el.getAsJsonPrimitive();
+            if (pr.isBoolean() || pr.isNumber()) {
+                out.append(el.getAsString()).append('\n');
+            } else {
+                out.append("'").append(el.getAsString()).append("'\n");
+            }
+        } else if (el.isJsonArray()) {
+            out.append('\n');
+            jsonToYaml(el.getAsJsonArray(), currentIndent);
+        } else if (el.isJsonObject()) {
+            out.append('\n');
+            jsonToYaml(el.getAsJsonObject(), currentIndent);
+        } else {
+            throw new UnsupportedOperationException("Unsupported element: " + el);
+        }
+    }
+
+    private void jsonToYaml(JsonObject obj, int currentIndent) throws IOException {
+        final int newindent = currentIndent + indent;
+        for (final Map.Entry<String, JsonElement> entry : obj.entrySet()) {
+            doIndent(currentIndent);
+            out.append(entry.getKey()).append(": ");
+            dispatch(entry.getValue(), newindent);
+        }
+    }
+
+    private void jsonToYaml(JsonArray array, int currentIndent) throws IOException {
+        for (final JsonElement el : array) {
+            doIndent(currentIndent);
+            out.append("- ");
+            if (el.isJsonArray()) {
+                jsonToYaml(el.getAsJsonArray(), 0);
+            } else if (el.isJsonObject()) {
+                jsonToYaml(el.getAsJsonObject(), 0);
+            } else {
+                dispatch(el, 0);
+            }
+        }
+    }
+
+    private void doIndent(int currentIndent) throws IOException {
+        for (int i = 0; i < currentIndent; i++)
+            out.append(' ');
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/json/src/main/java/com/redhat/thermostat/common/yaml/RefResolver.java	Fri Sep 15 11:41:22 2017 -0400
@@ -0,0 +1,99 @@
+/*
+ * 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.common.yaml;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.redhat.thermostat.common.json.JsonUtil;
+
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * Given a JSON object (created from a Swagger YAML or JSON file), resolve all $ref paths
+ * TODO: currently doesn't test for circular references
+ */
+public class RefResolver {
+
+    public static JsonObject resolvePath(JsonObject defsroot, JsonObject current, String path) throws IOException {
+        String[] pathElements = path.split("/");
+        for (String pe : pathElements) {
+            if ("#".equals(pe)) {
+                current = defsroot;
+            } else if (current.has(pe)) {
+                current = current.getAsJsonObject(pe);
+            } else {
+                throw new IOException("unresolvable reference '" + pe + "' in path '" + path + "'");
+            }
+        }
+        return current;
+    }
+
+    private static final String REF_MEMBER = "$ref";
+
+    public static JsonElement resolveRefs(JsonObject defsRoot, JsonElement objToResolve) throws IOException {
+
+        if (objToResolve.isJsonObject()) {
+            final JsonObject obj = objToResolve.getAsJsonObject();
+            for (Map.Entry<String, JsonElement> entry : obj.entrySet()) {
+                if (REF_MEMBER.equals(entry.getKey())) {
+                    String ref = entry.getValue().getAsString();
+                    obj.remove(REF_MEMBER);
+                    JsonObject refObject = resolvePath(defsRoot, obj, ref);
+                    resolveRefs(defsRoot, refObject);
+                    // hoist all members of refObject to contents
+                    for (Map.Entry<String, JsonElement> refentry : refObject.entrySet()) {
+                        obj.add(refentry.getKey(), JsonUtil.deepCopy(refentry.getValue()));
+                    }
+                } else {
+                    resolveRefs(defsRoot, entry.getValue());
+                }
+            }
+        } else if (objToResolve.isJsonArray()) {
+            final JsonArray contents = objToResolve.getAsJsonArray();
+            for (int i = 0; i < contents.size(); i++) {
+                JsonElement el = contents.get(i);
+                if (el.isJsonObject()) {
+                    contents.set(i, resolveRefs(defsRoot, el.getAsJsonObject()));
+                }
+            }
+        }
+        return objToResolve;
+    }
+
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/json/src/main/java/com/redhat/thermostat/common/yaml/YamlToJson.java	Fri Sep 15 11:41:22 2017 -0400
@@ -0,0 +1,309 @@
+/*
+ * 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.common.yaml;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonNull;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.Stack;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Utility class to read YAML files and convert to GSON objects
+ *
+ * Limitations:
+ * Only enough code to read Swagger YAML is implemented
+ * $ref only works within the same document, not across the network
+ */
+public class YamlToJson {
+
+    public YamlToJson() {
+    }
+
+    public JsonObject yamlToJsonObject(final File fn) throws IOException {
+        try (final Reader in = new FileReader(fn)) {
+            return yamlToJsonObject(in);
+        }
+    }
+
+    public JsonObject yamlToJsonObject(final String yaml) throws IOException {
+        try (final Reader in = new StringReader(yaml)) {
+            return yamlToJsonObject(in);
+        }
+    }
+
+    public JsonObject yamlToJsonObject(final Reader in) throws IOException {
+        try (final PushableReader pr = new PushableReader(in instanceof BufferedReader ? (BufferedReader)in : new BufferedReader(in));) {
+            final JsonObject obj = new JsonObject();
+            yamlToJson(pr, obj, 0);
+            pr.close();
+            return obj;
+        }
+    }
+
+    private void yamlToJson(PushableReader in, JsonElement parent, int currentIndent) throws IOException {
+        for (String s = in.readLine(); s != null; s = in.readLine()) {
+            YamlLine line = new YamlLine(s);
+            if (line.indent == 0 && line.value == null && line.name == null) {
+                // blsnk or unparseable line
+                continue;
+            }
+            if (line.indent < currentIndent) {
+                in.push(s);
+                return;
+            }
+            final JsonElement child;
+            if (line.hasColon) {
+                if (line.value != null && !line.value.isEmpty()) {
+                    if (">-".equals(line.value)) {
+                        //the next few lines are the value for the current line
+                        child = makeUnquotedPrimitive(absorb(in, currentIndent));
+                    } else {
+                        JsonElement ce = makeUnquotedPrimitive(line.value);
+                        if (parent.isJsonArray()) {
+                            JsonObject childobj = new JsonObject();
+                            childobj.add(makeUnquotedString(line.name), ce);
+                            child = childobj;
+                        } else {
+                            child = ce;
+                        }
+                    }
+                } else {
+                    // no value specified; this could be the start of an object or array
+                    final String next = in.readLine();
+                    boolean isArray = false;
+                    if (next != null) {
+                        YamlLine nextLine = new YamlLine(next);
+                        isArray = nextLine.hasDashPrefix;
+                        in.push(next);
+                    }
+                    child = isArray ? new JsonArray() : new JsonObject();
+                    yamlToJson(in, child, line.indent);
+                }
+            }
+            else {
+                child = (line.value != null) ? makeUnquotedPrimitive(line.value) : new JsonPrimitive("");
+            }
+            if (parent.isJsonArray()) {
+                if (line.hasDashPrefix) {
+                    JsonArray parray = parent.getAsJsonArray();
+                    parray.add(child);
+                } else {
+                    System.err.println("Array member must begin with dash: " + s);
+                }
+            } else if (parent.isJsonObject()) {
+                JsonObject pobject = parent.getAsJsonObject();
+                pobject.add(makeUnquotedString(line.name), child);
+            } else {
+                System.err.println("Error: parent is neither object nor array");
+            }
+            currentIndent = line.indent;
+        }
+    }
+
+    private static String makeUnquotedString(final String s) {
+        String ret = s;
+        if (s.length() >= 2) {
+            if ((s.charAt(0) == '\'') && (s.charAt(s.length() - 1) == '\'')
+                    || (s.charAt(0) == '"') && (s.charAt(s.length() - 1) == '"')) {
+                ret = s.substring(1, s.length() - 1);
+            }
+        }
+        return ret;
+    }
+
+    private static JsonPrimitive makeUnquotedPrimitive(final String s) {
+        final String uqString = makeUnquotedString(s);
+        // anything quoted will return a string, otherwise try to parse
+        if (uqString.length() == s.length()) {
+            if ("true".equals(uqString))
+                return new JsonPrimitive(true);
+            else if ("false".equals(uqString))
+                return new JsonPrimitive(false);
+            else if ("null".equals(uqString))
+                return null;
+            else {
+                // test for number
+                final char c0 = uqString.charAt(0);
+                if (Character.isDigit(c0) || c0 == '+' || c0 == '-') {
+                    try {
+                        return new JsonPrimitive(new Long(uqString));
+                    } catch (NumberFormatException ignored) {
+                        ; // fall through to return String
+                    }
+
+                }
+            }
+        }
+        return new JsonPrimitive(uqString);
+    }
+
+    private static String absorb(PushableReader in, int currentIndent) throws IOException {
+        // append lines until the indentation is the same as the currentIndent
+        StringBuilder a = new StringBuilder();
+        for (String s = in.readLine(); s != null; s = in.readLine()) {
+            int wsCount;
+            for (wsCount = 0; wsCount < s.length(); wsCount++) {
+                if (!Character.isWhitespace(s.charAt(wsCount))) {
+                    break;
+                }
+            }
+            if (wsCount > currentIndent) {
+                a.append(a.length() == 0 ? s.trim() : ' ' + s.trim());
+            } else {
+                in.push(s);
+                break;
+            }
+        }
+        return a.toString();
+    }
+
+    /**
+     * YamlLine - parse a single line of YAML file
+     */
+    static class YamlLine {
+        int indent;
+        String name;
+        String value;
+        boolean hasColon;
+        boolean hasDashPrefix;
+
+        private static final String regexpr = "^([\\s]*)([-]?[\\s]*)([^:]+)([:]?)(.*)$";
+        private static final Pattern p = Pattern.compile(regexpr);
+
+        YamlLine(String line) throws IOException {
+            Matcher m = p.matcher(line);
+            if (m.matches()) {
+                final String s1 = m.group(1); // leading whitespace
+                final String s2 = m.group(2); // optional '-' indicating array element
+                final String s3 = m.group(3); // key
+                final String s4 = m.group(4); // ':'
+                final String s5 = m.group(5).trim(); // value or optional '>-'
+                hasColon = !(s4 == null || s4.isEmpty());
+                hasDashPrefix = !(s2 == null || s2.isEmpty());
+                indent = s1.length() + (hasDashPrefix ? s2.length() : 0);
+                if (hasColon) {
+                    name = s3.trim();
+                    value = s5.trim();
+                } else {
+                    name = null;
+                    value = s3 + s5;
+                    value = value.trim();
+                }
+            } else {
+                if (line.isEmpty()) {
+                    name = null;
+                    value = null;
+                    indent = 0;
+                } else {
+                    throw new IOException("parse error in YAML '" + line + "'");
+                }
+            }
+        }
+    }
+
+    static class PushableReader implements AutoCloseable {
+
+        final Stack<String> stack = new Stack<>();
+        final BufferedReader in;
+
+        PushableReader(BufferedReader in) {
+            this.in = in;
+        }
+
+        String readLine() throws IOException {
+            if (stack.isEmpty()) {
+                return in.readLine();
+            } else {
+                return stack.pop();
+            }
+        }
+
+        void push(String s) {
+            stack.push(s);
+        }
+
+        @Override
+        public void close() throws IOException {
+            in.close();
+        }
+    }
+
+    /*
+     * utility function to convert YAML file to JSON stdout
+     */
+    public static void main(String[] args) {
+        boolean pretty = false;
+        for (final String fn : args) {
+            if ("--pretty".equals(fn)) {
+                pretty = true;
+            } else if ("--help".equals("fn")) {
+                System.err.println("YamlToJason [--pretty] [--help] infile1 [infile2] ...");
+            } else {
+                try {
+                    final YamlToJson y2j = new YamlToJson();
+                    final File fin = new File(fn);
+                    final JsonObject json = y2j.yamlToJsonObject(fin);
+                    if (pretty) {
+                        final GsonBuilder gsonBuilder = new GsonBuilder();
+                        gsonBuilder.setPrettyPrinting();
+                        final Gson gson = gsonBuilder.create();
+                        System.out.println(gson.toJson(json));
+                    } else {
+                        System.out.println(json.toString());
+                    }
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/json/src/test/java/com/redhat/thermostat/common/json/JsonUtilTest.java	Fri Sep 15 11:41:22 2017 -0400
@@ -0,0 +1,130 @@
+/*
+ * 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.common.json;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParser;
+import org.junit.Test;
+
+import java.util.NoSuchElementException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+public class JsonUtilTest {
+
+    private static final String JSON =
+            "{\n" +
+                    "  \"parents\" : [ \"homer\", \"marge\" ],\n" +
+                    "  \"children\" : {\n" +
+                    "    \"bart\" : {\n" +
+                    "      \"title\" : \"son\",\n" +
+                    "      \"age\" : \"14\"\n" +
+                    "      },\n" +
+                    "    \"lisa\" : {\n" +
+                    "      \"title\" : \"daughter\",\n" +
+                    "      \"age\" : \"12\"\n" +
+                    "      },\n" +
+                    "    \"maggie\" : {\n" +
+                    "      \"title\" : \"daughter\",\n" +
+                    "      \"age\" : \"1\"\n" +
+                    "      }\n" +
+                    "      \n" +
+                    "  },\n" +
+                    "  \"surname\" : \"simpsons\"\n" +
+                    "}\n";
+
+    @Test
+    public void testCapitalize() {
+        assertEquals("Fred", JsonUtil.capitalize("Fred"));
+        assertEquals("Barney", JsonUtil.capitalize("barney"));
+    }
+
+    @Test
+    public void testDeepCopy() {
+        final JsonParser parser = new JsonParser();
+        final JsonElement j0 = parser.parse(JSON);
+        final JsonElement j1 = JsonUtil.deepCopy(j0);
+        assertEquals(j0, j1);
+
+        j1.getAsJsonObject().get("children").getAsJsonObject().get("bart").getAsJsonObject().addProperty("age", 99);
+        assertNotEquals(j0, j1);
+    }
+
+    @Test
+    public void testFetchObject() {
+        final JsonParser parser = new JsonParser();
+        final JsonElement j0 = parser.parse(JSON);
+        final JsonElement bartAge = JsonUtil.fetch(j0, "children.bart.age");
+        assertEquals(14, bartAge.getAsInt());
+    }
+
+    @Test(expected = NoSuchElementException.class)
+    public void testFetchTooMuch() {
+        final JsonParser parser = new JsonParser();
+        final JsonElement j0 = parser.parse(JSON);
+        JsonUtil.fetch(j0, "children.bart.age.overflow");
+    }
+
+    @Test(expected = NoSuchElementException.class)
+    public void testFetchTooLittle() {
+        final JsonParser parser = new JsonParser();
+        final JsonElement j0 = parser.parse(JSON);
+        JsonUtil.fetch(j0, "");
+    }
+
+    @Test
+    public void testFetchArray() {
+        final JsonParser parser = new JsonParser();
+        final JsonElement j0 = parser.parse(JSON);
+        final JsonElement homer = JsonUtil.fetch(j0, "parents.0");
+        assertEquals("homer", homer.getAsString());
+        final JsonElement marge = JsonUtil.fetch(j0, "parents.1");
+        assertEquals("marge", marge.getAsString());
+    }
+
+    //@Test
+    // disable until this syntax feature is implemented
+    public void testFetchArraySquareBrackets() {
+        final JsonParser parser = new JsonParser();
+        final JsonElement j0 = parser.parse(JSON);
+        final JsonElement homer = JsonUtil.fetch(j0, "parents[0]");
+        assertEquals("homer", homer.getAsString());
+        final JsonElement marge = JsonUtil.fetch(j0, "parents[1]");
+        assertEquals("marge", marge.getAsString());
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/json/src/test/java/com/redhat/thermostat/common/json/models/SchemaDefTest.java	Fri Sep 15 11:41:22 2017 -0400
@@ -0,0 +1,65 @@
+/*
+ * 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.common.json.models;
+
+import com.redhat.thermostat.common.yaml.JsonToYaml;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class SchemaDefTest {
+
+    @Test
+    public void testShallowFunctionality() {
+        final SchemaDef def = new SchemaDef(JsonToYaml.class);
+        assertTrue(def.getProperties().get("out") instanceof SchemaDef.SimpleType);
+        final SchemaDef.SimpleType typeOfOut = (SchemaDef.SimpleType)def.getProperties().get("out");
+        assertEquals(SchemaType.TypeClass.OBJECT, typeOfOut.getTypeClass());
+        assertEquals("Writer", typeOfOut.getTypeName());
+    }
+
+    @Test
+    public void testDeepFunctionality() {
+        SchemaDef def = new SchemaDef(JsonToYaml.class, 99);
+        assertTrue(def.getProperties().get("out") instanceof SchemaDef);
+        final SchemaDef typeOfOut = (SchemaDef)def.getProperties().get("out");
+        assertEquals(SchemaType.TypeClass.OBJECT, typeOfOut.getTypeClass());
+        assertFalse(typeOfOut.getProperties().isEmpty());
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/json/src/test/java/com/redhat/thermostat/common/yaml/JsonToYamlTest.java	Fri Sep 15 11:41:22 2017 -0400
@@ -0,0 +1,112 @@
+/*
+ * 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.common.yaml;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+
+import org.junit.Test;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Writer;
+
+
+import static org.junit.Assert.assertEquals;
+
+public class JsonToYamlTest {
+
+    private static final File API1_YAML_FILE = new File("./src/test/resources/systems-swagger.yaml");
+    private static final File API1_JSON_FILE = new File("./src/test/resources/systems-swagger.json");
+
+    @Test
+    public void testConvert() throws IOException {
+
+        // read source JSON file
+        JsonObject goodjson = readFileToJson(API1_JSON_FILE).getAsJsonObject();
+
+        final JsonToYaml j2y = new JsonToYaml();
+
+        // convert to YAML string
+        final String yamlStr = j2y.jsonToYamlString(goodjson);
+
+        // get a temp file name
+        final File tmpFile = mktempfile("JsonToYamlTest.testConvert", ".yaml");
+
+        // write a new YAML file
+        writeFile(tmpFile, yamlStr);
+
+        // read the known good YAML file
+        final YamlToJson y2j = new YamlToJson();
+        final JsonObject goodyaml = y2j.yamlToJsonObject(API1_YAML_FILE);
+        assertEquals(goodjson, goodyaml);
+
+        // read the new YAML file
+        final JsonObject testyaml = y2j.yamlToJsonObject(tmpFile);
+        assertEquals(goodjson, testyaml);
+    }
+
+    private File mktempfile(final String prefix, final String suffix) throws IOException {
+        final File f = File.createTempFile(prefix, suffix);
+        f.deleteOnExit();
+        return f;
+    }
+
+    private JsonElement readFileToJson(File f) throws IOException {
+        try (final Reader in = new BufferedReader(new FileReader(f))) {
+            final JsonParser parser = new JsonParser();
+            return parser.parse(in);
+        } catch (IOException e) {
+            throw e;
+        }
+    }
+
+    private void writeFile(File f, String contents) throws IOException {
+        try (final Writer out = new BufferedWriter(new FileWriter(f))) {
+            out.write(contents);
+            out.close();
+        } catch (IOException e) {
+            throw e;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/json/src/test/java/com/redhat/thermostat/common/yaml/RefResolverTest.java	Fri Sep 15 11:41:22 2017 -0400
@@ -0,0 +1,74 @@
+/*
+ * 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.common.yaml;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.redhat.thermostat.common.json.JsonUtil;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+public class RefResolverTest {
+
+    private static final File API1_YAML_FILE = new File("./src/test/resources/systems-swagger.yaml");
+
+    @Test
+    public void resolverTest() throws IOException {
+        YamlToJson y2j = new YamlToJson();
+        JsonObject yaml = y2j.yamlToJsonObject(API1_YAML_FILE);
+        assertNotNull(yaml);
+
+        JsonElement getRefType = JsonUtil.fetch(yaml, "paths./systems/{systemId}.get.responses.200.schema.$ref");
+        assertTrue(getRefType.getAsString().endsWith("definitions/systems-get-response"));
+
+        // ref1 is the defintion pointed to by the reference
+        JsonElement ref1 = JsonUtil.fetch(yaml, "definitions.systems-get-response");
+
+        // resolve all refs
+        RefResolver.resolveRefs(yaml, yaml);
+
+        // get the response type, which should no longer be a reference but a copy of the definition pointed to by the reference
+        JsonElement resolvedType = JsonUtil.fetch(yaml, "paths./systems/{systemId}.get.responses.200.schema");
+        assertEquals(ref1, resolvedType);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/json/src/test/java/com/redhat/thermostat/common/yaml/YamlToJsonTest.java	Fri Sep 15 11:41:22 2017 -0400
@@ -0,0 +1,110 @@
+/*
+ * 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.common.yaml;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.redhat.thermostat.common.json.JsonUtil;
+import com.redhat.thermostat.common.json.models.SwaggerDef;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+public class YamlToJsonTest {
+
+    private static final File API1_YAML_FILE = new File("./src/test/resources/systems-swagger.yaml");
+
+    @Test
+    public void readYamlTest() throws IOException {
+        YamlToJson y2j = new YamlToJson();
+        JsonObject yaml = y2j.yamlToJsonObject(API1_YAML_FILE);
+        assertNotNull(yaml);
+        JsonElement getDescr = JsonUtil.fetch(yaml, "paths./systems/{systemId}.get.description");
+        assertNotNull(getDescr);
+        assertTrue(getDescr.isJsonPrimitive());
+        assertEquals("Get information for system {systemId}.", getDescr.getAsString());
+    }
+
+    @Test
+    public void yamlToModelTest() throws IOException {
+        YamlToJson y2j = new YamlToJson();
+        JsonObject yaml = y2j.yamlToJsonObject(API1_YAML_FILE);
+        assertNotNull(yaml);
+
+        // resolve all refs
+        RefResolver.resolveRefs(yaml, yaml);
+
+        // convert JSON to actual Java class
+        Gson gson = new Gson();
+        SwaggerDef swaggerDef = gson.fromJson(yaml, SwaggerDef.class);
+
+        final String getdescr = JsonUtil.fetch(yaml, "paths./systems/{systemId}.get.description").getAsString();
+        assertEquals(getdescr, swaggerDef.getPaths().get("/systems/{systemId}").getGETAPI().getDescription());
+    }
+
+    private static final String GPL = "GPL v2 with Classpath Exception";
+
+    private static final String YAML_WITH_NULL =
+            "info:\n" +
+            "  title: Thermostat Web Gateway JVM Information API\n" +
+            "  license:\n" +
+            "    name: " + GPL + "\n" +
+            "    url: 'http://www.gnu.org/licenses'\n" +
+            "    org:  null\n" +
+            "    phone: 416-363-6097\n" +
+            "consumes:\n" +
+            "  - application/json\n" +
+            "produces:\n" +
+            "  - application/json\n" +
+            "  - text/html; charset=utf-8\n" +
+            "basePath: /jvms/0.0.1\n";
+
+    @Test
+    public void testNull() throws IOException {
+        YamlToJson y2j = new YamlToJson();
+        JsonObject yaml = y2j.yamlToJsonObject(YAML_WITH_NULL);
+        assertEquals(GPL, JsonUtil.fetch(yaml, "info.license.name").getAsString());
+        final JsonElement nell = JsonUtil.fetch(yaml, "info.license.org");
+        assertTrue(nell.isJsonNull());
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/json/src/test/resources/jvms-swagger.json	Fri Sep 15 11:41:22 2017 -0400
@@ -0,0 +1,480 @@
+{
+  "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": {
+          "$ref": "#/definitions/metric"
+        },
+        "stopTime": {
+          "$ref": "#/definitions/metric"
+        },
+        "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": {
+          "$ref": "#/definitions/metric"
+        },
+        "username": {
+          "type": "string"
+        },
+        "lastUpdated": {
+          "$ref": "#/definitions/metric"
+        }
+      }
+    },
+    "jvm-post-info": {
+      "type": "object",
+      "properties": {
+        "agentId": {
+          "type": "string"
+        },
+        "jvmId": {
+          "type": "string"
+        },
+        "mainClass": {
+          "type": "string"
+        },
+        "startTime": {
+          "$ref": "#/definitions/metric"
+        },
+        "stopTime": {
+          "$ref": "#/definitions/metric"
+        },
+        "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": {
+          "$ref": "#/definitions/metric"
+        },
+        "username": {
+          "type": "string"
+        },
+        "lastUpdated": {
+          "$ref": "#/definitions/metric"
+        }
+      }
+    },
+    "environment-items": {
+      "type": "object",
+      "properties": {
+        "key": {
+          "type": "string"
+        },
+        "value": {
+          "type": "string"
+        }
+      }
+    },
+    "metric": {
+      "type": "object",
+      "properties": {
+        "$numberLong": {
+          "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 UNIX timestamp in milliseconds 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/common/json/src/test/resources/jvms-swagger.yaml	Fri Sep 15 11:41:22 2017 -0400
@@ -0,0 +1,337 @@
+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:
+        $ref: '#/definitions/metric'
+      stopTime:
+        $ref: '#/definitions/metric'
+      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:
+        $ref: '#/definitions/metric'
+      username:
+        type: string
+      lastUpdated:
+        $ref: '#/definitions/metric'
+
+  jvm-post-info:
+    type: object
+    properties:
+      agentId:
+        type: string
+      jvmId:
+        type: string
+      mainClass:
+        type: string
+      startTime:
+        $ref: '#/definitions/metric'
+      stopTime:
+        $ref: '#/definitions/metric'
+      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:
+        $ref: '#/definitions/metric'
+      username:
+        type: string
+      lastUpdated:
+        $ref: '#/definitions/metric'
+  environment-items:
+    type: object
+    properties:
+      key:
+        type: string
+      value:
+        type: string
+
+  metric:
+    type: object
+    properties:
+      $numberLong:
+        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 UNIX timestamp in milliseconds 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/common/json/src/test/resources/systems-swagger.json	Fri Sep 15 11:41:22 2017 -0400
@@ -0,0 +1,283 @@
+{
+  "swagger": "2.0",
+  "info": {
+    "version": "0.0.1",
+    "title": "Thermostat Web Gateway System 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": "/systems/0.0.1",
+  "paths": {
+    "/": {
+      "get": {
+        "description": "Get information for all systems.",
+        "parameters": [
+          {
+            "$ref": "#/parameters/limit"
+          },
+          {
+            "$ref": "#/parameters/offset"
+          },
+          {
+            "$ref": "#/parameters/sort"
+          },
+          {
+            "$ref": "#/parameters/include"
+          },
+          {
+            "$ref": "#/parameters/exclude"
+          },
+          {
+            "$ref": "#/parameters/query"
+          },
+          {
+            "$ref": "#/parameters/alive"
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "OK",
+            "schema": {
+              "$ref": "#/definitions/systems-get-response"
+            }
+          }
+        }
+      }
+    },
+    "/systems/{systemId}": {
+      "parameters": [
+        {
+          "$ref": "#/parameters/system-id"
+        }
+      ],
+      "get": {
+        "description": "Get information for system {systemId}.",
+        "parameters": [
+          {
+            "$ref": "#/parameters/limit"
+          },
+          {
+            "$ref": "#/parameters/offset"
+          },
+          {
+            "$ref": "#/parameters/sort"
+          },
+          {
+            "$ref": "#/parameters/include"
+          },
+          {
+            "$ref": "#/parameters/exclude"
+          },
+          {
+            "$ref": "#/parameters/query"
+          },
+          {
+            "$ref": "#/parameters/alive"
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "OK",
+            "schema": {
+              "$ref": "#/definitions/systems-get-response"
+            }
+          }
+        }
+      },
+      "put": {
+        "description": "Update information for system {systemId}.",
+        "parameters": [
+          {
+            "$ref": "#/parameters/systems-put-body"
+          },
+          {
+            "$ref": "#/parameters/query"
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "OK"
+          }
+        }
+      },
+      "post": {
+        "description": "Add information for system {systemId}",
+        "parameters": [
+          {
+            "$ref": "#/parameters/system-info-array"
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "OK"
+          }
+        }
+      },
+      "delete": {
+        "description": "Delete information for system ID {systemId}.",
+        "parameters": [
+          {
+            "$ref": "#/parameters/query"
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "OK"
+          }
+        }
+      }
+    }
+  },
+  "definitions": {
+    "systems-get-response": {
+      "type": "object",
+      "properties": {
+        "response": {
+          "$ref": "#/definitions/system-info-array"
+        }
+      }
+    },
+    "system-info-array": {
+      "type": "array",
+      "items": {
+        "$ref": "#/definitions/system-info"
+      }
+    },
+    "system-info": {
+      "type": "object",
+      "properties": {
+        "systemId": {
+          "type": "string"
+        },
+        "agentId": {
+          "type": "string"
+        },
+        "hostname": {
+          "type": "string"
+        },
+        "osName": {
+          "type": "string"
+        },
+        "osKernel": {
+          "type": "string"
+        },
+        "osArch": {
+          "type": "string"
+        },
+        "cpuCount": {
+          "type": "integer"
+        },
+        "cpuModel": {
+          "type": "string"
+        },
+        "totalMemory": {
+          "type": "integer",
+          "format": "int64"
+        },
+        "timeCreated": {
+          "type": "integer",
+          "format": "int64"
+        },
+        "lastUpdated": {
+          "type": "integer",
+          "format": "int64"
+        }
+      }
+    },
+    "systems-put-body": {
+      "type": "object",
+      "properties": {
+        "set": {
+          "type": "object"
+        }
+      }
+    }
+  },
+  "parameters": {
+    "system-id": {
+      "name": "systemId",
+      "in": "path",
+      "required": true,
+      "type": "string"
+    },
+    "system-info-array": {
+      "name": "system-info-array",
+      "in": "body",
+      "description": "The system information",
+      "required": true,
+      "schema": {
+        "$ref": "#/definitions/system-info-array"
+      }
+    },
+    "systems-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' field. Example { \"set\" : { \"field\" : \"value\", \"field2\":{\"object\":\"item\"} }",
+      "required": true,
+      "schema": {
+        "$ref": "#/definitions/systems-put-body"
+      }
+    },
+    "alive": {
+      "name": "alive",
+      "in": "query",
+      "description": "Whether to return only systems that are currently running",
+      "type": "boolean",
+      "required": false,
+      "default": true
+    },
+    "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": true,
+      "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
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/json/src/test/resources/systems-swagger.yaml	Fri Sep 15 11:41:22 2017 -0400
@@ -0,0 +1,192 @@
+swagger: '2.0'
+info:
+  version: 0.0.1
+  title: Thermostat Web Gateway System 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: /systems/0.0.1
+paths:
+  /:
+    get:
+      description: Get information for all systems.
+      parameters:
+        - $ref: '#/parameters/limit'
+        - $ref: '#/parameters/offset'
+        - $ref: '#/parameters/sort'
+        - $ref: '#/parameters/include'
+        - $ref: '#/parameters/exclude'
+        - $ref: '#/parameters/query'
+        - $ref: '#/parameters/alive'
+      responses:
+        '200':
+          description: OK
+          schema:
+            $ref: '#/definitions/systems-get-response'
+  /systems/{systemId}:
+    parameters:
+      - $ref: '#/parameters/system-id'
+    get:
+      description: Get information for system {systemId}.
+      parameters:
+        - $ref: '#/parameters/limit'
+        - $ref: '#/parameters/offset'
+        - $ref: '#/parameters/sort'
+        - $ref: '#/parameters/include'
+        - $ref: '#/parameters/exclude'
+        - $ref: '#/parameters/query'
+        - $ref: '#/parameters/alive'
+      responses:
+        '200':
+          description: OK
+          schema:
+            $ref: '#/definitions/systems-get-response'
+    put:
+      description: Update information for system {systemId}.
+      parameters:
+        - $ref: '#/parameters/systems-put-body'
+        - $ref: '#/parameters/query'
+      responses:
+        '200':
+          description: OK
+    post:
+      description: Add information for system {systemId}
+      parameters:
+        - $ref: '#/parameters/system-info-array'
+      responses:
+        '200':
+          description: OK
+    delete:
+      description: Delete information for system ID {systemId}.
+      parameters:
+        - $ref: '#/parameters/query'
+      responses:
+        '200':
+          description: OK
+definitions:
+  systems-get-response:
+    type: object
+    properties:
+      response:
+          $ref: '#/definitions/system-info-array'
+  system-info-array:
+    type: array
+    items:
+      $ref: '#/definitions/system-info'
+  system-info:
+    type: object
+    properties:
+      systemId:
+        type: string
+      agentId:
+        type: string
+      hostname:
+        type: string
+      osName:
+        type: string
+      osKernel:
+        type: string
+      osArch:
+        type: string
+      cpuCount:
+        type: integer
+      cpuModel:
+        type: string
+      totalMemory:
+        type: integer
+        format: int64
+      timeCreated:
+        type: integer
+        format: int64
+      lastUpdated:
+        type: integer
+        format: int64
+  systems-put-body:
+    type: object
+    properties:
+      "set":
+        type: object
+parameters:
+  system-id:
+    name: systemId
+    in: path
+    required: true
+    type: string
+  system-info-array:
+    name: system-info-array
+    in: body
+    description: The system information
+    required: true
+    schema:
+      $ref: '#/definitions/system-info-array'
+  systems-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' field. Example { "set" : {
+      "field" : "value", "field2":{"object":"item"} }
+    required: true
+    schema:
+      $ref: '#/definitions/systems-put-body'
+  alive:
+    name: alive
+    in: query
+    description: Whether to return only systems that are currently running
+    type: boolean
+    required: false
+    default: true
+  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: true
+    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