changeset 1515:1744e42fc8b5

Rewrite JSON serializers using com.google.gson.stream API. Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2014-August/010582.html Reviewed-by: omajid, vanaltj, neugens
author Severin Gehwolf <sgehwolf@redhat.com>
date Wed, 17 Sep 2014 11:36:58 +0200
parents f1eeee8d9f80
children c880893faba9
files common/test/src/main/java/com/redhat/thermostat/testutils/PerformanceTest.java pom.xml web/client/src/main/java/com/redhat/thermostat/web/client/internal/WebStorage.java web/client/src/test/java/com/redhat/thermostat/web/client/internal/WebStorageTest.java web/common/pom.xml web/common/src/main/java/com/redhat/thermostat/web/common/PreparedParameterSerializer.java web/common/src/main/java/com/redhat/thermostat/web/common/ThermostatGSONConverter.java web/common/src/main/java/com/redhat/thermostat/web/common/WebPreparedStatementSerializer.java web/common/src/main/java/com/redhat/thermostat/web/common/WebQueryResponseSerializer.java web/common/src/main/java/com/redhat/thermostat/web/common/typeadapters/PojoTypeAdapter.java web/common/src/main/java/com/redhat/thermostat/web/common/typeadapters/PojoTypeAdapterFactory.java web/common/src/main/java/com/redhat/thermostat/web/common/typeadapters/PreparedParameterTypeAdapter.java web/common/src/main/java/com/redhat/thermostat/web/common/typeadapters/PreparedParameterTypeAdapterFactory.java web/common/src/main/java/com/redhat/thermostat/web/common/typeadapters/PreparedParametersTypeAdapter.java web/common/src/main/java/com/redhat/thermostat/web/common/typeadapters/PreparedParametersTypeAdapterFactory.java web/common/src/main/java/com/redhat/thermostat/web/common/typeadapters/WebPreparedStatementResponseTypeAdapter.java web/common/src/main/java/com/redhat/thermostat/web/common/typeadapters/WebPreparedStatementResponseTypeAdapterFactory.java web/common/src/main/java/com/redhat/thermostat/web/common/typeadapters/WebPreparedStatementTypeAdapter.java web/common/src/main/java/com/redhat/thermostat/web/common/typeadapters/WebPreparedStatementTypeAdapterFactory.java web/common/src/main/java/com/redhat/thermostat/web/common/typeadapters/WebQueryResponseTypeAdapter.java web/common/src/main/java/com/redhat/thermostat/web/common/typeadapters/WebQueryResponseTypeAdapterFactory.java web/common/src/test/java/com/redhat/thermostat/web/common/PreparedParameterSerializerTest.java web/common/src/test/java/com/redhat/thermostat/web/common/PreparedParametersSerializerTest.java web/common/src/test/java/com/redhat/thermostat/web/common/ThermostatGSONConverterTest.java web/common/src/test/java/com/redhat/thermostat/web/common/WebPreparedStatementResponseSerializerTest.java web/common/src/test/java/com/redhat/thermostat/web/common/WebPreparedStatementSerializerTest.java web/common/src/test/java/com/redhat/thermostat/web/common/WebQueryResponseSerializerTest.java web/common/src/test/java/com/redhat/thermostat/web/common/typeadapters/JsonPerformanceTest.java web/common/src/test/java/com/redhat/thermostat/web/common/typeadapters/LegacyGSONConverter.java web/common/src/test/java/com/redhat/thermostat/web/common/typeadapters/LegacyPreparedParameterSerializer.java web/common/src/test/java/com/redhat/thermostat/web/common/typeadapters/LegacyWebPreparedStatementSerializer.java web/common/src/test/java/com/redhat/thermostat/web/common/typeadapters/LegacyWebQueryResponseSerializer.java web/common/src/test/java/com/redhat/thermostat/web/common/typeadapters/PojoTypeAdapterTest.java web/common/src/test/java/com/redhat/thermostat/web/common/typeadapters/PreparedParameterJSONPerformanceTest.java web/common/src/test/java/com/redhat/thermostat/web/common/typeadapters/PreparedParameterTypeAdapterTest.java web/common/src/test/java/com/redhat/thermostat/web/common/typeadapters/PreparedParametersJSONPerformanceTest.java web/common/src/test/java/com/redhat/thermostat/web/common/typeadapters/PreparedParametersTypeAdapterTest.java web/common/src/test/java/com/redhat/thermostat/web/common/typeadapters/WebPreparedStatementJSONPerformanceTest.java web/common/src/test/java/com/redhat/thermostat/web/common/typeadapters/WebPreparedStatementResponseJSONPerformanceTest.java web/common/src/test/java/com/redhat/thermostat/web/common/typeadapters/WebPreparedStatementResponseTypeAdapterTest.java web/common/src/test/java/com/redhat/thermostat/web/common/typeadapters/WebPreparedStatementTypeAdapterTest.java web/common/src/test/java/com/redhat/thermostat/web/common/typeadapters/WebQueryResponseTypeAdapterTest.java web/server/src/main/java/com/redhat/thermostat/web/server/WebStorageEndPoint.java web/server/src/test/java/com/redhat/thermostat/web/server/WebStorageEndpointTest.java
diffstat 44 files changed, 4455 insertions(+), 1686 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/test/src/main/java/com/redhat/thermostat/testutils/PerformanceTest.java	Wed Sep 17 11:36:58 2014 +0200
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2012-2014 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.testutils;
+
+/**
+ * 
+ * JUnit category class for performance tests.
+ *
+ */
+public interface PerformanceTest {
+    // empty
+}
--- a/pom.xml	Tue Sep 30 11:57:25 2014 +0200
+++ b/pom.xml	Wed Sep 17 11:36:58 2014 +0200
@@ -48,10 +48,22 @@
   <url>${thermostat.url}</url>
 
   <profiles>
+    <!-- Profile for running performance tests. Performance tests are
+         excluded from normal builds. That is, they'll only run if
+         explicitly requested via -Pperf-tests. See web/common/pom.xml 
+         for an example as to how this property is used. -->
+    <profile>
+      <id>perf-tests</id>
+      <properties>
+        <!-- define it empty so as to NOT exclude the PerformanceTests
+             categorized tests. -->
+        <surefire-perftests-exclusion />
+      </properties>
+    </profile>
     <!-- Development settings for web.xml. Release builds should
          have the "environment.type=release" property. -->
     <profile>
-    <id>dev-build</id>
+      <id>dev-build</id>
       <activation>
         <property>
           <name>environment.type</name>
@@ -261,6 +273,8 @@
     <dev.users.snippet /> <!-- intentionally empty -->
     <dev.roles.snippet /> <!-- intentionally empty -->
     <agent.auth.snippet /> <!-- intentionally empty -->
+    <!-- see web/common/pom.xml for an example as to how this is used -->
+    <surefire-perftests-exclusion>com.redhat.thermostat.testutils.PerformanceTest</surefire-perftests-exclusion>
   </properties>
 
   <modules>
--- a/web/client/src/main/java/com/redhat/thermostat/web/client/internal/WebStorage.java	Tue Sep 30 11:57:25 2014 +0200
+++ b/web/client/src/main/java/com/redhat/thermostat/web/client/internal/WebStorage.java	Wed Sep 17 11:36:58 2014 +0200
@@ -67,7 +67,6 @@
 import org.apache.http.auth.AuthScope;
 import org.apache.http.auth.Credentials;
 import org.apache.http.auth.UsernamePasswordCredentials;
-import org.apache.http.client.CredentialsProvider;
 import org.apache.http.client.HttpClient;
 import org.apache.http.client.config.AuthSchemes;
 import org.apache.http.client.config.RequestConfig;
@@ -82,10 +81,8 @@
 import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
 import org.apache.http.entity.mime.MultipartEntityBuilder;
 import org.apache.http.entity.mime.content.InputStreamBody;
-import org.apache.http.impl.auth.BasicScheme;
 import org.apache.http.impl.auth.BasicSchemeFactory;
 import org.apache.http.impl.client.BasicCredentialsProvider;
-import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.http.impl.client.HttpClientBuilder;
 import org.apache.http.impl.client.HttpClients;
 import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
@@ -105,7 +102,6 @@
 import com.redhat.thermostat.storage.core.DescriptorParsingException;
 import com.redhat.thermostat.storage.core.IllegalDescriptorException;
 import com.redhat.thermostat.storage.core.IllegalPatchException;
-import com.redhat.thermostat.storage.core.PreparedParameter;
 import com.redhat.thermostat.storage.core.PreparedStatement;
 import com.redhat.thermostat.storage.core.SecureStorage;
 import com.redhat.thermostat.storage.core.StatementDescriptor;
@@ -114,14 +110,16 @@
 import com.redhat.thermostat.storage.core.StorageCredentials;
 import com.redhat.thermostat.storage.core.StorageException;
 import com.redhat.thermostat.storage.model.Pojo;
-import com.redhat.thermostat.web.common.PreparedParameterSerializer;
 import com.redhat.thermostat.web.common.PreparedStatementResponseCode;
-import com.redhat.thermostat.web.common.ThermostatGSONConverter;
 import com.redhat.thermostat.web.common.WebPreparedStatement;
 import com.redhat.thermostat.web.common.WebPreparedStatementResponse;
-import com.redhat.thermostat.web.common.WebPreparedStatementSerializer;
 import com.redhat.thermostat.web.common.WebQueryResponse;
-import com.redhat.thermostat.web.common.WebQueryResponseSerializer;
+import com.redhat.thermostat.web.common.typeadapters.PojoTypeAdapterFactory;
+import com.redhat.thermostat.web.common.typeadapters.PreparedParameterTypeAdapterFactory;
+import com.redhat.thermostat.web.common.typeadapters.PreparedParametersTypeAdapterFactory;
+import com.redhat.thermostat.web.common.typeadapters.WebPreparedStatementResponseTypeAdapterFactory;
+import com.redhat.thermostat.web.common.typeadapters.WebPreparedStatementTypeAdapterFactory;
+import com.redhat.thermostat.web.common.typeadapters.WebQueryResponseTypeAdapterFactory;
 
 public class WebStorage implements Storage, SecureStorage {
 
@@ -380,11 +378,13 @@
     
     private void init(String url, StorageCredentials creds, HttpClient client) {
         categoryIds = new HashMap<>();
-        gson = new GsonBuilder().registerTypeHierarchyAdapter(Pojo.class,
-                        new ThermostatGSONConverter())
-                .registerTypeAdapter(WebPreparedStatement.class, new WebPreparedStatementSerializer())
-                .registerTypeAdapter(WebQueryResponse.class, new WebQueryResponseSerializer<>())
-                .registerTypeAdapter(PreparedParameter.class, new PreparedParameterSerializer())
+        gson = new GsonBuilder()
+                .registerTypeAdapterFactory(new PojoTypeAdapterFactory())
+                .registerTypeAdapterFactory(new WebPreparedStatementResponseTypeAdapterFactory())
+                .registerTypeAdapterFactory(new WebQueryResponseTypeAdapterFactory())
+                .registerTypeAdapterFactory(new PreparedParameterTypeAdapterFactory())
+                .registerTypeAdapterFactory(new WebPreparedStatementTypeAdapterFactory())
+                .registerTypeAdapterFactory(new PreparedParametersTypeAdapterFactory())
                 .create();
         httpClient = client;
         synchronized (httpClientContextLock) {
--- a/web/client/src/test/java/com/redhat/thermostat/web/client/internal/WebStorageTest.java	Tue Sep 30 11:57:25 2014 +0200
+++ b/web/client/src/test/java/com/redhat/thermostat/web/client/internal/WebStorageTest.java	Wed Sep 17 11:36:58 2014 +0200
@@ -43,9 +43,9 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 
 import java.io.BufferedReader;
@@ -96,7 +96,6 @@
 import com.redhat.thermostat.storage.core.DescriptorParsingException;
 import com.redhat.thermostat.storage.core.IllegalDescriptorException;
 import com.redhat.thermostat.storage.core.Key;
-import com.redhat.thermostat.storage.core.PreparedParameter;
 import com.redhat.thermostat.storage.core.PreparedParameters;
 import com.redhat.thermostat.storage.core.PreparedStatement;
 import com.redhat.thermostat.storage.core.StatementDescriptor;
@@ -106,14 +105,15 @@
 import com.redhat.thermostat.storage.model.Pojo;
 import com.redhat.thermostat.test.FreePortFinder;
 import com.redhat.thermostat.test.FreePortFinder.TryPort;
-import com.redhat.thermostat.web.common.PreparedParameterSerializer;
 import com.redhat.thermostat.web.common.PreparedStatementResponseCode;
-import com.redhat.thermostat.web.common.ThermostatGSONConverter;
 import com.redhat.thermostat.web.common.WebPreparedStatement;
 import com.redhat.thermostat.web.common.WebPreparedStatementResponse;
-import com.redhat.thermostat.web.common.WebPreparedStatementSerializer;
 import com.redhat.thermostat.web.common.WebQueryResponse;
-import com.redhat.thermostat.web.common.WebQueryResponseSerializer;
+import com.redhat.thermostat.web.common.typeadapters.PojoTypeAdapterFactory;
+import com.redhat.thermostat.web.common.typeadapters.PreparedParameterTypeAdapterFactory;
+import com.redhat.thermostat.web.common.typeadapters.WebPreparedStatementResponseTypeAdapterFactory;
+import com.redhat.thermostat.web.common.typeadapters.WebPreparedStatementTypeAdapterFactory;
+import com.redhat.thermostat.web.common.typeadapters.WebQueryResponseTypeAdapterFactory;
 
 public class WebStorageTest {
 
@@ -263,7 +263,8 @@
     @Test
     public void preparingFaultyDescriptorThrowsException() throws UnsupportedEncodingException, IOException {
         Gson gson = new GsonBuilder()
-                .registerTypeAdapter(WebPreparedStatement.class, new WebPreparedStatementSerializer())
+                .registerTypeAdapterFactory(new WebPreparedStatementTypeAdapterFactory())
+                .registerTypeAdapterFactory(new WebPreparedStatementResponseTypeAdapterFactory())
                 .create();
 
         // missing quotes for LHS key
@@ -287,7 +288,8 @@
     @Test
     public void preparingUnknownDescriptorThrowsException() throws UnsupportedEncodingException, IOException {
         Gson gson = new GsonBuilder()
-                .registerTypeAdapter(WebPreparedStatement.class, new WebPreparedStatementSerializer())
+                .registerTypeAdapterFactory(new WebPreparedStatementTypeAdapterFactory())
+                .registerTypeAdapterFactory(new WebPreparedStatementResponseTypeAdapterFactory())
                 .create();
 
         String strDesc = "QUERY test WHERE 'property1' = ?s";
@@ -310,11 +312,13 @@
     
     @Test
     public void forbiddenExecuteQueryThrowsConsumingExcptn() throws UnsupportedEncodingException, IOException {
-        Gson gson = new GsonBuilder().registerTypeHierarchyAdapter(PreparedParameter.class, new PreparedParameterSerializer())
-                .registerTypeAdapter(WebPreparedStatement.class, new WebPreparedStatementSerializer())
-                .registerTypeAdapter(WebQueryResponse.class, new WebQueryResponseSerializer<>())
-                .registerTypeAdapter(Pojo.class, new ThermostatGSONConverter())
-                .create();
+        Gson gson = new GsonBuilder()
+                            .registerTypeAdapterFactory(new PojoTypeAdapterFactory())
+                            .registerTypeAdapterFactory(new WebPreparedStatementResponseTypeAdapterFactory())
+                            .registerTypeAdapterFactory(new WebQueryResponseTypeAdapterFactory())
+                            .registerTypeAdapterFactory(new PreparedParameterTypeAdapterFactory())
+                            .registerTypeAdapterFactory(new WebPreparedStatementTypeAdapterFactory())
+                            .create();
 
         String strDesc = "QUERY test WHERE 'property1' = ?s";
         StatementDescriptor<TestObj> desc = new StatementDescriptor<>(category, strDesc);
@@ -396,11 +400,13 @@
         obj1.setProperty1("fluffor1");
         TestObj obj2 = new TestObj();
         obj2.setProperty1("fluffor2");
-        Gson gson = new GsonBuilder().registerTypeHierarchyAdapter(PreparedParameter.class, new PreparedParameterSerializer())
-                .registerTypeAdapter(WebPreparedStatement.class, new WebPreparedStatementSerializer())
-                .registerTypeAdapter(WebQueryResponse.class, new WebQueryResponseSerializer<>())
-                .registerTypeAdapter(Pojo.class, new ThermostatGSONConverter())
-                .create();
+        Gson gson = new GsonBuilder()
+                            .registerTypeAdapterFactory(new PojoTypeAdapterFactory())
+                            .registerTypeAdapterFactory(new WebPreparedStatementResponseTypeAdapterFactory())
+                            .registerTypeAdapterFactory(new WebQueryResponseTypeAdapterFactory())
+                            .registerTypeAdapterFactory(new PreparedParameterTypeAdapterFactory())
+                            .registerTypeAdapterFactory(new WebPreparedStatementTypeAdapterFactory())
+                            .create();
 
         String strDesc = "QUERY test WHERE 'property1' = ?s";
         StatementDescriptor<TestObj> desc = new StatementDescriptor<>(category, strDesc);
@@ -462,10 +468,13 @@
         obj1.setProperty1("fluffor1");
         TestObj obj2 = new TestObj();
         obj2.setProperty1("fluffor2");
-        Gson gson = new GsonBuilder().registerTypeAdapter(PreparedParameter.class, new PreparedParameterSerializer())
-                .registerTypeAdapter(WebPreparedStatement.class, new WebPreparedStatementSerializer())
-                .registerTypeHierarchyAdapter(Pojo.class, new ThermostatGSONConverter())
-                .create();
+        Gson gson = new GsonBuilder()
+                            .registerTypeAdapterFactory(new PojoTypeAdapterFactory())
+                            .registerTypeAdapterFactory(new WebPreparedStatementResponseTypeAdapterFactory())
+                            .registerTypeAdapterFactory(new WebQueryResponseTypeAdapterFactory())
+                            .registerTypeAdapterFactory(new PreparedParameterTypeAdapterFactory())
+                            .registerTypeAdapterFactory(new WebPreparedStatementTypeAdapterFactory())
+                            .create();
 
         String strDesc = "ADD test SET 'property1' = ?s";
         StatementDescriptor<TestObj> desc = new StatementDescriptor<>(category, strDesc);
@@ -510,10 +519,13 @@
     
     @Test
     public void forbiddenExecuteWriteReturnsGenericWriteFailure() {
-        Gson gson = new GsonBuilder().registerTypeAdapter(PreparedParameter.class, new PreparedParameterSerializer())
-                .registerTypeAdapter(WebPreparedStatement.class, new WebPreparedStatementSerializer())
-                .registerTypeHierarchyAdapter(Pojo.class, new ThermostatGSONConverter())
-                .create();
+        Gson gson = new GsonBuilder()
+                            .registerTypeAdapterFactory(new PojoTypeAdapterFactory())
+                            .registerTypeAdapterFactory(new WebPreparedStatementResponseTypeAdapterFactory())
+                            .registerTypeAdapterFactory(new WebQueryResponseTypeAdapterFactory())
+                            .registerTypeAdapterFactory(new PreparedParameterTypeAdapterFactory())
+                            .registerTypeAdapterFactory(new WebPreparedStatementTypeAdapterFactory())
+                            .create();
 
         String strDesc = "ADD test SET 'property1' = ?s";
         StatementDescriptor<TestObj> desc = new StatementDescriptor<>(category, strDesc);
--- a/web/common/pom.xml	Tue Sep 30 11:57:25 2014 +0200
+++ b/web/common/pom.xml	Wed Sep 17 11:36:58 2014 +0200
@@ -57,7 +57,12 @@
       <artifactId>junit</artifactId>
       <scope>test</scope>
     </dependency>
-
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-common-test</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
     <dependency>
       <groupId>com.google.code.gson</groupId>
       <artifactId>gson</artifactId>
@@ -90,13 +95,21 @@
             <Bundle-SymbolicName>com.redhat.thermostat.web.common</Bundle-SymbolicName>
             <Bundle-Vendor>Red Hat, Inc.</Bundle-Vendor>
             <Export-Package>
-              com.redhat.thermostat.web.common
+              com.redhat.thermostat.web.common,
+              com.redhat.thermostat.web.common.typeadapters,
             </Export-Package>
             <!-- Do not autogenerate uses clauses in Manifests -->
             <_nouses>true</_nouses>
           </instructions>
         </configuration>
       </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <configuration>
+          <excludedGroups>${surefire-perftests-exclusion}</excludedGroups>
+        </configuration>
+      </plugin>
     </plugins>
   </build>
 
--- a/web/common/src/main/java/com/redhat/thermostat/web/common/PreparedParameterSerializer.java	Tue Sep 30 11:57:25 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,193 +0,0 @@
-/*
- * Copyright 2012-2014 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.web.common;
-
-import java.lang.reflect.Array;
-import java.lang.reflect.Type;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import com.google.gson.JsonDeserializationContext;
-import com.google.gson.JsonDeserializer;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParseException;
-import com.google.gson.JsonPrimitive;
-import com.google.gson.JsonSerializationContext;
-import com.google.gson.JsonSerializer;
-import com.redhat.thermostat.common.utils.LoggingUtils;
-import com.redhat.thermostat.storage.core.PreparedParameter;
-import com.redhat.thermostat.storage.model.Pojo;
-
-/**
- * GSON type adapter for {@link PreparedParameter}.
- *
- */
-public class PreparedParameterSerializer implements JsonDeserializer<PreparedParameter>, JsonSerializer<PreparedParameter>{
-
-    private static final Logger logger = LoggingUtils.getLogger(PreparedParameterSerializer.class);
-    private static final String PROP_TYPE = "type";
-    private static final String PROP_VALUE = "value";
-    private static final Set<Class<?>> VALID_CLASSES;
-    private static final Set<Class<?>> PRIMITIVES_NOT_ALLOWING_NULL_VAL;
-    // maps type names to classes:
-    //    "int" => int.class , "double" => double.class, "[I" => int[].class
-    //    and so on.
-    private static final Map<String, Class<?>> CLASSES_LOOKUP_TABLE;
-    
-    static {
-        VALID_CLASSES = new HashSet<>();
-        CLASSES_LOOKUP_TABLE = new HashMap<>();
-        CLASSES_LOOKUP_TABLE.put(int.class.getName(), int.class);
-        CLASSES_LOOKUP_TABLE.put(long.class.getName(), long.class);
-        CLASSES_LOOKUP_TABLE.put(boolean.class.getName(), boolean.class);
-        CLASSES_LOOKUP_TABLE.put(double.class.getName(), double.class);
-        CLASSES_LOOKUP_TABLE.put(String.class.getName(), String.class);
-        CLASSES_LOOKUP_TABLE.put(int[].class.getName(), int[].class);
-        CLASSES_LOOKUP_TABLE.put(long[].class.getName(), long[].class);
-        CLASSES_LOOKUP_TABLE.put(boolean[].class.getName(), boolean[].class);
-        CLASSES_LOOKUP_TABLE.put(double[].class.getName(), double[].class);
-        CLASSES_LOOKUP_TABLE.put(String[].class.getName(), String[].class);
-        VALID_CLASSES.addAll(CLASSES_LOOKUP_TABLE.values());
-        PRIMITIVES_NOT_ALLOWING_NULL_VAL = new HashSet<>();
-        PRIMITIVES_NOT_ALLOWING_NULL_VAL.add(int.class);
-        PRIMITIVES_NOT_ALLOWING_NULL_VAL.add(long.class);
-        PRIMITIVES_NOT_ALLOWING_NULL_VAL.add(boolean.class);
-        PRIMITIVES_NOT_ALLOWING_NULL_VAL.add(double.class);
-    }
-    
-    @Override
-    public JsonElement serialize(PreparedParameter param, Type type,
-            JsonSerializationContext ctxt) {
-        JsonObject result = new JsonObject();
-        JsonElement valueElem = serializeValue(ctxt, param.getValue(), param.getType());
-        result.add(PROP_VALUE, valueElem);
-        JsonPrimitive typeElem = new JsonPrimitive(param.getType().getName());
-        result.add(PROP_TYPE, typeElem);
-        return result;
-    }
-
-    private JsonElement serializeValue(JsonSerializationContext ctxt, Object value, Class<?> type) {
-        JsonElement element;
-        // Special case pojo list types: the value class is of array type for
-        // them, but the type is the component type.
-        if (value != null && value.getClass().isArray() && !type.isArray()) {
-            assert(Pojo.class.isAssignableFrom(type));
-            Class<?> arrayType = Array.newInstance(type, 0).getClass();
-            element = ctxt.serialize(value, arrayType);
-        } else {
-            element = ctxt.serialize(value, type);
-        }
-        return element;
-    }
-
-    @Override
-    public PreparedParameter deserialize(JsonElement jsonElem, Type type,
-            JsonDeserializationContext ctxt) throws JsonParseException {
-        JsonElement typeElem = jsonElem.getAsJsonObject().get(PROP_TYPE);
-        String className = typeElem.getAsString();
-        // perform some sanity checking on the types of classes we actually
-        // de-serialize
-        Class<?> typeVal = deserializeTypeVal(className);
-        validateSaneClassName(typeVal);
-        JsonElement valueElement = jsonElem.getAsJsonObject().get(PROP_VALUE);
-        Object value = deserializeValue(ctxt, valueElement, typeVal);
-        PreparedParameter param = new PreparedParameter();
-        param.setType(typeVal);
-        param.setValue(value);
-        return param;
-    }
-
-    private Class<?> deserializeTypeVal(String className) {
-        Class<?> typeVal = null;
-        if (CLASSES_LOOKUP_TABLE.containsKey(className)) {
-            typeVal = CLASSES_LOOKUP_TABLE.get(className);
-        } else {
-            try {
-                // We need this for Pojo + Pojo list type params. For pojo
-                // lists the name we get passed is the component type of the
-                // array.
-                typeVal = Class.forName(className);
-            } catch (ClassNotFoundException e) {
-                logger.log(Level.WARNING, "Failed to resolve class type for '"
-                        + className + "'.");
-            }
-        }
-        return typeVal;
-    }
-
-    private Object deserializeValue(JsonDeserializationContext ctxt,
-            JsonElement valueElement, Class<?> valType) {
-        // special case for Pojo/Pojo list types. In that case, the valType
-        // is the component type for arrays. In order to distinguish pojo
-        // lists from pojos, we use JSON's array info about the value element.
-        if (valueElement != null && valueElement.isJsonArray() && !valType.isArray()) {
-            assert(Pojo.class.isAssignableFrom(valType));
-            Class<?> arrayType = Array.newInstance(valType, 0).getClass();
-            return ctxt.deserialize(valueElement, arrayType);
-        } else {
-            Object value = ctxt.deserialize(valueElement, valType);
-            validatePrimitivesForNull(value, valType);
-            return value;
-        }
-    }
-
-    private void validatePrimitivesForNull(Object value, Class<?> valType) {
-        if (PRIMITIVES_NOT_ALLOWING_NULL_VAL.contains(valType) && value == null) {
-            // illegal value for primitive type. according to JLS spec they all
-            // have default values and are never null.
-            throw new IllegalStateException( valType + " primitive" +
-                                            " does not accept a null value!");
-        }
-    }
-
-    // Allow valid classes + Pojo types, refuse everything else
-    private void validateSaneClassName(Class<?> clazz) {
-        // isAssignableFrom throws NPE if clazz is null.
-        if (VALID_CLASSES.contains(clazz) ||
-                Pojo.class.isAssignableFrom(clazz)) {
-            return;
-        }
-        throw new IllegalStateException("Illegal type of parameter " + clazz.getCanonicalName());
-    }
-
-}
-
--- a/web/common/src/main/java/com/redhat/thermostat/web/common/ThermostatGSONConverter.java	Tue Sep 30 11:57:25 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,111 +0,0 @@
-/*
- * Copyright 2012-2014 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.web.common;
-
-import java.beans.PropertyDescriptor;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Type;
-
-import org.apache.commons.beanutils.PropertyUtils;
-
-import com.google.gson.JsonDeserializationContext;
-import com.google.gson.JsonDeserializer;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParseException;
-import com.google.gson.JsonSerializationContext;
-import com.google.gson.JsonSerializer;
-import com.redhat.thermostat.storage.core.Entity;
-import com.redhat.thermostat.storage.core.Persist;
-import com.redhat.thermostat.storage.model.Pojo;
-
-public class ThermostatGSONConverter implements JsonSerializer<Pojo>, JsonDeserializer<Pojo> {
-
-    @Override
-    public Pojo deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
-        @SuppressWarnings("unchecked")
-        Class<? extends Pojo> targetType = (Class<Pojo>) typeOfT;
-        try {
-            Pojo pojo = targetType.newInstance();
-            PropertyDescriptor[] descs = PropertyUtils.getPropertyDescriptors(pojo);
-            for (PropertyDescriptor desc : descs) {
-                Method writeMethod = desc.getWriteMethod();
-                if (writeMethod != null && writeMethod.isAnnotationPresent(Persist.class)) {
-                    String name = desc.getName();
-                    JsonElement child = json.getAsJsonObject().get(name);
-                    Object value = context.deserialize(child, desc.getPropertyType());
-                    PropertyUtils.setProperty(pojo, name, value);
-                }
-            }
-            return pojo;
-        } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    @Override
-    public JsonElement serialize(Pojo src, Type typeOfSrc, JsonSerializationContext context) {
-        Class<?> cls = (Class<?>) typeOfSrc;
-        if (! cls.isAnnotationPresent(Entity.class)) {
-            System.err.println("attempt to serialize non-Entity class: " + cls.getName());
-            throw new IllegalArgumentException("attempt to serialize non-Entity class: " + cls.getName());
-        }
-        JsonObject obj = new JsonObject();
-        PropertyDescriptor[] descs = PropertyUtils.getPropertyDescriptors(src);
-        for (PropertyDescriptor desc : descs) {
-            Method readMethod = desc.getReadMethod();
-            if (readMethod != null && readMethod.isAnnotationPresent(Persist.class)) {
-                String name = desc.getName();
-                
-                try {
-                    Object value = PropertyUtils.getProperty(src, name);
-                    obj.add(name, context.serialize(value));
-                } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
-                    throw new RuntimeException(e);
-                }
-            } else if (readMethod == null) {
-                System.err.println("WARNING: property without read method: " + src.getClass().getName() + "::" + desc.getName());
-            }
-        }
-        return obj;
-    }
-
-
-}
-
--- a/web/common/src/main/java/com/redhat/thermostat/web/common/WebPreparedStatementSerializer.java	Tue Sep 30 11:57:25 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,89 +0,0 @@
-/*
- * Copyright 2012-2014 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.web.common;
-
-import java.lang.reflect.Type;
-
-import com.google.gson.JsonDeserializationContext;
-import com.google.gson.JsonDeserializer;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParseException;
-import com.google.gson.JsonPrimitive;
-import com.google.gson.JsonSerializationContext;
-import com.google.gson.JsonSerializer;
-import com.redhat.thermostat.storage.core.PreparedParameters;
-
-/**
- * GSON type adapter for {@link WebPreparedStatement}.
- * Depends on {@link PreparedParameterSerializer} being registered as
- * type adapter as well.
- *
- */
-public class WebPreparedStatementSerializer implements
-        JsonDeserializer<WebPreparedStatement<?>>,
-        JsonSerializer<WebPreparedStatement<?>> {
-    
-    private static final String PROP_PARAMS = "p";
-    private static final String PROP_STMT_ID = "sid";
-
-    @Override
-    public JsonElement serialize(WebPreparedStatement<?> stmt, Type type,
-            JsonSerializationContext ctxt) {
-        JsonObject result = new JsonObject();
-        JsonElement parameters = ctxt.serialize(stmt.getParams(), PreparedParameters.class);
-        result.add(PROP_PARAMS, parameters);
-        JsonPrimitive stmtIdElem = new JsonPrimitive(stmt.getStatementId());
-        result.add(PROP_STMT_ID, stmtIdElem);
-        return result;
-    }
-
-    @Override
-    public WebPreparedStatement<?> deserialize(JsonElement jsonElem, Type type,
-            JsonDeserializationContext ctxt) throws JsonParseException {
-        JsonElement paramsElem = jsonElem.getAsJsonObject().get(PROP_PARAMS);
-        JsonElement stmtIdElem = jsonElem.getAsJsonObject().get(PROP_STMT_ID);
-        PreparedParameters params = ctxt.deserialize(paramsElem, PreparedParameters.class);
-        int stmtId = ctxt.deserialize(stmtIdElem, int.class);
-        WebPreparedStatement<?> stmt = new WebPreparedStatement<>();
-        stmt.setStatementId(stmtId);
-        stmt.setParams(params);
-        return stmt;
-    }
-
-}
-
--- a/web/common/src/main/java/com/redhat/thermostat/web/common/WebQueryResponseSerializer.java	Tue Sep 30 11:57:25 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,103 +0,0 @@
-/*
- * Copyright 2012-2014 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.web.common;
-
-import java.lang.reflect.Array;
-import java.lang.reflect.ParameterizedType;
-import java.lang.reflect.Type;
-
-import com.google.gson.JsonArray;
-import com.google.gson.JsonDeserializationContext;
-import com.google.gson.JsonDeserializer;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParseException;
-import com.google.gson.JsonPrimitive;
-import com.google.gson.JsonSerializationContext;
-import com.google.gson.JsonSerializer;
-import com.redhat.thermostat.storage.model.Pojo;
-
-/**
- * GSON type adapter for {@link QueryResponse}.
- * Depends on {@link ThermostatGSONConverter} being registered as
- * type adapter as well.
- *
- */
-public class WebQueryResponseSerializer<T extends Pojo> implements
-        JsonDeserializer<WebQueryResponse<T>>, JsonSerializer<WebQueryResponse<T>> {
-
-    private static final String PROP_RESULT = "payload";
-    private static final String PROP_ERROR_CODE = "errno";
-    
-    @Override
-    public JsonElement serialize(WebQueryResponse<T> qResponse, Type type,
-            JsonSerializationContext ctxt) {
-        JsonObject result = new JsonObject();
-        JsonElement resultsElem = ctxt.serialize(qResponse.getResultList());
-        result.add(PROP_RESULT, resultsElem);
-        JsonPrimitive errnoElem = new JsonPrimitive(qResponse.getResponseCode());
-        result.add(PROP_ERROR_CODE, errnoElem);
-        return result;
-    }
-    
-    @SuppressWarnings("unchecked")
-    @Override
-    public WebQueryResponse<T> deserialize(JsonElement jsonElem, Type type,
-            JsonDeserializationContext ctxt) throws JsonParseException {
-        // fromJson() calls need to pass in the right *parameterized* type token:
-        // example for AgentInformation as T:
-        //   Type queryResponseType = new TypeToken<WebQueryResponse<AgentInformation>>() {}.getType();
-        //   gson.fromJson(jsonStr, queryResponseType)
-        Type[] typeParameters = ((ParameterizedType)type).getActualTypeArguments();
-        Type queryResponseTypeParam = typeParameters[0]; // WebQueryResponse has only one parameterized type T
-        JsonArray resultElem = jsonElem.getAsJsonObject().get(PROP_RESULT).getAsJsonArray();
-        @SuppressWarnings("rawtypes")
-        Class typeOfGeneric = (Class)queryResponseTypeParam;
-        T[] array = (T[])Array.newInstance(typeOfGeneric, resultElem.size());
-        for (int i = 0; i < resultElem.size(); i++) {
-            array[i] = ctxt.deserialize(resultElem.get(i), queryResponseTypeParam);
-        }
-        JsonElement errorCodeElem = jsonElem.getAsJsonObject().get(PROP_ERROR_CODE);
-        int errorCode = ctxt.deserialize(errorCodeElem, int.class);
-        WebQueryResponse<T> qResponse = new WebQueryResponse<>();
-        qResponse.setResponseCode(errorCode);
-        qResponse.setResultList(array);
-        return qResponse;
-    }
-    
-}
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/common/src/main/java/com/redhat/thermostat/web/common/typeadapters/PojoTypeAdapter.java	Wed Sep 17 11:36:58 2014 +0200
@@ -0,0 +1,230 @@
+/*
+ * Copyright 2012-2014 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.web.common.typeadapters;
+
+import java.beans.PropertyDescriptor;
+import java.io.IOException;
+import java.lang.ref.SoftReference;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Objects;
+
+import org.apache.commons.beanutils.PropertyUtils;
+
+import com.google.gson.Gson;
+import com.google.gson.TypeAdapter;
+import com.google.gson.TypeAdapterFactory;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonToken;
+import com.google.gson.stream.JsonWriter;
+import com.redhat.thermostat.storage.core.Entity;
+import com.redhat.thermostat.storage.core.Persist;
+import com.redhat.thermostat.storage.model.Pojo;
+
+/**
+ * A generic type adapter for types implementing {@link Pojo}. It uses
+ * no special knowledge of the actual implementation types.
+ *
+ */
+class PojoTypeAdapter<T extends Pojo> extends TypeAdapter<Pojo> {
+    
+    private final Class<T> runtimeType;
+    private final Gson gson;
+    private final TypeAdapterFactory pojoFactory;
+    private SoftReference<HashMap<String, PropertyDescriptor>> propsCache;
+    
+    PojoTypeAdapter(TypeAdapterFactory factory, Gson gson, Class<T> runtimeType) {
+        assert(Pojo.class.isAssignableFrom(runtimeType));
+        this.runtimeType = Objects.requireNonNull(runtimeType);
+        this.gson = gson;
+        this.pojoFactory = factory;
+        // cache will be filled once first used.
+        this.propsCache = new SoftReference<>(null);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public void write(JsonWriter out, Pojo value) throws IOException {
+        // handle null
+        if (value == null) {
+            out.nullValue();
+            return;
+        }
+        
+        Class<?> cls = value.getClass();
+        if (! cls.isAnnotationPresent(Entity.class)) {
+            System.err.println("attempt to serialize non-Entity class: " + cls.getName());
+            throw new IllegalArgumentException("attempt to serialize non-Entity class: " + cls.getName());
+        }
+        
+        out.beginObject();
+        
+        PropertyDescriptor[] descs = PropertyUtils.getPropertyDescriptors(value);
+        for (PropertyDescriptor desc : descs) {
+            Method readMethod = desc.getReadMethod();
+            if (readMethod != null && readMethod.isAnnotationPresent(Persist.class)) {
+                String name = desc.getName();
+                out.name(name);
+                try {
+                    Object val = PropertyUtils.getProperty(value, name);
+                    if (val == null) {
+                        out.nullValue();
+                    } else {
+                        // non-null member case
+                        if (Pojo.class.isAssignableFrom(val.getClass())) {
+                            // recursive case
+                            PojoTypeAdapter<T> pojoMemAdapter= new PojoTypeAdapter<>(pojoFactory, gson, (Class<T>)val.getClass());
+                            pojoMemAdapter.write(out, (T)val);
+                        } else {
+                            // base case: non-pojo type
+                            Class<?> valClass = val.getClass();
+                            @SuppressWarnings("rawtypes")
+                            TypeToken memberTypeToken = TypeToken.get(valClass);
+                            @SuppressWarnings({"rawtypes"})
+                            TypeAdapter memberTypeAdapter = getNonPojoTypeAdapter(memberTypeToken);
+                            memberTypeAdapter.write(out, val);
+                        }
+                    }
+                } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
+                    throw new RuntimeException(e);
+                }
+            } else if (readMethod == null) {
+                System.err.println("WARNING: property without read method: " + value.getClass().getName() + "." + desc.getName());
+            }
+        }
+        
+        out.endObject();
+    }
+
+    @Override
+    public Pojo read(JsonReader in) throws IOException {
+        // handle null
+        JsonToken token = in.peek();
+        if (token == JsonToken.NULL) {
+            return null;
+        }
+        
+        
+        try {
+            in.beginObject();
+            
+            Pojo pojo = runtimeType.newInstance();
+            // loop over names in JSON
+            processNamesValues(pojo, in);
+            
+            in.endObject();
+
+            return pojo;
+        } catch (InstantiationException | IllegalAccessException e) {
+            throw new RuntimeException(e);
+        }
+    }
+    
+    private void processNamesValues(Pojo pojo, JsonReader in) throws IOException {
+        JsonToken token = in.peek();
+        switch(token) {
+        case NAME:
+            String name = in.nextName();
+            processValue(pojo, in, name);
+            // recursive case
+            processNamesValues(pojo, in);
+            break;
+        case END_OBJECT:
+            // Base case
+            return;
+        default:
+            throw new IllegalStateException("Expected NAME or END_OBJECT. Was: " + token);
+        }
+    }
+
+    private void processValue(Pojo pojo, JsonReader in, String name) throws IOException {
+        try {
+            PropertyDescriptor desc = getPropDescriptor(pojo, name);
+            if (desc == null) {
+                throw new IllegalStateException("Property descriptor null for: " + name);
+            }
+            Method writeMethod = desc.getWriteMethod();
+            Object value = null;
+            if (writeMethod != null && writeMethod.isAnnotationPresent(Persist.class)) {
+                Class<?> memberType = desc.getPropertyType();
+                if (Pojo.class.isAssignableFrom(memberType)) {
+                    // recursive case
+                    @SuppressWarnings("unchecked") // We've just checked the cast to Class<T> works
+                    PojoTypeAdapter<T> memberAdapter = new PojoTypeAdapter<>(pojoFactory, gson, (Class<T>)memberType);
+                    value = (Pojo)memberAdapter.read(in);
+                } else {
+                    // base case: non-pojo type
+                    TypeToken<?> memberTypeToken = TypeToken.get(memberType);
+                    TypeAdapter<?> memberTypeAdapter = getNonPojoTypeAdapter(memberTypeToken);
+                    value = memberTypeAdapter.read(in);
+                }
+                PropertyUtils.setProperty(pojo, name, value);
+            }
+        } catch (IllegalAccessException | InvocationTargetException e) {
+            throw new RuntimeException(e);
+        } catch (NoSuchMethodException e) {
+            System.err.println("Setter for '" + name + "' not found. The value will be ignored.");
+        }
+    }
+    
+    private PropertyDescriptor getPropDescriptor(Pojo pojo, String name) {
+        if (propsCache.get() == null) {
+            // build cache
+            HashMap<String, PropertyDescriptor> propDescCache = new HashMap<>();
+            PropertyDescriptor[] descs = PropertyUtils.getPropertyDescriptors(pojo);
+            PropertyDescriptor wanted = null;
+            for (PropertyDescriptor desc: descs) {
+                propDescCache.put(desc.getName(), desc);
+                if (desc.getName().equals(name)) {
+                    wanted = desc;
+                }
+            }
+            propsCache = new SoftReference<>(propDescCache);
+            return wanted;
+        } else {
+            HashMap<String, PropertyDescriptor> cacheMap = propsCache.get();
+            return cacheMap.get(name);
+        }
+    }
+
+    private <S> TypeAdapter<S> getNonPojoTypeAdapter(TypeToken<S> nonPojoType) {
+        return gson.getDelegateAdapter(pojoFactory, nonPojoType);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/common/src/main/java/com/redhat/thermostat/web/common/typeadapters/PojoTypeAdapterFactory.java	Wed Sep 17 11:36:58 2014 +0200
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2012-2014 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.web.common.typeadapters;
+
+import com.google.gson.Gson;
+import com.google.gson.TypeAdapter;
+import com.google.gson.TypeAdapterFactory;
+import com.google.gson.reflect.TypeToken;
+import com.redhat.thermostat.storage.model.Pojo;
+
+public class PojoTypeAdapterFactory implements TypeAdapterFactory {
+
+    @Override
+    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
+        Class<?> rawType = type.getRawType();
+        if (Pojo.class.isAssignableFrom(rawType)) {
+            // We've just verified that we have a Pojo type. The unchecked
+            // cast is safe.
+            @SuppressWarnings("unchecked")
+            Class<T> castedType = (Class<T>)rawType;
+            @SuppressWarnings({ "unchecked", "rawtypes" })
+            TypeAdapter<T> ta = (TypeAdapter<T>)new PojoTypeAdapter(this, gson, castedType);
+            return ta;
+        }
+        return null;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/common/src/main/java/com/redhat/thermostat/web/common/typeadapters/PreparedParameterTypeAdapter.java	Wed Sep 17 11:36:58 2014 +0200
@@ -0,0 +1,368 @@
+/*
+ * Copyright 2012-2014 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.web.common.typeadapters;
+
+import java.io.IOException;
+import java.lang.reflect.Array;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import com.google.gson.Gson;
+import com.google.gson.TypeAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonToken;
+import com.google.gson.stream.JsonWriter;
+import com.redhat.thermostat.storage.core.PreparedParameter;
+import com.redhat.thermostat.storage.model.Pojo;
+
+/**
+ * GSON type adapter for {@link PreparedParameter}.
+ *
+ */
+class PreparedParameterTypeAdapter extends TypeAdapter<PreparedParameter> {
+
+    private static final String PROP_TYPE = "type";
+    private static final String PROP_VALUE = "value";
+    private static final Set<Class<?>> VALID_CLASSES;
+    private static final Set<Class<?>> PRIMITIVES_NOT_ALLOWING_NULL_VAL;
+    private static final Set<Class<?>> BASIC_TYPES;
+    // maps type names to classes:
+    //    "int" => int.class , "double" => double.class, "[I" => int[].class
+    //    and so on.
+    private static final Map<String, Class<?>> CLASSES_LOOKUP_TABLE;
+    
+    private final Gson gson;
+    
+    static {
+        VALID_CLASSES = new HashSet<>();
+        BASIC_TYPES = new HashSet<>();
+        BASIC_TYPES.add(int.class);
+        BASIC_TYPES.add(long.class);
+        BASIC_TYPES.add(boolean.class);
+        BASIC_TYPES.add(double.class);
+        BASIC_TYPES.add(String.class);
+        CLASSES_LOOKUP_TABLE = new HashMap<>();
+        CLASSES_LOOKUP_TABLE.put(int.class.getName(), int.class);
+        CLASSES_LOOKUP_TABLE.put(long.class.getName(), long.class);
+        CLASSES_LOOKUP_TABLE.put(boolean.class.getName(), boolean.class);
+        CLASSES_LOOKUP_TABLE.put(double.class.getName(), double.class);
+        CLASSES_LOOKUP_TABLE.put(String.class.getName(), String.class);
+        CLASSES_LOOKUP_TABLE.put(int[].class.getName(), int[].class);
+        CLASSES_LOOKUP_TABLE.put(long[].class.getName(), long[].class);
+        CLASSES_LOOKUP_TABLE.put(boolean[].class.getName(), boolean[].class);
+        CLASSES_LOOKUP_TABLE.put(double[].class.getName(), double[].class);
+        CLASSES_LOOKUP_TABLE.put(String[].class.getName(), String[].class);
+        VALID_CLASSES.addAll(CLASSES_LOOKUP_TABLE.values());
+        PRIMITIVES_NOT_ALLOWING_NULL_VAL = new HashSet<>();
+        PRIMITIVES_NOT_ALLOWING_NULL_VAL.add(int.class);
+        PRIMITIVES_NOT_ALLOWING_NULL_VAL.add(long.class);
+        PRIMITIVES_NOT_ALLOWING_NULL_VAL.add(boolean.class);
+        PRIMITIVES_NOT_ALLOWING_NULL_VAL.add(double.class);
+    }
+    
+    
+    PreparedParameterTypeAdapter(Gson gson) {
+        this.gson = gson;
+    }
+    
+    
+    private Class<?> deserializeTypeVal(String className) {
+        Class<?> typeVal = null;
+        if (CLASSES_LOOKUP_TABLE.containsKey(className)) {
+            typeVal = CLASSES_LOOKUP_TABLE.get(className);
+        } else {
+            try {
+                // We need this for Pojo + Pojo list type params. For pojo
+                // lists the name we get passed is the component type of the
+                // array.
+                typeVal = Class.forName(className);
+            } catch (ClassNotFoundException e) {
+                throw new IllegalStateException("Failed to resolve class type for '"
+                        + className + "'.");
+            }
+        }
+        validateSaneClassName(typeVal);
+        return typeVal;
+    }
+
+    private void validatePrimitivesForNull(Object value, Class<?> valType) {
+        if (PRIMITIVES_NOT_ALLOWING_NULL_VAL.contains(valType) && value == null) {
+            // illegal value for primitive type. according to JLS spec they all
+            // have default values and are never null.
+            throw new IllegalStateException( valType + " primitive" +
+                                            " does not accept a null value!");
+        }
+    }
+
+    // Allow valid classes + Pojo types, refuse everything else
+    private void validateSaneClassName(Class<?> clazz) {
+        // isAssignableFrom throws NPE if clazz is null.
+        if (VALID_CLASSES.contains(clazz) ||
+                Pojo.class.isAssignableFrom(clazz)) {
+            return;
+        }
+        throw new IllegalStateException("Illegal type of parameter " + clazz.getCanonicalName());
+    }
+
+    @Override
+    public PreparedParameter read(JsonReader reader) throws IOException {
+        if (reader.peek() == JsonToken.NULL) {
+            reader.nextNull();
+            return null;
+        }
+        reader.beginObject();
+        
+        Class<?> type = readType(reader);
+        
+        assert(type != null);
+        assert(Pojo.class.isAssignableFrom(type) || VALID_CLASSES.contains(type));
+        
+        Object val = readValue(reader, type);
+        reader.endObject();
+        
+        PreparedParameter param = new PreparedParameter();
+        param.setValue(val);
+        param.setType(type);
+        return param;
+    }
+    
+    private Class<?> readType(JsonReader reader) throws IOException {
+        String name = reader.nextName();
+        if (!name.equals(PROP_TYPE)) {
+            throw new IllegalStateException("Expected " + PROP_VALUE + " but was " + name);
+        }
+        String className = reader.nextString();
+        return deserializeTypeVal(className);
+    }
+
+    private Object readValue(JsonReader reader, Class<?> valType) throws IOException {
+        // values may be null. In that case they are missing from the JSON
+        // string. Be sure to handle that case early.
+        if (reader.peek() == JsonToken.END_OBJECT) {
+            return null; // null parameter value
+        }
+        String name = reader.nextName();
+        if (!name.equals(PROP_VALUE)) {
+            throw new IllegalStateException("Expected " + PROP_VALUE + " but was " + name);
+        }
+        
+        JsonToken token = reader.peek();
+        if (token == JsonToken.NULL) {
+            reader.nextNull();
+            // Be sure to not allow null values for primitives. Note:
+            // valType may be an array type for which null is fine.
+            validatePrimitivesForNull(null, valType);
+            return null; // null value
+        }
+        
+        // special case for Pojo/Pojo list types. In that case, the valType
+        // is the component type for arrays. In order to distinguish pojo
+        // lists from pojos, we use JSON's array info about the value element.
+        if (token == JsonToken.BEGIN_ARRAY && !valType.isArray()) {
+            assert(Pojo.class.isAssignableFrom(valType));
+            Class<?> arrayType = Array.newInstance(valType, 0).getClass();
+            TypeAdapter<?> pojoArrayTa = gson.getAdapter(arrayType);
+            Object val = pojoArrayTa.read(reader);
+            return val;
+        } else {
+            TypeAdapter<?> nonPojoTypeAdapter = gson.getAdapter(valType);
+            Object val = nonPojoTypeAdapter.read(reader);
+            validatePrimitivesForNull(val, valType);
+            return val;
+        }
+    }
+
+
+    @Override
+    public void write(JsonWriter writer, PreparedParameter param)
+            throws IOException {
+        if (param == null) {
+            writer.nullValue();
+            return;
+        }
+        writer.beginObject();
+        
+        // serialize type which must not be null
+        writer.name(PROP_TYPE);
+        if (param.getType() == null) {
+            throw new IllegalStateException("Type of prepared parameter must not be null");
+        }
+        String typeStr = param.getType().getName();
+        writer.value(typeStr);
+        
+        // serialize value
+        writer.name(PROP_VALUE);
+        serializeValue(writer, param);
+        
+        writer.endObject();
+    }
+    
+    private void serializeValue(JsonWriter writer, PreparedParameter param) throws IOException {
+        if (param.getValue() == null) {
+            writer.nullValue();
+            return;
+        }
+        
+        Object val = param.getValue();
+        if (val.getClass().isArray()) {
+            serializeArrayValue(writer, param);
+        } else {
+            serializeBasicValue(writer, param);
+        }
+    }
+
+    /*
+     * Serialize single basic type or Pojo.
+     */
+    private void serializeBasicValue(JsonWriter writer, PreparedParameter param) throws IOException {
+        assert(param.getValue() != null);
+        
+        Class<?> type = param.getType();
+        if (BASIC_TYPES.contains(type)) {
+            serializeSingleBasicValue(writer, param.getValue(), param.getType());
+        } else {
+            assert(Pojo.class.isAssignableFrom(param.getType()));
+            serializeSinglePojo(writer, param);
+        }
+    }
+
+    private void serializeSinglePojo(JsonWriter writer, PreparedParameter param) throws IOException {
+        assert(param.getValue() != null);
+        
+        serializeSinglePojoImpl(writer, param.getValue(), param.getType());
+    }
+    
+    private void serializeSinglePojoImpl(JsonWriter writer, Object value, Class<?> type) throws IOException {
+        // precondition(s)
+        assert(Pojo.class.isAssignableFrom(type));
+        assert(value instanceof Pojo);
+        assert(value != null && !value.getClass().isArray());
+        
+        TypeAdapter<Pojo> pojoTypeAdapter = getTypeAdapter(Pojo.class);
+        pojoTypeAdapter.write(writer, (Pojo)value);
+    }
+
+    /*
+     * Serialize an array of primitives or Pojos
+     */
+    private void serializeArrayValue(JsonWriter writer,
+            PreparedParameter param) throws IOException {
+        Class<?> valType = param.getValue().getClass();
+        Class<?> typeType = param.getType();
+        // Special case pojo list types: the value class is of array type for
+        // them, but the type is the component type.
+        if (valType.isArray() && !typeType.isArray()) {
+            assert(Pojo.class.isAssignableFrom(typeType));
+            serializePojoList(writer, param);
+        } else {
+            serializeBasicList(writer, param);
+        }
+    }
+
+    /*
+     * Serialize a list of known primitives: boolean, int, long, double, String
+     */
+    private void serializeBasicList(JsonWriter writer,
+            PreparedParameter param) throws IOException {
+        // preconditions
+        assert(param.getValue() != null);
+        assert(param.getValue().getClass().isArray());
+        assert(param.getType().isArray());
+        
+        writer.beginArray();
+        int length = Array.getLength(param.getValue());
+        for (int i = 0; i < length; i++) {
+            Object elemVal = Array.get(param.getValue(), i);
+            Class<?> elemType = param.getType().getComponentType();
+            serializeSingleBasicValue(writer, elemVal, elemType);
+        }
+        writer.endArray();
+    }
+
+    private void serializeSingleBasicValue(JsonWriter writer, Object object, Class<?> type) throws IOException {
+        assert(!type.isArray());
+        if (object == null) {
+            // must have been a String type
+            // as other primitive types won't allow null
+            writer.nullValue();
+            return;
+        }
+        assert(!object.getClass().isArray());
+        assert(BASIC_TYPES.contains(type));
+        if (type.isPrimitive()) {
+            if (type == int.class) {
+                Integer intVal = (Integer)object;
+                writer.value(intVal);
+            } else if (type == long.class) {
+                writer.value((long)object);
+            } else if (type == double.class) {
+                writer.value((double)object);
+            } else if (type == boolean.class) {
+                writer.value((boolean)object);
+            }
+        } else {
+            assert(object instanceof String);
+            writer.value((String)object);
+        }
+    }
+
+    /*
+     *  Serialized an array of Pojo and only an array of Pojo
+     */
+    private void serializePojoList(JsonWriter writer, PreparedParameter param) throws IOException {
+        // preconditions
+        assert(param.getValue() != null);
+        assert(Pojo.class.isAssignableFrom(param.getType()));
+        assert(param.getValue().getClass().isArray());
+        
+        int length = Array.getLength(param.getValue());
+        writer.beginArray();
+        for (int i = 0; i < length; i++) {
+            Object value = Array.get(param.getValue(), i);
+            serializeSinglePojoImpl(writer, value, param.getType());
+        }
+        writer.endArray();
+    }
+    
+    private <T> TypeAdapter<T> getTypeAdapter(Class<T> typeClass) {
+        return gson.getAdapter(typeClass);
+    }
+    
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/common/src/main/java/com/redhat/thermostat/web/common/typeadapters/PreparedParameterTypeAdapterFactory.java	Wed Sep 17 11:36:58 2014 +0200
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2012-2014 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.web.common.typeadapters;
+
+import com.google.gson.Gson;
+import com.google.gson.TypeAdapter;
+import com.google.gson.TypeAdapterFactory;
+import com.google.gson.reflect.TypeToken;
+import com.redhat.thermostat.storage.core.PreparedParameter;
+
+public class PreparedParameterTypeAdapterFactory implements TypeAdapterFactory {
+
+    @Override
+    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
+        Class<?> rawType = type.getRawType();
+        if (rawType == PreparedParameter.class) {
+            @SuppressWarnings("unchecked") // we've just verified this will work
+            TypeAdapter<T> ta = (TypeAdapter<T>) new PreparedParameterTypeAdapter(gson);
+            return ta;
+        } else {
+            return null;
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/common/src/main/java/com/redhat/thermostat/web/common/typeadapters/PreparedParametersTypeAdapter.java	Wed Sep 17 11:36:58 2014 +0200
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2012-2014 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.web.common.typeadapters;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+
+import com.google.gson.Gson;
+import com.google.gson.TypeAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonToken;
+import com.google.gson.stream.JsonWriter;
+import com.redhat.thermostat.storage.core.PreparedParameter;
+import com.redhat.thermostat.storage.core.PreparedParameters;
+
+class PreparedParametersTypeAdapter extends TypeAdapter<PreparedParameters> {
+
+    private final Gson gson;
+    private final Field paramsField;
+    
+    PreparedParametersTypeAdapter(Gson gson) {
+        this.gson = gson;
+        this.paramsField = getParametersField();
+    }
+    
+    @Override
+    public void write(JsonWriter out, PreparedParameters value)
+            throws IOException {
+        // handle null
+        if (value == null) {
+            out.nullValue();
+            return;
+        }
+        
+        TypeAdapter<PreparedParameter[]> paramsTa = gson.getAdapter(PreparedParameter[].class);
+        paramsTa.write(out, value.getParams());
+    }
+
+    @Override
+    public PreparedParameters read(JsonReader in) throws IOException {
+        // handle null
+        if (in.peek() == JsonToken.NULL) {
+            in.nextNull();
+            return null;
+        }
+        
+        TypeAdapter<PreparedParameter[]> paramsTa = gson.getAdapter(PreparedParameter[].class);
+        PreparedParameters params = new PreparedParameters(0);
+        PreparedParameter[] payload = paramsTa.read(in);
+        setParamenters(params, payload);
+        return params;
+    }
+
+    private void setParamenters(PreparedParameters params, PreparedParameter[] payload) {
+        // Set parameters via reflection in order to not be forced to
+        // introduce a public setter.
+        try {
+            paramsField.set(params, payload);
+        } catch (SecurityException | IllegalArgumentException | IllegalAccessException e) {
+            throw new IllegalStateException("Failed to set params field");
+        }
+    }
+    
+    private Field getParametersField() {
+        try {
+            Field field = PreparedParameters.class.getDeclaredField("params");
+            field.setAccessible(true);
+            return field;
+        } catch (NoSuchFieldException | SecurityException e) {
+            throw new IllegalStateException("params field not found");
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/common/src/main/java/com/redhat/thermostat/web/common/typeadapters/PreparedParametersTypeAdapterFactory.java	Wed Sep 17 11:36:58 2014 +0200
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2012-2014 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.web.common.typeadapters;
+
+import com.google.gson.Gson;
+import com.google.gson.TypeAdapter;
+import com.google.gson.TypeAdapterFactory;
+import com.google.gson.reflect.TypeToken;
+import com.redhat.thermostat.storage.core.PreparedParameters;
+
+public class PreparedParametersTypeAdapterFactory implements TypeAdapterFactory {
+
+    @Override
+    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
+        Class<?> rawType = type.getRawType();
+        if (rawType == PreparedParameters.class) {
+            @SuppressWarnings("unchecked")
+            TypeAdapter<T> ta = (TypeAdapter<T>)new PreparedParametersTypeAdapter(gson);
+            return ta;
+        }
+        return null;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/common/src/main/java/com/redhat/thermostat/web/common/typeadapters/WebPreparedStatementResponseTypeAdapter.java	Wed Sep 17 11:36:58 2014 +0200
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2012-2014 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.web.common.typeadapters;
+
+import java.io.IOException;
+
+import com.google.gson.TypeAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonToken;
+import com.google.gson.stream.JsonWriter;
+import com.redhat.thermostat.web.common.WebPreparedStatementResponse;
+
+class WebPreparedStatementResponseTypeAdapter extends
+        TypeAdapter<WebPreparedStatementResponse> {
+    
+    private static final String NUM_FREE_VARS_NAME = "numFreeVars";
+    private static final String STMT_ID_NAME = "stmtId";
+
+    WebPreparedStatementResponseTypeAdapter() {
+        // package-private no-arg constructor
+    }
+    
+    @Override
+    public WebPreparedStatementResponse read(JsonReader reader)
+            throws IOException {
+        if (reader.peek() == JsonToken.NULL) {
+            reader.nextNull();
+            return null;
+        }
+        
+        WebPreparedStatementResponse response = new WebPreparedStatementResponse();
+        
+        reader.beginObject();
+        String name = reader.nextName();
+        if (name.equals(NUM_FREE_VARS_NAME)) {
+            response.setNumFreeVariables(reader.nextInt());
+        }
+        name = reader.nextName();
+        if (name.equals(STMT_ID_NAME)) {
+            response.setStatementId(reader.nextInt());
+        }
+        reader.endObject();
+        
+        return response;
+    }
+
+    @Override
+    public void write(JsonWriter writer, WebPreparedStatementResponse value)
+            throws IOException {
+        if (value == null) {
+            writer.nullValue();
+            return;
+        }
+        
+        int freeVars = value.getNumFreeVariables();
+        int stmtId = value.getStatementId();
+        
+        writer.beginObject();
+        
+        // Free variables
+        writer.name(NUM_FREE_VARS_NAME);
+        writer.value(freeVars);
+        // stmt id
+        writer.name(STMT_ID_NAME);
+        writer.value(stmtId);
+
+        writer.endObject();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/common/src/main/java/com/redhat/thermostat/web/common/typeadapters/WebPreparedStatementResponseTypeAdapterFactory.java	Wed Sep 17 11:36:58 2014 +0200
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2012-2014 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.web.common.typeadapters;
+
+import com.google.gson.Gson;
+import com.google.gson.TypeAdapter;
+import com.google.gson.TypeAdapterFactory;
+import com.google.gson.reflect.TypeToken;
+import com.redhat.thermostat.web.common.WebPreparedStatementResponse;
+
+public class WebPreparedStatementResponseTypeAdapterFactory implements
+        TypeAdapterFactory {
+
+    @Override
+    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
+        Class<?> rawType = type.getRawType();
+        if (rawType == WebPreparedStatementResponse.class) {
+            @SuppressWarnings("unchecked")
+            TypeAdapter<T> ta = (TypeAdapter<T>)new WebPreparedStatementResponseTypeAdapter();
+            return ta;
+        }
+        return null;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/common/src/main/java/com/redhat/thermostat/web/common/typeadapters/WebPreparedStatementTypeAdapter.java	Wed Sep 17 11:36:58 2014 +0200
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2012-2014 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.web.common.typeadapters;
+
+import java.io.IOException;
+
+import com.google.gson.Gson;
+import com.google.gson.TypeAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonToken;
+import com.google.gson.stream.JsonWriter;
+import com.redhat.thermostat.storage.core.PreparedParameters;
+import com.redhat.thermostat.web.common.WebPreparedStatement;
+
+@SuppressWarnings("rawtypes")
+class WebPreparedStatementTypeAdapter extends TypeAdapter<WebPreparedStatement> {
+
+    private static final String PROP_PARAMS = "p";
+    private static final String PROP_STMT_ID = "sid";
+    
+    private final Gson gson;
+    
+    WebPreparedStatementTypeAdapter(Gson gson) {
+        this.gson = gson;
+    }
+    
+    @Override
+    public void write(JsonWriter out, WebPreparedStatement value)
+            throws IOException {
+        // handle null
+        if (value == null) {
+            out.nullValue();
+            return;
+        }
+        
+        out.beginObject();
+        
+        // statement id
+        out.name(PROP_STMT_ID);
+        out.value(value.getStatementId());
+
+        // prepared parameters
+        out.name(PROP_PARAMS);
+        TypeAdapter<PreparedParameters> ta = gson.getAdapter(PreparedParameters.class);
+        ta.write(out, value.getParams());
+        
+        out.endObject();        
+    }
+
+    @Override
+    public WebPreparedStatement read(JsonReader in) throws IOException {
+        // handle null
+        if (in.peek() == JsonToken.NULL) {
+            in.nextNull();
+            return null;
+        }
+        
+        in.beginObject();
+        
+        
+        // statement id
+        String name = in.nextName();
+        if (!name.equals(PROP_STMT_ID)) {
+            throw new IllegalStateException("Expected name " + PROP_STMT_ID + " but was " + name);
+        }
+        int stmtId = in.nextInt();
+
+        // params
+        PreparedParameters params = null;
+        // params value might be null and missing.
+        if (in.peek() == JsonToken.NAME) {
+            name = in.nextName();
+            if (!name.equals(PROP_PARAMS)) {
+                throw new IllegalStateException("Expected name " + PROP_PARAMS + " but was " + name);
+            }
+            TypeAdapter<PreparedParameters> ta = gson.getAdapter(PreparedParameters.class);
+            params = ta.read(in);
+        }
+        
+        in.endObject();
+        
+        WebPreparedStatement<?> stmt = new WebPreparedStatement<>();
+        stmt.setParams(params);
+        stmt.setStatementId(stmtId);
+        return stmt;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/common/src/main/java/com/redhat/thermostat/web/common/typeadapters/WebPreparedStatementTypeAdapterFactory.java	Wed Sep 17 11:36:58 2014 +0200
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2012-2014 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.web.common.typeadapters;
+
+import com.google.gson.Gson;
+import com.google.gson.TypeAdapter;
+import com.google.gson.TypeAdapterFactory;
+import com.google.gson.reflect.TypeToken;
+import com.redhat.thermostat.web.common.WebPreparedStatement;
+
+public class WebPreparedStatementTypeAdapterFactory implements
+        TypeAdapterFactory {
+
+    @Override
+    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
+        Class<?> rawType = type.getRawType();
+        if (rawType == WebPreparedStatement.class) {
+            @SuppressWarnings("unchecked") // We've just checked the raw type
+            TypeAdapter<T> ta = (TypeAdapter<T>)new WebPreparedStatementTypeAdapter(gson);
+            return ta;
+        }
+        return null;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/common/src/main/java/com/redhat/thermostat/web/common/typeadapters/WebQueryResponseTypeAdapter.java	Wed Sep 17 11:36:58 2014 +0200
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2012-2014 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.web.common.typeadapters;
+
+import java.io.IOException;
+import java.lang.reflect.Array;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+
+import com.google.gson.Gson;
+import com.google.gson.TypeAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonToken;
+import com.google.gson.stream.JsonWriter;
+import com.redhat.thermostat.storage.model.Pojo;
+import com.redhat.thermostat.web.common.WebQueryResponse;
+
+class WebQueryResponseTypeAdapter<T extends Pojo> extends TypeAdapter<WebQueryResponse<T>> {
+
+    private static final String PROP_RESULT = "payload";
+    private static final String PROP_ERROR_CODE = "errno";
+    
+    // The runtime type of the Pojo
+    private final Class<T> runtimePojoType;
+    private final Gson gson;
+    
+    WebQueryResponseTypeAdapter(Type parameterizedType, Gson gson) {
+        this.runtimePojoType = extractPojoImplClassFromType(parameterizedType);
+        this.gson = gson;
+    }
+    
+    @Override
+    public void write(JsonWriter out, WebQueryResponse<T> value)
+            throws IOException {
+        // handle null
+        if (value == null) {
+            out.nullValue();
+            return;
+        }
+        
+        out.beginObject();
+        
+        // response code
+        out.name(PROP_ERROR_CODE);
+        out.value(value.getResponseCode());
+        
+        // payload
+        out.name(PROP_RESULT);
+        @SuppressWarnings("unchecked")
+        TypeAdapter<T[]> pojoTa = (TypeAdapter<T[]>)gson.getAdapter(value.getResultList().getClass());
+        pojoTa.write(out, value.getResultList());
+        
+        out.endObject();
+    }
+
+    @Override
+    public WebQueryResponse<T> read(JsonReader in) throws IOException {
+        // handle null
+        if (in.peek() == JsonToken.NULL) {
+            in.nextNull();
+            return null;
+        }
+        
+        in.beginObject();
+        
+        // response code
+        int responseCode = 0;
+        String name = in.nextName();
+        if (name.equals(PROP_ERROR_CODE)) {
+            responseCode = in.nextInt();
+        } else {
+            throw new IllegalStateException("Expected " + PROP_ERROR_CODE + " but got " + name);
+        }
+        
+        if (runtimePojoType == null) {
+            throw new IllegalStateException("Runtime pojo type unknown");
+        }
+        // Result list may be null. If it's there we get a name, otherwise
+        // we are done deserializing
+        T[] resultList = null;
+        if (in.peek() == JsonToken.NAME) {
+            name = in.nextName();
+            if (name.equals(PROP_RESULT)) {
+                @SuppressWarnings("unchecked")
+                T[] arrayType = (T[])Array.newInstance(runtimePojoType, 0);
+                @SuppressWarnings("unchecked")
+                Class<T[]> type = (Class<T[]>)arrayType.getClass();
+                TypeAdapter<T[]> pojoTa = gson.getAdapter(type);
+                resultList = pojoTa.read(in);
+            } else {
+                throw new IllegalStateException("Expected " + PROP_RESULT + " but got " + name);
+            }
+        }
+        
+        in.endObject();
+        
+        WebQueryResponse<T> qResponse = new WebQueryResponse<>();
+        qResponse.setResponseCode(responseCode);
+        qResponse.setResultList(resultList);
+        return qResponse;
+    }
+    
+    private Class<T> extractPojoImplClassFromType(Type type) {
+        if (type instanceof ParameterizedType) {
+            // fromJson() calls need to pass in the right *parameterized* type token:
+            // example for AgentInformation as T:
+            //   Type queryResponseType = new TypeToken<WebQueryResponse<AgentInformation>>() {}.getType();
+            //   gson.fromJson(jsonStr, queryResponseType)
+            Type[] typeParameters = ((ParameterizedType)type).getActualTypeArguments();
+            Type queryResponseTypeParam = typeParameters[0]; // WebQueryResponse has only one parameterized type T
+            Class<?> pojoClass = (Class<?>)queryResponseTypeParam;
+            if (!Pojo.class.isAssignableFrom(pojoClass)) {
+                throw new IllegalStateException("WebQueryResponse parametrized with non-pojo class: " + pojoClass);
+            } else {
+                @SuppressWarnings("unchecked") // We've just verified it's Pojo assignable.
+                Class<T> retval = (Class<T>)pojoClass;
+                return retval;
+            }
+        }
+        // should-be: write() will be called and only write().
+        return null;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/common/src/main/java/com/redhat/thermostat/web/common/typeadapters/WebQueryResponseTypeAdapterFactory.java	Wed Sep 17 11:36:58 2014 +0200
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2012-2014 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.web.common.typeadapters;
+
+import com.google.gson.Gson;
+import com.google.gson.TypeAdapter;
+import com.google.gson.TypeAdapterFactory;
+import com.google.gson.reflect.TypeToken;
+import com.redhat.thermostat.web.common.WebQueryResponse;
+
+public class WebQueryResponseTypeAdapterFactory implements TypeAdapterFactory {
+
+    @Override
+    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
+        Class<?> rawType = type.getRawType();
+        if (rawType == WebQueryResponse.class) {
+            @SuppressWarnings({ "unchecked", "rawtypes" })
+            TypeAdapter<T> ta = (TypeAdapter<T>)new WebQueryResponseTypeAdapter(type.getType(), gson);
+            return ta;
+        }
+        return null;
+    }
+
+}
--- a/web/common/src/test/java/com/redhat/thermostat/web/common/PreparedParameterSerializerTest.java	Tue Sep 30 11:57:25 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,483 +0,0 @@
-/*
- * Copyright 2012-2014 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.web.common;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import java.lang.reflect.Array;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import com.redhat.thermostat.storage.core.PreparedParameter;
-import com.redhat.thermostat.storage.model.AgentInformation;
-import com.redhat.thermostat.storage.model.Pojo;
-import com.redhat.thermostat.storage.model.VmInfo.KeyValuePair;
-
-public class PreparedParameterSerializerTest {
-
-    private Gson gson;
-    
-    @Before
-    public void setup() {
-        gson = new GsonBuilder()
-            .registerTypeHierarchyAdapter(Pojo.class, new ThermostatGSONConverter())
-            .registerTypeAdapter(
-                PreparedParameter.class,
-                new PreparedParameterSerializer()).create();
-    }
-    
-    @Test
-    public void canDeserializeBasic() {
-        // String
-        String jsonStr = "{ \"type\": \"java.lang.String\" , \"value\": \"testing\" }";
-        PreparedParameter param = gson.fromJson(jsonStr, PreparedParameter.class);
-        assertEquals(String.class, param.getType());
-        assertEquals("testing", param.getValue());
-        // Integer
-        jsonStr = "{ \"type\": \"int\" , \"value\": -1}";
-        param = gson.fromJson(jsonStr, PreparedParameter.class);
-        assertEquals(int.class, param.getType());
-        assertEquals(-1, param.getValue());
-        // Long
-        jsonStr = "{ \"type\": \"long\" , \"value\": -10}";
-        param = gson.fromJson(jsonStr, PreparedParameter.class);
-        assertEquals(long.class, param.getType());
-        assertEquals(-10L, param.getValue());
-        jsonStr = "{ \"type\": \"long\" , \"value\": 30000000003}";
-        param = gson.fromJson(jsonStr, PreparedParameter.class);
-        assertEquals(long.class, param.getType());
-        assertEquals(30000000003L, param.getValue());
-        // Boolean
-        jsonStr = "{ \"type\": \"boolean\" , \"value\": true}";
-        param = gson.fromJson(jsonStr, PreparedParameter.class);
-        assertEquals(boolean.class, param.getType());
-        assertEquals(true, param.getValue());
-        // String[]
-        String strArrayVal = "[ \"testing1\", \"testing2\", \"3\" ]";
-        jsonStr = "{ \"type\": \"[Ljava.lang.String;\" , \"value\": " + strArrayVal + "}";
-        param = gson.fromJson(jsonStr, PreparedParameter.class);
-        assertEquals(String[].class, param.getType());
-        assertTrue(param.getValue() instanceof String[]);
-        String[] vals = (String[])param.getValue();
-        assertEquals(3, vals.length);
-        assertEquals("testing1", vals[0]);
-        assertEquals("testing2", vals[1]);
-        assertEquals("3", vals[2]);
-    }
-    
-    @Test
-    public void allowsBooleanListNullDeserialization() {
-        String jsonStr = "{ \"type\": \"[Z\" , \"value\": null}";
-        PreparedParameter p = gson.fromJson(jsonStr, PreparedParameter.class);
-        assertEquals(boolean[].class, p.getType());
-        assertEquals(null, p.getValue());
-    }
-    
-    @Test
-    public void allowsIntListNullDeserialization() {
-        String jsonStr = "{ \"type\": \"[I\" , \"value\": null}";
-        PreparedParameter p = gson.fromJson(jsonStr, PreparedParameter.class);
-        assertEquals(int[].class, p.getType());
-        assertEquals(null, p.getValue());
-    }
-    
-    @Test
-    public void allowsLongListNullDeserialization() {
-        String jsonStr = "{ \"type\": \"[J\" , \"value\": null}";
-        PreparedParameter p = gson.fromJson(jsonStr, PreparedParameter.class);
-        assertEquals(long[].class, p.getType());
-        assertEquals(null, p.getValue());
-    }
-    
-    @Test
-    public void allowsDoubleListNullDeserialization() {
-        String jsonStr = "{ \"type\": \"[D\" , \"value\": null}";
-        PreparedParameter p = gson.fromJson(jsonStr, PreparedParameter.class);
-        assertEquals(double[].class, p.getType());
-        assertEquals(null, p.getValue());
-    }
-    
-    @Test
-    public void allowsStringNullDeserialization() {
-        String jsonStr = "{ \"type\": \"java.lang.String\" , \"value\": null}";
-        PreparedParameter p = gson.fromJson(jsonStr, PreparedParameter.class);
-        assertEquals(String.class, p.getType());
-        assertEquals(null, p.getValue());
-    }
-    
-    @Test
-    public void allowsStringListNullDeserialization() {
-        String jsonStr = "{ \"type\": \"[Ljava.lang.String;\" , \"value\": null}";
-        PreparedParameter p = gson.fromJson(jsonStr, PreparedParameter.class);
-        assertEquals(String[].class, p.getType());
-        assertEquals(null, p.getValue());
-    }
-    
-    @Test
-    public void rejectNullForBooleanPrimitive() {
-        String jsonStr = "{ \"type\": \"boolean\" , \"value\": null}";
-        doPrimitiveNullTest(jsonStr, "boolean");
-    }
-    
-    @Test
-    public void rejectNullForIntPrimitive() {
-        String jsonStr = "{ \"type\": \"int\" , \"value\": null}";
-        doPrimitiveNullTest(jsonStr, "int");
-    }
-    
-    @Test
-    public void rejectNullForDoublePrimitive() {
-        String jsonStr = "{ \"type\": \"double\" , \"value\": null}";
-        doPrimitiveNullTest(jsonStr, "double");
-    }
-    
-    @Test
-    public void rejectNullForLongPrimitive() {
-        String jsonStr = "{ \"type\": \"long\" , \"value\": null}";
-        doPrimitiveNullTest(jsonStr, "long");
-    }
-    
-    private void doPrimitiveNullTest(String jsonStr, String typeName) {
-        try {
-            gson.fromJson(jsonStr, PreparedParameter.class);
-            // The Java language spec does not permit this
-            fail(typeName + " null primitive should not deserialize");
-        } catch (Exception e) {
-            // pass
-            Throwable cause = e.getCause();
-            assertEquals(typeName + " primitive does not accept a null value!", cause.getMessage());
-        }
-    }
-    
-    @Test
-    public void failsDeserializationWrongTypeClass() {
-        String jsonStr = "{ \"type\": \"java.io.File\" , \"value\": true}";
-        try {
-            gson.fromJson(jsonStr, PreparedParameter.class);
-            fail("should have failed to serialize");
-        } catch (Exception e) {
-            // pass
-            Throwable cause = e.getCause();
-            assertTrue(cause.getMessage().contains("Illegal type of parameter"));
-        }
-    }
-    
-    @Test
-    public void failsDeserializationIfStringForInt() {
-        String jsonStr = "{ \"type\": \"int\" , \"value\":\"testing\"}";
-        try {
-            gson.fromJson(jsonStr, PreparedParameter.class);
-            fail("should have failed to serialize");
-        } catch (Exception e) {
-            // pass
-            Throwable cause = e.getCause();
-            assertTrue(cause instanceof NumberFormatException);
-        }
-    }
-    
-    @Test
-    public void failsDeserializationIfBooleanForInt() {
-        String jsonStr = "{ \"type\": \"int\" , \"value\": true}";
-        try {
-            gson.fromJson(jsonStr, PreparedParameter.class);
-            fail("should have failed to serialize");
-        } catch (Exception e) {
-            // pass
-            Throwable cause = e.getCause();
-            assertTrue(cause instanceof IllegalStateException);
-        }
-    }
-    
-    @Test
-    public void failsDeserializationIfIntForIntList() {
-        String jsonStr = "{ \"type\": \"[I\" , \"value\": -1}";
-        try {
-            gson.fromJson(jsonStr, PreparedParameter.class);
-            fail("should have failed to serialize");
-        } catch (Exception e) {
-            // pass
-            Throwable cause = e.getCause();
-            assertTrue(cause instanceof IllegalStateException);
-        }
-    }
-    
-    @Test
-    public void failsDeserializationIfDoubleForInt() {
-        String jsonStr = "{ \"type\": \"int\" , \"value\": -1.3}";
-        try {
-            gson.fromJson(jsonStr, PreparedParameter.class);
-            fail("should have failed to serialize");
-        } catch (Exception e) {
-            // pass
-            Throwable cause = e.getCause();
-            assertTrue(cause instanceof NumberFormatException);
-        }
-    }
-    
-    @Test
-    public void canSerializeBasic() {
-        // String
-        String expected = "{\"value\":\"testing\",\"type\":\"java.lang.String\"}";
-        PreparedParameter param = new PreparedParameter();
-        param.setType(String.class);
-        param.setValue("testing");
-        String actual = gson.toJson(param);
-        assertEquals(expected, actual);
-        // Integer
-        expected = "{\"value\":-1,\"type\":\"int\"}";
-        param.setType(int.class);
-        param.setValue(-1);
-        actual = gson.toJson(param);
-        assertEquals(expected, actual);
-        // Long
-        expected = "{\"value\":30000000003,\"type\":\"long\"}";
-        param.setType(long.class);
-        param.setValue(30000000003L);
-        actual = gson.toJson(param);
-        assertEquals(expected, actual);
-        // boolean
-        expected = "{\"value\":true,\"type\":\"boolean\"}";
-        param.setType(boolean.class);
-        param.setValue(true);
-        actual = gson.toJson(param);
-        assertEquals(expected, actual);
-        // String[]
-        String strArrayVal = "[\"testing1\",\"testing2\",\"3\"]";
-        expected = "{\"value\":" + strArrayVal + ",\"type\":\"[Ljava.lang.String;\"}";
-        param.setType(String[].class);
-        String[] array = new String[] {
-                "testing1", "testing2", "3"
-        };
-        param.setValue(array);
-        actual = gson.toJson(param);
-        assertEquals(expected, actual);
-    }
-    
-    @Test
-    public void canSerializeDeserializeInteger() {
-        PreparedParameter expected = new PreparedParameter();
-        expected.setType(int.class);
-        expected.setValue(3);
-        String jsonStr = gson.toJson(expected, PreparedParameter.class);
-        assertParameterEquals(expected, jsonStr);
-    }
-    
-    @Test
-    public void canSerializeDeserializeIntegerArray() {
-        PreparedParameter expected = new PreparedParameter();
-        expected.setType(int[].class);
-        expected.setValue(new int[] { 0, 3, 20 });
-        String jsonStr = gson.toJson(expected, PreparedParameter.class);
-        assertParameterEquals(expected, jsonStr);
-    }
-    
-    @Test
-    public void canSerializeDeserializeDouble() {
-        PreparedParameter expected = new PreparedParameter();
-        expected.setType(double.class);
-        expected.setValue(Math.E);
-        String jsonStr = gson.toJson(expected, PreparedParameter.class);
-        assertParameterEquals(expected, jsonStr);
-    }
-    
-    @Test
-    public void canSerializeDeserializeDoubleArray() {
-        PreparedParameter expected = new PreparedParameter();
-        expected.setType(double[].class);
-        expected.setValue(new double[] { 3.3, 1.0, Math.PI });
-        String jsonStr = gson.toJson(expected, PreparedParameter.class);
-        assertParameterEquals(expected, jsonStr);
-    }
-    
-    @Test
-    public void canSerializeDeserializeLong() {
-        PreparedParameter expected = new PreparedParameter();
-        expected.setType(long.class);
-        expected.setValue(30000000003L);
-        String jsonStr = gson.toJson(expected, PreparedParameter.class);
-        assertParameterEquals(expected, jsonStr);
-    }
-    
-    @Test
-    public void canSerializeDeserializeLongArray() {
-        PreparedParameter expected = new PreparedParameter();
-        expected.setType(long[].class);
-        expected.setValue(new long[] { 3000000000L, 3, 20 });
-        String jsonStr = gson.toJson(expected, PreparedParameter.class);
-        assertParameterEquals(expected, jsonStr);
-    }
-    
-    @Test
-    public void canSerializeDeserializeString() {
-        PreparedParameter expected = new PreparedParameter();
-        expected.setType(String.class);
-        expected.setValue("testing");
-        String jsonStr = gson.toJson(expected, PreparedParameter.class);
-        assertParameterEquals(expected, jsonStr);
-    }
-    
-    @Test
-    public void canSerializeDeserializeStringArray() {
-        PreparedParameter expected = new PreparedParameter();
-        expected.setType(String[].class);
-        String[] expectedArray = new String[] {
-                "one", "two", "three"      
-        };
-        expected.setValue(expectedArray);
-        String jsonStr = gson.toJson(expected, PreparedParameter.class);
-        assertParameterEquals(expected, jsonStr);
-    }
-    
-    @Test
-    public void canSerializeDeserializeBoolean() {
-        PreparedParameter expected = new PreparedParameter();
-        expected.setType(boolean.class);
-        expected.setValue(false);
-        String jsonStr = gson.toJson(expected, PreparedParameter.class);
-        assertParameterEquals(expected, jsonStr);
-        
-        expected = new PreparedParameter();
-        expected.setType(boolean.class);
-        expected.setValue(true);
-        jsonStr = gson.toJson(expected, PreparedParameter.class);
-        assertParameterEquals(expected, jsonStr);
-    }
-    
-    @Test
-    public void canSerializeDeserializeBooleanArray() {
-        PreparedParameter expected = new PreparedParameter();
-        expected.setType(boolean[].class);
-        expected.setValue(new boolean[] { true, false, false, true });
-        String jsonStr = gson.toJson(expected, PreparedParameter.class);
-        assertParameterEquals(expected, jsonStr);
-    }
-    
-    
-    @Test
-    public void canSerializeDeserializePojos() {
-        PreparedParameter expected = new PreparedParameter();
-        AgentInformation info = new AgentInformation("foo-writer");
-        expected.setType(info.getClass());
-        expected.setValue(info);
-        String jsonStr = gson.toJson(expected, PreparedParameter.class);
-        assertParameterEquals(expected, jsonStr);
-        
-        info = new AgentInformation("some-writer");
-        info.setAlive(true);
-        info.setConfigListenAddress("127.0.0.1:12000");
-        info.setStartTime(System.currentTimeMillis());
-        info.setStopTime(System.currentTimeMillis());
-        expected = new PreparedParameter();
-        expected.setType(info.getClass());
-        expected.setValue(info);
-        jsonStr = gson.toJson(expected, PreparedParameter.class);
-        assertParameterEquals(expected, jsonStr);
-        
-        // null pojo
-        expected = new PreparedParameter();
-        expected.setType(AgentInformation.class);
-        expected.setValue(null);
-        jsonStr = gson.toJson(expected, PreparedParameter.class);
-        assertParameterEquals(expected, jsonStr);
-    }
-    
-    @Test
-    public void canSerializeDeserializeInnerClassPojoTypes() {
-        PreparedParameter expected = new PreparedParameter();
-        KeyValuePair pair = new KeyValuePair();
-        pair.setKey("foo");
-        pair.setValue("bar");
-        expected.setType(pair.getClass());
-        expected.setValue(pair);
-        String jsonStr = gson.toJson(expected, PreparedParameter.class);
-        
-        PreparedParameter actual = gson.fromJson(jsonStr, PreparedParameter.class);
-        
-        assertEquals(expected.getType(), actual.getType());
-        assertTrue(actual.getValue() instanceof KeyValuePair);
-        KeyValuePair actualPair = (KeyValuePair)actual.getValue();
-        assertEquals(pair.getKey(), actualPair.getKey());
-        assertEquals(pair.getValue(), actualPair.getValue());
-    }
-    
-    @Test
-    public void canSerializeDeserializePojoLists() {
-        AgentInformation info1 = new AgentInformation("foo-writer");
-        AgentInformation info2 = new AgentInformation("some-writer");
-        info2.setAlive(true);
-        info2.setConfigListenAddress("127.0.0.1:12000");
-        info2.setStartTime(System.currentTimeMillis());
-        info2.setStopTime(System.currentTimeMillis());
-        AgentInformation[] infos = new AgentInformation[] {
-                info1, info2
-        };
-        PreparedParameter param = new PreparedParameter();
-        param.setType(AgentInformation.class);
-        param.setValue(infos);
-        String jsonStr = gson.toJson(param, PreparedParameter.class);
-        assertParameterEquals(param, jsonStr);
-    }
-    
-    private void assertParameterEquals(PreparedParameter expected,
-            String jsonStr) {
-        PreparedParameter actual = gson.fromJson(jsonStr, PreparedParameter.class);
-        assertEquals(expected.getType(), actual.getType());
-        if (actual.getValue() != null && actual.getValue().getClass().isArray()) {
-            // compare element by element
-            Object values = actual.getValue();
-            Object expectedVals = expected.getValue();
-            int expectedLength = Array.getLength(expectedVals);
-            int actualLength = Array.getLength(values);
-            assertEquals(expectedLength, actualLength);
-            // Make sure the deserialized array is of the correct expected type
-            assertEquals(expectedVals.getClass(), values.getClass());
-            for (int i = 0; i < expectedLength; i++) {
-                Object exp = Array.get(expectedVals, i);
-                Object act = Array.get(values, i);
-                assertEquals(exp, act);
-            }
-        } else {
-            assertEquals(expected.getValue(), actual.getValue());
-        }
-    }
-}
-
--- a/web/common/src/test/java/com/redhat/thermostat/web/common/PreparedParametersSerializerTest.java	Tue Sep 30 11:57:25 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,144 +0,0 @@
-/*
- * Copyright 2012-2014 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.web.common;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import com.redhat.thermostat.storage.core.PreparedParameter;
-import com.redhat.thermostat.storage.core.PreparedParameters;
-import com.redhat.thermostat.storage.model.AgentInformation;
-import com.redhat.thermostat.storage.model.Pojo;
-
-public class PreparedParametersSerializerTest {
-
-    private Gson gson;
-    
-    @Before
-    public void setup() {
-        gson = new GsonBuilder()
-                .registerTypeHierarchyAdapter(Pojo.class,
-                        new ThermostatGSONConverter())
-                .registerTypeAdapter(
-                        PreparedParameter.class,
-                        new PreparedParameterSerializer()).create();
-    }
-    
-    @Test
-    public void canSerializeDeserializeBasic() {
-        PreparedParameters params = new PreparedParameters(5);
-        params.setBoolean(0, true);
-        params.setInt(1, 2300);
-        params.setLong(2, 2200000000L);
-        params.setString(3, "testing");
-        String[] list = new String[] {
-          "a", "b", "c"      
-        };
-        params.setStringList(4, list);
-        
-        String jsonStr = gson.toJson(params, PreparedParameters.class);
-        PreparedParameters actualParams = gson.fromJson(jsonStr, PreparedParameters.class);
-        
-        PreparedParameter[] expected = params.getParams();
-        PreparedParameter[] actual = actualParams.getParams();
-        
-        // last element is the string array, which we check manually
-        for (int i = 0; i < expected.length - 1; i++) {
-            assertEquals(expected[i].getType(), actual[i].getType());
-            assertEquals(expected[i].getValue(), actual[i].getValue());
-        }
-        String actualList[] = (String[])actual[4].getValue();
-        for (int i = 0; i < list.length; i++) {
-            assertEquals(list[i], actualList[i]);
-        }
-    }
-    
-    @Test
-    public void canSerializeDeserializeMixedTypesWithPojoList() {
-        AgentInformation info1 = new AgentInformation("foo-agent");
-        info1.setAlive(true);
-        AgentInformation info2 = new AgentInformation("foo-agent");
-        info2.setAlive(false);
-        info2.setStartTime(System.currentTimeMillis());
-        info2.setStopTime(System.currentTimeMillis());
-        info2.setConfigListenAddress("127.0.0.1:12000");
-        AgentInformation[] infos = new AgentInformation[] {
-                info1, info2
-        };
-        long[] longs = new long[] { 3000000000L, -3, 300 };
-        // String, long[], Pojo[]
-        PreparedParameters params = new PreparedParameters(3);
-        params.setString(0, "foo-param");
-        params.setLongList(1, longs);
-        params.setPojoList(2, infos);
-        
-        String jsonStr = gson.toJson(params, PreparedParameters.class);
-        PreparedParameters actualParams = gson.fromJson(jsonStr, PreparedParameters.class);
-        
-        PreparedParameter[] expected = params.getParams();
-        PreparedParameter[] actual = actualParams.getParams();
-        
-        assertEquals(expected.length, actual.length);
-        
-        PreparedParameter param1 = actual[0];
-        assertEquals("foo-param", param1.getValue());
-        assertEquals(String.class, param1.getType());
-        
-        PreparedParameter param2 = actual[1];
-        assertEquals(long[].class, param2.getType());
-        long[] twoActuals = (long[])param2.getValue();
-        assertEquals(3, twoActuals.length);
-        assertEquals(3000000000L, (long)twoActuals[0]);
-        assertEquals(-3, (long)twoActuals[1]);
-        assertEquals(300, (long)twoActuals[2]);
-        
-        PreparedParameter param3 = actual[2];
-        assertEquals(AgentInformation.class, param3.getType());
-        assertTrue(param3.getValue().getClass().isArray());
-        Pojo[] pojos = (Pojo[])param3.getValue();
-        assertEquals(2, pojos.length);
-        for (int i = 0; i < pojos.length; i++) {
-            assertEquals(infos[i], pojos[i]);
-        }
-    }
-}
-
--- a/web/common/src/test/java/com/redhat/thermostat/web/common/ThermostatGSONConverterTest.java	Tue Sep 30 11:57:25 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,103 +0,0 @@
-/*
- * Copyright 2012-2014 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.web.common;
-
-import static org.junit.Assert.assertEquals;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import com.redhat.thermostat.storage.model.AgentInformation;
-import com.redhat.thermostat.storage.model.AggregateCount;
-import com.redhat.thermostat.storage.model.Pojo;
-
-public class ThermostatGSONConverterTest {
-
- private Gson gson;
-    
-    @Before
-    public void setup() {
-        gson = new GsonBuilder()
-                .registerTypeAdapter(Pojo.class, new ThermostatGSONConverter())
-                .create();
-    }
-    
-    @Test
-    public void canSerializeDeserializeBasic() {
-        // Our test pojo
-        AgentInformation agentInfo = new AgentInformation("testing");
-        agentInfo.setAlive(true);
-        
-        String jsonStr = gson.toJson(agentInfo);
-        
-        AgentInformation actual = gson.fromJson(jsonStr, AgentInformation.class);
-        
-        assertEquals("testing", actual.getAgentId());
-        assertEquals(true, actual.isAlive());
-    }
-    
-    @Test
-    public void canSerializeDeserializeArray() {
-        // Our test pojo
-        AgentInformation agentInfo = new AgentInformation("testing");
-        agentInfo.setAlive(true);
-        AgentInformation[] agentInfos = new AgentInformation[] {
-                agentInfo
-        };
-        
-        String jsonStr = gson.toJson(agentInfos);
-        
-        AgentInformation[] actual = gson.fromJson(jsonStr, AgentInformation[].class);
-        
-        assertEquals("testing", actual[0].getAgentId());
-        assertEquals(true, actual[0].isAlive());
-    }
-    
-    @Test
-    public void canSerializeDeserializeAggregateCount() {
-        long expectedCount = 3333000333L;
-        AggregateCount count = new AggregateCount();
-        count.setCount(expectedCount);
-        String jsonStr = gson.toJson(count);
-        // now do the reverse
-        AggregateCount c2 = gson.fromJson(jsonStr, AggregateCount.class);
-        assertEquals(expectedCount, c2.getCount());
-    }
-}
-
--- a/web/common/src/test/java/com/redhat/thermostat/web/common/WebPreparedStatementResponseSerializerTest.java	Tue Sep 30 11:57:25 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,72 +0,0 @@
-/*
- * Copyright 2012-2014 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.web.common;
-
-import static org.junit.Assert.assertEquals;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-
-public class WebPreparedStatementResponseSerializerTest {
-
-    private Gson gson;
-    
-    @Before
-    public void setup() {
-        gson = new GsonBuilder().create();
-    }
-    
-    @Test
-    public void testSerializationDeserializationBasic() {
-        WebPreparedStatementResponse response = new WebPreparedStatementResponse();
-        response.setNumFreeVariables(6);
-        response.setStatementId(WebPreparedStatementResponse.ILLEGAL_STATEMENT);
-        
-        String jsonStr = gson.toJson(response, WebPreparedStatementResponse.class);
-        String expectedString = "{\"numFreeVariables\":6,\"statementId\":-1}";
-        assertEquals(expectedString, jsonStr);
-        
-        WebPreparedStatementResponse actual = gson.fromJson(jsonStr, WebPreparedStatementResponse.class);
-        
-        assertEquals(6, actual.getNumFreeVariables());
-        assertEquals(WebPreparedStatementResponse.ILLEGAL_STATEMENT, actual.getStatementId());
-    }
-}
-
--- a/web/common/src/test/java/com/redhat/thermostat/web/common/WebPreparedStatementSerializerTest.java	Tue Sep 30 11:57:25 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,126 +0,0 @@
-/*
- * Copyright 2012-2014 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.web.common;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import com.redhat.thermostat.storage.core.PreparedParameter;
-import com.redhat.thermostat.storage.core.PreparedParameters;
-import com.redhat.thermostat.storage.model.AgentInformation;
-import com.redhat.thermostat.storage.model.Pojo;
-
-public class WebPreparedStatementSerializerTest {
-
-    private Gson gson;
-    
-    @Before
-    public void setup() {
-        gson = new GsonBuilder()
-                .registerTypeAdapter(WebPreparedStatement.class,
-                        new WebPreparedStatementSerializer())
-                .registerTypeHierarchyAdapter(Pojo.class, new ThermostatGSONConverter())
-                .registerTypeAdapter(PreparedParameter.class,
-                        new PreparedParameterSerializer()).create();
-    }
-    
-    @Test
-    public void canSerializeAndDeserialize() {
-        PreparedParameters params = new PreparedParameters(5);
-        params.setInt(0, 2);
-        params.setString(1, "testing");
-        params.setLong(2, 222L);
-        params.setStringList(3, new String[] { "one", "two" });
-        params.setBoolean(4, true);
-        WebPreparedStatement<?> stmt = new WebPreparedStatement<>();
-        stmt.setParams(params);
-        stmt.setStatementId(WebPreparedStatementResponse.DESCRIPTOR_PARSE_FAILED);
-        String jsonString = gson.toJson(stmt, WebPreparedStatement.class);
-        WebPreparedStatement<?> newStmt = gson.fromJson(jsonString, WebPreparedStatement.class);
-        assertNotNull(newStmt);
-        PreparedParameters newParams = newStmt.getParams();
-        PreparedParameter[] parameters = newParams.getParams();
-        assertEquals(5, parameters.length);
-        assertEquals(2, parameters[0].getValue());
-        assertEquals(int.class, parameters[0].getType());
-        assertEquals("testing", parameters[1].getValue());
-        assertEquals(String.class, parameters[1].getType());
-        assertEquals(222L, parameters[2].getValue());
-        assertEquals(long.class, parameters[2].getType());
-        String[] list = (String[])parameters[3].getValue();
-        assertEquals(2, list.length);
-        assertEquals("one", list[0]);
-        assertEquals("two", list[1]);
-        assertEquals(String[].class, parameters[3].getType());
-        assertEquals(true, parameters[4].getValue());
-        assertEquals(boolean.class, parameters[4].getType());
-        assertEquals(WebPreparedStatementResponse.DESCRIPTOR_PARSE_FAILED, newStmt.getStatementId());
-    }
-    
-    /*
-     * Writes need Pojo support for serialization. This is a basic test we do
-     * get Pojos across the wire in a prepared context. 
-     */
-    @Test
-    public void canSerializeDeserializePojoParameters() {
-        PreparedParameters params = new PreparedParameters(2);
-        params.setIntList(0, new int[] { 0, 300 });
-        AgentInformation pojo1 = new AgentInformation("foo-agent");
-        AgentInformation pojo2 = new AgentInformation("foo-agent");
-        pojo2.setAlive(true);
-        pojo2.setConfigListenAddress("127.0.0.1:38822");
-        params.setPojoList(1, new AgentInformation[] { pojo1, pojo2 });
-        
-        WebPreparedStatement<?> stmt = new WebPreparedStatement<>();
-        stmt.setParams(params);
-        stmt.setStatementId(WebPreparedStatementResponse.DESCRIPTOR_PARSE_FAILED);
-        
-        String jsonString = gson.toJson(stmt, WebPreparedStatement.class);
-        assertNotNull(jsonString);
-        
-        WebPreparedStatement<?> result = gson.fromJson(jsonString, WebPreparedStatement.class);
-        assertEquals(WebPreparedStatementResponse.DESCRIPTOR_PARSE_FAILED, result.getStatementId());
-        assertNotNull(result.getParams());
-        
-    }
-}
-
--- a/web/common/src/test/java/com/redhat/thermostat/web/common/WebQueryResponseSerializerTest.java	Tue Sep 30 11:57:25 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,187 +0,0 @@
-/*
- * Copyright 2012-2014 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.web.common;
-
-import static org.junit.Assert.assertEquals;
-
-import java.lang.reflect.Type;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import com.google.gson.reflect.TypeToken;
-import com.redhat.thermostat.storage.model.AgentInformation;
-import com.redhat.thermostat.storage.model.HostInfo;
-import com.redhat.thermostat.storage.model.Pojo;
-
-public class WebQueryResponseSerializerTest {
-
-    private Gson gson;
-    
-    @SuppressWarnings("rawtypes")
-    @Before
-    public void setup() {
-        gson = new GsonBuilder()
-                .registerTypeAdapter(Pojo.class, new ThermostatGSONConverter())
-                .registerTypeAdapter(WebQueryResponse.class,
-                        new WebQueryResponseSerializer()).create();
-    }
-    
-    @Test
-    public void canSerializeBasic() {
-        // Our test pojo
-        AgentInformation agentInfo = new AgentInformation("testing");
-        agentInfo.setAlive(false);
-        AgentInformation[] resultList = new AgentInformation[] {
-                agentInfo
-        };
-        
-        // create the query response
-        WebQueryResponse<AgentInformation> response = new WebQueryResponse<>();
-        response.setResultList(resultList);
-        response.setResponseCode(PreparedStatementResponseCode.ILLEGAL_PATCH);
-        
-        String jsonStr = gson.toJson(response);
-        String expectedJson = "{\"payload\":[{\"startTime\":0,\"stopTime\":0,\"alive\":false,\"agentId\":\"testing\"}],\"errno\":-1}";
-        assertEquals(expectedJson, jsonStr);
-    }
-    
-    @Test
-    public void canDeserializeBasic() {
-        String rawJson = "{\"payload\":[{\"startTime\":0,\"stopTime\":0,\"alive\":true,\"agentId\":\"testing\"}],\"errno\":-1}";
-        Type queryResponseType = new TypeToken<WebQueryResponse<AgentInformation>>() {}.getType();
-        WebQueryResponse<AgentInformation> actual = gson.fromJson(rawJson, queryResponseType);
-        
-        AgentInformation[] actualList = actual.getResultList();
-        
-        assertEquals(PreparedStatementResponseCode.ILLEGAL_PATCH, actual.getResponseCode());
-        assertEquals(1, actualList.length);
-        AgentInformation actualInfo = actualList[0];
-        assertEquals(true, actualInfo.isAlive());
-        assertEquals("testing", actualInfo.getAgentId());
-    }
-    
-    @Test
-    public void canSerializeAndDeserializeBasic() {
-        // Our test pojo
-        AgentInformation agentInfo = new AgentInformation("testing");
-        agentInfo.setAlive(false);
-        AgentInformation[] resultList = new AgentInformation[] {
-                agentInfo
-        };
-        
-        // create the query response
-        WebQueryResponse<AgentInformation> response = new WebQueryResponse<>();
-        response.setResultList(resultList);
-        response.setResponseCode(PreparedStatementResponseCode.ILLEGAL_PATCH);
-        
-        String jsonStr = gson.toJson(response);
-
-        // We need to tell GSON which parametrized type we want it to deserialize
-        // it to.
-        Type queryResponseType = new TypeToken<WebQueryResponse<AgentInformation>>() {}.getType();
-        WebQueryResponse<AgentInformation> actual = gson.fromJson(jsonStr, queryResponseType);
-        
-        AgentInformation[] actualList = actual.getResultList();
-        
-        assertEquals(PreparedStatementResponseCode.ILLEGAL_PATCH, actual.getResponseCode());
-        assertEquals(1, actualList.length);
-        AgentInformation actualInfo = actualList[0];
-        assertEquals(false, actualInfo.isAlive());
-        assertEquals("testing", actualInfo.getAgentId());
-    }
-    
-    @Test
-    public void canSerializeAndDeserializeVariousPojos() {
-        // Our test pojo
-        AgentInformation agentInfo = new AgentInformation("testing");
-        agentInfo.setAlive(false);
-        AgentInformation[] resultList = new AgentInformation[] {
-                agentInfo
-        };
-        
-        // create the query response
-        WebQueryResponse<AgentInformation> response = new WebQueryResponse<>();
-        response.setResultList(resultList);
-        response.setResponseCode(PreparedStatementResponseCode.ILLEGAL_PATCH);
-        
-        String jsonStr = gson.toJson(response);
-        String expectedJson = "{\"payload\":[{\"startTime\":0,\"stopTime\":0,\"alive\":false,\"agentId\":\"testing\"}],\"errno\":-1}";
-        assertEquals(expectedJson, jsonStr);
-
-        // We need to tell GSON which parametrized type we want it to deserialize
-        // it to.
-        Type queryResponseType = new TypeToken<WebQueryResponse<AgentInformation>>() {}.getType();
-        WebQueryResponse<AgentInformation> actual = gson.fromJson(jsonStr, queryResponseType);
-        
-        AgentInformation[] actualList = actual.getResultList();
-        
-        assertEquals(PreparedStatementResponseCode.ILLEGAL_PATCH, actual.getResponseCode());
-        assertEquals(1, actualList.length);
-        AgentInformation actualInfo = actualList[0];
-        assertEquals(false, actualInfo.isAlive());
-        assertEquals("testing", actualInfo.getAgentId());
-        
-        // Do it again using HostInfo as model
-        HostInfo hostInfo = new HostInfo();
-        hostInfo.setAgentId("something");
-        hostInfo.setCpuCount(56);
-        hostInfo.setHostname("flukebox");
-        
-        HostInfo[] hostInfoResults = new HostInfo[] {
-                hostInfo
-        };
-        
-        WebQueryResponse<HostInfo> expected = new WebQueryResponse<>();
-        expected.setResultList(hostInfoResults);
-        expected.setResponseCode(PreparedStatementResponseCode.QUERY_SUCCESS);
-        
-        jsonStr = gson.toJson(expected);
-        Type hostinfoQueryResponseType = new TypeToken<WebQueryResponse<HostInfo>>() {}.getType();
-        WebQueryResponse<HostInfo> actualResp = gson.fromJson(jsonStr, hostinfoQueryResponseType);
-        
-        assertEquals(PreparedStatementResponseCode.QUERY_SUCCESS, actualResp.getResponseCode());
-        HostInfo[] hostInfoList = actualResp.getResultList();
-        assertEquals(1, hostInfoList.length);
-        assertEquals("something", hostInfoList[0].getAgentId());
-        assertEquals(56, hostInfoList[0].getCpuCount());
-        assertEquals("flukebox", hostInfoList[0].getHostname());
-    }
-}
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/common/src/test/java/com/redhat/thermostat/web/common/typeadapters/JsonPerformanceTest.java	Wed Sep 17 11:36:58 2014 +0200
@@ -0,0 +1,346 @@
+/*
+ * Copyright 2012-2014 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.web.common.typeadapters;
+
+import static org.junit.Assert.assertTrue;
+
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Test;
+
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+
+/**
+ * 
+ * A generic GSON-based performance test for JSON serialization/deserialization.
+ *
+ * @param <T> The type which should get serialized/deserialized.
+ * 
+ * @see PreparedParameterJSONPerformanceTest
+ */
+public abstract class JsonPerformanceTest<T> {
+    
+    public enum OperationType {
+        DESERIALIZATION,
+        SERIALIZATION
+    }
+    
+    public enum GsonContext {
+        SLOW,
+        FASTER
+    }
+    
+    private static final String EXPECTATION_FORMAT = " (>=%s)";
+    private static final int ITERATIONS_FOR_CUMMULATIVE_AVG = 10;
+    
+    private final boolean debug;
+    private final String testClass;
+    
+    protected JsonPerformanceTest(boolean debug, String testClass) {
+        this.debug = debug;
+        this.testClass = testClass;
+    }
+    
+    /**
+     * 
+     * @return The base Gson to measure performance against
+     */
+    protected abstract Gson getSlowGson();
+
+    /**
+     * 
+     * @return The "improved" Gson for which the improvement over the Gson as returned by {{@link #getSlowGson()}
+     *         should be measured.
+     */
+    protected abstract Gson getFasterGson();
+    
+    /**
+     * The expected speedup used in asserts for the given iterations.
+     * 
+     * @param type The type of operation: serialization or deserialization.
+     * @param iterations
+     * @return The expected speed-up.
+     */
+    protected abstract double getExpectedSpeedup(OperationType type, int iterations);
+    
+    /**
+     * Formats the string to a valid json string by using
+     * {@code mutator}.
+     * 
+     * @param context The Gson context for this mutation.
+     * @param mutator A unique number for a performance run.
+     * 
+     * @return A valid JSON string representing an instance of type T.
+     */
+    protected abstract String mutateJsonString(GsonContext context, int mutator);
+    
+    /**
+     * Creates a mutated instance of T using mutator
+     * {@code mutator}.
+     * 
+     * @param mutator      A unique number for this performance test run.
+     * 
+     * @return The mutated instance.
+     */
+    protected abstract T mutateToBeSerializedInstance(int mutator);
+    
+    /**
+     * 
+     * @return The {@link TypeToken} for type T so that it can be properly
+     *         deserialized.
+     */
+    protected abstract TypeToken<T> getDeserializeTypeToken();
+    
+    /**
+     * 
+     * @return The number of iterations to run for the cold (not warmed-up)
+     *         serialization performance test.
+     */
+    protected abstract int getColdSerializationIterations();
+    
+    /**
+     * 
+     * @return The number of iterations to run for the "warmed-up" serialization
+     *         performance test.
+     */
+    protected abstract int getWarmSerializationIterations();
+    
+    /**
+     * 
+     * @return The number of iterations to run for the cold (not warmed-up)
+     *         deserialization performance test.
+     */
+    protected abstract int getColdDeserializationIterations();
+    
+    /**
+     * 
+     * @return The number of iterations to run for the "warmed-up" deserialization
+     *         performance test.
+     */
+    protected abstract int getWarmDeserializationIterations();
+    
+    /**
+     * 
+     * @return A delta to increase the "faster" gson serialization speed-up so
+     *         that basic assertions such as {@code speed-up >= 1} work. This is
+     *         useful for the base-case of comparing a gson instance to itself.
+     *         Two performance runs of the same gson are expected to be
+     *         approximately equally fast. Though, it will depend on the system.
+     */
+    protected abstract double getSelfSerializationDelta();
+    
+    /**
+     * 
+     * @return A delta to increase the "faster" gson deserialization speed-up so
+     *         that basic assertions such as {@code speed-up >= 1} work. This is
+     *         useful for the base-case of comparing a gson instance to itself.
+     *         Two performance runs of the same gson are expected to be
+     *         approximately equally fast. Though, it will depend on the system.
+     */
+    protected abstract double getSelfDeserializationDelta();
+    
+    @Test
+    public void verifySerializationSpeedCold() {
+        int iterations = getColdSerializationIterations();
+        double actualSpeedup = getAverageSerializationSpeedup(iterations);
+        final double expectedSpeedup = getExpectedSpeedup(OperationType.SERIALIZATION, iterations);
+        if (debug) {
+            System.out.println(testClass + ": actual cold serialization speed-up: " +
+                    formatSpeedup(actualSpeedup) + String.format(EXPECTATION_FORMAT, formatSpeedup(expectedSpeedup)));
+        }
+        assertTrue("Performance regression? Expected a speed-up of > " + expectedSpeedup +". Speed-up was: " + actualSpeedup, actualSpeedup > expectedSpeedup);
+    }
+    
+    @Test
+    public void verifySerializationSpeedWarm() {
+        int iterations = getWarmSerializationIterations();
+        double actualSpeedup = getAverageSerializationSpeedup(iterations);
+        final double expectedSpeedup = getExpectedSpeedup(OperationType.SERIALIZATION, iterations);
+        if (debug) {
+            System.out.println(testClass + ": actual warmed-up serialization speed-up: " + 
+                    formatSpeedup(actualSpeedup) + String.format(EXPECTATION_FORMAT, formatSpeedup(expectedSpeedup)));
+        }
+        assertTrue("Performance regression? Expected a speed-up of > " + expectedSpeedup +". Speed-up was: " + actualSpeedup, actualSpeedup > expectedSpeedup);
+    }
+    
+    @Test
+    public void verifyDeserializationSpeedCold() {
+        int iterations = getColdDeserializationIterations();
+        double actualSpeedup = getAverageDeserializationSpeedup(iterations);
+        final double expectedSpeedup = getExpectedSpeedup(OperationType.DESERIALIZATION, iterations);
+        if (debug) {
+            System.out.println(testClass + ": actual cold deserialization speed-up: " +
+                formatSpeedup(actualSpeedup) + String.format(EXPECTATION_FORMAT, formatSpeedup(expectedSpeedup)));
+        }
+        assertTrue("Performance regression? Expected a speed-up of > " + expectedSpeedup +". Speed-up was: " + actualSpeedup, actualSpeedup > expectedSpeedup);
+    }
+    
+    @Test
+    public void verifyDeserializationSpeedWarm() {
+        int iterations = getWarmDeserializationIterations();
+        double actualSpeedup = getAverageDeserializationSpeedup(iterations);
+        final double expectedSpeedup = getExpectedSpeedup(OperationType.DESERIALIZATION, iterations);
+        if (debug) {
+            System.out.println(testClass + ": actual warmed-up deserialization speed-up: " +
+                    formatSpeedup(actualSpeedup) + String.format(EXPECTATION_FORMAT, formatSpeedup(expectedSpeedup)));
+        }
+        assertTrue("Performance regression? Expected a speed-up of > " + expectedSpeedup +". Speed-up was: " + actualSpeedup, actualSpeedup > expectedSpeedup);
+    }
+    
+    private double getAverageSerializationSpeedup(final int iterations) {
+        double sum = 0;
+        for (int i = 0; i < ITERATIONS_FOR_CUMMULATIVE_AVG; i++) {
+            sum += measureSerializationSpeed(iterations);
+        }
+        double speedup = sum/ITERATIONS_FOR_CUMMULATIVE_AVG; 
+        double delta = getSelfSerializationDelta();
+        assertTrue("Performance Regression? Expected a speed-up of >= 1, but was: " + (speedup + delta),
+                        (speedup + delta) >= 1);
+        return speedup;
+    }
+    
+    private double measureSerializationSpeed(final int iterations) {
+        final Gson oldGson = getSlowGson();
+        final Gson newGson = getFasterGson();
+        PerfTestResult result = runSerializationPerformanceTest(iterations, oldGson, newGson);
+        return result.getSpeedup();
+    }
+    
+    private double getAverageDeserializationSpeedup(final int iterations) {
+        double sum = 0;
+        for (int i = 0; i < ITERATIONS_FOR_CUMMULATIVE_AVG; i++) {
+            sum += measureDeserializationSpeed(iterations);
+        }
+        double speedup = sum/ITERATIONS_FOR_CUMMULATIVE_AVG; 
+        double delta = getSelfDeserializationDelta();
+        assertTrue("Performance Regression? Expected a speed-up of >= 1, but was: " + (speedup + delta),
+                        (speedup + delta) >= 1);
+        return speedup;
+    }
+    
+    private double measureDeserializationSpeed(final int iterations) {
+        final Gson oldGson = getSlowGson();
+        final Gson newGson = getFasterGson();
+        PerfTestResult result = runDeserializationPerformanceTest(iterations, oldGson, newGson);
+        return result.getSpeedup();
+    }
+    
+    private PerfTestResult runSerializationPerformanceTest(final int iterations, final Gson oldGson, final Gson newGson) {
+        List<String> list = new ArrayList<>();
+        double oldSum = 0;
+        double newSum = 0;
+        long start = -1;
+        long end = -1;
+        Gson gson = null;
+        for (int i = 0; i < iterations * 2; i++) {
+            T instance = mutateToBeSerializedInstance(i);
+            if (i % 2 != 0) {
+                gson = newGson;
+            } else {
+                gson = oldGson;
+            }
+            start = System.nanoTime();
+            String json = gson.toJson(instance);
+            end = System.nanoTime();
+            if (i % 2 != 0) {
+                newSum += (end - start);
+            } else {
+                oldSum += (end - start);
+            }
+            // Do something silly just so that the JIT does not optimize-out the
+            // toJson() call.
+            list.add(json);
+        }
+        PerfTestResult res = new PerfTestResult();
+        res.oldPerf = oldSum/iterations;
+        res.newPerf = newSum/iterations;
+        return res;
+    }
+    
+    private PerfTestResult runDeserializationPerformanceTest(final int iterations, final Gson oldGson, final Gson newGson) {
+        List<T> list = new ArrayList<>();
+
+        double oldSum = 0;
+        double newSum = 0;
+        long start = -1;
+        long end = -1;
+        Gson gson = null;
+        String json;
+        for (int i = 0; i < iterations * 2; i++) {
+            if (i % 2 != 0) {
+                json = mutateJsonString(GsonContext.FASTER, i);
+                gson = newGson;
+            } else {
+                json = mutateJsonString(GsonContext.SLOW, i);
+                gson = oldGson;
+            }
+            start = System.nanoTime();
+            T instance = gson.fromJson(json, getDeserializeTypeToken().getType());
+            end = System.nanoTime();
+            if (i % 2 != 0) {
+                newSum += (end - start);
+            } else {
+                oldSum += (end - start);
+            }
+            // Do something silly just so that the JIT does not optimize-out the
+            // fromJson() call.
+            list.add(instance);
+        }
+        PerfTestResult res = new PerfTestResult();
+        res.oldPerf = oldSum/iterations;
+        res.newPerf = newSum/iterations;
+        return res;
+    }
+    
+    private String formatSpeedup(double value) {
+        DecimalFormat format = new DecimalFormat("#.##");
+        return format.format(value);
+    }
+    
+    private static class PerfTestResult {
+        private double oldPerf;
+        private double newPerf;
+        
+        private double getSpeedup() {
+            return oldPerf/newPerf;
+        }
+    }
+    
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/common/src/test/java/com/redhat/thermostat/web/common/typeadapters/LegacyGSONConverter.java	Wed Sep 17 11:36:58 2014 +0200
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2012-2014 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.web.common.typeadapters;
+
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+
+import org.apache.commons.beanutils.PropertyUtils;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+import com.redhat.thermostat.storage.core.Entity;
+import com.redhat.thermostat.storage.core.Persist;
+import com.redhat.thermostat.storage.model.Pojo;
+
+/**
+ * Old non-stream GSON API serializer. Used only in performance tests. 
+ */
+public class LegacyGSONConverter implements JsonSerializer<Pojo>, JsonDeserializer<Pojo> {
+
+    @Override
+    public Pojo deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
+        @SuppressWarnings("unchecked")
+        Class<? extends Pojo> targetType = (Class<Pojo>) typeOfT;
+        try {
+            Pojo pojo = targetType.newInstance();
+            PropertyDescriptor[] descs = PropertyUtils.getPropertyDescriptors(pojo);
+            for (PropertyDescriptor desc : descs) {
+                Method writeMethod = desc.getWriteMethod();
+                if (writeMethod != null && writeMethod.isAnnotationPresent(Persist.class)) {
+                    String name = desc.getName();
+                    JsonElement child = json.getAsJsonObject().get(name);
+                    Object value = context.deserialize(child, desc.getPropertyType());
+                    PropertyUtils.setProperty(pojo, name, value);
+                }
+            }
+            return pojo;
+        } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public JsonElement serialize(Pojo src, Type typeOfSrc, JsonSerializationContext context) {
+        Class<?> cls = (Class<?>) typeOfSrc;
+        if (! cls.isAnnotationPresent(Entity.class)) {
+            System.err.println("attempt to serialize non-Entity class: " + cls.getName());
+            throw new IllegalArgumentException("attempt to serialize non-Entity class: " + cls.getName());
+        }
+        JsonObject obj = new JsonObject();
+        PropertyDescriptor[] descs = PropertyUtils.getPropertyDescriptors(src);
+        for (PropertyDescriptor desc : descs) {
+            Method readMethod = desc.getReadMethod();
+            if (readMethod != null && readMethod.isAnnotationPresent(Persist.class)) {
+                String name = desc.getName();
+                
+                try {
+                    Object value = PropertyUtils.getProperty(src, name);
+                    obj.add(name, context.serialize(value));
+                } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
+                    throw new RuntimeException(e);
+                }
+            } else if (readMethod == null) {
+                System.err.println("WARNING: property without read method: " + src.getClass().getName() + "::" + desc.getName());
+            }
+        }
+        return obj;
+    }
+
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/common/src/test/java/com/redhat/thermostat/web/common/typeadapters/LegacyPreparedParameterSerializer.java	Wed Sep 17 11:36:58 2014 +0200
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2012-2014 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.web.common.typeadapters;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Type;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+import com.redhat.thermostat.common.utils.LoggingUtils;
+import com.redhat.thermostat.storage.core.PreparedParameter;
+import com.redhat.thermostat.storage.model.Pojo;
+
+/**
+ * Old non-stream GSON API serializer. Used only in performance tests. 
+ */
+public class LegacyPreparedParameterSerializer implements JsonSerializer<PreparedParameter>,
+                                                    JsonDeserializer<PreparedParameter>{
+
+    private static final Logger logger = LoggingUtils.getLogger(LegacyPreparedParameterSerializer.class);
+    private static final String PROP_TYPE = "type";
+    private static final String PROP_VALUE = "value";
+    private static final Set<Class<?>> VALID_CLASSES;
+    private static final Set<Class<?>> PRIMITIVES_NOT_ALLOWING_NULL_VAL;
+    // maps type names to classes:
+    //    "int" => int.class , "double" => double.class, "[I" => int[].class
+    //    and so on.
+    private static final Map<String, Class<?>> CLASSES_LOOKUP_TABLE;
+    
+    static {
+        VALID_CLASSES = new HashSet<>();
+        CLASSES_LOOKUP_TABLE = new HashMap<>();
+        CLASSES_LOOKUP_TABLE.put(int.class.getName(), int.class);
+        CLASSES_LOOKUP_TABLE.put(long.class.getName(), long.class);
+        CLASSES_LOOKUP_TABLE.put(boolean.class.getName(), boolean.class);
+        CLASSES_LOOKUP_TABLE.put(double.class.getName(), double.class);
+        CLASSES_LOOKUP_TABLE.put(String.class.getName(), String.class);
+        CLASSES_LOOKUP_TABLE.put(int[].class.getName(), int[].class);
+        CLASSES_LOOKUP_TABLE.put(long[].class.getName(), long[].class);
+        CLASSES_LOOKUP_TABLE.put(boolean[].class.getName(), boolean[].class);
+        CLASSES_LOOKUP_TABLE.put(double[].class.getName(), double[].class);
+        CLASSES_LOOKUP_TABLE.put(String[].class.getName(), String[].class);
+        VALID_CLASSES.addAll(CLASSES_LOOKUP_TABLE.values());
+        PRIMITIVES_NOT_ALLOWING_NULL_VAL = new HashSet<>();
+        PRIMITIVES_NOT_ALLOWING_NULL_VAL.add(int.class);
+        PRIMITIVES_NOT_ALLOWING_NULL_VAL.add(long.class);
+        PRIMITIVES_NOT_ALLOWING_NULL_VAL.add(boolean.class);
+        PRIMITIVES_NOT_ALLOWING_NULL_VAL.add(double.class);
+    }
+    
+    @Override
+    public JsonElement serialize(PreparedParameter param, Type type,
+            JsonSerializationContext ctxt) {
+        JsonObject result = new JsonObject();
+        JsonElement valueElem = serializeValue(ctxt, param.getValue(), param.getType());
+        result.add(PROP_VALUE, valueElem);
+        JsonPrimitive typeElem = new JsonPrimitive(param.getType().getName());
+        result.add(PROP_TYPE, typeElem);
+        return result;
+    }
+
+    private JsonElement serializeValue(JsonSerializationContext ctxt, Object value, Class<?> type) {
+        JsonElement element;
+        // Special case pojo list types: the value class is of array type for
+        // them, but the type is the component type.
+        if (value != null && value.getClass().isArray() && !type.isArray()) {
+            assert(Pojo.class.isAssignableFrom(type));
+            Class<?> arrayType = Array.newInstance(type, 0).getClass();
+            element = ctxt.serialize(value, arrayType);
+        } else {
+            element = ctxt.serialize(value, type);
+        }
+        return element;
+    }
+
+    @Override
+    public PreparedParameter deserialize(JsonElement jsonElem, Type type,
+            JsonDeserializationContext ctxt) throws JsonParseException {
+        JsonElement typeElem = jsonElem.getAsJsonObject().get(PROP_TYPE);
+        String className = typeElem.getAsString();
+        // perform some sanity checking on the types of classes we actually
+        // de-serialize
+        Class<?> typeVal = deserializeTypeVal(className);
+        validateSaneClassName(typeVal);
+        JsonElement valueElement = jsonElem.getAsJsonObject().get(PROP_VALUE);
+        Object value = deserializeValue(ctxt, valueElement, typeVal);
+        PreparedParameter param = new PreparedParameter();
+        param.setType(typeVal);
+        param.setValue(value);
+        return param;
+    }
+
+    private Class<?> deserializeTypeVal(String className) {
+        Class<?> typeVal = null;
+        if (CLASSES_LOOKUP_TABLE.containsKey(className)) {
+            typeVal = CLASSES_LOOKUP_TABLE.get(className);
+        } else {
+            try {
+                // We need this for Pojo + Pojo list type params. For pojo
+                // lists the name we get passed is the component type of the
+                // array.
+                typeVal = Class.forName(className);
+            } catch (ClassNotFoundException e) {
+                logger.log(Level.WARNING, "Failed to resolve class type for '"
+                        + className + "'.");
+            }
+        }
+        return typeVal;
+    }
+
+    private Object deserializeValue(JsonDeserializationContext ctxt,
+            JsonElement valueElement, Class<?> valType) {
+        // special case for Pojo/Pojo list types. In that case, the valType
+        // is the component type for arrays. In order to distinguish pojo
+        // lists from pojos, we use JSON's array info about the value element.
+        if (valueElement != null && valueElement.isJsonArray() && !valType.isArray()) {
+            assert(Pojo.class.isAssignableFrom(valType));
+            Class<?> arrayType = Array.newInstance(valType, 0).getClass();
+            return ctxt.deserialize(valueElement, arrayType);
+        } else {
+            Object value = ctxt.deserialize(valueElement, valType);
+            validatePrimitivesForNull(value, valType);
+            return value;
+        }
+    }
+
+    private void validatePrimitivesForNull(Object value, Class<?> valType) {
+        if (PRIMITIVES_NOT_ALLOWING_NULL_VAL.contains(valType) && value == null) {
+            // illegal value for primitive type. according to JLS spec they all
+            // have default values and are never null.
+            throw new IllegalStateException( valType + " primitive" +
+                                            " does not accept a null value!");
+        }
+    }
+
+    // Allow valid classes + Pojo types, refuse everything else
+    private void validateSaneClassName(Class<?> clazz) {
+        // isAssignableFrom throws NPE if clazz is null.
+        if (VALID_CLASSES.contains(clazz) ||
+                Pojo.class.isAssignableFrom(clazz)) {
+            return;
+        }
+        throw new IllegalStateException("Illegal type of parameter " + clazz.getCanonicalName());
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/common/src/test/java/com/redhat/thermostat/web/common/typeadapters/LegacyWebPreparedStatementSerializer.java	Wed Sep 17 11:36:58 2014 +0200
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2012-2014 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.web.common.typeadapters;
+
+import java.lang.reflect.Type;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+import com.redhat.thermostat.storage.core.PreparedParameters;
+import com.redhat.thermostat.web.common.WebPreparedStatement;
+
+/**
+ * Old non-stream GSON API serializer. Used only in performance tests.
+ *
+ */
+public class LegacyWebPreparedStatementSerializer implements
+        JsonDeserializer<WebPreparedStatement<?>>,
+        JsonSerializer<WebPreparedStatement<?>> {
+    
+    private static final String PROP_PARAMS = "p";
+    private static final String PROP_STMT_ID = "sid";
+
+    @Override
+    public JsonElement serialize(WebPreparedStatement<?> stmt, Type type,
+            JsonSerializationContext ctxt) {
+        JsonObject result = new JsonObject();
+        JsonElement parameters = ctxt.serialize(stmt.getParams(), PreparedParameters.class);
+        result.add(PROP_PARAMS, parameters);
+        JsonPrimitive stmtIdElem = new JsonPrimitive(stmt.getStatementId());
+        result.add(PROP_STMT_ID, stmtIdElem);
+        return result;
+    }
+
+    @Override
+    public WebPreparedStatement<?> deserialize(JsonElement jsonElem, Type type,
+            JsonDeserializationContext ctxt) throws JsonParseException {
+        JsonElement paramsElem = jsonElem.getAsJsonObject().get(PROP_PARAMS);
+        JsonElement stmtIdElem = jsonElem.getAsJsonObject().get(PROP_STMT_ID);
+        PreparedParameters params = ctxt.deserialize(paramsElem, PreparedParameters.class);
+        int stmtId = ctxt.deserialize(stmtIdElem, int.class);
+        WebPreparedStatement<?> stmt = new WebPreparedStatement<>();
+        stmt.setStatementId(stmtId);
+        stmt.setParams(params);
+        return stmt;
+    }
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/common/src/test/java/com/redhat/thermostat/web/common/typeadapters/LegacyWebQueryResponseSerializer.java	Wed Sep 17 11:36:58 2014 +0200
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2012-2014 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.web.common.typeadapters;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+import com.redhat.thermostat.storage.model.Pojo;
+import com.redhat.thermostat.web.common.WebQueryResponse;
+
+/**
+ * Old non-stream GSON API serializer. Used only in performance tests. 
+ */
+public class LegacyWebQueryResponseSerializer<T extends Pojo> implements
+        JsonDeserializer<WebQueryResponse<T>>, JsonSerializer<WebQueryResponse<T>> {
+
+    private static final String PROP_RESULT = "payload";
+    private static final String PROP_ERROR_CODE = "errno";
+    
+    @Override
+    public JsonElement serialize(WebQueryResponse<T> qResponse, Type type,
+            JsonSerializationContext ctxt) {
+        JsonObject result = new JsonObject();
+        JsonElement resultsElem = ctxt.serialize(qResponse.getResultList());
+        result.add(PROP_RESULT, resultsElem);
+        JsonPrimitive errnoElem = new JsonPrimitive(qResponse.getResponseCode());
+        result.add(PROP_ERROR_CODE, errnoElem);
+        return result;
+    }
+    
+    @SuppressWarnings("unchecked")
+    @Override
+    public WebQueryResponse<T> deserialize(JsonElement jsonElem, Type type,
+            JsonDeserializationContext ctxt) throws JsonParseException {
+        // fromJson() calls need to pass in the right *parameterized* type token:
+        // example for AgentInformation as T:
+        //   Type queryResponseType = new TypeToken<WebQueryResponse<AgentInformation>>() {}.getType();
+        //   gson.fromJson(jsonStr, queryResponseType)
+        Type[] typeParameters = ((ParameterizedType)type).getActualTypeArguments();
+        Type queryResponseTypeParam = typeParameters[0]; // WebQueryResponse has only one parameterized type T
+        JsonArray resultElem = jsonElem.getAsJsonObject().get(PROP_RESULT).getAsJsonArray();
+        @SuppressWarnings("rawtypes")
+        Class typeOfGeneric = (Class)queryResponseTypeParam;
+        T[] array = (T[])Array.newInstance(typeOfGeneric, resultElem.size());
+        for (int i = 0; i < resultElem.size(); i++) {
+            array[i] = ctxt.deserialize(resultElem.get(i), queryResponseTypeParam);
+        }
+        JsonElement errorCodeElem = jsonElem.getAsJsonObject().get(PROP_ERROR_CODE);
+        int errorCode = ctxt.deserialize(errorCodeElem, int.class);
+        WebQueryResponse<T> qResponse = new WebQueryResponse<>();
+        qResponse.setResponseCode(errorCode);
+        qResponse.setResultList(array);
+        return qResponse;
+    }
+    
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/common/src/test/java/com/redhat/thermostat/web/common/typeadapters/PojoTypeAdapterTest.java	Wed Sep 17 11:36:58 2014 +0200
@@ -0,0 +1,319 @@
+/*
+ * Copyright 2012-2014 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.web.common.typeadapters;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.redhat.thermostat.storage.core.Entity;
+import com.redhat.thermostat.storage.core.Persist;
+import com.redhat.thermostat.storage.model.AgentInformation;
+import com.redhat.thermostat.storage.model.AggregateCount;
+import com.redhat.thermostat.storage.model.Pojo;
+
+public class PojoTypeAdapterTest {
+
+    private Gson gson;
+    
+    @Before
+    public void setup() {
+        gson = new GsonBuilder()
+                    .registerTypeAdapterFactory(new PojoTypeAdapterFactory())
+                    .create();
+    }
+    
+    @Test
+    public void canSerializePojoLists() {
+        String expectedJson = "[{\"AInt\":200,\"fooString\":\"bar\"}," +
+                               "{\"AInt\":5,\"fooString\":\"baz\"}," +
+                               "{\"AInt\":200,\"fooString\":\"bar\"}]";
+        
+        FooPojo pojo1 = new FooPojo();
+        pojo1.setAInt(200);
+        pojo1.setFooString("bar");
+        
+        FooPojo pojo2 = new FooPojo();
+        pojo2.setAInt(5);
+        pojo2.setFooString("baz");
+        
+        FooPojo[] list = new FooPojo[] {
+                pojo1, pojo2, pojo1
+        };
+        String json = gson.toJson(list);
+        assertEquals(expectedJson, json);
+    }
+    
+    @Test
+    public void canDeserializePojoLists() {
+        String json = "[{\"AInt\":200,\"fooString\":\"bar\"}," +
+                               "{\"AInt\":5,\"fooString\":\"baz\"}," +
+                               "{\"AInt\":200,\"fooString\":\"bar\"}]";
+        
+        FooPojo[] actual = gson.fromJson(json, FooPojo[].class);
+        assertEquals(3, actual.length);
+        assertEquals(200, actual[0].getAInt());
+        assertEquals(5, actual[1].getAInt());
+        assertEquals(200, actual[2].getAInt());
+        assertEquals("bar", actual[0].getFooString());
+        assertEquals("bar", actual[2].getFooString());
+        assertEquals("baz", actual[1].getFooString());
+    }
+    
+    @Test
+    public void canSerializeNullPojo() {
+        FooPojo foo = null;
+        String jsonStr = gson.toJson(foo);
+        assertEquals("null", jsonStr);
+    }
+    
+    // Null values won't show up in the JSON string.
+    @Test
+    public void canSerializeNullMember() {
+        String expectedJson = "{\"AInt\":200,\"fooString\":\"bar\"}";
+        
+        FooPojo foo = new FooPojo();
+        foo.setAInt(200);
+        foo.setFooString("bar");
+        String jsonStr = gson.toJson(foo);
+        assertEquals(expectedJson, jsonStr);
+    }
+    
+    @Test
+    public void canDeserializeWithMembersUnset() {
+        String json = "{\"AInt\":200,\"boolArray\":null,\"fooString\":\"bar\"}";
+
+        FooPojo pojo = gson.fromJson(json, FooPojo.class);
+        assertNotNull(pojo);
+        assertEquals(200, pojo.getAInt());
+        assertEquals("bar", pojo.getFooString());
+        assertNull(pojo.getBoolArray());
+        
+        // Do it again with unset values omitted.
+        json = "{\"fooString\":\"bar\"}";
+        pojo = gson.fromJson(json, FooPojo.class);
+        assertNotNull(pojo);
+        assertEquals("bar", pojo.getFooString());
+        assertNull(pojo.getBoolArray());
+        assertEquals("0 is default int val", 0, pojo.getAInt());
+    }
+    
+    @Test
+    public void canDeserializeBasic() {
+        String json = "{\"AInt\":1223,\"boolArray\":[false,true,false],\"fooString\":\"fooStringValue\"}";
+        
+        FooPojo pojo = gson.fromJson(json, FooPojo.class);
+        assertEquals(1223, pojo.getAInt());
+        assertEquals("fooStringValue", pojo.getFooString());
+        assertEquals(3, pojo.getBoolArray().length);
+        assertFalse(pojo.getBoolArray()[0]);
+        assertTrue(pojo.getBoolArray()[1]);
+        assertFalse(pojo.getBoolArray()[2]);
+    }
+    
+    @Test
+    public void canDeserializeNullPojo() {
+        String nullStr = "null";
+        FooPojo pojo = gson.fromJson(nullStr, FooPojo.class);
+        assertNull(pojo);
+    }
+    
+    @Test
+    public void canSerializeBasic() {
+        String expectedJson = "{\"AInt\":1223,\"boolArray\":[false,true,false],\"fooString\":\"fooStringValue\"}";
+        
+        FooPojo fooPojo = new FooPojo();
+        fooPojo.setBoolArray(new boolean[] { false, true, false });
+        fooPojo.setAInt(1223);
+        fooPojo.setFooString("fooStringValue");
+        String jsonStr = gson.toJson(fooPojo);
+        assertEquals(expectedJson, jsonStr);
+    }
+    
+    @Test
+    public void canSerializeRecursivePojo() {
+        FooPojo fooPojo = new FooPojo();
+        fooPojo.setBoolArray(new boolean[] { false, true, false });
+        fooPojo.setAInt(1223);
+        fooPojo.setFooString("fooStringValue");
+        String fooPojoJson = gson.toJson(fooPojo);
+        
+        String expectedJson = "{\"myLong\":10000000000,\"pojo\":" + fooPojoJson + "}";
+        RecPojo recursive = new RecPojo();
+        recursive.setPojo(fooPojo);
+        recursive.setMyLong(10000000000L);
+        
+        String jsonStr = gson.toJson(recursive);
+        assertEquals(expectedJson, jsonStr);
+    }
+    
+    @Test
+    public void canDeserializeRecursivePojo() {
+        String json = "{\"myLong\":10000000000,\"pojo\":" +
+                                         "{\"AInt\":1223,\"boolArray\":[false,true,false],\"fooString\":\"fooStringValue\"}}";
+        
+        RecPojo rec = gson.fromJson(json, RecPojo.class);
+        
+        assertEquals(10000000000L, rec.getMyLong());
+        assertNotNull(rec.getPojo());
+        
+        FooPojo pojo = rec.getPojo();
+        assertEquals(1223, pojo.getAInt());
+        assertEquals("fooStringValue", pojo.getFooString());
+        assertEquals(3, pojo.getBoolArray().length);
+        assertFalse(pojo.getBoolArray()[0]);
+        assertTrue(pojo.getBoolArray()[1]);
+        assertFalse(pojo.getBoolArray()[2]);
+    }
+    
+    @Test
+    public void canSerializeDeserializeBasic() {
+        // Our test pojo
+        AgentInformation agentInfo = new AgentInformation("testing");
+        agentInfo.setAlive(true);
+        
+        String jsonStr = gson.toJson(agentInfo);
+        
+        AgentInformation actual = gson.fromJson(jsonStr, AgentInformation.class);
+        
+        assertEquals("testing", actual.getAgentId());
+        assertEquals(true, actual.isAlive());
+    }
+    
+    @Test
+    public void canSerializeDeserializeArray() {
+        // Our test pojo
+        AgentInformation agentInfo = new AgentInformation("testing");
+        agentInfo.setAlive(true);
+        AgentInformation[] agentInfos = new AgentInformation[] {
+                agentInfo
+        };
+        
+        String jsonStr = gson.toJson(agentInfos);
+        
+        AgentInformation[] actual = gson.fromJson(jsonStr, AgentInformation[].class);
+        
+        assertEquals("testing", actual[0].getAgentId());
+        assertEquals(true, actual[0].isAlive());
+    }
+    
+    @Test
+    public void canSerializeDeserializeAggregateCount() {
+        long expectedCount = 3333000333L;
+        AggregateCount count = new AggregateCount();
+        count.setCount(expectedCount);
+        String jsonStr = gson.toJson(count);
+        // now do the reverse
+        AggregateCount c2 = gson.fromJson(jsonStr, AggregateCount.class);
+        assertEquals(expectedCount, c2.getCount());
+    }
+    
+    @Entity
+    public static class FooPojo implements Pojo {
+        
+        private String fooString;
+        private int fooInt;
+        private boolean[] boolArray;
+        
+        @Persist
+        public String getFooString() {
+            return fooString;
+        }
+        
+        @Persist
+        public void setFooString(String fooString) {
+            this.fooString = fooString;
+        }
+        
+        @Persist
+        public int getAInt() {
+            return fooInt;
+        }
+        
+        @Persist
+        public void setAInt(int fooInt) {
+            this.fooInt = fooInt;
+        }
+        
+        @Persist
+        public boolean[] getBoolArray() {
+            return boolArray;
+        }
+        
+        @Persist
+        public void setBoolArray(boolean[] boolArray) {
+            this.boolArray = boolArray;
+        }
+        
+        
+    }
+    
+    @Entity
+    public static class RecPojo implements Pojo {
+        
+        private long myLong;
+        private FooPojo pojo;
+        
+        @Persist
+        public long getMyLong() {
+            return myLong;
+        }
+        
+        @Persist
+        public void setMyLong(long myLong) {
+            this.myLong = myLong;
+        }
+        
+        @Persist
+        public FooPojo getPojo() {
+            return pojo;
+        }
+        
+        @Persist
+        public void setPojo(FooPojo pojo) {
+            this.pojo = pojo;
+        }
+        
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/common/src/test/java/com/redhat/thermostat/web/common/typeadapters/PreparedParameterJSONPerformanceTest.java	Wed Sep 17 11:36:58 2014 +0200
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2012-2014 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.web.common.typeadapters;
+
+import org.junit.experimental.categories.Category;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.reflect.TypeToken;
+import com.redhat.thermostat.storage.core.PreparedParameter;
+import com.redhat.thermostat.testutils.PerformanceTest;
+import com.redhat.thermostat.web.common.typeadapters.PojoTypeAdapterFactory;
+import com.redhat.thermostat.web.common.typeadapters.PreparedParameterTypeAdapterFactory;
+
+/**
+ * JUnit categorized performance test. It'll be only run for
+ * the perf-tests profile during a full build.
+ */
+@Category(PerformanceTest.class)
+public class PreparedParameterJSONPerformanceTest extends
+        JsonPerformanceTest<PreparedParameter> {
+    
+    private static final boolean DEBUG = true;
+
+    public PreparedParameterJSONPerformanceTest() {
+        super(DEBUG, PreparedParameterJSONPerformanceTest.class.getSimpleName());
+    }
+
+    @Override
+    protected Gson getSlowGson() {
+        return new GsonBuilder()
+                    .registerTypeAdapterFactory(new PojoTypeAdapterFactory())
+                    .registerTypeAdapter(PreparedParameter.class, new LegacyPreparedParameterSerializer())
+                    .create();
+    }
+
+    @Override
+    protected Gson getFasterGson() {
+        return new GsonBuilder()
+            .registerTypeAdapterFactory(new PojoTypeAdapterFactory())
+            .registerTypeAdapterFactory(new PreparedParameterTypeAdapterFactory())
+            .create();
+    }
+
+    @Override
+    protected double getExpectedSpeedup(
+            OperationType type,
+            int iterations) {
+        if (type == OperationType.SERIALIZATION && iterations == getColdSerializationIterations()) {
+            return 1.7;
+        } else if (type == OperationType.SERIALIZATION && iterations == getWarmSerializationIterations()) {
+            return 1.3;
+        } else if (type == OperationType.DESERIALIZATION && iterations == getColdDeserializationIterations()) {
+            return 1;
+        } else if (type == OperationType.DESERIALIZATION && iterations == getWarmDeserializationIterations()) {
+            return 1;
+        } else {
+            throw new IllegalStateException("bug");
+        }
+    }
+
+    @Override
+    protected String mutateJsonString(
+            GsonContext context,
+            int mutator) {
+        return String.format("{\"type\":\"int\",\"value\":%d}", mutator);
+    }
+
+    @Override
+    protected PreparedParameter mutateToBeSerializedInstance(int mutator) {
+        PreparedParameter param = new PreparedParameter();
+        param.setValue(mutator);
+        param.setType(int.class);
+        return param;
+    }
+
+    @Override
+    protected TypeToken<PreparedParameter> getDeserializeTypeToken() {
+        return TypeToken.get(PreparedParameter.class);
+    }
+
+    @Override
+    protected int getColdSerializationIterations() {
+        return 1;
+    }
+
+    @Override
+    protected int getWarmSerializationIterations() {
+        return 1000;
+    }
+
+    @Override
+    protected int getColdDeserializationIterations() {
+        return 1;
+    }
+
+    @Override
+    protected int getWarmDeserializationIterations() {
+        return 1000;
+    }
+
+    @Override
+    protected double getSelfSerializationDelta() {
+        return 0.1;
+    }
+
+    @Override
+    protected double getSelfDeserializationDelta() {
+        return 0.1;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/common/src/test/java/com/redhat/thermostat/web/common/typeadapters/PreparedParameterTypeAdapterTest.java	Wed Sep 17 11:36:58 2014 +0200
@@ -0,0 +1,481 @@
+/*
+ * Copyright 2012-2014 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.web.common.typeadapters;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.lang.reflect.Array;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.redhat.thermostat.storage.core.PreparedParameter;
+import com.redhat.thermostat.storage.model.AgentInformation;
+import com.redhat.thermostat.storage.model.VmInfo.KeyValuePair;
+
+public class PreparedParameterTypeAdapterTest {
+
+    private Gson gson;
+    
+    @Before
+    public void setup() {
+        gson = new GsonBuilder()
+                .registerTypeAdapterFactory(new PojoTypeAdapterFactory())
+                .registerTypeAdapterFactory(new PreparedParameterTypeAdapterFactory())
+                .create();
+    }
+    
+    @Test
+    public void canDeserializeBasic() {
+        // String
+        String jsonStr = "{ \"type\": \"java.lang.String\" , \"value\": \"testing\" }";
+        PreparedParameter param = gson.fromJson(jsonStr, PreparedParameter.class);
+        assertEquals(String.class, param.getType());
+        assertEquals("testing", param.getValue());
+        // Integer
+        jsonStr = "{ \"type\": \"int\" , \"value\": -1}";
+        param = gson.fromJson(jsonStr, PreparedParameter.class);
+        assertEquals(int.class, param.getType());
+        assertEquals(-1, param.getValue());
+        // Long
+        jsonStr = "{ \"type\": \"long\" , \"value\": -10}";
+        param = gson.fromJson(jsonStr, PreparedParameter.class);
+        assertEquals(long.class, param.getType());
+        assertEquals(-10L, param.getValue());
+        jsonStr = "{ \"type\": \"long\" , \"value\": 30000000003}";
+        param = gson.fromJson(jsonStr, PreparedParameter.class);
+        assertEquals(long.class, param.getType());
+        assertEquals(30000000003L, param.getValue());
+        // Boolean
+        jsonStr = "{ \"type\": \"boolean\" , \"value\": true}";
+        param = gson.fromJson(jsonStr, PreparedParameter.class);
+        assertEquals(boolean.class, param.getType());
+        assertEquals(true, param.getValue());
+        // String[]
+        String strArrayVal = "[ \"testing1\", \"testing2\", \"3\" ]";
+        jsonStr = "{ \"type\": \"[Ljava.lang.String;\" , \"value\": " + strArrayVal + "}";
+        param = gson.fromJson(jsonStr, PreparedParameter.class);
+        assertEquals(String[].class, param.getType());
+        assertTrue(param.getValue() instanceof String[]);
+        String[] vals = (String[])param.getValue();
+        assertEquals(3, vals.length);
+        assertEquals("testing1", vals[0]);
+        assertEquals("testing2", vals[1]);
+        assertEquals("3", vals[2]);
+    }
+    
+    @Test
+    public void allowsBooleanListNullDeserialization() {
+        String jsonStr = "{ \"type\": \"[Z\" , \"value\": null}";
+        PreparedParameter p = gson.fromJson(jsonStr, PreparedParameter.class);
+        assertEquals(boolean[].class, p.getType());
+        assertEquals(null, p.getValue());
+    }
+    
+    @Test
+    public void allowsIntListNullDeserialization() {
+        String jsonStr = "{ \"type\": \"[I\" , \"value\": null}";
+        PreparedParameter p = gson.fromJson(jsonStr, PreparedParameter.class);
+        assertEquals(int[].class, p.getType());
+        assertEquals(null, p.getValue());
+    }
+    
+    @Test
+    public void allowsLongListNullDeserialization() {
+        String jsonStr = "{ \"type\": \"[J\" , \"value\": null}";
+        PreparedParameter p = gson.fromJson(jsonStr, PreparedParameter.class);
+        assertEquals(long[].class, p.getType());
+        assertEquals(null, p.getValue());
+    }
+    
+    @Test
+    public void allowsDoubleListNullDeserialization() {
+        String jsonStr = "{ \"type\": \"[D\" , \"value\": null}";
+        PreparedParameter p = gson.fromJson(jsonStr, PreparedParameter.class);
+        assertEquals(double[].class, p.getType());
+        assertEquals(null, p.getValue());
+    }
+    
+    @Test
+    public void allowsStringNullDeserialization() {
+        String jsonStr = "{ \"type\": \"java.lang.String\" , \"value\": null}";
+        PreparedParameter p = gson.fromJson(jsonStr, PreparedParameter.class);
+        assertEquals(String.class, p.getType());
+        assertEquals(null, p.getValue());
+    }
+    
+    @Test
+    public void allowsStringListNullDeserialization() {
+        String jsonStr = "{ \"type\": \"[Ljava.lang.String;\" , \"value\": null}";
+        PreparedParameter p = gson.fromJson(jsonStr, PreparedParameter.class);
+        assertEquals(String[].class, p.getType());
+        assertEquals(null, p.getValue());
+    }
+    
+    @Test
+    public void rejectNullForBooleanPrimitive() {
+        String jsonStr = "{ \"type\": \"boolean\" , \"value\": null}";
+        doPrimitiveNullTest(jsonStr, "boolean");
+    }
+    
+    @Test
+    public void rejectNullForIntPrimitive() {
+        String jsonStr = "{ \"type\": \"int\" , \"value\": null}";
+        doPrimitiveNullTest(jsonStr, "int");
+    }
+    
+    @Test
+    public void rejectNullForDoublePrimitive() {
+        String jsonStr = "{ \"type\": \"double\" , \"value\": null}";
+        doPrimitiveNullTest(jsonStr, "double");
+    }
+    
+    @Test
+    public void rejectNullForLongPrimitive() {
+        String jsonStr = "{ \"type\": \"long\" , \"value\": null}";
+        doPrimitiveNullTest(jsonStr, "long");
+    }
+    
+    private void doPrimitiveNullTest(String jsonStr, String typeName) {
+        try {
+            gson.fromJson(jsonStr, PreparedParameter.class);
+            // The Java language spec does not permit this
+            fail(typeName + " null primitive should not deserialize");
+        } catch (Exception e) {
+            // pass
+            Throwable cause = e.getCause();
+            assertEquals(typeName + " primitive does not accept a null value!", cause.getMessage());
+        }
+    }
+    
+    @Test
+    public void failsDeserializationWrongTypeClass() {
+        String jsonStr = "{ \"type\": \"java.io.File\" , \"value\": true}";
+        try {
+            gson.fromJson(jsonStr, PreparedParameter.class);
+            fail("should have failed to serialize");
+        } catch (Exception e) {
+            // pass
+            Throwable cause = e.getCause();
+            assertTrue(cause.getMessage().contains("Illegal type of parameter"));
+        }
+    }
+    
+    @Test
+    public void failsDeserializationIfStringForInt() {
+        String jsonStr = "{ \"type\": \"int\" , \"value\":\"testing\"}";
+        try {
+            gson.fromJson(jsonStr, PreparedParameter.class);
+            fail("should have failed to serialize");
+        } catch (Exception e) {
+            // pass
+            Throwable cause = e.getCause();
+            assertTrue(cause instanceof NumberFormatException);
+        }
+    }
+    
+    @Test
+    public void failsDeserializationIfBooleanForInt() {
+        String jsonStr = "{ \"type\": \"int\" , \"value\": true}";
+        try {
+            gson.fromJson(jsonStr, PreparedParameter.class);
+            fail("should have failed to serialize");
+        } catch (Exception e) {
+            // pass
+            Throwable cause = e.getCause();
+            assertTrue(cause instanceof IllegalStateException);
+        }
+    }
+    
+    @Test
+    public void failsDeserializationIfIntForIntList() {
+        String jsonStr = "{ \"type\": \"[I\" , \"value\": -1}";
+        try {
+            gson.fromJson(jsonStr, PreparedParameter.class);
+            fail("should have failed to serialize");
+        } catch (Exception e) {
+            // pass
+            Throwable cause = e.getCause();
+            assertTrue(cause instanceof IllegalStateException);
+        }
+    }
+    
+    @Test
+    public void failsDeserializationIfDoubleForInt() {
+        String jsonStr = "{ \"type\": \"int\" , \"value\": -1.3}";
+        try {
+            gson.fromJson(jsonStr, PreparedParameter.class);
+            fail("should have failed to serialize");
+        } catch (Exception e) {
+            // pass
+            Throwable cause = e.getCause();
+            assertTrue(cause instanceof NumberFormatException);
+        }
+    }
+    
+    @Test
+    public void canSerializeBasic() {
+        // String
+        String expected = "{\"type\":\"java.lang.String\",\"value\":\"testing\"}";
+        PreparedParameter param = new PreparedParameter();
+        param.setType(String.class);
+        param.setValue("testing");
+        String actual = gson.toJson(param);
+        assertEquals(expected, actual);
+        // Integer
+        expected = "{\"type\":\"int\",\"value\":-1}";
+        param.setType(int.class);
+        param.setValue(-1);
+        actual = gson.toJson(param);
+        assertEquals(expected, actual);
+        // Long
+        expected = "{\"type\":\"long\",\"value\":30000000003}";
+        param.setType(long.class);
+        param.setValue(30000000003L);
+        actual = gson.toJson(param);
+        assertEquals(expected, actual);
+        // boolean
+        expected = "{\"type\":\"boolean\",\"value\":true}";
+        param.setType(boolean.class);
+        param.setValue(true);
+        actual = gson.toJson(param);
+        assertEquals(expected, actual);
+        // String[]
+        String strArrayVal = "[\"testing1\",\"testing2\",\"3\"]";
+        expected = "{\"type\":\"[Ljava.lang.String;\",\"value\":" + strArrayVal + "}";
+        param.setType(String[].class);
+        String[] array = new String[] {
+                "testing1", "testing2", "3"
+        };
+        param.setValue(array);
+        actual = gson.toJson(param);
+        assertEquals(expected, actual);
+    }
+    
+    @Test
+    public void canSerializeDeserializeInteger() {
+        PreparedParameter expected = new PreparedParameter();
+        expected.setType(int.class);
+        expected.setValue(3);
+        String jsonStr = gson.toJson(expected, PreparedParameter.class);
+        assertParameterEquals(expected, jsonStr);
+    }
+    
+    @Test
+    public void canSerializeDeserializeIntegerArray() {
+        PreparedParameter expected = new PreparedParameter();
+        expected.setType(int[].class);
+        expected.setValue(new int[] { 0, 3, 20 });
+        String jsonStr = gson.toJson(expected, PreparedParameter.class);
+        assertParameterEquals(expected, jsonStr);
+    }
+    
+    @Test
+    public void canSerializeDeserializeDouble() {
+        PreparedParameter expected = new PreparedParameter();
+        expected.setType(double.class);
+        expected.setValue(Math.E);
+        String jsonStr = gson.toJson(expected, PreparedParameter.class);
+        assertParameterEquals(expected, jsonStr);
+    }
+    
+    @Test
+    public void canSerializeDeserializeDoubleArray() {
+        PreparedParameter expected = new PreparedParameter();
+        expected.setType(double[].class);
+        expected.setValue(new double[] { 3.3, 1.0, Math.PI });
+        String jsonStr = gson.toJson(expected, PreparedParameter.class);
+        assertParameterEquals(expected, jsonStr);
+    }
+    
+    @Test
+    public void canSerializeDeserializeLong() {
+        PreparedParameter expected = new PreparedParameter();
+        expected.setType(long.class);
+        expected.setValue(30000000003L);
+        String jsonStr = gson.toJson(expected, PreparedParameter.class);
+        assertParameterEquals(expected, jsonStr);
+    }
+    
+    @Test
+    public void canSerializeDeserializeLongArray() {
+        PreparedParameter expected = new PreparedParameter();
+        expected.setType(long[].class);
+        expected.setValue(new long[] { 3000000000L, 3, 20 });
+        String jsonStr = gson.toJson(expected, PreparedParameter.class);
+        assertParameterEquals(expected, jsonStr);
+    }
+    
+    @Test
+    public void canSerializeDeserializeString() {
+        PreparedParameter expected = new PreparedParameter();
+        expected.setType(String.class);
+        expected.setValue("testing");
+        String jsonStr = gson.toJson(expected, PreparedParameter.class);
+        assertParameterEquals(expected, jsonStr);
+    }
+    
+    @Test
+    public void canSerializeDeserializeStringArray() {
+        PreparedParameter expected = new PreparedParameter();
+        expected.setType(String[].class);
+        String[] expectedArray = new String[] {
+                "one", "two", "three"      
+        };
+        expected.setValue(expectedArray);
+        String jsonStr = gson.toJson(expected, PreparedParameter.class);
+        assertParameterEquals(expected, jsonStr);
+    }
+    
+    @Test
+    public void canSerializeDeserializeBoolean() {
+        PreparedParameter expected = new PreparedParameter();
+        expected.setType(boolean.class);
+        expected.setValue(false);
+        String jsonStr = gson.toJson(expected, PreparedParameter.class);
+        assertParameterEquals(expected, jsonStr);
+        
+        expected = new PreparedParameter();
+        expected.setType(boolean.class);
+        expected.setValue(true);
+        jsonStr = gson.toJson(expected, PreparedParameter.class);
+        assertParameterEquals(expected, jsonStr);
+    }
+    
+    @Test
+    public void canSerializeDeserializeBooleanArray() {
+        PreparedParameter expected = new PreparedParameter();
+        expected.setType(boolean[].class);
+        expected.setValue(new boolean[] { true, false, false, true });
+        String jsonStr = gson.toJson(expected, PreparedParameter.class);
+        assertParameterEquals(expected, jsonStr);
+    }
+    
+    
+    @Test
+    public void canSerializeDeserializePojos() {
+        PreparedParameter expected = new PreparedParameter();
+        AgentInformation info = new AgentInformation("foo-writer");
+        expected.setType(info.getClass());
+        expected.setValue(info);
+        String jsonStr = gson.toJson(expected, PreparedParameter.class);
+        assertParameterEquals(expected, jsonStr);
+        
+        info = new AgentInformation("some-writer");
+        info.setAlive(true);
+        info.setConfigListenAddress("127.0.0.1:12000");
+        info.setStartTime(System.currentTimeMillis());
+        info.setStopTime(System.currentTimeMillis());
+        expected = new PreparedParameter();
+        expected.setType(info.getClass());
+        expected.setValue(info);
+        jsonStr = gson.toJson(expected, PreparedParameter.class);
+        assertParameterEquals(expected, jsonStr);
+        
+        // null pojo
+        expected = new PreparedParameter();
+        expected.setType(AgentInformation.class);
+        expected.setValue(null);
+        jsonStr = gson.toJson(expected, PreparedParameter.class);
+        assertParameterEquals(expected, jsonStr);
+    }
+    
+    @Test
+    public void canSerializeDeserializeInnerClassPojoTypes() {
+        PreparedParameter expected = new PreparedParameter();
+        KeyValuePair pair = new KeyValuePair();
+        pair.setKey("foo");
+        pair.setValue("bar");
+        expected.setType(pair.getClass());
+        expected.setValue(pair);
+        String jsonStr = gson.toJson(expected, PreparedParameter.class);
+        
+        PreparedParameter actual = gson.fromJson(jsonStr, PreparedParameter.class);
+        
+        assertEquals(expected.getType(), actual.getType());
+        assertTrue(actual.getValue() instanceof KeyValuePair);
+        KeyValuePair actualPair = (KeyValuePair)actual.getValue();
+        assertEquals(pair.getKey(), actualPair.getKey());
+        assertEquals(pair.getValue(), actualPair.getValue());
+    }
+    
+    @Test
+    public void canSerializeDeserializePojoLists() {
+        AgentInformation info1 = new AgentInformation("foo-writer");
+        AgentInformation info2 = new AgentInformation("some-writer");
+        info2.setAlive(true);
+        info2.setConfigListenAddress("127.0.0.1:12000");
+        info2.setStartTime(System.currentTimeMillis());
+        info2.setStopTime(System.currentTimeMillis());
+        AgentInformation[] infos = new AgentInformation[] {
+                info1, info2
+        };
+        PreparedParameter param = new PreparedParameter();
+        param.setType(AgentInformation.class);
+        param.setValue(infos);
+        String jsonStr = gson.toJson(param, PreparedParameter.class);
+        assertParameterEquals(param, jsonStr);
+    }
+    
+    private void assertParameterEquals(PreparedParameter expected,
+            String jsonStr) {
+        PreparedParameter actual = gson.fromJson(jsonStr, PreparedParameter.class);
+        assertEquals(expected.getType(), actual.getType());
+        if (actual.getValue() != null && actual.getValue().getClass().isArray()) {
+            // compare element by element
+            Object values = actual.getValue();
+            Object expectedVals = expected.getValue();
+            int expectedLength = Array.getLength(expectedVals);
+            int actualLength = Array.getLength(values);
+            assertEquals(expectedLength, actualLength);
+            // Make sure the deserialized array is of the correct expected type
+            assertEquals(expectedVals.getClass(), values.getClass());
+            for (int i = 0; i < expectedLength; i++) {
+                Object exp = Array.get(expectedVals, i);
+                Object act = Array.get(values, i);
+                assertEquals(exp, act);
+            }
+        } else {
+            assertEquals(expected.getValue(), actual.getValue());
+        }
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/common/src/test/java/com/redhat/thermostat/web/common/typeadapters/PreparedParametersJSONPerformanceTest.java	Wed Sep 17 11:36:58 2014 +0200
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2012-2014 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.web.common.typeadapters;
+
+import org.junit.experimental.categories.Category;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.reflect.TypeToken;
+import com.redhat.thermostat.storage.core.PreparedParameters;
+import com.redhat.thermostat.testutils.PerformanceTest;
+import com.redhat.thermostat.web.common.typeadapters.PojoTypeAdapterFactory;
+import com.redhat.thermostat.web.common.typeadapters.PreparedParameterTypeAdapterFactory;
+import com.redhat.thermostat.web.common.typeadapters.PreparedParametersTypeAdapterFactory;
+
+/**
+ * JUnit categorized performance test. It'll be only run for
+ * the perf-tests profile.
+ */
+@Category(PerformanceTest.class)
+public class PreparedParametersJSONPerformanceTest extends
+        JsonPerformanceTest<PreparedParameters> {
+    
+    private static final String OLD_JSON_FORMAT = "{\"params\":[]}";
+    private static final String NEW_JSON_FORMAT = "[]";
+    private static final boolean DEBUG = true;
+
+    public PreparedParametersJSONPerformanceTest() {
+        super(DEBUG, PreparedParametersJSONPerformanceTest.class.getSimpleName());
+    }
+
+    @Override
+    protected Gson getSlowGson() {
+        return new GsonBuilder()
+                     .registerTypeAdapterFactory(new PojoTypeAdapterFactory())
+                     .registerTypeAdapterFactory(new PreparedParameterTypeAdapterFactory())
+                     .create();
+    }
+
+    @Override
+    protected Gson getFasterGson() {
+        return new GsonBuilder()
+                    .registerTypeAdapterFactory(new PojoTypeAdapterFactory())
+                    .registerTypeAdapterFactory(new PreparedParameterTypeAdapterFactory())
+                    .registerTypeAdapterFactory(new PreparedParametersTypeAdapterFactory())
+                    .create();
+    }
+
+    @Override
+    protected double getExpectedSpeedup(OperationType type,
+            int iterations) {
+        if (type == OperationType.SERIALIZATION && iterations == getColdSerializationIterations()) {
+            return 2;
+        } else if (type == OperationType.SERIALIZATION && iterations == getWarmSerializationIterations()) {
+            // actual should really be approaching 1.2.
+            return 0.95;
+        } else if (type == OperationType.DESERIALIZATION && iterations == getColdDeserializationIterations()) {
+            return 1.5;
+        } else if (type == OperationType.DESERIALIZATION && iterations == getWarmDeserializationIterations()) {
+            return 0.95;
+        } else {
+            throw new IllegalStateException("bug");
+        }
+    }
+
+    @Override
+    protected TypeToken<PreparedParameters> getDeserializeTypeToken() {
+        return TypeToken.get(PreparedParameters.class);
+    }
+
+    @Override
+    protected int getColdSerializationIterations() {
+        return 1;
+    }
+
+    @Override
+    protected int getWarmSerializationIterations() {
+        return 10000;
+    }
+
+    @Override
+    protected int getColdDeserializationIterations() {
+        return 1;
+    }
+
+    @Override
+    protected int getWarmDeserializationIterations() {
+        return 10000;
+    }
+
+    @Override
+    protected double getSelfSerializationDelta() {
+        return 0.3;
+    }
+
+    @Override
+    protected double getSelfDeserializationDelta() {
+        return 0.3;
+    }
+
+    @Override
+    protected String mutateJsonString(GsonContext context, int mutator) {
+        if (context == GsonContext.FASTER) {
+            return NEW_JSON_FORMAT;
+        } else if (context == GsonContext.SLOW) {
+            return OLD_JSON_FORMAT;
+        } else {
+            throw new IllegalStateException("bug");
+        }
+    }
+
+    @Override
+    protected PreparedParameters mutateToBeSerializedInstance(int mutator) {
+        return new PreparedParameters(0);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/common/src/test/java/com/redhat/thermostat/web/common/typeadapters/PreparedParametersTypeAdapterTest.java	Wed Sep 17 11:36:58 2014 +0200
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2012-2014 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.web.common.typeadapters;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.redhat.thermostat.storage.core.PreparedParameter;
+import com.redhat.thermostat.storage.core.PreparedParameters;
+import com.redhat.thermostat.storage.model.AgentInformation;
+import com.redhat.thermostat.storage.model.Pojo;
+
+public class PreparedParametersTypeAdapterTest {
+
+    private Gson gson;
+    
+    @Before
+    public void setup() {
+        gson = new GsonBuilder()
+                    .registerTypeAdapterFactory(new PojoTypeAdapterFactory())
+                    .registerTypeAdapterFactory(new PreparedParameterTypeAdapterFactory())
+                    .registerTypeAdapterFactory(new PreparedParametersTypeAdapterFactory())
+                    .create();
+    }
+    
+    @Test
+    public void canSerializeDeserializeBasic() {
+        PreparedParameters params = new PreparedParameters(5);
+        params.setBoolean(0, true);
+        params.setInt(1, 2300);
+        params.setLong(2, 2200000000L);
+        params.setString(3, "testing");
+        String[] list = new String[] {
+          "a", "b", "c"      
+        };
+        params.setStringList(4, list);
+        
+        String jsonStr = gson.toJson(params, PreparedParameters.class);
+        PreparedParameters actualParams = gson.fromJson(jsonStr, PreparedParameters.class);
+        
+        PreparedParameter[] expected = params.getParams();
+        PreparedParameter[] actual = actualParams.getParams();
+        
+        // last element is the string array, which we check manually
+        for (int i = 0; i < expected.length - 1; i++) {
+            assertEquals(expected[i].getType(), actual[i].getType());
+            assertEquals(expected[i].getValue(), actual[i].getValue());
+        }
+        String actualList[] = (String[])actual[4].getValue();
+        for (int i = 0; i < list.length; i++) {
+            assertEquals(list[i], actualList[i]);
+        }
+    }
+    
+    @Test
+    public void canSerializeDeserializeMixedTypesWithPojoList() {
+        AgentInformation info1 = new AgentInformation("foo-agent");
+        info1.setAlive(true);
+        AgentInformation info2 = new AgentInformation("foo-agent");
+        info2.setAlive(false);
+        info2.setStartTime(System.currentTimeMillis());
+        info2.setStopTime(System.currentTimeMillis());
+        info2.setConfigListenAddress("127.0.0.1:12000");
+        AgentInformation[] infos = new AgentInformation[] {
+                info1, info2
+        };
+        long[] longs = new long[] { 3000000000L, -3, 300 };
+        // String, long[], Pojo[]
+        PreparedParameters params = new PreparedParameters(3);
+        params.setString(0, "foo-param");
+        params.setLongList(1, longs);
+        params.setPojoList(2, infos);
+        
+        String jsonStr = gson.toJson(params, PreparedParameters.class);
+        PreparedParameters actualParams = gson.fromJson(jsonStr, PreparedParameters.class);
+        
+        PreparedParameter[] expected = params.getParams();
+        PreparedParameter[] actual = actualParams.getParams();
+        
+        assertEquals(expected.length, actual.length);
+        
+        PreparedParameter param1 = actual[0];
+        assertEquals("foo-param", param1.getValue());
+        assertEquals(String.class, param1.getType());
+        
+        PreparedParameter param2 = actual[1];
+        assertEquals(long[].class, param2.getType());
+        long[] twoActuals = (long[])param2.getValue();
+        assertEquals(3, twoActuals.length);
+        assertEquals(3000000000L, (long)twoActuals[0]);
+        assertEquals(-3, (long)twoActuals[1]);
+        assertEquals(300, (long)twoActuals[2]);
+        
+        PreparedParameter param3 = actual[2];
+        assertEquals(AgentInformation.class, param3.getType());
+        assertTrue(param3.getValue().getClass().isArray());
+        Pojo[] pojos = (Pojo[])param3.getValue();
+        assertEquals(2, pojos.length);
+        for (int i = 0; i < pojos.length; i++) {
+            assertEquals(infos[i], pojos[i]);
+        }
+    }
+    
+    
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/common/src/test/java/com/redhat/thermostat/web/common/typeadapters/WebPreparedStatementJSONPerformanceTest.java	Wed Sep 17 11:36:58 2014 +0200
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2012-2014 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.web.common.typeadapters;
+
+import org.junit.experimental.categories.Category;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.reflect.TypeToken;
+import com.redhat.thermostat.storage.model.Pojo;
+import com.redhat.thermostat.testutils.PerformanceTest;
+import com.redhat.thermostat.web.common.WebPreparedStatement;
+import com.redhat.thermostat.web.common.typeadapters.PojoTypeAdapterFactory;
+import com.redhat.thermostat.web.common.typeadapters.WebPreparedStatementTypeAdapterFactory;
+
+/**
+ * JUnit categorized performance test. It'll be only run for
+ * the perf-tests profile during a full build.
+ */
+@Category(PerformanceTest.class)
+@SuppressWarnings("rawtypes")
+public class WebPreparedStatementJSONPerformanceTest extends
+        JsonPerformanceTest<WebPreparedStatement> {
+
+    private static final boolean DEBUG = true;
+    
+    public WebPreparedStatementJSONPerformanceTest() {
+        super(DEBUG, WebPreparedStatementJSONPerformanceTest.class.getSimpleName());
+    }
+    
+    @Override
+    protected Gson getSlowGson() {
+        return new GsonBuilder()
+                    .registerTypeHierarchyAdapter(Pojo.class, new LegacyGSONConverter())
+                    .registerTypeAdapter(WebPreparedStatement.class, new LegacyWebPreparedStatementSerializer())
+                    .create();
+    }
+
+    @Override
+    protected Gson getFasterGson() {
+        return new GsonBuilder()
+                    .registerTypeAdapterFactory(new PojoTypeAdapterFactory())
+                    .registerTypeAdapterFactory(new WebPreparedStatementTypeAdapterFactory())
+                    .create();
+    }
+
+    @Override
+    protected double getExpectedSpeedup(OperationType type, int iterations) {
+        if (type == OperationType.SERIALIZATION && iterations == getColdSerializationIterations()) {
+            return 0.95;
+        } else if (type == OperationType.SERIALIZATION && iterations == getWarmSerializationIterations()) {
+            return 1.5;
+        } else if (type == OperationType.DESERIALIZATION && iterations == getColdDeserializationIterations()) {
+            return 0.95;
+        } else if (type == OperationType.DESERIALIZATION && iterations == getWarmDeserializationIterations()) {
+            return 2;
+        } else {
+            throw new IllegalStateException("Bug");
+        }
+    }
+
+    @Override
+    protected String mutateJsonString(GsonContext ctx, int mutator) {
+        return String.format("{\"sid\":%d,\"p\":{\"params\":[]}}", mutator);
+    }
+
+    @Override
+    protected WebPreparedStatement mutateToBeSerializedInstance(int mutator) {
+        WebPreparedStatement retval = new WebPreparedStatement<>(0, 555);
+        retval.setStatementId(mutator);
+        return retval;
+    }
+
+    @Override
+    protected TypeToken<WebPreparedStatement> getDeserializeTypeToken() {
+        return TypeToken.get(WebPreparedStatement.class);
+    }
+
+    @Override
+    protected int getColdSerializationIterations() {
+        return 1;
+    }
+
+    @Override
+    protected int getWarmSerializationIterations() {
+        return 10000;
+    }
+
+    @Override
+    protected int getColdDeserializationIterations() {
+        return 1;
+    }
+
+    @Override
+    protected int getWarmDeserializationIterations() {
+        return 10000;
+    }
+
+    @Override
+    protected double getSelfSerializationDelta() {
+        return 0.4;
+    }
+
+    @Override
+    protected double getSelfDeserializationDelta() {
+        return 0.1;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/common/src/test/java/com/redhat/thermostat/web/common/typeadapters/WebPreparedStatementResponseJSONPerformanceTest.java	Wed Sep 17 11:36:58 2014 +0200
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2012-2014 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.web.common.typeadapters;
+
+import org.junit.experimental.categories.Category;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.reflect.TypeToken;
+import com.redhat.thermostat.testutils.PerformanceTest;
+import com.redhat.thermostat.web.common.WebPreparedStatementResponse;
+import com.redhat.thermostat.web.common.typeadapters.WebPreparedStatementResponseTypeAdapterFactory;
+
+/**
+ * JUnit categorized performance test. It'll be only run for
+ * the perf-tests profile during a full build.
+ */
+@Category(PerformanceTest.class)
+public class WebPreparedStatementResponseJSONPerformanceTest extends
+        JsonPerformanceTest<WebPreparedStatementResponse> {
+
+    // Set to true in order to turn on debugging output
+    private static final boolean DEBUG = true;
+    
+    public WebPreparedStatementResponseJSONPerformanceTest() {
+        super(DEBUG, WebPreparedStatementResponseJSONPerformanceTest.class.getSimpleName()); 
+    }
+    
+    @Override
+    protected Gson getSlowGson() {
+        return new GsonBuilder().create();
+    }
+
+    @Override
+    protected Gson getFasterGson() {
+        return new GsonBuilder()
+                    .registerTypeAdapterFactory(new WebPreparedStatementResponseTypeAdapterFactory())
+                    .create();
+    }
+
+    @Override
+    protected double getExpectedSpeedup(OperationType type, int iterations) {
+        if (type == OperationType.DESERIALIZATION && iterations == getColdDeserializationIterations()) {
+            return 4;
+        } else if (type == OperationType.DESERIALIZATION && iterations == getWarmDeserializationIterations()) {
+            // Actual speed-up of the warmed up scenario is roughly
+            // >= 1.0. However, there are cases where it falls below it.
+            // A lower bound of 0.95 should be adequate in order for tests to
+            // not fail spuriously (at least not as often).
+            return 0.95;
+        } else if (type == OperationType.SERIALIZATION && iterations == getColdSerializationIterations()) {
+            return 4;
+        } else if (type == OperationType.SERIALIZATION && iterations == getWarmSerializationIterations()) {
+            return 1.2;
+        } else {
+            throw new IllegalStateException("Bug!");
+        }
+    }
+
+    @Override
+    protected String mutateJsonString(GsonContext ctx, int mutator) {
+        int stmtId = (mutator + 1) * 20;
+        int numFreeVars = mutator;
+        return String.format("{\"numFreeVars\":%d,\"stmtId\":%d}", numFreeVars, stmtId);
+    }
+
+    @Override
+    protected WebPreparedStatementResponse mutateToBeSerializedInstance(int mutator) {
+        WebPreparedStatementResponse response = new WebPreparedStatementResponse();
+        response.setNumFreeVariables(3);
+        response.setStatementId(mutator);
+        return response;
+    }
+
+    @Override
+    protected TypeToken<WebPreparedStatementResponse> getDeserializeTypeToken() {
+        return TypeToken.get(WebPreparedStatementResponse.class);
+    }
+
+    @Override
+    protected int getColdSerializationIterations() {
+        return 1;
+    }
+
+    @Override
+    protected int getWarmSerializationIterations() {
+        return 10000;
+    }
+
+    @Override
+    protected int getColdDeserializationIterations() {
+        return 1;
+    }
+
+    @Override
+    protected int getWarmDeserializationIterations() {
+        return 5000;
+    }
+
+    @Override
+    protected double getSelfSerializationDelta() {
+        return 0.1;
+    }
+
+    @Override
+    protected double getSelfDeserializationDelta() {
+        return 0.3;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/common/src/test/java/com/redhat/thermostat/web/common/typeadapters/WebPreparedStatementResponseTypeAdapterTest.java	Wed Sep 17 11:36:58 2014 +0200
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2012-2014 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.web.common.typeadapters;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonSyntaxException;
+import com.redhat.thermostat.web.common.WebPreparedStatementResponse;
+
+public class WebPreparedStatementResponseTypeAdapterTest {
+
+    private Gson gson;
+    
+    @Before
+    public void setup() {
+        gson = new GsonBuilder()
+                .registerTypeAdapterFactory(new WebPreparedStatementResponseTypeAdapterFactory())
+                .create();
+    }
+    
+    @Test
+    public void testSerializationDeserializationBasic() {
+        WebPreparedStatementResponse response = new WebPreparedStatementResponse();
+        response.setNumFreeVariables(6);
+        response.setStatementId(WebPreparedStatementResponse.ILLEGAL_STATEMENT);
+        
+        String jsonStr = gson.toJson(response, WebPreparedStatementResponse.class);
+        String expectedString = "{\"numFreeVars\":6,\"stmtId\":-1}";
+        assertEquals(expectedString, jsonStr);
+        
+        WebPreparedStatementResponse actual = gson.fromJson(jsonStr, WebPreparedStatementResponse.class);
+        
+        assertEquals(6, actual.getNumFreeVariables());
+        assertEquals(WebPreparedStatementResponse.ILLEGAL_STATEMENT, actual.getStatementId());
+    }
+    
+    @Test(expected=JsonSyntaxException.class)
+    public void failDeserializePrimitiveSetNull() {
+        String jsonString = "{\"numFreeVars\":11,\"stmtId\":null}";
+        gson.fromJson(jsonString, WebPreparedStatementResponse.class);
+    }
+    
+    @Test
+    public void canDeserializeBasic() {
+        String jsonString = "{\"numFreeVars\":11,\"stmtId\":6}";
+        WebPreparedStatementResponse actual = gson.fromJson(jsonString, WebPreparedStatementResponse.class);
+        assertEquals(6, actual.getStatementId());
+        assertEquals(11, actual.getNumFreeVariables());
+    }
+    
+    @Test(expected=JsonSyntaxException.class)
+    public void testDeserializationFail() {
+        String invalidString = "{\"forbar\":6,\"stmtId\":-1}";
+        gson.fromJson(invalidString, WebPreparedStatementResponse.class);
+    }
+    
+    @Test
+    public void canSerializeDeserializeNull() {
+        WebPreparedStatementResponse response = null;
+        assertNull(response);
+        String json = gson.toJson(response, WebPreparedStatementResponse.class);
+        assertEquals("null", json);
+        WebPreparedStatementResponse actual = gson.fromJson(json, WebPreparedStatementResponse.class);
+        assertNull(actual);
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/common/src/test/java/com/redhat/thermostat/web/common/typeadapters/WebPreparedStatementTypeAdapterTest.java	Wed Sep 17 11:36:58 2014 +0200
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2012-2014 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.web.common.typeadapters;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.redhat.thermostat.storage.core.PreparedParameter;
+import com.redhat.thermostat.storage.core.PreparedParameters;
+import com.redhat.thermostat.storage.model.AgentInformation;
+import com.redhat.thermostat.web.common.WebPreparedStatement;
+import com.redhat.thermostat.web.common.WebPreparedStatementResponse;
+
+public class WebPreparedStatementTypeAdapterTest {
+
+    private Gson gson;
+    
+    @Before
+    public void setup() {
+        gson = new GsonBuilder()
+                .registerTypeAdapterFactory(new WebPreparedStatementTypeAdapterFactory())
+                .registerTypeAdapterFactory(new PreparedParameterTypeAdapterFactory())
+                .registerTypeAdapterFactory(new PojoTypeAdapterFactory())
+                .create();
+    }
+    
+    @Test
+    public void canSerializeNullParams() {
+        WebPreparedStatement<?> stmt = new WebPreparedStatement<>();
+        stmt.setStatementId(500);
+        stmt.setParams(null);
+        
+        String expected = "{\"sid\":500}";
+        
+        String actual = gson.toJson(stmt);
+        assertEquals(expected, actual);
+    }
+    
+    @Test
+    public void canDeserializeNullParams() {
+        String json = "{\"sid\": 500}";
+        
+        WebPreparedStatement<?> stmt = gson.fromJson(json, WebPreparedStatement.class);
+        assertEquals(500, stmt.getStatementId());
+        assertNull(stmt.getParams());
+    }
+    
+    @Test
+    public void canSerializeEmptyParams() {
+        WebPreparedStatement<?> stmt = new WebPreparedStatement<>(0, 555);
+        
+        String expected = "{\"sid\":555,\"p\":{\"params\":[]}}";
+        String actual = gson.toJson(stmt);
+        assertEquals(expected, actual);
+    }
+    
+    @Test
+    public void canDeserializeEmptyParams() {
+        String json = "{\"sid\":555,\"p\":{\"params\":[]}}";
+        
+        WebPreparedStatement<?> stmt = gson.fromJson(json, WebPreparedStatement.class);
+        assertEquals(555, stmt.getStatementId());
+        assertNotNull(stmt.getParams());
+        assertEquals(0, stmt.getParams().getParams().length);
+    }
+    
+    @Test
+    public void canSerializeAndDeserialize() {
+        PreparedParameters params = new PreparedParameters(5);
+        params.setInt(0, 2);
+        params.setString(1, "testing");
+        params.setLong(2, 222L);
+        params.setStringList(3, new String[] { "one", "two" });
+        params.setBoolean(4, true);
+        WebPreparedStatement<?> stmt = new WebPreparedStatement<>();
+        stmt.setParams(params);
+        stmt.setStatementId(WebPreparedStatementResponse.DESCRIPTOR_PARSE_FAILED);
+        String jsonString = gson.toJson(stmt, WebPreparedStatement.class);
+        WebPreparedStatement<?> newStmt = gson.fromJson(jsonString, WebPreparedStatement.class);
+        assertNotNull(newStmt);
+        PreparedParameters newParams = newStmt.getParams();
+        PreparedParameter[] parameters = newParams.getParams();
+        assertEquals(5, parameters.length);
+        assertEquals(2, parameters[0].getValue());
+        assertEquals(int.class, parameters[0].getType());
+        assertEquals("testing", parameters[1].getValue());
+        assertEquals(String.class, parameters[1].getType());
+        assertEquals(222L, parameters[2].getValue());
+        assertEquals(long.class, parameters[2].getType());
+        String[] list = (String[])parameters[3].getValue();
+        assertEquals(2, list.length);
+        assertEquals("one", list[0]);
+        assertEquals("two", list[1]);
+        assertEquals(String[].class, parameters[3].getType());
+        assertEquals(true, parameters[4].getValue());
+        assertEquals(boolean.class, parameters[4].getType());
+        assertEquals(WebPreparedStatementResponse.DESCRIPTOR_PARSE_FAILED, newStmt.getStatementId());
+    }
+    
+    /*
+     * Writes need Pojo support for serialization. This is a basic test we do
+     * get Pojos across the wire in a prepared context. 
+     */
+    @Test
+    public void canSerializeDeserializePojoParameters() {
+        PreparedParameters params = new PreparedParameters(2);
+        params.setIntList(0, new int[] { 0, 300 });
+        AgentInformation pojo1 = new AgentInformation("foo-agent");
+        AgentInformation pojo2 = new AgentInformation("foo-agent");
+        pojo2.setAlive(true);
+        pojo2.setConfigListenAddress("127.0.0.1:38822");
+        params.setPojoList(1, new AgentInformation[] { pojo1, pojo2 });
+        
+        WebPreparedStatement<?> stmt = new WebPreparedStatement<>();
+        stmt.setParams(params);
+        stmt.setStatementId(WebPreparedStatementResponse.DESCRIPTOR_PARSE_FAILED);
+        
+        String jsonString = gson.toJson(stmt, WebPreparedStatement.class);
+        assertNotNull(jsonString);
+        
+        WebPreparedStatement<?> result = gson.fromJson(jsonString, WebPreparedStatement.class);
+        assertEquals(WebPreparedStatementResponse.DESCRIPTOR_PARSE_FAILED, result.getStatementId());
+        assertNotNull(result.getParams());
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/common/src/test/java/com/redhat/thermostat/web/common/typeadapters/WebQueryResponseTypeAdapterTest.java	Wed Sep 17 11:36:58 2014 +0200
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2012-2014 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.web.common.typeadapters;
+
+import static org.junit.Assert.assertEquals;
+
+import java.lang.reflect.Type;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.reflect.TypeToken;
+import com.redhat.thermostat.storage.model.AgentInformation;
+import com.redhat.thermostat.storage.model.HostInfo;
+import com.redhat.thermostat.web.common.PreparedStatementResponseCode;
+import com.redhat.thermostat.web.common.WebQueryResponse;
+import com.redhat.thermostat.web.common.typeadapters.PojoTypeAdapterFactory;
+import com.redhat.thermostat.web.common.typeadapters.WebQueryResponseTypeAdapterFactory;
+
+public class WebQueryResponseTypeAdapterTest {
+
+    private Gson gson;
+    
+    @Before
+    public void setup() {
+        gson = new GsonBuilder()
+                    .registerTypeAdapterFactory(new PojoTypeAdapterFactory())
+                    .registerTypeAdapterFactory(new WebQueryResponseTypeAdapterFactory())
+                    .create();
+    }
+    
+    @Test
+    public void canSerializeBasic() {
+        // Our test pojo
+        AgentInformation agentInfo = new AgentInformation("testing");
+        agentInfo.setAlive(false);
+        AgentInformation[] resultList = new AgentInformation[] {
+                agentInfo
+        };
+        
+        // create the query response
+        WebQueryResponse<AgentInformation> response = new WebQueryResponse<>();
+        response.setResultList(resultList);
+        response.setResponseCode(PreparedStatementResponseCode.ILLEGAL_PATCH);
+        
+        String jsonStr = gson.toJson(response);
+        String expectedJson = "{\"errno\":-1,\"payload\":[{\"agentId\":\"testing\",\"alive\":false,\"startTime\":0,\"stopTime\":0}]}";
+        assertEquals(expectedJson, jsonStr);
+    }
+    
+    @Test
+    public void canDeserializeBasic() {
+        String rawJson = "{\"errno\":-1,\"payload\":[{\"startTime\":0,\"stopTime\":0,\"alive\":true,\"agentId\":\"testing\"}]}";
+        Type queryResponseType = new TypeToken<WebQueryResponse<AgentInformation>>() {}.getType();
+        WebQueryResponse<AgentInformation> actual = gson.fromJson(rawJson, queryResponseType);
+        
+        AgentInformation[] actualList = actual.getResultList();
+        
+        assertEquals(PreparedStatementResponseCode.ILLEGAL_PATCH, actual.getResponseCode());
+        assertEquals(1, actualList.length);
+        AgentInformation actualInfo = actualList[0];
+        assertEquals(true, actualInfo.isAlive());
+        assertEquals("testing", actualInfo.getAgentId());
+    }
+    
+    @Test
+    public void canSerializeAndDeserializeBasic() {
+        // Our test pojo
+        AgentInformation agentInfo = new AgentInformation("testing");
+        agentInfo.setAlive(false);
+        AgentInformation[] resultList = new AgentInformation[] {
+                agentInfo
+        };
+        
+        // create the query response
+        WebQueryResponse<AgentInformation> response = new WebQueryResponse<>();
+        response.setResultList(resultList);
+        response.setResponseCode(PreparedStatementResponseCode.ILLEGAL_PATCH);
+        
+        String jsonStr = gson.toJson(response);
+
+        // We need to tell GSON which parametrized type we want it to deserialize
+        // it to.
+        Type queryResponseType = new TypeToken<WebQueryResponse<AgentInformation>>() {}.getType();
+        WebQueryResponse<AgentInformation> actual = gson.fromJson(jsonStr, queryResponseType);
+        
+        AgentInformation[] actualList = actual.getResultList();
+        
+        assertEquals(PreparedStatementResponseCode.ILLEGAL_PATCH, actual.getResponseCode());
+        assertEquals(1, actualList.length);
+        AgentInformation actualInfo = actualList[0];
+        assertEquals(false, actualInfo.isAlive());
+        assertEquals("testing", actualInfo.getAgentId());
+    }
+    
+    @Test
+    public void canSerializeAndDeserializeVariousPojos() {
+        // Our test pojo
+        AgentInformation agentInfo = new AgentInformation("testing");
+        agentInfo.setAlive(false);
+        AgentInformation[] resultList = new AgentInformation[] {
+                agentInfo
+        };
+        
+        // create the query response
+        WebQueryResponse<AgentInformation> response = new WebQueryResponse<>();
+        response.setResultList(resultList);
+        response.setResponseCode(PreparedStatementResponseCode.ILLEGAL_PATCH);
+        
+        String jsonStr = gson.toJson(response);
+        String expectedJson = "{\"errno\":-1,\"payload\":[{\"agentId\":\"testing\",\"alive\":false,\"startTime\":0,\"stopTime\":0}]}";
+        assertEquals(expectedJson, jsonStr);
+
+        // We need to tell GSON which parametrized type we want it to deserialize
+        // it to.
+        Type queryResponseType = new TypeToken<WebQueryResponse<AgentInformation>>() {}.getType();
+        WebQueryResponse<AgentInformation> actual = gson.fromJson(jsonStr, queryResponseType);
+        
+        AgentInformation[] actualList = actual.getResultList();
+        
+        assertEquals(PreparedStatementResponseCode.ILLEGAL_PATCH, actual.getResponseCode());
+        assertEquals(1, actualList.length);
+        AgentInformation actualInfo = actualList[0];
+        assertEquals(false, actualInfo.isAlive());
+        assertEquals("testing", actualInfo.getAgentId());
+        
+        // Do it again using HostInfo as model
+        HostInfo hostInfo = new HostInfo();
+        hostInfo.setAgentId("something");
+        hostInfo.setCpuCount(56);
+        hostInfo.setHostname("flukebox");
+        
+        HostInfo[] hostInfoResults = new HostInfo[] {
+                hostInfo
+        };
+        
+        WebQueryResponse<HostInfo> expected = new WebQueryResponse<>();
+        expected.setResultList(hostInfoResults);
+        expected.setResponseCode(PreparedStatementResponseCode.QUERY_SUCCESS);
+        
+        jsonStr = gson.toJson(expected);
+        Type hostinfoQueryResponseType = new TypeToken<WebQueryResponse<HostInfo>>() {}.getType();
+        WebQueryResponse<HostInfo> actualResp = gson.fromJson(jsonStr, hostinfoQueryResponseType);
+        
+        assertEquals(PreparedStatementResponseCode.QUERY_SUCCESS, actualResp.getResponseCode());
+        HostInfo[] hostInfoList = actualResp.getResultList();
+        assertEquals(1, hostInfoList.length);
+        assertEquals("something", hostInfoList[0].getAgentId());
+        assertEquals(56, hostInfoList[0].getCpuCount());
+        assertEquals("flukebox", hostInfoList[0].getHostname());
+    }
+}
+
--- a/web/server/src/main/java/com/redhat/thermostat/web/server/WebStorageEndPoint.java	Tue Sep 30 11:57:25 2014 +0200
+++ b/web/server/src/main/java/com/redhat/thermostat/web/server/WebStorageEndPoint.java	Wed Sep 17 11:36:58 2014 +0200
@@ -98,14 +98,16 @@
 import com.redhat.thermostat.storage.query.BinaryLogicalExpression;
 import com.redhat.thermostat.storage.query.BinaryLogicalOperator;
 import com.redhat.thermostat.storage.query.Expression;
-import com.redhat.thermostat.web.common.PreparedParameterSerializer;
 import com.redhat.thermostat.web.common.PreparedStatementResponseCode;
-import com.redhat.thermostat.web.common.ThermostatGSONConverter;
 import com.redhat.thermostat.web.common.WebPreparedStatement;
 import com.redhat.thermostat.web.common.WebPreparedStatementResponse;
-import com.redhat.thermostat.web.common.WebPreparedStatementSerializer;
 import com.redhat.thermostat.web.common.WebQueryResponse;
-import com.redhat.thermostat.web.common.WebQueryResponseSerializer;
+import com.redhat.thermostat.web.common.typeadapters.PojoTypeAdapterFactory;
+import com.redhat.thermostat.web.common.typeadapters.PreparedParameterTypeAdapterFactory;
+import com.redhat.thermostat.web.common.typeadapters.PreparedParametersTypeAdapterFactory;
+import com.redhat.thermostat.web.common.typeadapters.WebPreparedStatementResponseTypeAdapterFactory;
+import com.redhat.thermostat.web.common.typeadapters.WebPreparedStatementTypeAdapterFactory;
+import com.redhat.thermostat.web.common.typeadapters.WebQueryResponseTypeAdapterFactory;
 import com.redhat.thermostat.web.server.auth.FilterResult;
 import com.redhat.thermostat.web.server.auth.PrincipalCallback;
 import com.redhat.thermostat.web.server.auth.PrincipalCallbackFactory;
@@ -169,11 +171,12 @@
         checkThermostatHome();
         
         gson = new GsonBuilder()
-                .registerTypeHierarchyAdapter(Pojo.class,
-                        new ThermostatGSONConverter())
-                .registerTypeAdapter(WebQueryResponse.class, new WebQueryResponseSerializer<>())
-                .registerTypeAdapter(PreparedParameter.class, new PreparedParameterSerializer())
-                .registerTypeAdapter(WebPreparedStatement.class, new WebPreparedStatementSerializer())
+                .registerTypeAdapterFactory(new PojoTypeAdapterFactory())
+                .registerTypeAdapterFactory(new WebPreparedStatementResponseTypeAdapterFactory())
+                .registerTypeAdapterFactory(new WebQueryResponseTypeAdapterFactory())
+                .registerTypeAdapterFactory(new PreparedParameterTypeAdapterFactory())
+                .registerTypeAdapterFactory(new WebPreparedStatementTypeAdapterFactory())
+                .registerTypeAdapterFactory(new PreparedParametersTypeAdapterFactory())
                 .create();
         categoryIds = new HashMap<>();
         categories = new HashMap<>();
--- a/web/server/src/test/java/com/redhat/thermostat/web/server/WebStorageEndpointTest.java	Tue Sep 30 11:57:25 2014 +0200
+++ b/web/server/src/test/java/com/redhat/thermostat/web/server/WebStorageEndpointTest.java	Wed Sep 17 11:36:58 2014 +0200
@@ -123,20 +123,21 @@
 import com.redhat.thermostat.storage.model.AggregateCount;
 import com.redhat.thermostat.storage.model.BasePojo;
 import com.redhat.thermostat.storage.model.HostInfo;
-import com.redhat.thermostat.storage.model.Pojo;
 import com.redhat.thermostat.storage.query.BinarySetMembershipExpression;
 import com.redhat.thermostat.storage.query.Expression;
 import com.redhat.thermostat.storage.query.ExpressionFactory;
 import com.redhat.thermostat.test.FreePortFinder;
 import com.redhat.thermostat.test.FreePortFinder.TryPort;
-import com.redhat.thermostat.web.common.PreparedParameterSerializer;
 import com.redhat.thermostat.web.common.PreparedStatementResponseCode;
-import com.redhat.thermostat.web.common.ThermostatGSONConverter;
 import com.redhat.thermostat.web.common.WebPreparedStatement;
 import com.redhat.thermostat.web.common.WebPreparedStatementResponse;
-import com.redhat.thermostat.web.common.WebPreparedStatementSerializer;
 import com.redhat.thermostat.web.common.WebQueryResponse;
-import com.redhat.thermostat.web.common.WebQueryResponseSerializer;
+import com.redhat.thermostat.web.common.typeadapters.PojoTypeAdapterFactory;
+import com.redhat.thermostat.web.common.typeadapters.PreparedParameterTypeAdapterFactory;
+import com.redhat.thermostat.web.common.typeadapters.PreparedParametersTypeAdapterFactory;
+import com.redhat.thermostat.web.common.typeadapters.WebPreparedStatementResponseTypeAdapterFactory;
+import com.redhat.thermostat.web.common.typeadapters.WebPreparedStatementTypeAdapterFactory;
+import com.redhat.thermostat.web.common.typeadapters.WebQueryResponseTypeAdapterFactory;
 import com.redhat.thermostat.web.server.auth.BasicRole;
 import com.redhat.thermostat.web.server.auth.RolePrincipal;
 import com.redhat.thermostat.web.server.auth.Roles;
@@ -330,10 +331,12 @@
         conn.setDoInput(true);
         conn.setDoOutput(true);
         Gson gson = new GsonBuilder()
-                .registerTypeHierarchyAdapter(WebQueryResponse.class, new WebQueryResponseSerializer<>())
-                .registerTypeAdapter(Pojo.class, new ThermostatGSONConverter())
-                .registerTypeAdapter(WebPreparedStatement.class, new WebPreparedStatementSerializer())
-                .registerTypeAdapter(PreparedParameter.class, new PreparedParameterSerializer()).create();
+                        .registerTypeAdapterFactory(new PojoTypeAdapterFactory())
+                        .registerTypeAdapterFactory(new WebPreparedStatementResponseTypeAdapterFactory())
+                        .registerTypeAdapterFactory(new WebQueryResponseTypeAdapterFactory())
+                        .registerTypeAdapterFactory(new PreparedParameterTypeAdapterFactory())
+                        .registerTypeAdapterFactory(new WebPreparedStatementTypeAdapterFactory())
+                        .create();
         OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream());
         String body = "query-descriptor=" + URLEncoder.encode(strDescriptor, "UTF-8") + "&category-id=" + categoryId;
         out.write(body + "\n");
@@ -414,10 +417,13 @@
         conn.setDoInput(true);
         conn.setDoOutput(true);
         Gson gson = new GsonBuilder()
-                .registerTypeHierarchyAdapter(WebQueryResponse.class, new WebQueryResponseSerializer<>())
-                .registerTypeAdapter(Pojo.class, new ThermostatGSONConverter())
-                .registerTypeAdapter(WebPreparedStatement.class, new WebPreparedStatementSerializer())
-                .registerTypeAdapter(PreparedParameter.class, new PreparedParameterSerializer()).create();
+                            .registerTypeAdapterFactory(new PojoTypeAdapterFactory())
+                            .registerTypeAdapterFactory(new WebPreparedStatementResponseTypeAdapterFactory())
+                            .registerTypeAdapterFactory(new WebQueryResponseTypeAdapterFactory())
+                            .registerTypeAdapterFactory(new PreparedParameterTypeAdapterFactory())
+                            .registerTypeAdapterFactory(new WebPreparedStatementTypeAdapterFactory())
+                            .registerTypeAdapterFactory(new PreparedParametersTypeAdapterFactory())
+                            .create();
         OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream());
         String body = "query-descriptor=" + URLEncoder.encode(strDescriptor, "UTF-8") + "&category-id=" + categoryId;
         out.write(body + "\n");
@@ -544,10 +550,13 @@
             conn.setDoInput(true);
             conn.setDoOutput(true);
             Gson gson = new GsonBuilder()
-                    .registerTypeHierarchyAdapter(WebQueryResponse.class, new WebQueryResponseSerializer<>())
-                    .registerTypeAdapter(Pojo.class, new ThermostatGSONConverter())
-                    .registerTypeAdapter(WebPreparedStatement.class, new WebPreparedStatementSerializer())
-                    .registerTypeAdapter(PreparedParameter.class, new PreparedParameterSerializer()).create();
+                                .registerTypeAdapterFactory(new PojoTypeAdapterFactory())
+                                .registerTypeAdapterFactory(new WebPreparedStatementResponseTypeAdapterFactory())
+                                .registerTypeAdapterFactory(new WebQueryResponseTypeAdapterFactory())
+                                .registerTypeAdapterFactory(new PreparedParameterTypeAdapterFactory())
+                                .registerTypeAdapterFactory(new WebPreparedStatementTypeAdapterFactory())
+                                .registerTypeAdapterFactory(new PreparedParametersTypeAdapterFactory())
+                                .create();
             OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream());
             String body = "query-descriptor=" + URLEncoder.encode(strDescriptor, "UTF-8") + "&category-id=" + categoryId;
             out.write(body + "\n");
@@ -664,10 +673,13 @@
         conn.setDoInput(true);
         conn.setDoOutput(true);
         Gson gson = new GsonBuilder()
-                .registerTypeHierarchyAdapter(WebQueryResponse.class, new WebQueryResponseSerializer<>())
-                .registerTypeAdapter(Pojo.class, new ThermostatGSONConverter())
-                .registerTypeAdapter(WebPreparedStatement.class, new WebPreparedStatementSerializer())
-                .registerTypeAdapter(PreparedParameter.class, new PreparedParameterSerializer()).create();
+                            .registerTypeAdapterFactory(new PojoTypeAdapterFactory())
+                            .registerTypeAdapterFactory(new WebPreparedStatementResponseTypeAdapterFactory())
+                            .registerTypeAdapterFactory(new WebQueryResponseTypeAdapterFactory())
+                            .registerTypeAdapterFactory(new PreparedParameterTypeAdapterFactory())
+                            .registerTypeAdapterFactory(new WebPreparedStatementTypeAdapterFactory())
+                            .registerTypeAdapterFactory(new PreparedParametersTypeAdapterFactory())
+                            .create();
         OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream());
         String body = "query-descriptor=" + URLEncoder.encode(strDescriptor, "UTF-8") + "&category-id=" + categoryId;
         out.write(body + "\n");
@@ -793,9 +805,12 @@
             conn.setDoInput(true);
             conn.setDoOutput(true);
             Gson gson = new GsonBuilder()
-                    .registerTypeHierarchyAdapter(Pojo.class, new ThermostatGSONConverter())
-                    .registerTypeAdapter(WebPreparedStatement.class, new WebPreparedStatementSerializer())
-                    .registerTypeAdapter(PreparedParameter.class, new PreparedParameterSerializer()).create();
+                .registerTypeAdapterFactory(new PojoTypeAdapterFactory())
+                .registerTypeAdapterFactory(new PreparedParameterTypeAdapterFactory())
+                .registerTypeAdapterFactory(new WebPreparedStatementTypeAdapterFactory())
+                .registerTypeAdapterFactory(new WebPreparedStatementResponseTypeAdapterFactory())
+                .registerTypeAdapterFactory(new PreparedParametersTypeAdapterFactory())
+                .create();
             OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream());
             String body = "query-descriptor=" + URLEncoder.encode(strDescriptor, "UTF-8") + "&category-id=" + categoryId;
             out.write(body + "\n");