changeset 1266:d387d381858b

Make prepared writes work with WebStorage. Reviewed-by: omajid Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2013-September/008323.html
author Severin Gehwolf <sgehwolf@redhat.com>
date Wed, 25 Sep 2013 19:09:58 +0200
parents 0e0e2b6041ad
children f55a4281c8f0
files host-cpu/common/src/main/java/com/redhat/thermostat/host/cpu/common/internal/CpuStatDAOImplStatementDescriptorRegistration.java host-cpu/common/src/test/java/com/redhat/thermostat/host/cpu/common/internal/CpuStatDAOImplStatementDescriptorRegistrationTest.java host-memory/common/src/main/java/com/redhat/thermostat/host/memory/common/internal/MemoryStatDAOImplStatementDescriptorRegistration.java host-memory/common/src/test/java/com/redhat/thermostat/host/memory/common/internal/MemoryStatDAOImplStatementDescriptorRegistrationTest.java integration-tests/src/test/java/com/redhat/thermostat/itest/MongoQueriesTest.java integration-tests/src/test/java/com/redhat/thermostat/itest/WebAppTest.java numa/common/src/main/java/com/redhat/thermostat/numa/common/internal/NumaDAOImplStatementDescriptorRegistration.java numa/common/src/test/java/com/redhat/thermostat/numa/common/internal/NumaDAOImplStatementDescriptorRegistrationTest.java storage/core/src/main/java/com/redhat/thermostat/storage/core/BackingStorage.java storage/core/src/main/java/com/redhat/thermostat/storage/core/PreparedParameter.java storage/core/src/main/java/com/redhat/thermostat/storage/core/PreparedParameters.java storage/core/src/main/java/com/redhat/thermostat/storage/core/QueuedBackingStorage.java storage/core/src/main/java/com/redhat/thermostat/storage/core/QueuedStorage.java storage/core/src/main/java/com/redhat/thermostat/storage/core/Storage.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/dao/DAOImplStatementDescriptorRegistration.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/LimitExpression.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/SortMember.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/StatementDescriptorParser.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/TerminalNode.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/UnfinishedValueNode.java storage/core/src/test/java/com/redhat/thermostat/storage/core/QueuedBackingStorageTest.java storage/core/src/test/java/com/redhat/thermostat/storage/core/QueuedStorageTest.java storage/core/src/test/java/com/redhat/thermostat/storage/internal/dao/DAOImplStatementDescriptorRegistrationTest.java storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/LimitExpressionTest.java storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/ParsedStatementImplTest.java storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/PatchedSetListPojoConverterTest.java storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/SortMemberTest.java storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/StatementDescriptorParserTest.java storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/TerminalNodeTest.java storage/mongo/src/main/java/com/redhat/thermostat/storage/mongodb/internal/MongoStorage.java thread/collector/src/main/java/com/redhat/thermostat/thread/dao/impl/ThreadDaoImplStatementDescriptorRegistration.java thread/collector/src/test/java/com/redhat/thermostat/thread/dao/impl/ThreadDAOImplStatementDescriptorRegistrationTest.java vm-classstat/common/src/main/java/com/redhat/thermostat/vm/classstat/common/internal/VmClassStatDAOImplStatementDescriptorRegistration.java vm-classstat/common/src/test/java/com/redhat/thermostat/vm/classstat/common/internal/VmClassStatDAOImplStatementDescriptorRegistrationTest.java vm-cpu/common/src/main/java/com/redhat/thermostat/vm/cpu/common/internal/VmCpuStatDAOImplStatementDescriptorRegistration.java vm-cpu/common/src/test/java/com/redhat/thermostat/vm/cpu/common/internal/VmCpuStatDAOImplStatementDescriptorRegistrationTest.java vm-gc/common/src/main/java/com/redhat/thermostat/vm/gc/common/internal/VmGcStatDAOImplStatementDescriptorRegistration.java vm-gc/common/src/test/java/com/redhat/thermostat/vm/gc/common/internal/VmGcStatDAOImplStatementDescriptorRegistrationTest.java vm-heap-analysis/common/src/main/java/com/redhat/thermostat/vm/heap/analysis/common/internal/HeapDAOImplStatementDescriptorRegistration.java vm-heap-analysis/common/src/test/java/com/redhat/thermostat/vm/heap/analysis/common/internal/HeapDAOImplStatementDescriptorRegistrationTest.java vm-jmx/common/src/main/java/com/redhat/thermostat/vm/jmx/common/internal/JmxNotificationDAOImplStatementDescriptorRegistration.java vm-jmx/common/src/test/java/com/redhat/thermostat/vm/jmx/common/internal/JmxNotificationDAOImplStatementDescriptorRegistrationTest.java vm-memory/common/src/main/java/com/redhat/thermostat/vm/memory/common/internal/VmMemoryStatDAOImplStatementDescriptorRegistration.java vm-memory/common/src/test/java/com/redhat/thermostat/vm/memory/common/internal/VmMemoryStatDAOImplStatementDescriptorRegistrationTest.java 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/src/main/java/com/redhat/thermostat/web/common/ExpressionSerializer.java web/common/src/main/java/com/redhat/thermostat/web/common/OperatorSerializer.java web/common/src/main/java/com/redhat/thermostat/web/common/PreparedParameterSerializer.java web/common/src/main/java/com/redhat/thermostat/web/common/PreparedStatementResponseCode.java web/common/src/main/java/com/redhat/thermostat/web/common/WebAdd.java web/common/src/main/java/com/redhat/thermostat/web/common/WebQueryResponse.java web/common/src/main/java/com/redhat/thermostat/web/common/WebRemove.java web/common/src/main/java/com/redhat/thermostat/web/common/WebReplace.java web/common/src/main/java/com/redhat/thermostat/web/common/WebUpdate.java web/common/src/test/java/com/redhat/thermostat/web/common/ExpressionSerializerTest.java web/common/src/test/java/com/redhat/thermostat/web/common/OperatorSerializerTest.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/WebPreparedStatementSerializerTest.java web/common/src/test/java/com/redhat/thermostat/web/common/WebQueryResponseSerializerTest.java web/server/src/main/java/com/redhat/thermostat/web/server/WebStorageEndPoint.java web/server/src/main/java/com/redhat/thermostat/web/server/auth/Roles.java web/server/src/test/java/com/redhat/thermostat/web/server/KnownDescriptorRegistryTest.java web/server/src/test/java/com/redhat/thermostat/web/server/WebStorageEndpointTest.java
diffstat 65 files changed, 1579 insertions(+), 2521 deletions(-) [+]
line wrap: on
line diff
--- a/host-cpu/common/src/main/java/com/redhat/thermostat/host/cpu/common/internal/CpuStatDAOImplStatementDescriptorRegistration.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/host-cpu/common/src/main/java/com/redhat/thermostat/host/cpu/common/internal/CpuStatDAOImplStatementDescriptorRegistration.java	Wed Sep 25 19:09:58 2013 +0200
@@ -61,6 +61,7 @@
     public Set<String> getStatementDescriptors() {
         Set<String> descs = new HashSet<>(1);
         descs.add(DESCRIPTOR);
+        descs.add(CpuStatDAOImpl.DESC_ADD_CPU_STAT);
         return descs;
     }
 
--- a/host-cpu/common/src/test/java/com/redhat/thermostat/host/cpu/common/internal/CpuStatDAOImplStatementDescriptorRegistrationTest.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/host-cpu/common/src/test/java/com/redhat/thermostat/host/cpu/common/internal/CpuStatDAOImplStatementDescriptorRegistrationTest.java	Wed Sep 25 19:09:58 2013 +0200
@@ -61,10 +61,10 @@
 public class CpuStatDAOImplStatementDescriptorRegistrationTest {
 
     @Test
-    public void registersAllQueries() {
+    public void registersAllDescriptors() {
         CpuStatDAOImplStatementDescriptorRegistration reg = new CpuStatDAOImplStatementDescriptorRegistration();
         Set<String> descriptors = reg.getStatementDescriptors();
-        assertEquals(1, descriptors.size());
+        assertEquals(2, descriptors.size());
         assertFalse("null descriptor not allowed", descriptors.contains(null));
     }
     
@@ -92,7 +92,7 @@
         // storage-core + this module
         assertEquals(2, registrations.size());
         assertNotNull(cpuStatReg);
-        assertEquals(1, cpuStatReg.getStatementDescriptors().size());
+        assertEquals(2, cpuStatReg.getStatementDescriptors().size());
     }
     
     @Test
--- a/host-memory/common/src/main/java/com/redhat/thermostat/host/memory/common/internal/MemoryStatDAOImplStatementDescriptorRegistration.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/host-memory/common/src/main/java/com/redhat/thermostat/host/memory/common/internal/MemoryStatDAOImplStatementDescriptorRegistration.java	Wed Sep 25 19:09:58 2013 +0200
@@ -60,6 +60,7 @@
     public Set<String> getStatementDescriptors() {
         Set<String> descs = new HashSet<>(1);
         descs.add(descriptor);
+        descs.add(MemoryStatDAOImpl.DESC_ADD_MEMORY_STAT);
         return descs;
     }
 
--- a/host-memory/common/src/test/java/com/redhat/thermostat/host/memory/common/internal/MemoryStatDAOImplStatementDescriptorRegistrationTest.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/host-memory/common/src/test/java/com/redhat/thermostat/host/memory/common/internal/MemoryStatDAOImplStatementDescriptorRegistrationTest.java	Wed Sep 25 19:09:58 2013 +0200
@@ -61,10 +61,10 @@
 public class MemoryStatDAOImplStatementDescriptorRegistrationTest {
 
     @Test
-    public void registersAllQueries() {
+    public void registersAllDescriptors() {
         MemoryStatDAOImplStatementDescriptorRegistration reg = new MemoryStatDAOImplStatementDescriptorRegistration();
         Set<String> descriptors = reg.getStatementDescriptors();
-        assertEquals(1, descriptors.size());
+        assertEquals(2, descriptors.size());
         assertFalse("null descriptor not allowed", descriptors.contains(null));
     }
     
@@ -92,7 +92,7 @@
         // storage-core + this module
         assertEquals(2, registrations.size());
         assertNotNull(memoryStatReg);
-        assertEquals(1, memoryStatReg.getStatementDescriptors().size());
+        assertEquals(2, memoryStatReg.getStatementDescriptors().size());
     }
     
     @Test
--- a/integration-tests/src/test/java/com/redhat/thermostat/itest/MongoQueriesTest.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/integration-tests/src/test/java/com/redhat/thermostat/itest/MongoQueriesTest.java	Wed Sep 25 19:09:58 2013 +0200
@@ -153,7 +153,7 @@
     private static void addCpuData(int numberOfItems) throws InterruptedException {
         CountDownLatch latch = new CountDownLatch(1);
         ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
-        Storage storage = getAndConnectStorage(listener);
+        BackingStorage storage = getAndConnectStorage(listener);
         latch.await();
         storage.getConnection().removeListener(listener);
         
@@ -161,7 +161,7 @@
 
         for (int i = 0; i < numberOfItems; i++) {
             CpuStat pojo = new CpuStat("test-agent-id", i, new double[] {i, i*2});
-            Add add = storage.createAdd(CpuStatDAO.cpuStatCategory);
+            Add<CpuStat> add = storage.createAdd(CpuStatDAO.cpuStatCategory);
             add.setPojo(pojo);
             add.apply();
         }
@@ -201,12 +201,12 @@
     public void testMongoAdd() throws Exception {
         CountDownLatch latch = new CountDownLatch(1);
         ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
-        Storage mongoStorage = getAndConnectStorage(listener);
+        BackingStorage mongoStorage = getAndConnectStorage(listener);
         latch.await();
         mongoStorage.getConnection().removeListener(listener);
         mongoStorage.registerCategory(VmClassStatDAO.vmClassStatsCategory);
         
-        Add add = mongoStorage.createAdd(VmClassStatDAO.vmClassStatsCategory);
+        Add<VmClassStat> add = mongoStorage.createAdd(VmClassStatDAO.vmClassStatsCategory);
         VmClassStat pojo = new VmClassStat();
         pojo.setAgentId("fluff");
         pojo.setLoadedClasses(12345);
@@ -513,7 +513,7 @@
         long timeStamp = 5;
         double cpuLoad = 0.15;
         VmCpuStat pojo = new VmCpuStat(uuid.toString(), timeStamp, VM_ID1, cpuLoad);
-        Add add = mongoStorage.createAdd(VmCpuStatDAO.vmCpuStatCategory);
+        Add<VmCpuStat> add = mongoStorage.createAdd(VmCpuStatDAO.vmCpuStatCategory);
         add.setPojo(pojo);
         add.apply();
 
--- a/integration-tests/src/test/java/com/redhat/thermostat/itest/WebAppTest.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/integration-tests/src/test/java/com/redhat/thermostat/itest/WebAppTest.java	Wed Sep 25 19:09:58 2013 +0200
@@ -78,6 +78,7 @@
 import com.redhat.thermostat.storage.config.ConnectionConfiguration;
 import com.redhat.thermostat.storage.config.StartupConfiguration;
 import com.redhat.thermostat.storage.core.Add;
+import com.redhat.thermostat.storage.core.BackingStorage;
 import com.redhat.thermostat.storage.core.Category;
 import com.redhat.thermostat.storage.core.CategoryAdapter;
 import com.redhat.thermostat.storage.core.Connection.ConnectionListener;
@@ -97,6 +98,7 @@
 import com.redhat.thermostat.storage.model.HostInfo;
 import com.redhat.thermostat.storage.dao.AgentInfoDAO;
 import com.redhat.thermostat.storage.model.AgentInformation;
+import com.redhat.thermostat.storage.mongodb.internal.MongoStorage;
 import com.redhat.thermostat.storage.query.Expression;
 import com.redhat.thermostat.storage.query.ExpressionFactory;
 import com.redhat.thermostat.test.FreePortFinder;
@@ -151,7 +153,7 @@
     private static final String KEY_AUTHORIZED_QUERY_NOT = "authorizedQueryNot";
     private static final String KEY_AUTHORIZED_QUERY_AND = "authorizedQueryAnd";
     private static final String KEY_AUTHORIZED_QUERY_OR = "authorizedQueryOr";
-    private static final String KEY_SET_DEFAULT_AGENT_ID = "setDefaultAgentID";
+    private static final String KEY_STORAGE_PURGE = "storagePurge";
     private static final String KEY_AUTHORIZED_FILTERED_QUERY = "authorizedFilteredQuerySubset";
     
     static {
@@ -167,7 +169,7 @@
         descMap.put(KEY_AUTHORIZED_QUERY_NOT, "QUERY cpu-stats WHERE NOT 'timeStamp' > ?l SORT 'timeStamp' ASC");
         descMap.put(KEY_AUTHORIZED_QUERY_AND, "QUERY cpu-stats WHERE 'timeStamp' > 0 AND 'timeStamp' < ?l SORT 'timeStamp' ASC");
         descMap.put(KEY_AUTHORIZED_QUERY_OR, "QUERY cpu-stats WHERE 'timeStamp' > ?l OR 'timeStamp' < ?l SORT 'timeStamp' ASC");
-        descMap.put(KEY_SET_DEFAULT_AGENT_ID, "QUERY vm-cpu-stats");
+        descMap.put(KEY_STORAGE_PURGE, "QUERY vm-cpu-stats");
         Set<String> trustedDescriptors = new HashSet<>();
         Map<String, DescriptorMetadata> metadata = new HashMap<>();
         DescriptorMetadata descMetadata = new DescriptorMetadata();
@@ -289,6 +291,19 @@
         Files.copy(backupUsers, new File(THERMOSTAT_USERS_FILE).toPath(), StandardCopyOption.REPLACE_EXISTING);
         Files.copy(backupRoles, new File(THERMOSTAT_ROLES_FILE).toPath(), StandardCopyOption.REPLACE_EXISTING);
     }
+    
+    /*
+     * Queries tests use write operations to put things into storage. For them
+     * we don't want to go through the hassles of using prepared writes. Instead
+     * use mongo-storage directly (which is a BackingStorage). 
+     */
+    private static BackingStorage getAndConnectBackingStorage() {
+        String url = "mongodb://127.0.0.1:27518";
+        StartupConfiguration config = new ConnectionConfiguration(url, "", "");
+        BackingStorage storage = new MongoStorage(config);
+        storage.getConnection().connect();
+        return storage;
+    }
 
     /*
      * Using the given username and password, set up a user for JAAS in the web app,
@@ -386,13 +401,7 @@
     }
 
     private static void addCpuData(int numberOfItems) throws IOException {
-        String[] roleNames = new String[] {
-                Roles.REGISTER_CATEGORY,
-                Roles.ACCESS_REALM,
-                Roles.LOGIN,
-                Roles.APPEND
-        };
-        Storage storage = getAndConnectStorage(PREP_USER, PREP_PASSWORD, roleNames);
+        BackingStorage storage = getAndConnectBackingStorage();
         storage.registerCategory(CpuStatDAO.cpuStatCategory);
 
         for (int i = 0; i < numberOfItems; i++) {
@@ -406,13 +415,7 @@
     }
     
     private static void addHostInfoData(int numberOfItems) throws IOException {
-        String[] roleNames = new String[] {
-                Roles.REGISTER_CATEGORY,
-                Roles.ACCESS_REALM,
-                Roles.LOGIN,
-                Roles.APPEND
-        };
-        Storage storage = getAndConnectStorage(PREP_USER, PREP_PASSWORD, roleNames);
+        BackingStorage storage = getAndConnectBackingStorage();
         storage.registerCategory(HostInfoDAO.hostInfoCategory);
 
         for (int i = 0; i < numberOfItems; i++) {
@@ -426,13 +429,7 @@
     }
     
     private static void addAgentConfigData(List<AgentInformation> items) throws IOException {
-        String[] roleNames = new String[] {
-                Roles.REGISTER_CATEGORY,
-                Roles.ACCESS_REALM,
-                Roles.LOGIN,
-                Roles.APPEND
-        };
-        Storage storage = getAndConnectStorage(PREP_USER, PREP_PASSWORD, roleNames);
+        BackingStorage storage = getAndConnectBackingStorage();
         storage.registerCategory(AgentInfoDAO.CATEGORY);
 
         for (AgentInformation info: items) {
@@ -466,13 +463,7 @@
     }
     
     private static void deleteAgentConfigData(List<AgentInformation> items) throws IOException {
-        String[] roleNames = new String[] {
-                Roles.REGISTER_CATEGORY,
-                Roles.ACCESS_REALM,
-                Roles.LOGIN,
-                Roles.DELETE
-        };
-        Storage storage = getAndConnectStorage(PREP_USER, PREP_PASSWORD, roleNames);
+        BackingStorage storage = getAndConnectBackingStorage();
         storage.registerCategory(AgentInfoDAO.CATEGORY);
         ExpressionFactory factory = new ExpressionFactory();
         Remove<AgentInformation> remove = storage.createRemove(AgentInfoDAO.CATEGORY);
@@ -503,47 +494,63 @@
     }
 
     @Test
-    public void authorizedAdd() throws Exception {
+    public void authorizedPreparedAdd() throws Exception {
         String[] roleNames = new String[] {
                 Roles.REGISTER_CATEGORY,
-                Roles.ACCESS_REALM,
+                Roles.WRITE,
                 Roles.LOGIN,
-                Roles.APPEND
+                Roles.ACCESS_REALM,
+                Roles.PREPARE_STATEMENT,
         };
+        
         Storage webStorage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
         webStorage.registerCategory(VmClassStatDAO.vmClassStatsCategory);
         
-        Add<VmClassStat> add = webStorage.createAdd(VmClassStatDAO.vmClassStatsCategory);
+        // This is the same descriptor as VmClassStatDAOImpl uses. It also
+        // gets registered automatically for that reason, no need to do it
+        // manually for this test.
+        String strDesc = "ADD vm-class-stats SET 'agentId' = ?s , " +
+                                "'vmId' = ?s , " +
+                                "'timeStamp' = ?l , " + 
+                                "'loadedClasses' = ?l";
+        StatementDescriptor<VmClassStat> desc = new StatementDescriptor<>(VmClassStatDAO.vmClassStatsCategory, strDesc);
         VmClassStat pojo = new VmClassStat();
         pojo.setAgentId("fluff");
         pojo.setLoadedClasses(12345);
         pojo.setTimeStamp(42);
         pojo.setVmId(VM_ID1);
-        add.setPojo(pojo);
-        add.apply();
+        PreparedStatement<VmClassStat> add;
+        add = webStorage.prepareStatement(desc);
+        addPreparedVmClassStat(pojo, add);
         
         // Add another couple of entries
-        add = webStorage.createAdd(VmClassStatDAO.vmClassStatsCategory);
         pojo = new VmClassStat();
         pojo.setAgentId("fluff");
         pojo.setLoadedClasses(67890);
         pojo.setTimeStamp(42);
         pojo.setVmId(VM_ID2);
-        add.setPojo(pojo);
-        add.apply();
+        addPreparedVmClassStat(pojo, add);
         
-        add = webStorage.createAdd(VmClassStatDAO.vmClassStatsCategory);
         pojo = new VmClassStat();
         pojo.setAgentId("fluff");
         pojo.setLoadedClasses(34567);
         pojo.setTimeStamp(42);
         pojo.setVmId(VM_ID3);
-        add.setPojo(pojo);
-        add.apply();
-
+        addPreparedVmClassStat(pojo, add);
+        
         webStorage.getConnection().disconnect();
     }
     
+    private void addPreparedVmClassStat(VmClassStat pojo,
+            PreparedStatement<VmClassStat> add)
+            throws StatementExecutionException {
+        add.setString(0, pojo.getAgentId());
+        add.setString(1, pojo.getVmId());
+        add.setLong(2, pojo.getTimeStamp());
+        add.setLong(3, pojo.getLoadedClasses());
+        add.execute();
+    }
+    
     /*
      * Tests whether a query only returns results which a user is allowed to see.
      * 
@@ -697,7 +704,9 @@
             };
             Storage webStorage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
             Category<AggregateCount> adapted = new CategoryAdapter<HostInfo, AggregateCount>(HostInfoDAO.hostInfoCategory).getAdapted(AggregateCount.class);
-            // register adapted category.
+            // register non-adapted + adapted category in that order. Adapted
+            // category needs to be registered, since it gets it's own mapped id
+            webStorage.registerCategory(HostInfoDAO.hostInfoCategory);
             webStorage.registerCategory(adapted);
             
             // storage-core registers this descriptor. no need to do it in this
@@ -1021,29 +1030,29 @@
 
     @Test
     public void storagePurge() throws Exception {
+        // Add some data to purge (uses backing storage)
+        UUID uuid = new UUID(42, 24);
+        long timeStamp = 5;
+        double cpuLoad = 0.15;
+        VmCpuStat pojo = new VmCpuStat(uuid.toString(), timeStamp, VM_ID1, cpuLoad);
+        addVmCpuStat(pojo);
+
         String[] roleNames = new String[] {
                 Roles.ACCESS_REALM,
                 Roles.LOGIN,
-                Roles.REGISTER_CATEGORY,
-                Roles.READ,
-                Roles.APPEND,
                 Roles.PURGE,
                 Roles.PREPARE_STATEMENT,
-                Roles.GRANT_READ_ALL
+                Roles.READ,
+                Roles.GRANT_READ_ALL,
+                Roles.REGISTER_CATEGORY
         };
-        Storage storage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
-        UUID uuid = new UUID(42, 24);
-        storage.registerCategory(VmCpuStatDAO.vmCpuStatCategory);
-        long timeStamp = 5;
-        double cpuLoad = 0.15;
-        VmCpuStat pojo = new VmCpuStat(uuid.toString(), timeStamp, VM_ID1, cpuLoad);
-        Add<VmCpuStat> add = storage.createAdd(VmCpuStatDAO.vmCpuStatCategory);
-        add.setPojo(pojo);
-        add.apply();
-
-        String strDesc = DESCRIPTOR_MAP.get(KEY_SET_DEFAULT_AGENT_ID);
+        
+        Storage webStorage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
+        webStorage.registerCategory(VmCpuStatDAO.vmCpuStatCategory);
+        
+        String strDesc = DESCRIPTOR_MAP.get(KEY_STORAGE_PURGE);
         StatementDescriptor<VmCpuStat> queryDesc = new StatementDescriptor<>(VmCpuStatDAO.vmCpuStatCategory, strDesc);
-        PreparedStatement<VmCpuStat> query = storage.prepareStatement(queryDesc);
+        PreparedStatement<VmCpuStat> query = webStorage.prepareStatement(queryDesc);
         Cursor<VmCpuStat> cursor = query.executeQuery();
         assertTrue(cursor.hasNext());
         pojo = cursor.next();
@@ -1054,6 +1063,15 @@
         assertEquals(cpuLoad, pojo.getCpuLoad(), EQUALS_DELTA);
         assertEquals(uuid.toString(), pojo.getAgentId());
 
-        storage.purge(uuid.toString());
+        webStorage.purge(uuid.toString());
+    }
+
+    private void addVmCpuStat(VmCpuStat pojo) {
+        BackingStorage storage = getAndConnectBackingStorage();
+        storage.registerCategory(VmCpuStatDAO.vmCpuStatCategory);
+        Add<VmCpuStat> add = storage.createAdd(VmCpuStatDAO.vmCpuStatCategory);
+        add.setPojo(pojo);
+        add.apply();
+        storage.getConnection().disconnect();        
     }
 }
--- a/numa/common/src/main/java/com/redhat/thermostat/numa/common/internal/NumaDAOImplStatementDescriptorRegistration.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/numa/common/src/main/java/com/redhat/thermostat/numa/common/internal/NumaDAOImplStatementDescriptorRegistration.java	Wed Sep 25 19:09:58 2013 +0200
@@ -62,6 +62,8 @@
                 NumaDAO.numaStatCategory.getName());
         descs.add(descriptor);
         descs.add(NumaDAOImpl.QUERY_NUMA_INFO);
+        descs.add(NumaDAOImpl.DESC_ADD_NUMA_HOST_INFO);
+        descs.add(NumaDAOImpl.DESC_ADD_NUMA_STAT);
     }
 
     @Override
--- a/numa/common/src/test/java/com/redhat/thermostat/numa/common/internal/NumaDAOImplStatementDescriptorRegistrationTest.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/numa/common/src/test/java/com/redhat/thermostat/numa/common/internal/NumaDAOImplStatementDescriptorRegistrationTest.java	Wed Sep 25 19:09:58 2013 +0200
@@ -63,10 +63,10 @@
 public class NumaDAOImplStatementDescriptorRegistrationTest {
 
     @Test
-    public void registersAllQueries() {
+    public void registersAllDescriptors() {
         NumaDAOImplStatementDescriptorRegistration reg = new NumaDAOImplStatementDescriptorRegistration();
         Set<String> descriptors = reg.getStatementDescriptors();
-        assertEquals(2, descriptors.size());
+        assertEquals(4, descriptors.size());
         assertFalse("null descriptor not allowed", descriptors.contains(null));
     }
     
@@ -94,7 +94,7 @@
         // storage-core + this module
         assertEquals(2, registrations.size());
         assertNotNull(numaReg);
-        assertEquals(2, numaReg.getStatementDescriptors().size());
+        assertEquals(4, numaReg.getStatementDescriptors().size());
     }
     
     @Test
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/core/BackingStorage.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/BackingStorage.java	Wed Sep 25 19:09:58 2013 +0200
@@ -55,6 +55,12 @@
     
     <T extends Pojo> Query<T> createAggregateQuery(AggregateFunction function, Category<T> category);
     
-    // TODO Move createUpdate and createRemove here
+    <T extends Pojo> Add<T> createAdd(Category<T> category);
+    
+    <T extends Pojo> Replace<T> createReplace(Category<T> category);
+    
+    <T extends Pojo> Update<T> createUpdate(Category<T> category);
+    
+    <T extends Pojo> Remove<T> createRemove(Category<T> category);
 
 }
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/core/PreparedParameter.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/PreparedParameter.java	Wed Sep 25 19:09:58 2013 +0200
@@ -44,11 +44,16 @@
 public class PreparedParameter {
 
     private Object value;
+    // The simple type of value. Even if value is an array.
     private Class<?> type;
+    // true if and only if value is an array of simple types
+    // as specified by the type instance variable.
+    private boolean isArrayType;
     
-    PreparedParameter(Object value, Class<?> type) {
+    PreparedParameter(Object value, Class<?> type, boolean isArrayType) {
         this.value = value;
         this.type = type;
+        this.isArrayType = isArrayType;
     }
     
     public PreparedParameter() {
@@ -70,4 +75,12 @@
     public void setType(Class<?> type) {
         this.type = type;
     }
+
+    public boolean isArrayType() {
+        return isArrayType;
+    }
+
+    public void setArrayType(boolean isArrayType) {
+        this.isArrayType = isArrayType;
+    }
 }
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/core/PreparedParameters.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/PreparedParameters.java	Wed Sep 25 19:09:58 2013 +0200
@@ -36,6 +36,8 @@
 
 package com.redhat.thermostat.storage.core;
 
+import java.lang.reflect.Modifier;
+
 import com.redhat.thermostat.storage.model.Pojo;
 
 /**
@@ -58,69 +60,83 @@
     
     @Override
     public void setLong(int paramIndex, long paramValue) {
-        setType(paramIndex, paramValue, Long.class);
+        setType(paramIndex, paramValue, Long.class, false);
     }
 
     @Override
     public void setLongList(int paramIndex, long[] paramValue) {
-        setType(paramIndex, paramValue, Long[].class);
+        setType(paramIndex, paramValue, Long.class, true);
     }
 
     @Override
     public void setInt(int paramIndex, int paramValue) {
-        setType(paramIndex, paramValue, Integer.class);
+        setType(paramIndex, paramValue, Integer.class, false);
     }
 
     @Override
     public void setIntList(int paramIndex, int[] paramValue) {
-        setType(paramIndex, paramValue, Integer[].class);
+        setType(paramIndex, paramValue, Integer.class, true);
     }
 
     @Override
     public void setBoolean(int paramIndex, boolean paramValue) {
-        setType(paramIndex, paramValue, Boolean.class);
+        setType(paramIndex, paramValue, Boolean.class, false);
     }
 
     @Override
     public void setBooleanList(int paramIndex, boolean[] paramValue) {
-        setType(paramIndex, paramValue, Boolean[].class);
+        setType(paramIndex, paramValue, Boolean.class, true);
     }
 
     @Override
     public void setString(int paramIndex, String paramValue) {
-        setType(paramIndex, paramValue, String.class);
+        setType(paramIndex, paramValue, String.class, false);
     }
 
     @Override
     public void setStringList(int paramIndex, String[] paramValue) {
-        setType(paramIndex, paramValue, String[].class);
+        setType(paramIndex, paramValue, String.class, true);
     }
 
     @Override
     public void setDouble(int paramIndex, double paramValue) {
-        setType(paramIndex, paramValue, Double.class);
+        setType(paramIndex, paramValue, Double.class, false);
     }
 
     @Override
     public void setDoubleList(int paramIndex, double[] paramValue) {
-        setType(paramIndex, paramValue, Double[].class);
+        setType(paramIndex, paramValue, Double.class, true);
     }
 
     @Override
     public void setPojo(int paramIndex, Pojo paramValue) {
-        setType(paramIndex, paramValue, Pojo.class);
+        Class<?> runtimeType = paramValue.getClass();
+        performPojoChecks(runtimeType, "Type");
+        setType(paramIndex, paramValue, runtimeType, false);
     }
 
     @Override
     public void setPojoList(int paramIndex, Pojo[] paramValue) {
-        setType(paramIndex, paramValue, Pojo[].class);
+        Class<?> componentType = paramValue.getClass().getComponentType();
+        performPojoChecks(componentType, "Component type");
+        setType(paramIndex, paramValue, componentType, true);
+    }
+    
+    private void performPojoChecks(Class<?> type, String errorMsgPrefix) {
+        if (type.isInterface() || Modifier.isAbstract(type.getModifiers())) {
+            // Due to serealization we only allow concrete instantiable types.
+            // Instantiation would fail later in ThermostatGSONConverter for this
+            // reason anyway. Let's do this check early.            
+            throw new IllegalArgumentException(errorMsgPrefix + "'" +
+                        type.getName() + "' not instantiable!");
+        }
     }
 
-    private void setType(int paramIndex, Object paramValue, Class<?> paramType) {
+    private void setType(int paramIndex, Object paramValue, Class<?> paramType, boolean isArrayType) {
         if (paramIndex >= params.length) {
             throw new IllegalArgumentException("Parameter index '" + paramIndex + "' out of range.");
         }
-        PreparedParameter param = new PreparedParameter(paramValue, paramType);
+        PreparedParameter param = new PreparedParameter(paramValue, paramType, isArrayType);
         params[paramIndex] = param;
     }
     
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/core/QueuedBackingStorage.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/QueuedBackingStorage.java	Wed Sep 25 19:09:58 2013 +0200
@@ -40,10 +40,133 @@
 
 import com.redhat.thermostat.storage.core.AggregateQuery.AggregateFunction;
 import com.redhat.thermostat.storage.model.Pojo;
+import com.redhat.thermostat.storage.query.Expression;
 
 public class QueuedBackingStorage extends QueuedStorage implements
         BackingStorage {
     
+    private class QueuedReplace<T extends Pojo> implements Replace<T> {
+
+        private final Replace<T> delegateReplace;
+        
+        private QueuedReplace(Replace<T> delegateReplace) {
+            this.delegateReplace = delegateReplace;
+        }
+        
+        @Override
+        public int apply() {
+            executor.execute(new Runnable() {
+                
+                @Override
+                public void run() {
+                    delegateReplace.apply();
+                }
+
+            });
+            return DataModifyingStatement.DEFAULT_STATUS_SUCCESS;
+        }
+
+        @Override
+        public void where(Expression expression) {
+            delegateReplace.where(expression);
+        }
+
+        @Override
+        public void setPojo(Pojo pojo) {
+            delegateReplace.setPojo(pojo);
+        }
+        
+    }
+
+    private class QueuedAdd<T extends Pojo> implements Add<T> {
+        
+        private final Add<T> delegateAdd;
+        
+        private QueuedAdd(Add<T> delegateAdd) {
+            this.delegateAdd = delegateAdd;
+        }
+        
+        @Override
+        public int apply() {
+            executor.execute(new Runnable() {
+                
+                @Override
+                public void run() {
+                    delegateAdd.apply();
+                }
+
+            });
+            return DataModifyingStatement.DEFAULT_STATUS_SUCCESS;
+        }
+
+        @Override
+        public void setPojo(Pojo pojo) {
+            delegateAdd.setPojo(pojo);
+        }
+        
+    }
+
+    private class QueuedUpdate<T extends Pojo> implements Update<T> {
+        
+        private final Update<T> delegateUpdate;
+
+        private QueuedUpdate(Update<T> delegateUpdate) {
+            this.delegateUpdate = delegateUpdate;
+        }
+
+        @Override
+        public void where(Expression expr) {
+            delegateUpdate.where(expr);
+            
+        }
+
+        @Override
+        public <S> void set(Key<S> key, S value) {
+            delegateUpdate.set(key, value);
+        }
+
+        @Override
+        public int apply() {
+            executor.execute(new Runnable() {
+                
+                @Override
+                public void run() {
+                    delegateUpdate.apply();
+                }
+
+            });
+            return DataModifyingStatement.DEFAULT_STATUS_SUCCESS;
+        }
+
+    }
+    
+    private class QueuedRemove<T extends Pojo> implements Remove<T> {
+        
+        private final Remove<T> delegateRemove;
+        
+        private QueuedRemove(Remove<T> delegateRemove) {
+            this.delegateRemove = delegateRemove;
+        }
+
+        @Override
+        public void where(Expression where) {
+            delegateRemove.where(where);
+        }
+
+        @Override
+        public int apply() {
+            executor.execute(new Runnable() {
+                
+                @Override
+                public void run() {
+                    delegateRemove.apply();
+                }
+
+            });
+            return DataModifyingStatement.DEFAULT_STATUS_SUCCESS;
+        }
+    }
+    
     public QueuedBackingStorage(BackingStorage delegate) {
         super(delegate);
     }
@@ -77,4 +200,32 @@
         return ((BackingStorage) delegate).createAggregateQuery(function, category);
     }
 
+    @Override
+    public <T extends Pojo> Add<T> createAdd(Category<T> category) {
+        Add<T> delegateAdd = ((BackingStorage)delegate).createAdd(category);
+        QueuedAdd<T> add = new QueuedAdd<>(delegateAdd);
+        return add;
+    }
+
+    @Override
+    public <T extends Pojo> Replace<T> createReplace(Category<T> category) {
+        Replace<T> delegateReplace = ((BackingStorage)delegate).createReplace(category);
+        QueuedReplace<T> replace = new QueuedReplace<>(delegateReplace);
+        return replace;
+    }
+
+    @Override
+    public <T extends Pojo> Update<T> createUpdate(Category<T> category) {
+        Update<T> delegateUpdate = ((BackingStorage)delegate).createUpdate(category);
+        QueuedUpdate<T> update = new QueuedUpdate<>(delegateUpdate);
+        return update;
+    }
+
+    @Override
+    public <T extends Pojo> Remove<T> createRemove(Category<T> category) {
+        Remove<T> delegateRemove = ((BackingStorage) delegate).createRemove(category);
+        QueuedRemove<T> remove = new QueuedRemove<>(delegateRemove);
+        return remove;
+    }
+
 }
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/core/QueuedStorage.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/QueuedStorage.java	Wed Sep 25 19:09:58 2013 +0200
@@ -38,86 +38,16 @@
 package com.redhat.thermostat.storage.core;
 
 import java.io.InputStream;
-import java.util.Objects;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
 
 import com.redhat.thermostat.storage.model.Pojo;
-import com.redhat.thermostat.storage.query.Expression;
 
 public class QueuedStorage implements Storage {
 
     private static final int SHUTDOWN_TIMEOUT_SECONDS = 3;
 
-    private class QueuedReplace<T extends Pojo> extends AddReplaceHelper implements Replace<T> {
-
-        private Expression expression;
-        
-        private QueuedReplace(Category<?> category) {
-            super(category);
-        }
-        
-        @Override
-        public int apply() {
-            replaceImpl(getCategory(), getPojo(), expression);
-            return DataModifyingStatement.DEFAULT_STATUS_SUCCESS;
-        }
-
-        @Override
-        public void where(Expression expression) {
-            this.expression = Objects.requireNonNull(expression);
-        }
-        
-    }
-
-    private class QueuedAdd<T extends Pojo> extends AddReplaceHelper implements Add<T> {
-
-        private QueuedAdd(Category<?> category) {
-            super(category);
-        }
-        
-        @Override
-        public int apply() {
-            addImpl(getCategory(), getPojo());
-            return DataModifyingStatement.DEFAULT_STATUS_SUCCESS;
-        }
-        
-    }
-
-    private class QueuedUpdate<T extends Pojo> implements Update<T> {
-        private Update<T> delegateUpdate;
-
-        QueuedUpdate(Update<T> delegateUpdate) {
-            this.delegateUpdate = delegateUpdate;
-        }
-
-        @Override
-        public void where(Expression expr) {
-            delegateUpdate.where(expr);
-            
-        }
-
-        @Override
-        public <S> void set(Key<S> key, S value) {
-            delegateUpdate.set(key, value);
-        }
-
-        @Override
-        public int apply() {
-            executor.execute(new Runnable() {
-                
-                @Override
-                public void run() {
-                    delegateUpdate.apply();
-                }
-
-            });
-            return DataModifyingStatement.DEFAULT_STATUS_SUCCESS;
-        }
-
-    }
-
     protected final Storage delegate;
     protected final ExecutorService executor;
     protected final ExecutorService fileExecutor;
@@ -150,49 +80,6 @@
     }
 
     @Override
-    public <T extends Pojo> Add<T> createAdd(Category<T> into) {
-        QueuedAdd<T> add = new QueuedAdd<>(into);
-        return add;
-    }
-
-    @Override
-    public <T extends Pojo> Replace<T> createReplace(Category<T> into) {
-        QueuedReplace<T> replace = new QueuedReplace<>(into);
-        return replace;
-    }
-
-    private void replaceImpl(final Category<?> category, final Pojo pojo, final Expression expression) {
-        
-        executor.execute(new Runnable() {
-            
-            @Override
-            public void run() {
-                Replace<?> replace = delegate.createReplace(category);
-                replace.setPojo(pojo);
-                replace.where(expression);
-                replace.apply();
-            }
-
-        });
-
-    }
-
-    private void addImpl(final Category<?> category, final Pojo pojo) {
-        
-        executor.execute(new Runnable() {
-            
-            @Override
-            public void run() {
-                Add<?> add = delegate.createAdd(category);
-                add.setPojo(pojo);
-                add.apply();
-            }
-
-        });
-
-    }
-
-    @Override
     public void purge(final String agentId) {
 
         executor.execute(new Runnable() {
@@ -226,17 +113,6 @@
     }
 
     @Override
-    public <T extends Pojo> Update<T> createUpdate(Category<T> category) {
-        QueuedUpdate<T> update = new QueuedUpdate<>(delegate.createUpdate(category));
-        return update;
-    }
-
-    @Override
-    public <T extends Pojo> Remove<T> createRemove(Category<T> category) {
-        return delegate.createRemove(category);
-    }
-
-    @Override
     public void registerCategory(final Category<?> category) {
         delegate.registerCategory(category);
     }
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/core/Storage.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/Storage.java	Wed Sep 25 19:09:58 2013 +0200
@@ -75,9 +75,6 @@
      */
     Connection getConnection();
 
-    <T extends Pojo> Add<T> createAdd(Category<T> category);
-    <T extends Pojo> Replace<T> createReplace(Category<T> category);
-
     /**
      * Drop all data related to the specified agent.
      */
@@ -87,9 +84,6 @@
 
     InputStream loadFile(String filename);
 
-    <T extends Pojo> Update<T> createUpdate(Category<T> category);
-    <T extends Pojo> Remove<T> createRemove(Category<T> category);
-
     void shutdown();
 
 }
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/dao/DAOImplStatementDescriptorRegistration.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/dao/DAOImplStatementDescriptorRegistration.java	Wed Sep 25 19:09:58 2013 +0200
@@ -59,15 +59,24 @@
         daoDescs.add(AgentInfoDAOImpl.QUERY_ALIVE_AGENTS);
         daoDescs.add(AgentInfoDAOImpl.QUERY_ALL_AGENTS);
         daoDescs.add(AgentInfoDAOImpl.AGGREGATE_COUNT_ALL_AGENTS);
+        daoDescs.add(AgentInfoDAOImpl.DESC_ADD_AGENT_INFO);
+        daoDescs.add(AgentInfoDAOImpl.DESC_REMOVE_AGENT_INFO);
+        daoDescs.add(AgentInfoDAOImpl.DESC_UPDATE_AGENT_INFO);
         daoDescs.add(BackendInfoDAOImpl.QUERY_BACKEND_INFO);
+        daoDescs.add(BackendInfoDAOImpl.DESC_ADD_BACKEND_INFO);
+        daoDescs.add(BackendInfoDAOImpl.DESC_REMOVE_BACKEND_INFO);
         daoDescs.add(HostInfoDAOImpl.QUERY_HOST_INFO);
         daoDescs.add(HostInfoDAOImpl.QUERY_ALL_HOSTS);
         daoDescs.add(HostInfoDAOImpl.AGGREGATE_COUNT_ALL_HOSTS);
+        daoDescs.add(HostInfoDAOImpl.DESC_ADD_HOST_INFO);
         daoDescs.add(NetworkInterfaceInfoDAOImpl.QUERY_NETWORK_INFO);
+        daoDescs.add(NetworkInterfaceInfoDAOImpl.DESC_REPLACE_NETWORK_INFO);
         daoDescs.add(VmInfoDAOImpl.QUERY_ALL_VMS_FOR_HOST);
         daoDescs.add(VmInfoDAOImpl.QUERY_ALL_VMS);
         daoDescs.add(VmInfoDAOImpl.QUERY_VM_INFO);
         daoDescs.add(VmInfoDAOImpl.AGGREGATE_COUNT_ALL_VMS);
+        daoDescs.add(VmInfoDAOImpl.DESC_ADD_VM_INFO);
+        daoDescs.add(VmInfoDAOImpl.DESC_UPDATE_VM_STOP_TIME);
         return daoDescs;
     }
 
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/LimitExpression.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/LimitExpression.java	Wed Sep 25 19:09:58 2013 +0200
@@ -68,7 +68,7 @@
             try {
                 PreparedParameter param = params[unfinished.getParameterIndex()];
                 Class<?> typeClass = param.getType();
-                if (typeClass != Integer.class) {
+                if (typeClass != Integer.class || param.isArrayType()) {
                     String msg = "Invalid parameter type for limit expression. Expected integer!";
                     IllegalArgumentException e = new IllegalArgumentException(msg);
                     throw e;
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/SortMember.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/SortMember.java	Wed Sep 25 19:09:58 2013 +0200
@@ -81,7 +81,8 @@
             if (getSortKey() instanceof Unfinished) {
                 Unfinished unfinished = (Unfinished)getSortKey();
                 PreparedParameter p = params[unfinished.getParameterIndex()];
-                if (p.getType() != String.class) {
+                // Should only allow patching of ?s type NOT ?s[
+                if (p.getType() != String.class || p.isArrayType()) {
                     String msg = "Illegal parameter type for index "
                             + unfinished.getParameterIndex()
                             + ". Expected String!";
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/StatementDescriptorParser.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/StatementDescriptorParser.java	Wed Sep 25 19:09:58 2013 +0200
@@ -595,11 +595,12 @@
             patchNode.setParameterIndex(placeHolderCount - 1);
             patchNode.setLHS(isLHS);
             // figure out the expected type
-            Class<?> expectedType = getType(term.substring(1));
+            FreeVarTypeToken expectedType = getType(term.substring(1));
             if (expectedType == null) {
                 throw new DescriptorParsingException("Unknown type of free parameter: '" + term + "'");
             }
-            patchNode.setType(expectedType);
+            patchNode.setType(expectedType.componentType);
+            patchNode.setArrayType(expectedType.isArrayType);
             node.setValue(patchNode);
             return;
         }
@@ -659,7 +660,7 @@
         return term.substring(1, term.length() - 1);
     }
 
-    private Class<?> getType(String term) {
+    private FreeVarTypeToken getType(String term) {
         if (term.equals("")) {
             // illegal type
             return null;
@@ -667,61 +668,61 @@
         // free variable types can have 1 or 2 characters.
         assert(term.length() > 0 && term.length() < 3);
         char switchChar = term.charAt(0);
-        Class<?> type = null;
+        FreeVarTypeToken typeToken = null;
         switch (switchChar) {
         case 'i': {
             if (term.length() == 1) {
-                type = Integer.class;
+                typeToken = new FreeVarTypeToken(Integer.class, false);
             } else if (term.length() == 2 && term.charAt(1) == '[') {
-                type = Integer[].class;
+                typeToken = new FreeVarTypeToken(Integer.class, true);
             }
             break;
         }
         case 'l': {
             if (term.length() == 1) {
-                type = Long.class;
+                typeToken = new FreeVarTypeToken(Long.class, false);
             } else if (term.length() == 2 && term.charAt(1) == '[') {
-                type = Long[].class;
+                typeToken = new FreeVarTypeToken(Long.class, true);
             }
             break;
         }
         case 's': {
             if (term.length() == 1) {
-                type = String.class;
+                typeToken = new FreeVarTypeToken(String.class, false);
             } else if (term.length() == 2 && term.charAt(1) == '[') {
-                type = String[].class;
+                typeToken = new FreeVarTypeToken(String.class, true);
             }
             break;
         }
         case 'b': {
             if (term.length() == 1) {
-                type = Boolean.class;
+                typeToken = new FreeVarTypeToken(Boolean.class, false);
             } else if (term.length() == 2 && term.charAt(1) == '[') {
-                type = Boolean[].class;
+                typeToken = new FreeVarTypeToken(Boolean.class, true);
             }
             break;
         }
         case 'd': {
             if (term.length() == 1) {
-                type = Double.class;
+                typeToken = new FreeVarTypeToken(Double.class, false);
             } else if (term.length() == 2 && term.charAt(1) == '[') {
-                type = Double[].class;
+                typeToken = new FreeVarTypeToken(Double.class, true);
             }
             break;
         }
         case 'p': {
             if (term.length() == 1) {
-                type = Pojo.class;
+                typeToken = new FreeVarTypeToken(Pojo.class, false);
             } else if (term.length() == 2 && term.charAt(1) == '[') {
-                type = Pojo[].class;
+                typeToken = new FreeVarTypeToken(Pojo.class, true);
             }
             break;
         }
         default:
-            assert(type == null);
+            assert(typeToken == null);
             break;
         }
-        return type;
+        return typeToken;
     }
 
     private String getTerm() throws DescriptorParsingException {
@@ -873,4 +874,15 @@
             throw new DescriptorParsingException("Unknown statement type: '" + tokens[currTokenIndex] + "'");
         }
     }
+    
+    private static class FreeVarTypeToken {
+        
+        private final boolean isArrayType;
+        private final Class<?> componentType;
+        
+        private FreeVarTypeToken(Class<?> componentType, boolean isArrayType) {
+            this.isArrayType = isArrayType;
+            this.componentType = componentType;
+        }
+    }
 }
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/TerminalNode.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/TerminalNode.java	Wed Sep 25 19:09:58 2013 +0200
@@ -41,6 +41,7 @@
 import com.redhat.thermostat.storage.core.IllegalPatchException;
 import com.redhat.thermostat.storage.core.Key;
 import com.redhat.thermostat.storage.core.PreparedParameter;
+import com.redhat.thermostat.storage.model.Pojo;
 import com.redhat.thermostat.storage.query.LiteralExpression;
 
 /**
@@ -71,14 +72,8 @@
             } catch (Exception e) {
                 throw new IllegalPatchException(e);
             }
-            if (param.getType() != patch.getType()) {
-                String msg = TerminalNode.class.getSimpleName()
-                        + " invalid type when attempting to patch. Expected "
-                        + patch.getType().getName() + " but was "
-                        + param.getType().getName();
-                IllegalArgumentException iae = new IllegalArgumentException(msg);
-                throw new IllegalPatchException(iae);
-            }
+            // Do some type sanity checking for free parameters
+            ensureTypeCompatibility(patch, param);
             if (patch.isLHS()) {
                 // LHS need to get patched to keys
                 Key<?> valueKey = new Key<>((String)param.getValue());
@@ -93,6 +88,39 @@
         return new PatchedWhereExpressionImpl(literalExp);
     }
     
+    private void ensureTypeCompatibility(UnfinishedValueNode patch,
+            PreparedParameter param) throws IllegalPatchException {
+        if (patch.getType() == Pojo.class) {
+            // handle pojo case
+            if (Pojo.class.isAssignableFrom(param.getType()) &&
+                    patch.isArrayType() == param.isArrayType()) {
+                return; // pojo-type match: OK
+            }
+            // dead-end
+            IllegalArgumentException iae = constructIllegalArgumentException(patch, param);
+            throw new IllegalPatchException(iae);
+        } else {
+            // primitive types or primitive list types
+            if (param.getType() != patch.getType() || param.isArrayType() != patch.isArrayType()) {
+                IllegalArgumentException iae = constructIllegalArgumentException(patch, param);
+                throw new IllegalPatchException(iae);
+            }
+            // passed primitive (array) type check
+        }
+    }
+        
+    private IllegalArgumentException constructIllegalArgumentException(
+            UnfinishedValueNode patch, PreparedParameter param) {
+        String patchArrayPrefix = patch.isArrayType() ? "[" : "";
+        String paramArrayPrefix = param.isArrayType() ? "[" : "";
+        String msg = TerminalNode.class.getSimpleName()
+                + " invalid type when attempting to patch. Expected "
+                + patchArrayPrefix + patch.getType().getName() + " but was "
+                + paramArrayPrefix + param.getType().getName();
+        IllegalArgumentException iae = new IllegalArgumentException(msg);
+        return iae;
+    }
+
     @Override
     public boolean equals(Object other) {
         if (other == null) {
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/UnfinishedValueNode.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/UnfinishedValueNode.java	Wed Sep 25 19:09:58 2013 +0200
@@ -52,8 +52,11 @@
     // determines if this patched value is a LHS or if false a RHS of a
     // binary comparison.
     private boolean isLHS;
-    // Specifies the expected type of this free parameter.
+    // Specifies the expected (component) type of this free parameter.
     private Class<?> type;
+    // Specifies if the free parameter is an array type. If so, the
+    // type instance variable represents the component type.
+    private boolean isArrayType;
 
     Class<?> getType() {
         return type;
@@ -81,6 +84,14 @@
         this.parameterIndex = parameterIndex;
     }
     
+    public boolean isArrayType() {
+        return isArrayType;
+    }
+
+    public void setArrayType(boolean isArrayType) {
+        this.isArrayType = isArrayType;
+    }
+
     @Override
     public String toString() {
         return "Unfinished value (" + getParameterIndex() + ") " + getType() +
@@ -98,12 +109,13 @@
         }
         UnfinishedValueNode o = (UnfinishedValueNode)other;
         return basics && Objects.equals(isLHS(), o.isLHS) &&
-                Objects.equals(getType(), o.getType());
+                Objects.equals(getType(), o.getType()) &&
+                        isArrayType() == o.isArrayType();
     }
     
     @Override
     public int hashCode() {
-        return Objects.hash(getParameterIndex(), isLHS(), getType());
+        return Objects.hash(getParameterIndex(), isLHS(), getType(), isArrayType());
     }
 
 }
--- a/storage/core/src/test/java/com/redhat/thermostat/storage/core/QueuedBackingStorageTest.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/storage/core/src/test/java/com/redhat/thermostat/storage/core/QueuedBackingStorageTest.java	Wed Sep 25 19:09:58 2013 +0200
@@ -159,6 +159,7 @@
     private QueuedBackingStorage queuedStorage;
     private BackingStorage delegateStorage;
     private Query<TestPojo> delegateQuery;
+    private Replace<?> delegateReplace;
 
     private TestExecutor executor;
     private TestExecutor fileExecutor;
@@ -172,10 +173,12 @@
         fileExecutor = new TestExecutor();
         delegateStorage = mock(BackingStorage.class);
 
+        delegateReplace = mock(Replace.class);
         delegateQuery = (Query<TestPojo>) mock(Query.class);
         expectedResults = (Cursor<TestPojo>) mock(Cursor.class);
         when(delegateStorage.createQuery(any(Category.class))).thenReturn(delegateQuery);
         when(delegateQuery.execute()).thenReturn(expectedResults);
+        when(delegateStorage.createReplace(any(Category.class))).thenReturn(delegateReplace);
         
         queuedStorage = new QueuedBackingStorage(delegateStorage, executor, fileExecutor);
     }
--- a/storage/core/src/test/java/com/redhat/thermostat/storage/core/QueuedStorageTest.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/storage/core/src/test/java/com/redhat/thermostat/storage/core/QueuedStorageTest.java	Wed Sep 25 19:09:58 2013 +0200
@@ -42,7 +42,6 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Matchers.same;
@@ -56,7 +55,6 @@
 import java.io.InputStream;
 import java.util.Collection;
 import java.util.List;
-import java.util.UUID;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
@@ -69,8 +67,6 @@
 import org.junit.Test;
 
 import com.redhat.thermostat.storage.model.Pojo;
-import com.redhat.thermostat.storage.query.Expression;
-import com.redhat.thermostat.storage.query.ExpressionFactory;
 
 
 public class QueuedStorageTest {
@@ -197,29 +193,19 @@
     }
 
     private QueuedStorage queuedStorage;
-    private Storage delegateStorage;
-    private Add<?> delegateAdd;
-    private Replace<?> delegateReplace;
+    private BackingStorage delegateStorage;
 
     private TestExecutor executor;
     private TestExecutor fileExecutor;
 
     private InputStream expectedFile;
 
-    @SuppressWarnings("unchecked")
     @Before
     public void setUp() {
         executor = new TestExecutor();
         fileExecutor = new TestExecutor();
-        delegateStorage = mock(Storage.class);
-
-        delegateAdd = mock(Add.class);
-        delegateReplace = mock(Replace.class);
+        delegateStorage = mock(BackingStorage.class);
 
-        Remove<?> remove = mock(Remove.class);
-        when(delegateStorage.createAdd(any(Category.class))).thenReturn(delegateAdd);
-        when(delegateStorage.createReplace(any(Category.class))).thenReturn(delegateReplace);
-        when(delegateStorage.createRemove(any(Category.class))).thenReturn(remove);
         expectedFile = mock(InputStream.class);
         when(delegateStorage.loadFile(anyString())).thenReturn(expectedFile);
         queuedStorage = new QueuedStorage(delegateStorage, executor, fileExecutor);
@@ -235,56 +221,6 @@
     }
 
     @Test
-    public void testReplace() {
-        Category<?> category = mock(Category.class);
-        Pojo pojo = mock(Pojo.class);
-
-        Replace<?> replace = queuedStorage.createReplace(category);
-        replace.setPojo(pojo);
-        Expression expression = new ExpressionFactory().equalTo(Key.AGENT_ID, "foo");
-        replace.where(expression);
-        replace.apply();
-
-        Runnable r = executor.getTask();
-        assertNotNull(r);
-        verifyZeroInteractions(delegateStorage);
-        verifyZeroInteractions(delegateReplace);
-
-        r.run();
-        verify(delegateStorage).createReplace(category);
-        verify(delegateReplace).setPojo(pojo);
-        verify(delegateReplace).where(expression);
-        verify(delegateReplace).apply();
-        verifyNoMoreInteractions(delegateStorage);
-
-        assertNull(fileExecutor.getTask());
-    }
-
-    @SuppressWarnings("unchecked")
-    @Test
-    public void testUpdatePojo() {
-        Update<?> delegateUpdate = mock(Update.class);
-        when(delegateStorage.createUpdate(any(Category.class))).thenReturn(delegateUpdate);
-
-        Category<?> category = mock(Category.class);
-
-        Update<?> update = queuedStorage.createUpdate(category);
-        verify(delegateStorage).createUpdate(category);
-        verifyNoMoreInteractions(delegateStorage);
-
-        update.apply();
-
-        Runnable r = executor.getTask();
-        assertNotNull(r);
-        verifyZeroInteractions(delegateUpdate);
-        r.run();
-        verify(delegateUpdate).apply();
-        verifyNoMoreInteractions(delegateUpdate);
-
-        assertNull(fileExecutor.getTask());
-    }
-
-    @Test
     public void testPurge() {
 
         queuedStorage.purge("fluff");
@@ -396,19 +332,7 @@
         public Connection getConnection() {
             // not implemented
             throw new AssertionError();
-        }
-
-        @Override
-        public <T extends Pojo> Add<T> createAdd(Category<T> category) {
-            // not implemented
-            throw new AssertionError();
-        }
-
-        @Override
-        public <T extends Pojo> Replace<T> createReplace(Category<T> category) {
-            // not implemented
-            throw new AssertionError();
-        }
+        }        
 
         @Override
         public void purge(String agentId) {
@@ -430,18 +354,6 @@
         }
 
         @Override
-        public <T extends Pojo> Update<T> createUpdate(Category<T> category) {
-            // not implemented
-            throw new AssertionError();
-        }
-
-        @Override
-        public <T extends Pojo> Remove<T> createRemove(Category<T> category) {
-            // not implemented
-            throw new AssertionError();
-        }
-
-        @Override
         public void shutdown() {
             shutDownTime = System.currentTimeMillis();
             // delay shutdown just a little
--- a/storage/core/src/test/java/com/redhat/thermostat/storage/internal/dao/DAOImplStatementDescriptorRegistrationTest.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/storage/core/src/test/java/com/redhat/thermostat/storage/internal/dao/DAOImplStatementDescriptorRegistrationTest.java	Wed Sep 25 19:09:58 2013 +0200
@@ -62,7 +62,7 @@
     public void registersAllQueries() {
         DAOImplStatementDescriptorRegistration reg = new DAOImplStatementDescriptorRegistration();
         Set<String> descriptors = reg.getStatementDescriptors();
-        assertEquals(13, descriptors.size());
+        assertEquals(22, descriptors.size());
         assertFalse(descriptors.contains(null));
     }
     
@@ -80,7 +80,7 @@
             registrations.add(r);
         }
         assertEquals(1, registrations.size());
-        assertEquals(13, registrations.get(0).getStatementDescriptors().size());
+        assertEquals(22, registrations.get(0).getStatementDescriptors().size());
     }
     
     @Test
@@ -98,9 +98,11 @@
         DAOImplStatementDescriptorRegistration factory = new DAOImplStatementDescriptorRegistration();
         List<String> errorList = new ArrayList<>();
         for (String desc: factory.getStatementDescriptors()) {
-            // should be able to get metadata for all descriptors
+            // should be able to get metadata for all query descriptors
             try {
-                factory.getDescriptorMetadata(desc, fakeParams);
+                if (desc.startsWith("QUERY")) {
+                    factory.getDescriptorMetadata(desc, fakeParams);
+                }
             } catch (IllegalArgumentException e) {
                 errorList.add(e.getMessage());
             }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/LimitExpressionTest.java	Wed Sep 25 19:09:58 2013 +0200
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2012, 2013 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.storage.internal.statement;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.redhat.thermostat.storage.core.IllegalPatchException;
+import com.redhat.thermostat.storage.core.PreparedParameter;
+
+public class LimitExpressionTest {
+    
+    private LimitExpression expn;
+    
+    @Before
+    public void setup() {
+        expn = new LimitExpression();
+        UnfinishedLimitValue unfinished = new UnfinishedLimitValue();
+        unfinished.setParameterIndex(0);
+        expn.setValue(unfinished);
+    }
+
+    @Test
+    public void canPatchWithInt() {
+        PreparedParameter p = new PreparedParameter();
+        p.setType(Integer.class);
+        p.setArrayType(false);
+        p.setValue(3);
+        
+        PatchedLimitExpression pLimit = null;
+        try {
+            pLimit = expn.patch(new PreparedParameter[] { p });
+            // pass
+        } catch (IllegalPatchException e) {
+            fail(e.getMessage());
+        }
+        assertNotNull(pLimit);
+        assertEquals(3, pLimit.getLimitValue());
+    }
+    
+    @Test
+    public void rejectPatchWithIntList() {
+        PreparedParameter p = new PreparedParameter();
+        p.setType(Integer.class);
+        p.setArrayType(true);
+        p.setValue(new int[] { 3 });
+        
+        try {
+            expn.patch(new PreparedParameter[] { p });
+            fail("Should not be able to patch with int list");
+        } catch (IllegalPatchException e) {
+            // pass
+            assertTrue(e.getMessage().contains("Invalid parameter type for limit expression."));
+        }
+    }
+    
+    @Test
+    public void rejectPatchWithString() {
+        PreparedParameter p = new PreparedParameter();
+        p.setType(String.class);
+        p.setArrayType(false);
+        p.setValue("foo");
+        
+        try {
+            expn.patch(new PreparedParameter[] { p });
+            fail("Should not be able to patch with wrong type");
+        } catch (IllegalPatchException e) {
+            // pass
+            assertTrue(e.getMessage().contains("Invalid parameter type for limit expression."));
+        }
+    }
+}
--- a/storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/ParsedStatementImplTest.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/ParsedStatementImplTest.java	Wed Sep 25 19:09:58 2013 +0200
@@ -395,7 +395,8 @@
         TerminalNode somePropertyVal = new TerminalNode(null);
         UnfinishedValueNode unfinishedPojoList = new UnfinishedValueNode();
         unfinishedPojoList.setLHS(false);
-        unfinishedPojoList.setType(Pojo[].class);
+        unfinishedPojoList.setType(Pojo.class);
+        unfinishedPojoList.setArrayType(true);
         unfinishedPojoList.setParameterIndex(0);
         somePropertyVal.setValue(unfinishedPojoList);
         SetListValue value = new SetListValue();
--- a/storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/PatchedSetListPojoConverterTest.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/PatchedSetListPojoConverterTest.java	Wed Sep 25 19:09:58 2013 +0200
@@ -36,14 +36,14 @@
 
 package com.redhat.thermostat.storage.internal.statement;
 
-import org.junit.Test;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import org.junit.Test;
+
 import com.redhat.thermostat.storage.core.Key;
 import com.redhat.thermostat.storage.internal.statement.PatchedSetListPojoConverter.IllegalPojoException;
 import com.redhat.thermostat.storage.model.Pojo;
@@ -67,6 +67,30 @@
     }
     
     @Test
+    public void testConversionWithLists() throws IllegalPojoException {
+        PatchedSetListMember mem1 = new PatchedSetListMember(new Key<>("foo"), "foo-val");
+        PatchedSetListMember mem2 = new PatchedSetListMember(new Key<>("barKey"), Long.MAX_VALUE);
+        double[] list = new double[] {Math.PI, 3.3};
+        PatchedSetListMember mem3 = new PatchedSetListMember(new Key<>("doubleList"), list);
+        PatchedSetListMember[] members = new PatchedSetListMember[] {
+                mem1,
+                mem2,
+                mem3
+        };
+        PatchedSetList setList = mock(PatchedSetList.class);
+        when(setList.getSetListMembers()).thenReturn(members);
+        PatchedSetListPojoConverter<ListPojo> converter = new PatchedSetListPojoConverter<>(setList, ListPojo.class);
+        ListPojo instance = converter.convertToPojo();
+        assertEquals("foo-val", instance.getFoo());
+        assertEquals(Long.MAX_VALUE, instance.getBarKey());
+        double[] values = instance.getDoubleList();
+        assertEquals(2, values.length);
+        double delta = 0.002;
+        assertEquals(Math.PI, values[0], delta);
+        assertEquals(3.3, values[1], delta);
+    }
+    
+    @Test
     public void testConversionFailBasic() {
         PatchedSetListMember mem1 = new PatchedSetListMember(new Key<>("wrong-Prop"), "foo-val");
         PatchedSetListMember[] members = new PatchedSetListMember[] {
@@ -102,4 +126,17 @@
             this.barKey = barKey;
         }
     }
+    
+    public static class ListPojo extends TestMe {
+        
+        private double[] doubleList;
+        
+        public double[] getDoubleList() {
+            return doubleList;
+        }
+        public void setDoubleList(double[] doubleList) {
+            this.doubleList = doubleList;
+        }
+        
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/SortMemberTest.java	Wed Sep 25 19:09:58 2013 +0200
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2012, 2013 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.storage.internal.statement;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.redhat.thermostat.storage.core.IllegalPatchException;
+import com.redhat.thermostat.storage.core.Key;
+import com.redhat.thermostat.storage.core.PreparedParameter;
+import com.redhat.thermostat.storage.core.Query.SortDirection;
+
+public class SortMemberTest {
+    
+    private SortMember member;
+    
+    @Before
+    public void setup() {
+        member = new SortMember();
+        member.setDirection(SortDirection.ASCENDING);
+        UnfinishedSortKey unfinished = new UnfinishedSortKey();
+        unfinished.setParameterIndex(0);
+        member.setSortKey(unfinished);
+    }
+
+    @Test
+    public void testPatchWithString() {
+        PreparedParameter p = new PreparedParameter();
+        p.setType(String.class);
+        p.setArrayType(false);
+        p.setValue("foo");
+        PatchedSortMemberExpression expn = null;
+        try {
+            // test patching
+            expn = member.patch(new PreparedParameter[] { p });
+            // pass
+        } catch (IllegalPatchException e) {
+            fail(e.getMessage());
+        }
+        assertNotNull(expn);
+        assertEquals(new Key<>("foo"), expn.getSortMember().getSortKey());
+    }
+    
+    @Test
+    public void rejectPatchWithStringList() {
+        PreparedParameter p = new PreparedParameter();
+        p.setType(String.class);
+        p.setArrayType(true);
+        p.setValue(new String[] { "foo" });
+        try {
+            member.patch(new PreparedParameter[] { p });
+            fail("Patching with string list should not be possible!");
+        } catch (IllegalPatchException e) {
+            // pass
+            assertTrue(e.getMessage().contains("Illegal parameter type"));
+        }
+    }
+    
+    @Test
+    public void rejectPatchWithNumber() {
+        PreparedParameter p = new PreparedParameter();
+        p.setType(Integer.class);
+        p.setArrayType(false);
+        p.setValue(1);
+        try {
+            member.patch(new PreparedParameter[] { p });
+            fail("Patching with integer should not be possible!");
+        } catch (IllegalPatchException e) {
+            // pass
+            assertTrue(e.getMessage().contains("Illegal parameter type"));
+        }
+    }
+}
--- a/storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/StatementDescriptorParserTest.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/StatementDescriptorParserTest.java	Wed Sep 25 19:09:58 2013 +0200
@@ -247,7 +247,8 @@
         String descString = "ADD " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = ?s[";
         UnfinishedValueNode unfinished = new UnfinishedValueNode();
         unfinished.setLHS(false);
-        unfinished.setType(String[].class);
+        unfinished.setType(String.class);
+        unfinished.setArrayType(true);
         unfinished.setParameterIndex(0);
         doListTypeTest(descString, unfinished);
     }
@@ -262,7 +263,8 @@
         String descString = "ADD " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = ?d[";
         UnfinishedValueNode unfinished = new UnfinishedValueNode();
         unfinished.setLHS(false);
-        unfinished.setType(Double[].class);
+        unfinished.setType(Double.class);
+        unfinished.setArrayType(true);
         unfinished.setParameterIndex(0);
         doListTypeTest(descString, unfinished);
     }
@@ -277,7 +279,8 @@
         String descString = "ADD " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = ?i[";
         UnfinishedValueNode unfinished = new UnfinishedValueNode();
         unfinished.setLHS(false);
-        unfinished.setType(Integer[].class);
+        unfinished.setType(Integer.class);
+        unfinished.setArrayType(true);
         unfinished.setParameterIndex(0);
         doListTypeTest(descString, unfinished);
     }
@@ -292,7 +295,8 @@
         String descString = "ADD " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = ?b[";
         UnfinishedValueNode unfinished = new UnfinishedValueNode();
         unfinished.setLHS(false);
-        unfinished.setType(Boolean[].class);
+        unfinished.setType(Boolean.class);
+        unfinished.setArrayType(true);
         unfinished.setParameterIndex(0);
         doListTypeTest(descString, unfinished);
     }
@@ -307,7 +311,8 @@
         String descString = "ADD " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = ?l[";
         UnfinishedValueNode unfinished = new UnfinishedValueNode();
         unfinished.setLHS(false);
-        unfinished.setType(Long[].class);
+        unfinished.setType(Long.class);
+        unfinished.setArrayType(true);
         unfinished.setParameterIndex(0);
         doListTypeTest(descString, unfinished);
     }
@@ -337,7 +342,8 @@
         String descString = "ADD " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = ?p[";
         UnfinishedValueNode unfinished = new UnfinishedValueNode();
         unfinished.setLHS(false);
-        unfinished.setType(Pojo[].class);
+        unfinished.setType(Pojo.class);
+        unfinished.setArrayType(true);
         unfinished.setParameterIndex(0);
         doListTypeTest(descString, unfinished);
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/TerminalNodeTest.java	Wed Sep 25 19:09:58 2013 +0200
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2012, 2013 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.storage.internal.statement;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+
+import com.redhat.thermostat.storage.core.IllegalPatchException;
+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.query.LiteralExpression;
+
+public class TerminalNodeTest {
+
+    @Test
+    public void canPatchWithCorrectType() {
+        TerminalNode node = new TerminalNode(null);
+        UnfinishedValueNode unfinished = new UnfinishedValueNode();
+        unfinished.setLHS(false);
+        unfinished.setParameterIndex(0);
+        unfinished.setType(String.class);
+        unfinished.setArrayType(false);
+        
+        node.setValue(unfinished);
+        
+        PreparedParameter p = new PreparedParameter();
+        p.setType(String.class);
+        p.setArrayType(false);
+        p.setValue("foo-bar");
+        
+        PatchedWhereExpression expn = null;
+        try {
+            expn = node.patch(new PreparedParameter[] { p });
+            // pass
+        } catch (IllegalPatchException e) {
+            fail(e.getMessage());
+        }
+        assertNotNull(expn);
+        LiteralExpression<?> literal = (LiteralExpression<?>)expn.getExpression();
+        assertEquals("foo-bar", literal.getValue());
+    }
+    
+    @Test
+    public void canPatchWithPojoType() {
+        TerminalNode node = new TerminalNode(null);
+        UnfinishedValueNode unfinished = new UnfinishedValueNode();
+        unfinished.setLHS(false);
+        unfinished.setParameterIndex(0);
+        unfinished.setType(Pojo.class);
+        unfinished.setArrayType(false);
+        
+        node.setValue(unfinished);
+        
+        PreparedParameter p = new PreparedParameter();
+        p.setType(AgentInformation.class);
+        p.setArrayType(false);
+        AgentInformation info = new AgentInformation("foo-bar");
+        p.setValue(info);
+        
+        PatchedWhereExpression expn = null;
+        try {
+            expn = node.patch(new PreparedParameter[] { p });
+            // pass
+        } catch (IllegalPatchException e) {
+            fail(e.getMessage());
+        }
+        assertNotNull(expn);
+        LiteralExpression<?> literal = (LiteralExpression<?>)expn.getExpression();
+        assertEquals(info, literal.getValue());
+    }
+    
+    @Test
+    public void rejectPatchingWithIncorrectListType() {
+        TerminalNode node = new TerminalNode(null);
+        UnfinishedValueNode unfinished = new UnfinishedValueNode();
+        unfinished.setLHS(false);
+        unfinished.setParameterIndex(0);
+        unfinished.setType(String.class);
+        unfinished.setArrayType(false);
+        
+        node.setValue(unfinished);
+        
+        PreparedParameter p = new PreparedParameter();
+        p.setType(String.class);
+        p.setArrayType(true);
+        p.setValue(new String[] { "foo-bar" });
+        
+        try {
+            node.patch(new PreparedParameter[] { p });
+            fail("Should not be able to patch string with string list.");
+        } catch (IllegalPatchException e) {
+            // pass
+            assertTrue(e.getMessage().contains("invalid type when attempting to patch."));
+        }
+    }
+    
+    @Test
+    public void rejectPatchingWithIncorrectPojoListType() {
+        TerminalNode node = new TerminalNode(null);
+        UnfinishedValueNode unfinished = new UnfinishedValueNode();
+        unfinished.setLHS(false);
+        unfinished.setParameterIndex(0);
+        unfinished.setType(Pojo.class);
+        unfinished.setArrayType(false);
+        
+        node.setValue(unfinished);
+        
+        PreparedParameter p = new PreparedParameter();
+        p.setType(AgentInformation.class);
+        p.setArrayType(true);
+        AgentInformation info = new AgentInformation("testing");
+        p.setValue(new AgentInformation[] { info });
+        
+        try {
+            node.patch(new PreparedParameter[] { p });
+            fail("Should not be able to patch string with string list.");
+        } catch (IllegalPatchException e) {
+            // pass
+            assertTrue(e.getMessage().contains("invalid type when attempting to patch."));
+        }
+    }
+    
+    @Test
+    public void rejectPatchingWithIncorrectType() {
+        TerminalNode node = new TerminalNode(null);
+        UnfinishedValueNode unfinished = new UnfinishedValueNode();
+        unfinished.setLHS(false);
+        unfinished.setParameterIndex(0);
+        unfinished.setType(Integer.class);
+        unfinished.setArrayType(false);
+        
+        node.setValue(unfinished);
+        
+        PreparedParameter p = new PreparedParameter();
+        p.setType(String.class);
+        p.setArrayType(true);
+        p.setValue(new String[] { "foo-bar" });
+        
+        try {
+            node.patch(new PreparedParameter[] { p });
+            fail("Should not be able to patch integer with string list.");
+        } catch (IllegalPatchException e) {
+            // pass
+            assertTrue(e.getMessage().contains("invalid type when attempting to patch."));
+        }
+    }
+}
--- a/storage/mongo/src/main/java/com/redhat/thermostat/storage/mongodb/internal/MongoStorage.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/storage/mongo/src/main/java/com/redhat/thermostat/storage/mongodb/internal/MongoStorage.java	Wed Sep 25 19:09:58 2013 +0200
@@ -407,10 +407,9 @@
     @Override
     public <T extends Pojo> PreparedStatement<T> prepareStatement(StatementDescriptor<T> statementDesc)
             throws DescriptorParsingException {
-        // FIXME: Use some kind of cache in order to avoid parsing of
-        // descriptors each time this is called. At least if the descriptor
-        // class is the same we should be able to do something here.
-        return PreparedStatementFactory.getInstance(this, statementDesc);
+        // Queued storage decorator should override this. This should never
+        // be called.
+        throw new IllegalStateException();
     }
 
     @Override
--- a/thread/collector/src/main/java/com/redhat/thermostat/thread/dao/impl/ThreadDaoImplStatementDescriptorRegistration.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/thread/collector/src/main/java/com/redhat/thermostat/thread/dao/impl/ThreadDaoImplStatementDescriptorRegistration.java	Wed Sep 25 19:09:58 2013 +0200
@@ -62,6 +62,11 @@
         descs.add(ThreadDaoImpl.QUERY_SUMMARY_SINCE);
         descs.add(ThreadDaoImpl.QUERY_THREAD_CAPS);
         descs.add(ThreadDaoImpl.QUERY_THREAD_INFO);
+        descs.add(ThreadDaoImpl.DESC_ADD_THREAD_DEADLOCK_DATA);
+        descs.add(ThreadDaoImpl.DESC_ADD_THREAD_HARVESTING_STATUS);
+        descs.add(ThreadDaoImpl.DESC_ADD_THREAD_INFO);
+        descs.add(ThreadDaoImpl.DESC_ADD_THREAD_SUMMARY);
+        descs.add(ThreadDaoImpl.DESC_REPLACE_THREAD_CAPS);
     }
     
     @Override
--- a/thread/collector/src/test/java/com/redhat/thermostat/thread/dao/impl/ThreadDAOImplStatementDescriptorRegistrationTest.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/thread/collector/src/test/java/com/redhat/thermostat/thread/dao/impl/ThreadDAOImplStatementDescriptorRegistrationTest.java	Wed Sep 25 19:09:58 2013 +0200
@@ -73,10 +73,10 @@
     }
     
     @Test
-    public void registersAllQueries() {
+    public void registersAllDescriptors() {
         ThreadDaoImplStatementDescriptorRegistration reg = new ThreadDaoImplStatementDescriptorRegistration();
         Set<String> descriptors = reg.getStatementDescriptors();
-        assertEquals(6, descriptors.size());
+        assertEquals(11, descriptors.size());
         assertFalse("null descriptor not allowed", descriptors.contains(null));
     }
     
@@ -104,7 +104,7 @@
         // storage-core + this module
         assertEquals(2, registrations.size());
         assertNotNull(threadDaoReg);
-        assertEquals(6, threadDaoReg.getStatementDescriptors().size());
+        assertEquals(11, threadDaoReg.getStatementDescriptors().size());
     }
     
     private Triple<String, String, PreparedParameter[]> setupForMetaDataTest() {
--- a/vm-classstat/common/src/main/java/com/redhat/thermostat/vm/classstat/common/internal/VmClassStatDAOImplStatementDescriptorRegistration.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/vm-classstat/common/src/main/java/com/redhat/thermostat/vm/classstat/common/internal/VmClassStatDAOImplStatementDescriptorRegistration.java	Wed Sep 25 19:09:58 2013 +0200
@@ -60,6 +60,7 @@
     public Set<String> getStatementDescriptors() {
         Set<String> descs = new HashSet<>(1);
         descs.add(QUERY);
+        descs.add(VmClassStatDAOImpl.DESC_ADD_VM_CLASS_STAT);
         return descs;
     }
 
--- a/vm-classstat/common/src/test/java/com/redhat/thermostat/vm/classstat/common/internal/VmClassStatDAOImplStatementDescriptorRegistrationTest.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/vm-classstat/common/src/test/java/com/redhat/thermostat/vm/classstat/common/internal/VmClassStatDAOImplStatementDescriptorRegistrationTest.java	Wed Sep 25 19:09:58 2013 +0200
@@ -61,10 +61,10 @@
 public class VmClassStatDAOImplStatementDescriptorRegistrationTest {
 
     @Test
-    public void registersAllQueries() {
+    public void registersAllDescriptors() {
         VmClassStatDAOImplStatementDescriptorRegistration reg = new VmClassStatDAOImplStatementDescriptorRegistration();
         Set<String> descriptors = reg.getStatementDescriptors();
-        assertEquals(1, descriptors.size());
+        assertEquals(2, descriptors.size());
         assertFalse("null descriptor not allowed", descriptors.contains(null));
     }
     
@@ -92,7 +92,7 @@
         // storage-core + this module
         assertEquals(2, registrations.size());
         assertNotNull(vmClassStatReg);
-        assertEquals(1, vmClassStatReg.getStatementDescriptors().size());
+        assertEquals(2, vmClassStatReg.getStatementDescriptors().size());
     }
     
     @Test
--- a/vm-cpu/common/src/main/java/com/redhat/thermostat/vm/cpu/common/internal/VmCpuStatDAOImplStatementDescriptorRegistration.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/vm-cpu/common/src/main/java/com/redhat/thermostat/vm/cpu/common/internal/VmCpuStatDAOImplStatementDescriptorRegistration.java	Wed Sep 25 19:09:58 2013 +0200
@@ -59,6 +59,7 @@
     @Override
     public Set<String> getStatementDescriptors() {
         Set<String> descs = new HashSet<>();
+        descs.add(VmCpuStatDAOImpl.DESC_ADD_VM_CPU_STAT);
         descs.add(descriptor);
         return descs;
     }
--- a/vm-cpu/common/src/test/java/com/redhat/thermostat/vm/cpu/common/internal/VmCpuStatDAOImplStatementDescriptorRegistrationTest.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/vm-cpu/common/src/test/java/com/redhat/thermostat/vm/cpu/common/internal/VmCpuStatDAOImplStatementDescriptorRegistrationTest.java	Wed Sep 25 19:09:58 2013 +0200
@@ -61,10 +61,10 @@
 public class VmCpuStatDAOImplStatementDescriptorRegistrationTest {
 
     @Test
-    public void registersAllQueries() {
+    public void registersAllDescriptors() {
         VmCpuStatDAOImplStatementDescriptorRegistration reg = new VmCpuStatDAOImplStatementDescriptorRegistration();
         Set<String> descriptors = reg.getStatementDescriptors();
-        assertEquals(1, descriptors.size());
+        assertEquals(2, descriptors.size());
         assertFalse("null descriptor not allowed", descriptors.contains(null));
     }
     
@@ -92,7 +92,7 @@
         // storage-core + this module
         assertEquals(2, registrations.size());
         assertNotNull(vmCpuStatReg);
-        assertEquals(1, vmCpuStatReg.getStatementDescriptors().size());
+        assertEquals(2, vmCpuStatReg.getStatementDescriptors().size());
     }
     
     @Test
--- a/vm-gc/common/src/main/java/com/redhat/thermostat/vm/gc/common/internal/VmGcStatDAOImplStatementDescriptorRegistration.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/vm-gc/common/src/main/java/com/redhat/thermostat/vm/gc/common/internal/VmGcStatDAOImplStatementDescriptorRegistration.java	Wed Sep 25 19:09:58 2013 +0200
@@ -61,6 +61,7 @@
     public Set<String> getStatementDescriptors() {
         Set<String> descs = new HashSet<>();
         descs.add(descriptor);
+        descs.add(VmGcStatDAOImpl.DESC_ADD_VM_GC_STAT);
         return descs;
     }
 
--- a/vm-gc/common/src/test/java/com/redhat/thermostat/vm/gc/common/internal/VmGcStatDAOImplStatementDescriptorRegistrationTest.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/vm-gc/common/src/test/java/com/redhat/thermostat/vm/gc/common/internal/VmGcStatDAOImplStatementDescriptorRegistrationTest.java	Wed Sep 25 19:09:58 2013 +0200
@@ -61,10 +61,10 @@
 public class VmGcStatDAOImplStatementDescriptorRegistrationTest {
 
     @Test
-    public void registersAllQueries() {
+    public void registersAllDescriptors() {
         VmGcStatDAOImplStatementDescriptorRegistration reg = new VmGcStatDAOImplStatementDescriptorRegistration();
         Set<String> descriptors = reg.getStatementDescriptors();
-        assertEquals(1, descriptors.size());
+        assertEquals(2, descriptors.size());
         assertFalse("null descriptor not allowed", descriptors.contains(null));
     }
     
@@ -92,7 +92,7 @@
         // storage-core + this module
         assertEquals(2, registrations.size());
         assertNotNull(vmGcStatReg);
-        assertEquals(1, vmGcStatReg.getStatementDescriptors().size());
+        assertEquals(2, vmGcStatReg.getStatementDescriptors().size());
     }
     
     @Test
--- a/vm-heap-analysis/common/src/main/java/com/redhat/thermostat/vm/heap/analysis/common/internal/HeapDAOImplStatementDescriptorRegistration.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/vm-heap-analysis/common/src/main/java/com/redhat/thermostat/vm/heap/analysis/common/internal/HeapDAOImplStatementDescriptorRegistration.java	Wed Sep 25 19:09:58 2013 +0200
@@ -56,6 +56,7 @@
         Set<String> descs = new HashSet<>(2);
         descs.add(HeapDAOImpl.QUERY_ALL_HEAPS);
         descs.add(HeapDAOImpl.QUERY_HEAP_INFO);
+        descs.add(HeapDAOImpl.DESC_ADD_VM_HEAP_INFO);
         return descs;
     }
 
--- a/vm-heap-analysis/common/src/test/java/com/redhat/thermostat/vm/heap/analysis/common/internal/HeapDAOImplStatementDescriptorRegistrationTest.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/vm-heap-analysis/common/src/test/java/com/redhat/thermostat/vm/heap/analysis/common/internal/HeapDAOImplStatementDescriptorRegistrationTest.java	Wed Sep 25 19:09:58 2013 +0200
@@ -61,10 +61,10 @@
 public class HeapDAOImplStatementDescriptorRegistrationTest {
 
     @Test
-    public void registersAllQueries() {
+    public void registersAllDescriptors() {
         HeapDAOImplStatementDescriptorRegistration reg = new HeapDAOImplStatementDescriptorRegistration();
         Set<String> descriptors = reg.getStatementDescriptors();
-        assertEquals(2, descriptors.size());
+        assertEquals(3, descriptors.size());
         assertFalse("null descriptor not allowed", descriptors.contains(null));
     }
     
@@ -92,7 +92,7 @@
         // storage-core + this module
         assertEquals(2, registrations.size());
         assertNotNull(heapDaoReg);
-        assertEquals(2, heapDaoReg.getStatementDescriptors().size());
+        assertEquals(3, heapDaoReg.getStatementDescriptors().size());
     }
     
     @Test
--- a/vm-jmx/common/src/main/java/com/redhat/thermostat/vm/jmx/common/internal/JmxNotificationDAOImplStatementDescriptorRegistration.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/vm-jmx/common/src/main/java/com/redhat/thermostat/vm/jmx/common/internal/JmxNotificationDAOImplStatementDescriptorRegistration.java	Wed Sep 25 19:09:58 2013 +0200
@@ -57,6 +57,8 @@
         descs = new HashSet<>(2);
         descs.add(JmxNotificationDAOImpl.QUERY_LATEST_NOTIFICATION_STATUS);
         descs.add(JmxNotificationDAOImpl.QUERY_NOTIFICATIONS);
+        descs.add(JmxNotificationDAOImpl.DESC_ADD_NOTIFICATION);
+        descs.add(JmxNotificationDAOImpl.DESC_ADD_NOTIFICATION_STATUS);
     }
 
     @Override
--- a/vm-jmx/common/src/test/java/com/redhat/thermostat/vm/jmx/common/internal/JmxNotificationDAOImplStatementDescriptorRegistrationTest.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/vm-jmx/common/src/test/java/com/redhat/thermostat/vm/jmx/common/internal/JmxNotificationDAOImplStatementDescriptorRegistrationTest.java	Wed Sep 25 19:09:58 2013 +0200
@@ -61,10 +61,10 @@
 public class JmxNotificationDAOImplStatementDescriptorRegistrationTest {
 
     @Test
-    public void registersAllQueries() {
+    public void registersAllDescriptors() {
         JmxNotificationDAOImplStatementDescriptorRegistration reg = new JmxNotificationDAOImplStatementDescriptorRegistration();
         Set<String> descriptors = reg.getStatementDescriptors();
-        assertEquals(2, descriptors.size());
+        assertEquals(4, descriptors.size());
         assertFalse("null descriptor not allowed", descriptors.contains(null));
     }
     
@@ -92,7 +92,7 @@
         // storage-core + this module
         assertEquals(2, registrations.size());
         assertNotNull(jmxDaoReg);
-        assertEquals(2, jmxDaoReg.getStatementDescriptors().size());
+        assertEquals(4, jmxDaoReg.getStatementDescriptors().size());
     }
     
     @Test
--- a/vm-memory/common/src/main/java/com/redhat/thermostat/vm/memory/common/internal/VmMemoryStatDAOImplStatementDescriptorRegistration.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/vm-memory/common/src/main/java/com/redhat/thermostat/vm/memory/common/internal/VmMemoryStatDAOImplStatementDescriptorRegistration.java	Wed Sep 25 19:09:58 2013 +0200
@@ -61,6 +61,7 @@
                 VmMemoryStatDAO.vmMemoryStatsCategory.getName());
         descs.add(descriptor);
         descs.add(VmMemoryStatDAOImpl.QUERY_LATEST);
+        descs.add(VmMemoryStatDAOImpl.DESC_ADD_VM_MEMORY_STAT);
     }
 
     @Override
--- a/vm-memory/common/src/test/java/com/redhat/thermostat/vm/memory/common/internal/VmMemoryStatDAOImplStatementDescriptorRegistrationTest.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/vm-memory/common/src/test/java/com/redhat/thermostat/vm/memory/common/internal/VmMemoryStatDAOImplStatementDescriptorRegistrationTest.java	Wed Sep 25 19:09:58 2013 +0200
@@ -63,10 +63,10 @@
 public class VmMemoryStatDAOImplStatementDescriptorRegistrationTest {
 
     @Test
-    public void registersAllQueries() {
+    public void registersAllDescriptors() {
         VmMemoryStatDAOImplStatementDescriptorRegistration reg = new VmMemoryStatDAOImplStatementDescriptorRegistration();
         Set<String> descriptors = reg.getStatementDescriptors();
-        assertEquals(2, descriptors.size());
+        assertEquals(3, descriptors.size());
         assertFalse("null descriptor not allowed", descriptors.contains(null));
     }
     
@@ -94,7 +94,7 @@
         // storage-core + this module
         assertEquals(2, registrations.size());
         assertNotNull(vmMemoryDaoReg);
-        assertEquals(2, vmMemoryDaoReg.getStatementDescriptors().size());
+        assertEquals(3, vmMemoryDaoReg.getStatementDescriptors().size());
     }
     
     @Test
--- a/web/client/src/main/java/com/redhat/thermostat/web/client/internal/WebStorage.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/web/client/src/main/java/com/redhat/thermostat/web/client/internal/WebStorage.java	Wed Sep 25 19:09:58 2013 +0200
@@ -42,12 +42,10 @@
 import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.io.Reader;
-import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Type;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.security.SecureRandom;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
@@ -59,7 +57,6 @@
 import javax.net.ssl.SSLContext;
 import javax.servlet.http.HttpServletResponse;
 
-import org.apache.commons.beanutils.BeanUtils;
 import org.apache.commons.codec.binary.Base64;
 import org.apache.http.Header;
 import org.apache.http.HttpEntity;
@@ -88,42 +85,29 @@
 import com.redhat.thermostat.common.utils.LoggingUtils;
 import com.redhat.thermostat.storage.config.AuthenticationConfiguration;
 import com.redhat.thermostat.storage.config.StartupConfiguration;
-import com.redhat.thermostat.storage.core.Add;
 import com.redhat.thermostat.storage.core.AuthToken;
 import com.redhat.thermostat.storage.core.Category;
 import com.redhat.thermostat.storage.core.Connection;
 import com.redhat.thermostat.storage.core.Cursor;
-import com.redhat.thermostat.storage.core.DataModifyingStatement;
 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.Key;
 import com.redhat.thermostat.storage.core.PreparedParameter;
 import com.redhat.thermostat.storage.core.PreparedStatement;
-import com.redhat.thermostat.storage.core.Remove;
-import com.redhat.thermostat.storage.core.Replace;
 import com.redhat.thermostat.storage.core.SecureStorage;
 import com.redhat.thermostat.storage.core.StatementDescriptor;
 import com.redhat.thermostat.storage.core.StatementExecutionException;
 import com.redhat.thermostat.storage.core.Storage;
 import com.redhat.thermostat.storage.core.StorageException;
-import com.redhat.thermostat.storage.core.Update;
 import com.redhat.thermostat.storage.model.Pojo;
-import com.redhat.thermostat.storage.query.Expression;
-import com.redhat.thermostat.storage.query.Operator;
-import com.redhat.thermostat.web.common.ExpressionSerializer;
-import com.redhat.thermostat.web.common.OperatorSerializer;
 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.WebAdd;
 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.WebRemove;
-import com.redhat.thermostat.web.common.WebReplace;
-import com.redhat.thermostat.web.common.WebUpdate;
 
 public class WebStorage implements Storage, SecureStorage {
 
@@ -299,53 +283,6 @@
 
     }
 
-    private class WebAddImpl<T extends Pojo> extends WebAdd<T> {
-
-        private WebAddImpl(int categoryId) {
-            super(categoryId);
-        }
-        
-        @Override
-        public int apply() {
-            return addImpl(this);
-        }
-        
-    }
-
-    private class WebReplaceImpl<T extends Pojo> extends WebReplace<T> {
-        
-        private WebReplaceImpl(int categoryId) {
-            super(categoryId);
-        }
-        
-        @Override
-        public int apply() {
-            return replaceImpl(this);
-        }
-        
-    }
-
-    private class WebUpdateImpl<T extends Pojo> extends WebUpdate<T> {
-    
-        @Override
-        public int apply() {
-            return updatePojo(this);
-        }
-    }
-    
-    private class WebRemoveImpl<T extends Pojo> extends WebRemove<T> {
-        
-        private WebRemoveImpl(int categoryId) {
-            super(categoryId);
-        }
-        
-        @Override
-        public int apply() {
-            return removePojo(this);
-        }
-        
-    }
-    
     private class WebPreparedStatementImpl<T extends Pojo> extends WebPreparedStatement<T> {
 
         // The type of the query result objects we'd get back upon
@@ -359,7 +296,7 @@
         
         @Override
         public int execute() throws StatementExecutionException {
-            throw new IllegalStateException("Not yet implemented!");
+            return doWriteExecute(this);
         }
 
         @Override
@@ -396,9 +333,6 @@
         categoryIds = new HashMap<>();
         gson = new GsonBuilder().registerTypeHierarchyAdapter(Pojo.class,
                         new ThermostatGSONConverter())
-                .registerTypeHierarchyAdapter(Expression.class,
-                        new ExpressionSerializer())
-                .registerTypeHierarchyAdapter(Operator.class, new OperatorSerializer())
                 .registerTypeAdapter(WebPreparedStatement.class, new WebPreparedStatementSerializer())
                 .registerTypeAdapter(WebQueryResponse.class, new WebQueryResponseSerializer<>())
                 .registerTypeAdapter(PreparedParameter.class, new PreparedParameterSerializer())
@@ -531,18 +465,6 @@
         }
     }
 
-    @Override
-    public <T extends Pojo> Remove<T> createRemove(Category<T> category) {
-        return new WebRemoveImpl<>(categoryIds.get(category));
-    }
-
-    @Override
-    public <T extends Pojo> Update<T> createUpdate(Category<T> category) {
-        WebUpdateImpl<T> updateImpl = new WebUpdateImpl<>();
-        updateImpl.setCategoryId(categoryIds.get(category));
-        return updateImpl;
-    }
-
     /**
      * Executes a prepared query
      * 
@@ -569,10 +491,10 @@
         } catch (Exception e) {
             throw new StatementExecutionException(e);
         }
-        if (qResp.getResponseCode() == WebQueryResponse.SUCCESS) {
+        if (qResp.getResponseCode() == PreparedStatementResponseCode.QUERY_SUCCESS) {
             T[] result = qResp.getResultList();
             return new WebCursor<T>(result);
-        } else if (qResp.getResponseCode() == WebQueryResponse.ILLEGAL_PATCH) {
+        } else if (qResp.getResponseCode() == PreparedStatementResponseCode.ILLEGAL_PATCH) {
             String msg = "Illegal statement argument. See server logs for details.";
             IllegalArgumentException iae = new IllegalArgumentException(msg);
             IllegalPatchException e = new IllegalPatchException(iae);
@@ -586,6 +508,38 @@
             throw new StatementExecutionException(ise);
         }
     }
+    
+    /**
+     * Executes a prepared write
+     * 
+     * @param stmt
+     *            The prepared statement to execute
+     * @return The response code of executing the underlying data modifying
+     *         statement.
+     * @throws StatementExecutionException
+     *             If execution of the statement failed. For example if the
+     *             values set as prepared parameters did not work or were
+     *             partially missing for the prepared statement.
+     */
+    private <T extends Pojo> int doWriteExecute(WebPreparedStatement<T> stmt)
+            throws StatementExecutionException {
+        NameValuePair queryParam = new BasicNameValuePair("prepared-stmt", gson.toJson(stmt, WebPreparedStatement.class));
+        List<NameValuePair> formparams = Arrays.asList(queryParam);
+        int responseCode = PreparedStatementResponseCode.WRITE_GENERIC_FAILURE;
+        try (CloseableHttpEntity entity = post(endpoint + "/write-execute", formparams)) {
+            Reader reader = getContentAsReader(entity);
+            responseCode = gson.fromJson(reader, int.class);
+        } catch (Exception e) {
+            throw new StatementExecutionException(e);
+        }
+        if (responseCode == PreparedStatementResponseCode.ILLEGAL_PATCH) {
+            String msg = "Illegal statement argument. See server logs for details.";
+            IllegalArgumentException iae = new IllegalArgumentException(msg);
+            IllegalPatchException e = new IllegalPatchException(iae);
+            throw new StatementExecutionException(e);
+        }
+        return responseCode;
+    }
 
     @Override
     public Connection getConnection() {
@@ -628,80 +582,6 @@
         post(endpoint + "/purge", agentIdParams).close();
     }
 
-    @Override
-    public <T extends Pojo> Add<T> createAdd(Category<T> into) {
-        int categoryId = getCategoryId(into);
-        WebAdd<T> add = new WebAddImpl<>(categoryId);
-        return add;
-    }
-
-    @Override
-    public <T extends Pojo> Replace<T> createReplace(Category<T> into) {
-        int categoryId = getCategoryId(into);
-        WebReplace<T> replace = new WebReplaceImpl<>(categoryId);
-        return replace;
-    }
-    
-    private int addImpl(final WebAdd<?> add) throws StorageException {
-        Pojo pojo = add.getPojo();
-        checkAgentIdIsSet(pojo);
-        NameValuePair pojoParam = new BasicNameValuePair("pojo",
-                gson.toJson(pojo));
-        NameValuePair addParam = new BasicNameValuePair("add",
-                gson.toJson(add));
-        List<NameValuePair> formParams = Arrays.asList(addParam, pojoParam);
-        post(endpoint + "/add-pojo", formParams).close();
-        return DataModifyingStatement.DEFAULT_STATUS_SUCCESS;
-    }
-
-    private int replaceImpl(final WebReplace<?> replace) throws StorageException {
-        Pojo pojo = replace.getPojo();
-        checkAgentIdIsSet(pojo);
-        NameValuePair replaceParam = new BasicNameValuePair("replace",
-                gson.toJson(replace));
-        NameValuePair pojoParam = new BasicNameValuePair("pojo",
-                gson.toJson(pojo));
-        List<NameValuePair> formParams = Arrays.asList(replaceParam, pojoParam);
-        post(endpoint + "/replace-pojo", formParams).close();
-        return DataModifyingStatement.DEFAULT_STATUS_SUCCESS;
-    }
-
-    private void checkAgentIdIsSet(final Pojo pojo) throws AssertionError {
-        try {
-            if (BeanUtils.getProperty(pojo, Key.AGENT_ID.getName()) == null) {
-                throw new AssertionError("agentId must be set!");
-            }
-        } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
-            throw new AssertionError("Pojo needs to have an agentId property");
-        }
-    }
-
-    private int removePojo(Remove<?> remove) throws StorageException {
-        NameValuePair removeParam = new BasicNameValuePair("remove",
-                gson.toJson(remove));
-        List<NameValuePair> formparams = Arrays.asList(removeParam);
-        post(endpoint + "/remove-pojo", formparams).close();
-        return DataModifyingStatement.DEFAULT_STATUS_SUCCESS;
-    }
-
-    private int updatePojo(Update<?> update) throws StorageException {
-        WebUpdate<?> webUp = (WebUpdate<?>) update;
-        List<WebUpdate.UpdateValue> updateValues = webUp.getUpdates();
-        List<Object> values = new ArrayList<>(updateValues.size());
-        for (WebUpdate.UpdateValue updateValue : updateValues) {
-            values.add(updateValue.getValue());
-        }
-
-        NameValuePair updateParam = new BasicNameValuePair("update",
-                gson.toJson(update));
-        NameValuePair valuesParam = new BasicNameValuePair("values",
-                gson.toJson(values));
-        List<NameValuePair> formparams = Arrays
-                .asList(updateParam, valuesParam);
-        post(endpoint + "/update-pojo", formparams).close();
-        return DataModifyingStatement.DEFAULT_STATUS_SUCCESS;
-    }
-
     public void setEndpoint(String endpoint) {
         this.endpoint = endpoint;
     }
--- a/web/client/src/test/java/com/redhat/thermostat/web/client/internal/WebStorageTest.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/web/client/src/test/java/com/redhat/thermostat/web/client/internal/WebStorageTest.java	Wed Sep 25 19:09:58 2013 +0200
@@ -58,10 +58,8 @@
 import java.util.Arrays;
 import java.util.Enumeration;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
-import java.util.UUID;
 import java.util.concurrent.CountDownLatch;
 
 import javax.servlet.ServletException;
@@ -86,11 +84,7 @@
 
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonParser;
-import com.google.gson.JsonSyntaxException;
 import com.redhat.thermostat.storage.config.StartupConfiguration;
-import com.redhat.thermostat.storage.core.Add;
 import com.redhat.thermostat.storage.core.AuthToken;
 import com.redhat.thermostat.storage.core.Categories;
 import com.redhat.thermostat.storage.core.Category;
@@ -103,30 +97,19 @@
 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.Remove;
-import com.redhat.thermostat.storage.core.Replace;
 import com.redhat.thermostat.storage.core.StatementDescriptor;
 import com.redhat.thermostat.storage.core.StatementExecutionException;
-import com.redhat.thermostat.storage.core.Update;
 import com.redhat.thermostat.storage.model.Pojo;
-import com.redhat.thermostat.storage.query.Expression;
-import com.redhat.thermostat.storage.query.ExpressionFactory;
-import com.redhat.thermostat.storage.query.Operator;
 import com.redhat.thermostat.test.FreePortFinder;
 import com.redhat.thermostat.test.FreePortFinder.TryPort;
-import com.redhat.thermostat.web.common.ExpressionSerializer;
-import com.redhat.thermostat.web.common.OperatorSerializer;
 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.WebAdd;
 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.WebRemove;
-import com.redhat.thermostat.web.common.WebReplace;
-import com.redhat.thermostat.web.common.WebUpdate;
 
 public class WebStorageTest {
 
@@ -147,16 +130,13 @@
 
     private static Category<TestObj> category;
     private static Key<String> key1;
-    private static Key<Integer> key2;
 
     private WebStorage storage;
 
-    private ExpressionFactory factory;
 
     @BeforeClass
     public static void setupCategory() {
         key1 = new Key<>("property1");
-        key2 = new Key<>("property2");
         category = new Category<>("test", TestObj.class, key1);
     }
 
@@ -188,7 +168,6 @@
         storage.setEndpoint("http://localhost:" + port + "/");
         headers = new HashMap<>();
         registerCategory();
-        factory = new ExpressionFactory();
     }
 
     private void startServer(int port) throws Exception {
@@ -365,7 +344,7 @@
         assertEquals(String.class, params.getParams()[0].getType());
         
         WebQueryResponse<TestObj> fakeQueryResponse = new WebQueryResponse<>();
-        fakeQueryResponse.setResponseCode(WebQueryResponse.SUCCESS);
+        fakeQueryResponse.setResponseCode(PreparedStatementResponseCode.QUERY_SUCCESS);
         fakeQueryResponse.setResultList(new TestObj[] { obj1, obj2 });
         prepareServer(gson.toJson(fakeQueryResponse));
         Cursor<TestObj> results = null;
@@ -392,181 +371,56 @@
     }
     
     @Test
-    public void testAdd() throws IOException, JsonSyntaxException, ClassNotFoundException {
-
-        UUID agentId = new UUID(1, 2);
-        TestObj obj = new TestObj();
-        obj.setProperty1("fluff");
-        obj.setAgentId(agentId.toString());
-
-        Add<TestObj> add = storage.createAdd(category);
-        add.setPojo(obj);
-
-        prepareServer();
-        add.apply();
-
-        Gson gson = new Gson();
-        StringReader reader = new StringReader(requestBody);
-        BufferedReader bufRead = new BufferedReader(reader);
-        String line = URLDecoder.decode(bufRead.readLine(), "UTF-8");
-        String [] params = line.split("&");
-        assertEquals(2, params.length);
-        String[] parts = params[0].split("=");
-        assertEquals("add", parts[0]);
-        @SuppressWarnings("unchecked")
-        WebAdd<TestObj> add2 = gson.fromJson(parts[1], WebAdd.class);
-        assertEquals(42, add2.getCategoryId());
-
-        parts = params[1].split("=");
-        assertEquals(2, parts.length);
-        assertEquals("pojo", parts[0]);
-        Object resultObj = gson.fromJson(parts[1], TestObj.class);
-        assertEquals(obj, resultObj);
-    }
-
-    @Test
-    public void testReplace() throws IOException, JsonSyntaxException, ClassNotFoundException {
-
-        // We need an agentId, so that we can check automatic insert of agentId.
-        UUID agentId = new UUID(1, 2);
-        TestObj obj = new TestObj();
-        obj.setAgentId(agentId.toString());
-        obj.setProperty1("fluff");
-
-        Replace<TestObj> replace = storage.createReplace(category);
-        Expression expr = new ExpressionFactory().equalTo(key1, "fluff");
-        replace.setPojo(obj);
-        replace.where(expr);
-
-        prepareServer();
-        replace.apply();
-
-        Gson gson = new GsonBuilder()
-                .registerTypeHierarchyAdapter(Expression.class,
-                        new ExpressionSerializer())
-                .registerTypeHierarchyAdapter(Operator.class,
-                        new OperatorSerializer()).create();
-        StringReader reader = new StringReader(requestBody);
-        BufferedReader bufRead = new BufferedReader(reader);
-        String line = URLDecoder.decode(bufRead.readLine(), "UTF-8");
-        String [] params = line.split("&");
-        assertEquals(2, params.length);
-        String[] parts = params[0].split("=");
-        assertEquals("replace", parts[0]);
-        @SuppressWarnings("unchecked")
-        WebReplace<TestObj> insert = gson.fromJson(parts[1], WebReplace.class);
-        assertEquals(42, insert.getCategoryId());
-
-        parts = params[1].split("=");
-        assertEquals(2, parts.length);
-        assertEquals("pojo", parts[0]);
-        Object resultObj = gson.fromJson(parts[1], TestObj.class);
-        assertEquals(obj, resultObj);
-    }
-
-    @Test
-    public void testCreateRemove() {
-        WebRemove<?> remove = (WebRemove<?>) storage.createRemove(category);
-        assertEquals(42, remove.getCategoryId());
-        Expression expr = factory.equalTo(key1, "test");
-        remove.where(expr);
-        assertEquals(expr, remove.getWhereExpression());
-    }
-
-    @Test
-    public void testRemovePojo() throws UnsupportedEncodingException, IOException {
-        Expression expr = factory.equalTo(key1, "test");
-        Remove<?> remove = storage.createRemove(category);
-        remove.where(expr);
+    public void canPrepareAndExecuteWrite() {
+        TestObj obj1 = new TestObj();
+        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();
 
-        prepareServer();
-        remove.apply();
-
-        Gson gson = new GsonBuilder()
-                .registerTypeHierarchyAdapter(Expression.class,
-                        new ExpressionSerializer())
-                .registerTypeHierarchyAdapter(Operator.class,
-                        new OperatorSerializer()).create();
-        StringReader reader = new StringReader(requestBody);
-        BufferedReader bufRead = new BufferedReader(reader);
-        String line = URLDecoder.decode(bufRead.readLine(), "UTF-8");
-        String[] parts = line.split("=");
-        assertEquals("remove", parts[0]);
-        WebRemove<?> actualRemove = gson.fromJson(parts[1], WebRemove.class);
+        String strDesc = "ADD test SET 'property1' = ?s";
+        StatementDescriptor<TestObj> desc = new StatementDescriptor<>(category, strDesc);
+        PreparedStatement<TestObj> stmt = null;
         
-        assertEquals(42, actualRemove.getCategoryId());
-        assertEquals(expr, actualRemove.getWhereExpression());
-    }
-
-    @Test
-    public void testCreateUpdate() {
-        WebUpdate<?> update = (WebUpdate<?>) storage.createUpdate(category);
-        assertNotNull(update);
-        assertEquals(42, update.getCategoryId());
-
-        Expression expr = factory.equalTo(key1, "test");
-        update.where(expr);
-        assertEquals(expr, update.getWhereExpression());
-
-        update.set(key1, "fluff");
-        List<WebUpdate.UpdateValue> updates = update.getUpdates();
-        assertEquals(1, updates.size());
-        assertEquals("fluff", updates.get(0).getValue());
-        assertEquals(key1, updates.get(0).getKey());
-        assertEquals("java.lang.String", updates.get(0).getValueClass());
-    }
-
-    @Test
-    public void testUpdate() throws UnsupportedEncodingException, IOException, JsonSyntaxException, ClassNotFoundException {
-
-        Update<?> update = storage.createUpdate(category);
-        Expression expr = factory.equalTo(key1, "test");
-        update.where(expr);
-        update.set(key1, "fluff");
-        update.set(key2, 42);
-
-        prepareServer();
-        update.apply();
-
-        Gson gson = new GsonBuilder()
-                .registerTypeHierarchyAdapter(Expression.class,
-                        new ExpressionSerializer())
-                .registerTypeHierarchyAdapter(Operator.class,
-                        new OperatorSerializer()).create();
-        StringReader reader = new StringReader(requestBody);
-        BufferedReader bufRead = new BufferedReader(reader);
-        String line = URLDecoder.decode(bufRead.readLine(), "UTF-8");
-        String [] params = line.split("&");
-        assertEquals(2, params.length);
-        String[] parts = params[0].split("=");
-        assertEquals("update", parts[0]);
-        WebUpdate<?> receivedUpdate = gson.fromJson(parts[1], WebUpdate.class);
-        assertEquals(42, receivedUpdate.getCategoryId());
-
-        List<WebUpdate.UpdateValue> updates = receivedUpdate.getUpdates();
-        assertEquals(2, updates.size());
-
-        WebUpdate.UpdateValue update1 = updates.get(0);
-        assertEquals(key1, update1.getKey());
-        assertEquals("java.lang.String", update1.getValueClass());
-        assertNull(update1.getValue());
-
-        WebUpdate.UpdateValue update2 = updates.get(1);
-        assertEquals(key2, update2.getKey());
-        assertEquals("java.lang.Integer", update2.getValueClass());
-        assertNull(update2.getValue());
-
-        assertEquals(expr, receivedUpdate.getWhereExpression());
-
-        parts = params[1].split("=");
-        assertEquals(2, parts.length);
-        assertEquals("values", parts[0]);
-        JsonParser jsonParser = new JsonParser();
-        JsonArray jsonArray = jsonParser.parse(parts[1]).getAsJsonArray();
-        String value1 = gson.fromJson(jsonArray.get(0), String.class);
-        assertEquals("fluff", value1);
-        int value2 = gson.fromJson(jsonArray.get(1), Integer.class);
-        assertEquals(42, value2);
+        int fakePrepStmtId = 3;
+        WebPreparedStatementResponse fakeResponse = new WebPreparedStatementResponse();
+        fakeResponse.setNumFreeVariables(1);
+        fakeResponse.setStatementId(fakePrepStmtId);
+        prepareServer(gson.toJson(fakeResponse));
+        try {
+            stmt = storage.prepareStatement(desc);
+        } catch (DescriptorParsingException e) {
+            // descriptor should parse fine and is trusted
+            fail(e.getMessage());
+        }
+        assertTrue(stmt instanceof WebPreparedStatement);
+        WebPreparedStatement<TestObj> webStmt = (WebPreparedStatement<TestObj>)stmt;
+        assertEquals(fakePrepStmtId, webStmt.getStatementId());
+        PreparedParameters params = webStmt.getParams();
+        assertEquals(1, params.getParams().length);
+        assertNull(params.getParams()[0]);
+        
+        // now set a parameter
+        stmt.setString(0, "fluff");
+        assertEquals("fluff", params.getParams()[0].getValue());
+        assertEquals(String.class, params.getParams()[0].getType());
+        assertFalse(params.getParams()[0].isArrayType());
+        
+        prepareServer(gson.toJson(PreparedStatementResponseCode.WRITE_GENERIC_FAILURE));
+        
+        int response = Integer.MAX_VALUE;
+        try {
+            response = stmt.execute();
+        } catch (StatementExecutionException e) {
+            // should execute fine
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+        
+        assertEquals(PreparedStatementResponseCode.WRITE_GENERIC_FAILURE, response);
     }
 
     @Test
--- a/web/common/src/main/java/com/redhat/thermostat/web/common/ExpressionSerializer.java	Mon Sep 16 15:28:13 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,286 +0,0 @@
-/*
- * Copyright 2012, 2013 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 java.util.HashSet;
-import java.util.Set;
-
-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.JsonSerializationContext;
-import com.google.gson.JsonSerializer;
-import com.redhat.thermostat.storage.core.Key;
-import com.redhat.thermostat.storage.query.BinaryComparisonExpression;
-import com.redhat.thermostat.storage.query.BinaryComparisonOperator;
-import com.redhat.thermostat.storage.query.BinaryLogicalExpression;
-import com.redhat.thermostat.storage.query.BinaryLogicalOperator;
-import com.redhat.thermostat.storage.query.BinarySetMembershipExpression;
-import com.redhat.thermostat.storage.query.BinarySetMembershipOperator;
-import com.redhat.thermostat.storage.query.ComparisonExpression;
-import com.redhat.thermostat.storage.query.Expression;
-import com.redhat.thermostat.storage.query.LiteralExpression;
-import com.redhat.thermostat.storage.query.LiteralSetExpression;
-import com.redhat.thermostat.storage.query.Operator;
-import com.redhat.thermostat.storage.query.UnaryLogicalExpression;
-import com.redhat.thermostat.storage.query.UnaryLogicalOperator;
-
-public class ExpressionSerializer implements JsonSerializer<Expression>,
-        JsonDeserializer<Expression> {
-    /* The concrete Expression fully-qualified class name */
-    static final String PROP_CLASS_NAME = "PROP_CLASS_NAME";
-    /* Serialized operand for a UnaryExpression */
-    static final String PROP_OPERAND = "PROP_OPERAND";
-    /* Serialized left operand for a BinaryExpression */
-    static final String PROP_OPERAND_LEFT = "PROP_OPERAND_LEFT";
-    /* Serialized right operand for a BinaryExpression */
-    static final String PROP_OPERAND_RIGHT = "PROP_OPERAND_RIGHT";
-    /* Serialized operator for an Expression */
-    static final String PROP_OPERATOR = "PROP_OPERATOR";
-    /* Serialized value for a LiteralExpression */
-    static final String PROP_VALUE = "PROP_VALUE";
-    /* Fully-qualified class name of a LiteralExpression's value */
-    static final String PROP_VALUE_CLASS = "PROP_VALUE_CLASS";
-    
-    @Override
-    public Expression deserialize(JsonElement json, Type typeOfT,
-            JsonDeserializationContext context) throws JsonParseException {
-        JsonElement jsonClassName = json.getAsJsonObject().get(PROP_CLASS_NAME);
-        if (jsonClassName == null) {
-            throw new JsonParseException("No class name property provided");
-        }
-        String className = jsonClassName.getAsString();
-        Expression result;
-        try {
-            Class<?> clazz = (Class<?>) Class.forName(className);
-            // Deserialize using concrete implementations to avoid reflection
-            // and unchecked casts
-            if (BinaryComparisonExpression.class.isAssignableFrom(clazz)) {
-                result = deserializeBinaryComparisonExpression(json, context);
-            }
-            else if (BinarySetMembershipExpression.class.isAssignableFrom(clazz)) {
-                result = deserializeBinarySetMembershipExpression(json, context);
-            }
-            else if (BinaryLogicalExpression.class.isAssignableFrom(clazz)) {
-                result = deserializeBinaryLogicalExpression(json, context);
-            }
-            else if (UnaryLogicalExpression.class.isAssignableFrom(clazz)) {
-                result = deserializeUnaryLogicalExpression(json, context);
-            }
-            else if (LiteralExpression.class.isAssignableFrom(clazz)) {
-                result = deserializeLiteralExpression(json, context);
-            }
-            else if (LiteralSetExpression.class.isAssignableFrom(clazz)) {
-                result = deserializeLiteralArrayExpression(json, context);
-            }
-            else {
-                throw new JsonParseException("Unknown Expression of type " + className);
-            }
-        } catch (ClassNotFoundException e) {
-            throw new JsonParseException("Unable to deserialize Expression", e);
-        }
-        return result;
-    }
-
-    private <T> Expression deserializeBinaryComparisonExpression(JsonElement json,
-            JsonDeserializationContext context) {
-        JsonElement jsonLeft = json.getAsJsonObject().get(PROP_OPERAND_LEFT);
-        JsonElement jsonRight = json.getAsJsonObject().get(PROP_OPERAND_RIGHT);
-        JsonElement jsonOp = json.getAsJsonObject().get(PROP_OPERATOR);
-        
-        LiteralExpression<Key<T>> left = context.deserialize(jsonLeft, Expression.class);
-        LiteralExpression<T> right = context.deserialize(jsonRight, Expression.class);
-        BinaryComparisonOperator op = context.deserialize(jsonOp, Operator.class);
-        return new BinaryComparisonExpression<>(left, op, right);
-    }
-    
-    private <T> Expression deserializeBinarySetMembershipExpression(JsonElement json,
-            JsonDeserializationContext context) {
-        JsonElement jsonLeft = json.getAsJsonObject().get(PROP_OPERAND_LEFT);
-        JsonElement jsonRight = json.getAsJsonObject().get(PROP_OPERAND_RIGHT);
-        JsonElement jsonOp = json.getAsJsonObject().get(PROP_OPERATOR);
-        
-        LiteralExpression<Key<T>> left = context.deserialize(jsonLeft, Expression.class);
-        LiteralSetExpression<T> right = context.deserialize(jsonRight, Expression.class);
-        BinarySetMembershipOperator op = context.deserialize(jsonOp, Operator.class);
-        return new BinarySetMembershipExpression<>(left, op, right);
-    }
-
-    private <S extends Expression, T extends Expression> Expression deserializeBinaryLogicalExpression(JsonElement json,
-            JsonDeserializationContext context) {
-        JsonElement jsonLeft = json.getAsJsonObject().get(PROP_OPERAND_LEFT);
-        JsonElement jsonRight = json.getAsJsonObject().get(PROP_OPERAND_RIGHT);
-        JsonElement jsonOp = json.getAsJsonObject().get(PROP_OPERATOR);
-        
-        S left = context.deserialize(jsonLeft, Expression.class);
-        T right = context.deserialize(jsonRight, Expression.class);
-        BinaryLogicalOperator op = context.deserialize(jsonOp, Operator.class);
-        return new BinaryLogicalExpression<>(left, op, right);
-    }
-
-    private <T extends ComparisonExpression> Expression deserializeUnaryLogicalExpression(JsonElement json,
-            JsonDeserializationContext context) {
-        JsonElement jsonOperand = json.getAsJsonObject().get(PROP_OPERAND);
-        JsonElement jsonOp = json.getAsJsonObject().get(PROP_OPERATOR);
-        
-        T operand = context.deserialize(jsonOperand, Expression.class);
-        UnaryLogicalOperator operator = context.deserialize(jsonOp, Operator.class);
-        return new UnaryLogicalExpression<>(operand, operator);
-    }
-
-    private Expression deserializeLiteralExpression(JsonElement json,
-            JsonDeserializationContext context) throws ClassNotFoundException {
-        JsonElement jsonValue = json.getAsJsonObject().get(PROP_VALUE);
-        JsonElement jsonValueClass = json.getAsJsonObject().get(PROP_VALUE_CLASS);
-        String valueClassName = jsonValueClass.getAsString();
-        Class<?> valueClass = Class.forName(valueClassName);
-        return makeLiteralExpression(context, jsonValue, valueClass);
-    }
-    
-    private <T> Expression makeLiteralExpression(JsonDeserializationContext context, 
-            JsonElement jsonValue, Class<T> valueClass) throws ClassNotFoundException {
-        T value = context.deserialize(jsonValue, valueClass);
-        return new LiteralExpression<>(value);
-    }
-
-    private Expression deserializeLiteralArrayExpression(JsonElement json,
-            JsonDeserializationContext context) throws ClassNotFoundException {
-        JsonElement jsonValue = json.getAsJsonObject().get(PROP_VALUE);
-        if (jsonValue instanceof JsonArray) {
-            JsonElement jsonValueClass = json.getAsJsonObject().get(PROP_VALUE_CLASS);
-            String valueClassName = jsonValueClass.getAsString();
-            Class<?> type = Class.forName(valueClassName);
-            JsonArray jsonArr = (JsonArray) jsonValue;
-            return makeLiteralArrayExpression(context, jsonArr, type);
-        }
-        else {
-            throw new JsonParseException("No JsonArray supplied for " + PROP_VALUE);
-        }
-    }
-
-    private <T> Expression makeLiteralArrayExpression(JsonDeserializationContext context,
-            JsonArray jsonArr, Class<T> valueClass) {
-        Set<T> values = new HashSet<>();
-        for (JsonElement element : jsonArr) {
-            T value = context.deserialize(element, valueClass);
-            values.add(value);
-        }
-        return new LiteralSetExpression<>(values, valueClass);
-    }
-
-    @Override
-    public JsonElement serialize(Expression src, Type typeOfSrc,
-            JsonSerializationContext context) {
-        JsonObject result;
-        // Only concrete implementations are public
-        if (src instanceof BinaryLogicalExpression) {
-            BinaryLogicalExpression<?, ?> binExpr = (BinaryLogicalExpression<?, ?>) src;
-            JsonElement left = context.serialize(binExpr.getLeftOperand());
-            JsonElement op = context.serialize(binExpr.getOperator());
-            JsonElement right = context.serialize(binExpr.getRightOperand());
-            result = new JsonObject();
-            result.add(PROP_OPERAND_LEFT, left);
-            result.add(PROP_OPERATOR, op);
-            result.add(PROP_OPERAND_RIGHT, right);
-        }
-        else if (src instanceof BinaryComparisonExpression) {
-            BinaryComparisonExpression<?> binExpr = (BinaryComparisonExpression<?>) src;
-            JsonElement left = context.serialize(binExpr.getLeftOperand());
-            JsonElement op = context.serialize(binExpr.getOperator());
-            JsonElement right = context.serialize(binExpr.getRightOperand());
-            result = new JsonObject();
-            result.add(PROP_OPERAND_LEFT, left);
-            result.add(PROP_OPERATOR, op);
-            result.add(PROP_OPERAND_RIGHT, right);
-        }
-        else if (src instanceof BinarySetMembershipExpression) {
-            BinarySetMembershipExpression<?> binExpr = (BinarySetMembershipExpression<?>) src;
-            JsonElement left = context.serialize(binExpr.getLeftOperand());
-            JsonElement op = context.serialize(binExpr.getOperator());
-            JsonElement right = context.serialize(binExpr.getRightOperand());
-            result = new JsonObject();
-            result.add(PROP_OPERAND_LEFT, left);
-            result.add(PROP_OPERATOR, op);
-            result.add(PROP_OPERAND_RIGHT, right);
-        }
-        else if (src instanceof UnaryLogicalExpression) {
-            UnaryLogicalExpression<?> unaryExpr = (UnaryLogicalExpression<?>) src;
-            JsonElement operand = context.serialize(unaryExpr.getOperand());
-            JsonElement operator = context.serialize(unaryExpr.getOperator());
-            result = new JsonObject();
-            result.add(PROP_OPERAND, operand);
-            result.add(PROP_OPERATOR, operator);
-        }
-        else if (src instanceof LiteralExpression) {
-            LiteralExpression<?> litExpr = (LiteralExpression<?>) src;
-            JsonElement value = context.serialize(litExpr.getValue());
-            result = new JsonObject();
-            result.add(PROP_VALUE, value);
-            // Store the type of value to properly deserialize it later
-            result.addProperty(PROP_VALUE_CLASS, litExpr.getValue().getClass().getCanonicalName());
-        }
-        else if (src instanceof LiteralSetExpression) {
-            LiteralSetExpression<?> litArrExpr = (LiteralSetExpression<?>) src;
-            result = serializeLiteralArrayExpression(litArrExpr, context);
-        }
-        else {
-            throw new JsonParseException("Unknown expression of type " + src.getClass());
-        }
-        result.addProperty(PROP_CLASS_NAME, src.getClass().getCanonicalName());
-        return result;
-    }
-    
-    private <T> JsonObject serializeLiteralArrayExpression(LiteralSetExpression<T> expr,
-            JsonSerializationContext context) {
-        JsonObject result = new JsonObject();
-        Set<T> list = expr.getValues();
-        Class<T> type = expr.getType();
-        JsonArray arr = new JsonArray();
-        for (T element : list) {
-            arr.add(context.serialize(element, type));
-        }
-        result.add(PROP_VALUE, arr);
-        // Store type of list elements
-        result.addProperty(PROP_VALUE_CLASS, type.getCanonicalName());
-        return result;
-    }
-
-}
--- a/web/common/src/main/java/com/redhat/thermostat/web/common/OperatorSerializer.java	Mon Sep 16 15:28:13 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,104 +0,0 @@
-/*
- * Copyright 2012, 2013 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.JsonSerializationContext;
-import com.google.gson.JsonSerializer;
-import com.redhat.thermostat.storage.query.Operator;
-
-public class OperatorSerializer implements JsonDeserializer<Operator>,
-        JsonSerializer<Operator> {
-    /* The name of the enum constant */
-    static final String PROP_CONST = "PROP_CONST";
-    /* The Operator implementation fully-qualified class name */
-    static final String PROP_CLASS_NAME = "PROP_CLASS_NAME";
-
-    @Override
-    public JsonElement serialize(Operator src, Type typeOfSrc,
-            JsonSerializationContext context) {
-        JsonObject result = new JsonObject();
-        // All concrete Operators should be Enums
-        if (src instanceof Enum) {
-            Enum<?> operator = (Enum<?>) src;
-            result.addProperty(PROP_CONST, operator.name());
-            result.addProperty(PROP_CLASS_NAME, operator.getDeclaringClass().getCanonicalName());
-        }
-        else {
-            throw new JsonParseException("Concrete Operator must be an enum");
-        }
-        return result;
-    }
-
-    @Override
-    public Operator deserialize(JsonElement json, Type typeOfT,
-            JsonDeserializationContext context) throws JsonParseException {
-        JsonElement jsonClassName = json.getAsJsonObject().get(PROP_CLASS_NAME);
-        if (jsonClassName == null) {
-            throw new JsonParseException("Class name must be specified for Operator");
-        }
-        String className = jsonClassName.getAsString();
-        Operator result;
-        try {
-            Class<?> clazz = Class.forName(className);
-            result = getEnum(json, clazz);
-        } catch (ClassNotFoundException e) {
-            throw new JsonParseException("Unable to deserialize Operator", e);
-        }
-        return result;
-    }
-
-    private <T extends Enum<T> & Operator> Operator getEnum(JsonElement json, Class<?> clazz) {
-        if (Operator.class.isAssignableFrom(clazz) && clazz.isEnum()) {
-            @SuppressWarnings("unchecked") // Checked with above condition
-            Class<T> operatorClass = (Class<T>) clazz;
-            JsonElement jsonConst = json.getAsJsonObject().get(PROP_CONST);
-            String enumConst = jsonConst.getAsString();
-            return Enum.valueOf(operatorClass, enumConst);
-        }
-        else {
-            throw new JsonParseException(clazz.getName() + " must be an Enum implementing Operator");
-        }
-    }
-
-}
--- a/web/common/src/main/java/com/redhat/thermostat/web/common/PreparedParameterSerializer.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/web/common/src/main/java/com/redhat/thermostat/web/common/PreparedParameterSerializer.java	Wed Sep 25 19:09:58 2013 +0200
@@ -36,14 +36,15 @@
 
 package com.redhat.thermostat.web.common;
 
+import java.lang.reflect.Array;
 import java.lang.reflect.Type;
-import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
+import java.util.Map;
 import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 
-import com.google.gson.JsonArray;
 import com.google.gson.JsonDeserializationContext;
 import com.google.gson.JsonDeserializer;
 import com.google.gson.JsonElement;
@@ -52,8 +53,9 @@
 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}.
@@ -61,54 +63,49 @@
  */
 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_IS_ARRAY_TYPE = "isArray";
     private static final String PROP_VALUE = "value";
-    // The set of valid classes for types
-    private static final Set<String> VALID_CLASSNAMES;
+    private static final Set<Class<?>> WRAPPER_CLASSES;
+    // maps wrapper classes to primitives:
+    //    Integer.class => int.class , Double.class => double.class, etc.
+    private static final Map<Class<?>, Class<?>> TO_PRIMITIVE_ARRAY_MAP;
     
     static {
-        VALID_CLASSNAMES = new HashSet<>();
-        VALID_CLASSNAMES.add(String.class.getCanonicalName());
-        VALID_CLASSNAMES.add(String[].class.getCanonicalName());
-        VALID_CLASSNAMES.add(Integer.class.getCanonicalName());
-        VALID_CLASSNAMES.add(Long.class.getCanonicalName());
-        VALID_CLASSNAMES.add(Boolean.class.getCanonicalName());
+        WRAPPER_CLASSES = new HashSet<>();
+        TO_PRIMITIVE_ARRAY_MAP = new HashMap<>();
+        WRAPPER_CLASSES.add(Integer.class);
+        TO_PRIMITIVE_ARRAY_MAP.put(Integer.class, int.class);
+        WRAPPER_CLASSES.add(Long.class);
+        TO_PRIMITIVE_ARRAY_MAP.put(Long.class, long.class);
+        WRAPPER_CLASSES.add(Boolean.class);
+        TO_PRIMITIVE_ARRAY_MAP.put(Boolean.class, boolean.class);
+        WRAPPER_CLASSES.add(Double.class);
+        TO_PRIMITIVE_ARRAY_MAP.put(Double.class, double.class);
+        
     }
     
     @Override
     public JsonElement serialize(PreparedParameter param, Type type,
             JsonSerializationContext ctxt) {
         JsonObject result = new JsonObject();
-        JsonElement valueElem = serializeValue(param.getValue());
+        JsonElement valueElem = serializeValue(ctxt, param.getValue(), param.getType(), param.isArrayType());
         result.add(PROP_VALUE, valueElem);
-        JsonPrimitive typeElem = new JsonPrimitive(param.getType().getCanonicalName());
+        JsonPrimitive typeElem = new JsonPrimitive(param.getType().getName());
         result.add(PROP_TYPE, typeElem);
+        JsonPrimitive arrayType = new JsonPrimitive(param.isArrayType());
+        result.add(PROP_IS_ARRAY_TYPE, arrayType);
         return result;
     }
 
-    private JsonElement serializeValue(Object value) {
+    private JsonElement serializeValue(JsonSerializationContext ctxt, Object value, Class<?> compType, boolean isArray) {
         JsonElement element;
-        if (value instanceof Integer) {
-            int val = ((Integer)value).intValue();
-            element = new JsonPrimitive(val);
-        } else if (value instanceof Long) {
-            long val = ((Long)value).longValue();
-            element = new JsonPrimitive(val);
-        } else if (value instanceof String) {
-            String val = (String)value;
-            element = new JsonPrimitive(val);
-        } else if (value instanceof String[]) {
-            String[] val = (String[])value;
-            JsonArray array = new JsonArray();
-            for (int i = 0; i < val.length; i++) {
-                array.add(new JsonPrimitive(val[i]));
-            }
-            element = array;
-        } else if (value instanceof Boolean) {
-            Boolean val = (Boolean)value;
-            element = new JsonPrimitive(val.booleanValue());
+        if (isArray) {
+                Class<?> arrayType = Array.newInstance(compType, 0).getClass();
+                element = ctxt.serialize(value, arrayType);
         } else {
-            throw new IllegalStateException("Unexpected value for serialization '" + value + "'");
+            element = ctxt.serialize(value, compType);
         }
         return element;
     }
@@ -118,57 +115,62 @@
             JsonDeserializationContext ctxt) throws JsonParseException {
         JsonElement typeElem = jsonElem.getAsJsonObject().get(PROP_TYPE);
         String className = typeElem.getAsString();
-        // perform some sanity checking on which classes we do forName() :)
-        validateSaneClassName(className);
+        // 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);
+        JsonElement isArrayElement = jsonElem.getAsJsonObject().get(PROP_IS_ARRAY_TYPE);
+        boolean isArray = isArrayElement.getAsBoolean();
+        Object value = deserializeValue(ctxt, valueElement, typeVal, isArray);
         PreparedParameter param = new PreparedParameter();
         param.setType(typeVal);
         param.setValue(value);
+        param.setArrayType(isArray);
         return param;
     }
 
     private Class<?> deserializeTypeVal(String className) {
         Class<?> typeVal = null;
-        if (className.equals(String[].class.getCanonicalName())) {
-            typeVal = String[].class;
-        } else {
-            try {
-                typeVal = Class.forName(className);
-            } catch (ClassNotFoundException ignored) {
-                // we only load valid classes that way
-            };
+        try {
+            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) {
-        if (valueElement.isJsonPrimitive()) {
-            // By telling GSON the type, we get the rightly casted
+            JsonElement valueElement, Class<?> valType, boolean isArray) {
+        if (!isArray) {
+            // By telling GSON the type, we get the correctly casted
             // value back.
             return ctxt.deserialize(valueElement, valType);
-        } else if (valueElement.isJsonArray()) {
-            // Only string arrays are supported
-            List<String> values = new ArrayList<>();
-            JsonArray jsonArray = (JsonArray)valueElement;
-            Iterator<JsonElement> it = jsonArray.iterator();
-            while (it.hasNext()) {
-                JsonElement elem = it.next();
-                String strElem = ctxt.deserialize(elem, String.class);
-                values.add(strElem);
+        } else {
+            Class<?> arrayType = Array.newInstance(valType, 0).getClass();
+            Object array;
+            // Make sure we get primitive type arrays if this is an array type
+            // of one of the wrapped primitives.
+            if (WRAPPER_CLASSES.contains(valType)) {
+                Class<?> primType = Array.newInstance(TO_PRIMITIVE_ARRAY_MAP.get(valType), 0).getClass();
+                array = ctxt.deserialize(valueElement, primType);
+            } else {
+                array = ctxt.deserialize(valueElement, arrayType);
             }
-            return values.toArray(new String[0]);
-        } else {
-            throw new IllegalStateException("Illegal json for parameter value");
+            return array;
         }
     }
 
-    private void validateSaneClassName(String className) {
-        if (!VALID_CLASSNAMES.contains(className)) {
-            throw new IllegalStateException("Illegal type of parameter " + className);
+    // Allow wrapper classes, String + Pojo types, refuse everything else
+    private void validateSaneClassName(Class<?> clazz) {
+        if (WRAPPER_CLASSES.contains(clazz) ||
+                String.class == 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/main/java/com/redhat/thermostat/web/common/PreparedStatementResponseCode.java	Wed Sep 25 19:09:58 2013 +0200
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2012, 2013 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 com.redhat.thermostat.storage.core.PreparedStatement;
+
+/**
+ * Common response codes for prepared statement responses.
+ *
+ */
+public interface PreparedStatementResponseCode {
+
+    /**
+     * Response code for successful prepared queries.
+     */
+    public static final int QUERY_SUCCESS = 0;
+    
+    /**
+     * Response code if patching of a {@link PreparedStatement} failed during
+     * statement execution.
+     * <p>
+     * For example a patching failure could happen if there was a type mismatch
+     * between the descriptor and the parameter provided. Providing not all
+     * parameters and attempting execution of a {@link PreparedStatement} would
+     * be another example.
+     */
+    public static final int ILLEGAL_PATCH = -1;
+    
+    /**
+     * Failure to execute a prepared write statement for some unknown reason.
+     */
+    public static final int WRITE_GENERIC_FAILURE = -200;
+    
+}
--- a/web/common/src/main/java/com/redhat/thermostat/web/common/WebAdd.java	Mon Sep 16 15:28:13 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,82 +0,0 @@
-/*
- * Copyright 2012, 2013 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 com.redhat.thermostat.storage.core.Add;
-import com.redhat.thermostat.storage.model.Pojo;
-
-
-public class WebAdd<T extends Pojo> implements Add<T> {
-
-    private int categoryId;
-    private transient Pojo pojo;
-
-    public WebAdd() {
-    }
-
-    public WebAdd(int categoryId) {
-        this.categoryId = categoryId;
-    }
-
-    public int getCategoryId() {
-        return categoryId;
-    }
-
-    public void setCategoryId(int categoryId) {
-        this.categoryId = categoryId;
-    }
-
-
-    @Override
-    public void setPojo(Pojo pojo) {
-        this.pojo = pojo;
-    }
-    
-    public Pojo getPojo() {
-        return pojo;
-    }
-
-    @Override
-    public int apply() {
-        // Should never be called directly, but overridden by
-        // the actual implementation. Here only so that it can be used
-        // for serialization.
-        throw new IllegalStateException();
-    }
-}
-
--- a/web/common/src/main/java/com/redhat/thermostat/web/common/WebQueryResponse.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/web/common/src/main/java/com/redhat/thermostat/web/common/WebQueryResponse.java	Wed Sep 25 19:09:58 2013 +0200
@@ -47,9 +47,6 @@
  */
 public class WebQueryResponse<T extends Pojo> {
     
-    public static final int SUCCESS = 0;
-    public static final int ILLEGAL_PATCH = -1;
-
     private int responseCode;
     private T[] resultList;
     
--- a/web/common/src/main/java/com/redhat/thermostat/web/common/WebRemove.java	Mon Sep 16 15:28:13 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,73 +0,0 @@
-/*
- * Copyright 2012, 2013 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 com.redhat.thermostat.storage.core.Remove;
-import com.redhat.thermostat.storage.model.Pojo;
-import com.redhat.thermostat.storage.query.Expression;
-
-public class WebRemove<T extends Pojo> implements Remove<T> {
-
-    private final int categoryId;
-    private Expression whereExpression;
-
-    public WebRemove(int categoryId) {
-        this.categoryId = categoryId;
-    }
-
-    @Override
-    public void where(Expression expr) {
-        whereExpression = expr;
-    }
-
-    public int getCategoryId() {
-        return categoryId;
-    }
-
-    public Expression getWhereExpression() {
-        return whereExpression;
-    }
-
-    @Override
-    public int apply() {
-        // This should never be called. Overridden by the actual
-        // implementation.
-        throw new IllegalStateException();
-    }
-    
-}
-
--- a/web/common/src/main/java/com/redhat/thermostat/web/common/WebReplace.java	Mon Sep 16 15:28:13 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,91 +0,0 @@
-/*
- * Copyright 2012, 2013 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 com.redhat.thermostat.storage.core.Replace;
-import com.redhat.thermostat.storage.model.Pojo;
-import com.redhat.thermostat.storage.query.Expression;
-
-public class WebReplace<T extends Pojo> implements Replace<T> {
-    
-    private int categoryId;
-    private transient Pojo pojo;
-    private Expression whereExpression;
-    
-    public WebReplace() {
-        // nothing
-    }
-    
-    public WebReplace(int categoryId) {
-        this.categoryId = categoryId;
-    }
-
-    @Override
-    public void setPojo(Pojo pojo) {
-        this.pojo = pojo;
-    }
-
-    @Override
-    public int apply() {
-        // Should never be called directly, but overridden by
-        // the actual implementation. Here only so that it can be used
-        // for serialization.
-        throw new IllegalStateException();
-    }
-
-    @Override
-    public void where(Expression expression) {
-        this.whereExpression = expression;
-    }
-
-    public int getCategoryId() {
-        return categoryId;
-    }
-
-    public void setCategoryId(int categoryId) {
-        this.categoryId = categoryId;
-    }
-
-    public Pojo getPojo() {
-        return pojo;
-    }
-
-    public Expression getWhereExpression() {
-        return whereExpression;
-    }
-
-}
--- a/web/common/src/main/java/com/redhat/thermostat/web/common/WebUpdate.java	Mon Sep 16 15:28:13 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,132 +0,0 @@
-/*
- * Copyright 2012, 2013 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.util.ArrayList;
-import java.util.List;
-
-import com.redhat.thermostat.storage.core.Key;
-import com.redhat.thermostat.storage.core.Update;
-import com.redhat.thermostat.storage.model.Pojo;
-import com.redhat.thermostat.storage.query.Expression;
-
-public class WebUpdate<T extends Pojo> implements Update<T> {
-
-    public static class UpdateValue {
-        private Key<?> key;
-        private transient Object value;
-        private String valueClass;
-
-        public UpdateValue() {
-            this(null, null);
-        }
-
-        public UpdateValue(Key<?> key, Object value) {
-            this.key = key;
-            this.value = value;
-            if (value != null) {
-                valueClass = value.getClass().getName();
-            }
-        }
-
-        public Key<?> getKey() {
-            return key;
-        }
-
-        public void setKey(Key<?> key) {
-            this.key = key;
-        }
-
-        public Object getValue() {
-            return value;
-        }
-
-        public void setValue(Object value) {
-            this.value = value;
-        }
-
-        public String getValueClass() {
-            return valueClass;
-        }
-
-        public void setValueClass(String valueClass) {
-            this.valueClass = valueClass;
-        }
-
-    }
-
-    private int categoryId;
-    private Expression whereExpression;
-    private List<UpdateValue> updateValues;
-
-    public WebUpdate() {
-        updateValues = new ArrayList<>();
-    }
-
-    public void where(Expression expr) {
-        whereExpression = expr;
-    }
-
-    public <S> void set(Key<S> key, S value) {
-        updateValues.add(new UpdateValue(key, value));
-    }
-
-    public int getCategoryId() {
-        return categoryId;
-    }
-
-    public void setCategoryId(int categoryId) {
-        this.categoryId = categoryId;
-    }
-
-    public Expression getWhereExpression() {
-        return whereExpression;
-    }
-    
-    public List<WebUpdate.UpdateValue> getUpdates() {
-        return updateValues;
-    }
-
-    @Override
-    public int apply() {
-        // This should never be called. Overridden by the actual
-        // implementation.
-        throw new IllegalStateException();
-    }
-
-}
-
--- a/web/common/src/test/java/com/redhat/thermostat/web/common/ExpressionSerializerTest.java	Mon Sep 16 15:28:13 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,269 +0,0 @@
-/*
- * Copyright 2012, 2013 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.util.Arrays;
-import java.util.HashSet;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParseException;
-import com.redhat.thermostat.storage.core.Key;
-import com.redhat.thermostat.storage.query.BinaryComparisonExpression;
-import com.redhat.thermostat.storage.query.BinaryLogicalExpression;
-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.storage.query.LiteralSetExpression;
-import com.redhat.thermostat.storage.query.LiteralExpression;
-import com.redhat.thermostat.storage.query.Operator;
-import com.redhat.thermostat.storage.query.UnaryLogicalExpression;
-
-public class ExpressionSerializerTest {
-
-    private static final Key<String> key = new Key<>("hello");
-    private Gson gson;
-
-    private static final class TestExpression implements Expression {
-        
-    }
-    
-    @Before
-    public void setUp() throws Exception {
-        gson = new GsonBuilder()
-                .registerTypeHierarchyAdapter(Expression.class,
-                        new ExpressionSerializer())
-                .registerTypeHierarchyAdapter(Operator.class,
-                        new OperatorSerializer()).create();
-    }
-
-    @Test
-    public void testDeserializeLiteralExpression() {
-        Expression expr = new LiteralExpression<String>("hello");
-        
-        JsonObject json = new JsonObject();
-        json.addProperty(ExpressionSerializer.PROP_VALUE, "hello");
-        json.addProperty(ExpressionSerializer.PROP_VALUE_CLASS, String.class.getCanonicalName());
-        json.addProperty(ExpressionSerializer.PROP_CLASS_NAME, LiteralExpression.class.getCanonicalName());
-        
-        assertEquals(expr, gson.fromJson(json, Expression.class));
-    }
-    
-    @Test
-    public void testDeserializeLiteralArrayExpression() {
-        Expression expr = new LiteralSetExpression<String>(new HashSet<>(Arrays.asList("hello", "goodbye")), 
-                String.class);
-        
-        JsonObject json = new JsonObject();
-        JsonArray arr = new JsonArray();
-        arr.add(gson.toJsonTree("hello"));
-        arr.add(gson.toJsonTree("goodbye"));
-        json.add(ExpressionSerializer.PROP_VALUE, arr);
-        json.addProperty(ExpressionSerializer.PROP_VALUE_CLASS, String.class.getCanonicalName());
-        json.addProperty(ExpressionSerializer.PROP_CLASS_NAME, LiteralSetExpression.class.getCanonicalName());
-        
-        assertEquals(expr, gson.fromJson(json, Expression.class));
-    }
-    
-    @Test
-    public void testDeserializeBinaryComparisonExpression() {
-        ExpressionFactory factory = new ExpressionFactory();
-        BinaryComparisonExpression<String> expr = factory.equalTo(key, "world");
-        
-        JsonObject json = new JsonObject();
-        json.add(ExpressionSerializer.PROP_OPERAND_LEFT, gson.toJsonTree(expr.getLeftOperand()));
-        json.add(ExpressionSerializer.PROP_OPERATOR, gson.toJsonTree(expr.getOperator()));
-        json.add(ExpressionSerializer.PROP_OPERAND_RIGHT, gson.toJsonTree(expr.getRightOperand()));
-        json.addProperty(ExpressionSerializer.PROP_CLASS_NAME, BinaryComparisonExpression.class.getCanonicalName());
-        
-        assertEquals(expr, gson.fromJson(json, Expression.class));
-    }
-    
-    @Test
-    public void testDeserializeBinarySetMembershipExpression() {
-        ExpressionFactory factory = new ExpressionFactory();
-        BinarySetMembershipExpression<String> expr = factory.in(key, 
-                new HashSet<>(Arrays.asList("world", "goodbye")), String.class);
-        
-        JsonObject json = new JsonObject();
-        json.add(ExpressionSerializer.PROP_OPERAND_LEFT, gson.toJsonTree(expr.getLeftOperand()));
-        json.add(ExpressionSerializer.PROP_OPERATOR, gson.toJsonTree(expr.getOperator()));
-        json.add(ExpressionSerializer.PROP_OPERAND_RIGHT, gson.toJsonTree(expr.getRightOperand()));
-        json.addProperty(ExpressionSerializer.PROP_CLASS_NAME, BinarySetMembershipExpression.class.getCanonicalName());
-        
-        assertEquals(expr, gson.fromJson(json, Expression.class));
-    }
-    
-    @Test
-    public void testDeserializeBinaryLogicalExpression() {
-        ExpressionFactory factory = new ExpressionFactory();
-        BinaryLogicalExpression<BinaryComparisonExpression<String>, BinaryComparisonExpression<String>> expr = factory.and(factory.equalTo(key, "world"), factory.greaterThan(key, "goodbye"));
-        
-        JsonObject json = new JsonObject();
-        json.add(ExpressionSerializer.PROP_OPERAND_LEFT, gson.toJsonTree(expr.getLeftOperand()));
-        json.add(ExpressionSerializer.PROP_OPERATOR, gson.toJsonTree(expr.getOperator()));
-        json.add(ExpressionSerializer.PROP_OPERAND_RIGHT, gson.toJsonTree(expr.getRightOperand()));
-        json.addProperty(ExpressionSerializer.PROP_CLASS_NAME, BinaryLogicalExpression.class.getCanonicalName());
-        
-        assertEquals(expr, gson.fromJson(json, Expression.class));
-    }
-    
-    @Test
-    public void testDeserializeUnaryLogicalExpression() {
-        ExpressionFactory factory = new ExpressionFactory();
-        UnaryLogicalExpression<BinaryComparisonExpression<String>> expr = factory.not(factory.greaterThan(key, "goodbye"));
-        
-        JsonObject json = new JsonObject();
-        json.add(ExpressionSerializer.PROP_OPERAND, gson.toJsonTree(expr.getOperand()));
-        json.add(ExpressionSerializer.PROP_OPERATOR, gson.toJsonTree(expr.getOperator()));
-        json.addProperty(ExpressionSerializer.PROP_CLASS_NAME, UnaryLogicalExpression.class.getCanonicalName());
-        
-        assertEquals(expr, gson.fromJson(json, Expression.class));
-    }
-    
-    @Test(expected=JsonParseException.class)
-    public void testDeserializeNoClassName() {
-        JsonObject json = new JsonObject();
-        
-        gson.fromJson(json, Expression.class);
-    }
-    
-    @Test(expected=JsonParseException.class)
-    public void testDeserializeUnknownExpression() {
-        JsonObject json = new JsonObject();
-        json.addProperty(ExpressionSerializer.PROP_CLASS_NAME, TestExpression.class.getCanonicalName());
-        
-        gson.fromJson(json, Expression.class);
-    }
-
-    @Test
-    public void testSerializeLiteralExpression() {
-        Expression expr = new LiteralExpression<String>("hello");
-        
-        JsonObject json = new JsonObject();
-        json.addProperty(ExpressionSerializer.PROP_VALUE, "hello");
-        json.addProperty(ExpressionSerializer.PROP_VALUE_CLASS, String.class.getCanonicalName());
-        json.addProperty(ExpressionSerializer.PROP_CLASS_NAME, LiteralExpression.class.getCanonicalName());
-        
-        assertEquals(gson.toJson(json), gson.toJson(expr));
-    }
-    
-    @Test
-    public void testSerializeLiteralArrayExpression() {
-        Expression expr = new LiteralSetExpression<String>(new HashSet<>(Arrays.asList("hello", "goodbye")),
-                String.class);
-        
-        JsonObject json = new JsonObject();
-        JsonArray arr = new JsonArray();
-        arr.add(gson.toJsonTree("hello"));
-        arr.add(gson.toJsonTree("goodbye"));
-        json.add(ExpressionSerializer.PROP_VALUE, arr);
-        json.addProperty(ExpressionSerializer.PROP_VALUE_CLASS, String.class.getCanonicalName());
-        json.addProperty(ExpressionSerializer.PROP_CLASS_NAME, LiteralSetExpression.class.getCanonicalName());
-        
-        assertEquals(gson.toJson(json), gson.toJson(expr));
-    }
-    
-    @Test
-    public void testSerializeBinaryComparisonExpression() {
-        ExpressionFactory factory = new ExpressionFactory();
-        BinaryComparisonExpression<String> expr = factory.equalTo(key, "world");
-        
-        JsonObject json = new JsonObject();
-        json.add(ExpressionSerializer.PROP_OPERAND_LEFT, gson.toJsonTree(expr.getLeftOperand()));
-        json.add(ExpressionSerializer.PROP_OPERATOR, gson.toJsonTree(expr.getOperator()));
-        json.add(ExpressionSerializer.PROP_OPERAND_RIGHT, gson.toJsonTree(expr.getRightOperand()));
-        json.addProperty(ExpressionSerializer.PROP_CLASS_NAME, BinaryComparisonExpression.class.getCanonicalName());
-        
-        assertEquals(gson.toJson(json), gson.toJson(expr));
-    }
-    
-    @Test
-    public void testSerializeBinarySetMembershipExpression() {
-        ExpressionFactory factory = new ExpressionFactory();
-        BinarySetMembershipExpression<String> expr = factory.in(key, new HashSet<>(Arrays.asList("world", "goodbye")),
-                String.class);
-        
-        JsonObject json = new JsonObject();
-        json.add(ExpressionSerializer.PROP_OPERAND_LEFT, gson.toJsonTree(expr.getLeftOperand()));
-        json.add(ExpressionSerializer.PROP_OPERATOR, gson.toJsonTree(expr.getOperator()));
-        json.add(ExpressionSerializer.PROP_OPERAND_RIGHT, gson.toJsonTree(expr.getRightOperand()));
-        json.addProperty(ExpressionSerializer.PROP_CLASS_NAME, BinarySetMembershipExpression.class.getCanonicalName());
-        
-        assertEquals(gson.toJson(json), gson.toJson(expr));
-    }
-    
-    @Test
-    public void testSerializeBinaryLogicalExpression() {
-        ExpressionFactory factory = new ExpressionFactory();
-        BinaryLogicalExpression<BinaryComparisonExpression<String>, BinaryComparisonExpression<String>> expr = factory.and(factory.equalTo(key, "world"), factory.greaterThan(key, "goodbye"));
-        
-        JsonObject json = new JsonObject();
-        json.add(ExpressionSerializer.PROP_OPERAND_LEFT, gson.toJsonTree(expr.getLeftOperand()));
-        json.add(ExpressionSerializer.PROP_OPERATOR, gson.toJsonTree(expr.getOperator()));
-        json.add(ExpressionSerializer.PROP_OPERAND_RIGHT, gson.toJsonTree(expr.getRightOperand()));
-        json.addProperty(ExpressionSerializer.PROP_CLASS_NAME, BinaryLogicalExpression.class.getCanonicalName());
-        
-        assertEquals(gson.toJson(json), gson.toJson(expr));
-    }
-    
-    @Test
-    public void testSerializeUnaryLogicalExpression() {
-        ExpressionFactory factory = new ExpressionFactory();
-        UnaryLogicalExpression<BinaryComparisonExpression<String>> expr = factory.not(factory.greaterThan(key, "goodbye"));
-        
-        JsonObject json = new JsonObject();
-        json.add(ExpressionSerializer.PROP_OPERAND, gson.toJsonTree(expr.getOperand()));
-        json.add(ExpressionSerializer.PROP_OPERATOR, gson.toJsonTree(expr.getOperator()));
-        json.addProperty(ExpressionSerializer.PROP_CLASS_NAME, UnaryLogicalExpression.class.getCanonicalName());
-        
-        assertEquals(gson.toJson(json), gson.toJson(expr));
-    }
-    
-    @Test(expected=JsonParseException.class)
-    public void testSerializeUnknownExpression() {
-        gson.toJson(new TestExpression());
-    }
-
-}
--- a/web/common/src/test/java/com/redhat/thermostat/web/common/OperatorSerializerTest.java	Mon Sep 16 15:28:13 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,154 +0,0 @@
-/*
- * Copyright 2012, 2013 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.google.gson.JsonObject;
-import com.google.gson.JsonParseException;
-import com.redhat.thermostat.storage.query.BinaryComparisonOperator;
-import com.redhat.thermostat.storage.query.BinaryLogicalOperator;
-import com.redhat.thermostat.storage.query.Operator;
-import com.redhat.thermostat.storage.query.UnaryLogicalOperator;
-
-public class OperatorSerializerTest {
-
-    private Gson gson;
-
-    private static final class TestOperator implements Operator {
-        
-    }
-    
-    @Before
-    public void setUp() throws Exception {
-        gson = new GsonBuilder()
-                .registerTypeHierarchyAdapter(Operator.class,
-                        new OperatorSerializer()).create();
-    }
-
-    @Test
-    public void testDeserializeBinaryComparisonOperator() {
-        Operator op = BinaryComparisonOperator.EQUALS;
-        
-        JsonObject json = new JsonObject();
-        json.addProperty(OperatorSerializer.PROP_CONST, BinaryComparisonOperator.EQUALS.name());
-        json.addProperty(OperatorSerializer.PROP_CLASS_NAME, BinaryComparisonOperator.class.getCanonicalName());
-        
-        assertEquals(op, gson.fromJson(json, Operator.class));
-    }
-    
-    @Test
-    public void testDeserializeBinaryLogicalOperator() {
-        Operator op = BinaryLogicalOperator.AND;
-        
-        JsonObject json = new JsonObject();
-        json.addProperty(OperatorSerializer.PROP_CONST, BinaryLogicalOperator.AND.name());
-        json.addProperty(OperatorSerializer.PROP_CLASS_NAME, BinaryLogicalOperator.class.getCanonicalName());
-        
-        assertEquals(op, gson.fromJson(json, Operator.class));
-    }
-    
-    @Test
-    public void testDeserializeUnaryLogicalOperator() {
-        Operator op = UnaryLogicalOperator.NOT;
-        
-        JsonObject json = new JsonObject();
-        json.addProperty(OperatorSerializer.PROP_CONST, UnaryLogicalOperator.NOT.name());
-        json.addProperty(OperatorSerializer.PROP_CLASS_NAME, UnaryLogicalOperator.class.getCanonicalName());
-        
-        assertEquals(op, gson.fromJson(json, Operator.class));
-    }
-    
-    @Test(expected=JsonParseException.class)
-    public void testDeserializeNoClassName() {
-        JsonObject json = new JsonObject();
-        
-        gson.fromJson(json, Operator.class);
-    }
-    
-    @Test(expected=JsonParseException.class)
-    public void testDeserializeUnknownOperator() {
-        JsonObject json = new JsonObject();
-        json.addProperty(OperatorSerializer.PROP_CLASS_NAME, TestOperator.class.getCanonicalName());
-        
-        gson.fromJson(json, Operator.class);
-    }
-
-    @Test
-    public void testSerializeBinaryComparisonOperator() {
-        Operator op = BinaryComparisonOperator.EQUALS;
-        
-        JsonObject json = new JsonObject();
-        json.addProperty(OperatorSerializer.PROP_CONST, BinaryComparisonOperator.EQUALS.name());
-        json.addProperty(OperatorSerializer.PROP_CLASS_NAME, BinaryComparisonOperator.class.getCanonicalName());
-        
-        assertEquals(gson.toJson(json), gson.toJson(op));
-    }
-    
-    @Test
-    public void testSerializeBinaryLogicalOperator() {
-        Operator op = BinaryLogicalOperator.AND;
-        
-        JsonObject json = new JsonObject();
-        json.addProperty(OperatorSerializer.PROP_CONST, BinaryLogicalOperator.AND.name());
-        json.addProperty(OperatorSerializer.PROP_CLASS_NAME, BinaryLogicalOperator.class.getCanonicalName());
-        
-        assertEquals(gson.toJson(json), gson.toJson(op));
-    }
-    
-    @Test
-    public void testSerializeUnaryLogicalOperator() {
-        Operator op = UnaryLogicalOperator.NOT;
-        
-        JsonObject json = new JsonObject();
-        json.addProperty(OperatorSerializer.PROP_CONST, UnaryLogicalOperator.NOT.name());
-        json.addProperty(OperatorSerializer.PROP_CLASS_NAME, UnaryLogicalOperator.class.getCanonicalName());
-        
-        assertEquals(gson.toJson(json), gson.toJson(op));
-    }
-    
-    @Test(expected=JsonParseException.class)
-    public void testSerializeUnknownOperator() {
-        gson.toJson(new TestOperator());
-    }
-
-}
--- a/web/common/src/test/java/com/redhat/thermostat/web/common/PreparedParameterSerializerTest.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/web/common/src/test/java/com/redhat/thermostat/web/common/PreparedParameterSerializerTest.java	Wed Sep 25 19:09:58 2013 +0200
@@ -37,15 +37,21 @@
 package com.redhat.thermostat.web.common;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 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 {
 
@@ -53,7 +59,9 @@
     
     @Before
     public void setup() {
-        gson = new GsonBuilder().registerTypeAdapter(
+        gson = new GsonBuilder()
+            .registerTypeHierarchyAdapter(Pojo.class, new ThermostatGSONConverter())
+            .registerTypeAdapter(
                 PreparedParameter.class,
                 new PreparedParameterSerializer()).create();
     }
@@ -61,38 +69,44 @@
     @Test
     public void canDeserializeBasic() {
         // String
-        String jsonStr = "{ \"type\": \"java.lang.String\" , \"value\": \"testing\"}";
+        String jsonStr = "{ \"type\": \"java.lang.String\" , \"value\": \"testing\" , \"isArray\": false}";
         PreparedParameter param = gson.fromJson(jsonStr, PreparedParameter.class);
         assertEquals(String.class, param.getType());
         assertEquals("testing", param.getValue());
+        assertFalse(param.isArrayType());
         // Integer
-        jsonStr = "{ \"type\": \"java.lang.Integer\" , \"value\": -1}";
+        jsonStr = "{ \"type\": \"java.lang.Integer\" , \"value\": -1 , \"isArray\": false}";
         param = gson.fromJson(jsonStr, PreparedParameter.class);
         assertEquals(Integer.class, param.getType());
         assertTrue(param.getValue() instanceof Integer);
         assertEquals(-1, param.getValue());
+        assertFalse(param.isArrayType());
         // Long
-        jsonStr = "{ \"type\": \"java.lang.Long\" , \"value\": -10}";
+        jsonStr = "{ \"type\": \"java.lang.Long\" , \"value\": -10 , \"isArray\": false}";
         param = gson.fromJson(jsonStr, PreparedParameter.class);
         assertEquals(Long.class, param.getType());
         assertTrue(param.getValue() instanceof Long);
         assertEquals(-10L, param.getValue());
-        jsonStr = "{ \"type\": \"java.lang.Long\" , \"value\": 30000000003}";
+        assertFalse(param.isArrayType());
+        jsonStr = "{ \"type\": \"java.lang.Long\" , \"value\": 30000000003 , \"isArray\": false}";
         param = gson.fromJson(jsonStr, PreparedParameter.class);
         assertEquals(Long.class, param.getType());
         assertTrue(param.getValue() instanceof Long);
         assertEquals(30000000003L, param.getValue());
+        assertFalse(param.isArrayType());
         // Boolean
-        jsonStr = "{ \"type\": \"java.lang.Boolean\" , \"value\": true}";
+        jsonStr = "{ \"type\": \"java.lang.Boolean\" , \"value\": true , \"isArray\": false}";
         param = gson.fromJson(jsonStr, PreparedParameter.class);
         assertEquals(Boolean.class, param.getType());
         assertTrue(param.getValue() instanceof Boolean);
         assertEquals(true, param.getValue());
+        assertFalse(param.isArrayType());
         // String[]
         String strArrayVal = "[ \"testing1\", \"testing2\", \"3\" ]";
-        jsonStr = "{ \"type\": \"java.lang.String[]\" , \"value\": " + strArrayVal + "}";
+        jsonStr = "{ \"type\": \"java.lang.String\" , \"value\": " + strArrayVal + " , \"isArray\": true}";
         param = gson.fromJson(jsonStr, PreparedParameter.class);
-        assertEquals(String[].class, param.getType());
+        assertEquals(String.class, param.getType());
+        assertTrue(param.isArrayType());
         assertTrue(param.getValue() instanceof String[]);
         String[] vals = (String[])param.getValue();
         assertEquals(3, vals.length);
@@ -117,34 +131,39 @@
     @Test
     public void canSerializeBasic() {
         // String
-        String expected = "{\"value\":\"testing\",\"type\":\"java.lang.String\"}";
+        String expected = "{\"value\":\"testing\",\"type\":\"java.lang.String\",\"isArray\":false}";
         PreparedParameter param = new PreparedParameter();
         param.setType(String.class);
         param.setValue("testing");
+        param.setArrayType(false);
         String actual = gson.toJson(param);
         assertEquals(expected, actual);
         // Integer
-        expected = "{\"value\":-1,\"type\":\"java.lang.Integer\"}";
+        expected = "{\"value\":-1,\"type\":\"java.lang.Integer\",\"isArray\":false}";
         param.setType(Integer.class);
         param.setValue(-1);
+        param.setArrayType(false);
         actual = gson.toJson(param);
         assertEquals(expected, actual);
         // Long
-        expected = "{\"value\":30000000003,\"type\":\"java.lang.Long\"}";
+        expected = "{\"value\":30000000003,\"type\":\"java.lang.Long\",\"isArray\":false}";
         param.setType(Long.class);
         param.setValue(30000000003L);
+        param.setArrayType(false);
         actual = gson.toJson(param);
         assertEquals(expected, actual);
         // boolean
-        expected = "{\"value\":true,\"type\":\"java.lang.Boolean\"}";
+        expected = "{\"value\":true,\"type\":\"java.lang.Boolean\",\"isArray\":false}";
         param.setType(Boolean.class);
         param.setValue(true);
+        param.setArrayType(false);
         actual = gson.toJson(param);
         assertEquals(expected, actual);
         // String[]
         String strArrayVal = "[\"testing1\",\"testing2\",\"3\"]";
-        expected = "{\"value\":" + strArrayVal + ",\"type\":\"java.lang.String[]\"}";
-        param.setType(String[].class);
+        expected = "{\"value\":" + strArrayVal + ",\"type\":\"java.lang.String\",\"isArray\":true}";
+        param.setType(String.class);
+        param.setArrayType(true);
         String[] array = new String[] {
                 "testing1", "testing2", "3"
         };
@@ -158,15 +177,69 @@
         PreparedParameter expected = new PreparedParameter();
         expected.setType(Integer.class);
         expected.setValue(3);
+        expected.setArrayType(false);
+        String jsonStr = gson.toJson(expected, PreparedParameter.class);
+        assertParameterEquals(expected, jsonStr);
+    }
+    
+    @Test
+    public void canSerializeDeserializeIntegerArray() {
+        PreparedParameter expected = new PreparedParameter();
+        expected.setType(Integer.class);
+        // it's important for the expected type to be of primitive array type,
+        // rather than Integer[]. we want the serializer to deserialize to
+        // primitive types if possible. asserted in method assertParameterEquals()
+        // Note that model classes use primitive array types as well.
+        expected.setValue(new int[] { 0, 3, 20 });
+        expected.setArrayType(true);
         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);
+        expected.setArrayType(false);
+        String jsonStr = gson.toJson(expected, PreparedParameter.class);
+        assertParameterEquals(expected, jsonStr);
+    }
+    
+    @Test
+    public void canSerializeDeserializeDoubleArray() {
+        PreparedParameter expected = new PreparedParameter();
+        expected.setType(Double.class);
+        // it's important for the expected type to be of primitive array type,
+        // rather than Double[]. we want the serializer to deserialize to
+        // primitive types if possible. asserted in method assertParameterEquals()
+        // Note that model classes use primitive array types as well.
+        expected.setValue(new double[] { 3.3, 1.0, Math.PI });
+        expected.setArrayType(true);
+        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);
+        expected.setArrayType(false);
+        String jsonStr = gson.toJson(expected, PreparedParameter.class);
+        assertParameterEquals(expected, jsonStr);
+    }
+    
+    @Test
+    public void canSerializeDeserializeLongArray() {
+        PreparedParameter expected = new PreparedParameter();
+        expected.setType(Long.class);
+        // it's important for the expected type to be of primitive array type,
+        // rather than Long[]. we want the serializer to deserialize to
+        // primitive types if possible. asserted in method assertParameterEquals()
+        // Note that model classes use primitive array types as well.
+        expected.setValue(new long[] { 3000000000L, 3, 20 });
+        expected.setArrayType(true);
         String jsonStr = gson.toJson(expected, PreparedParameter.class);
         assertParameterEquals(expected, jsonStr);
     }
@@ -176,6 +249,20 @@
         PreparedParameter expected = new PreparedParameter();
         expected.setType(String.class);
         expected.setValue("testing");
+        expected.setArrayType(false);
+        String jsonStr = gson.toJson(expected, PreparedParameter.class);
+        assertParameterEquals(expected, jsonStr);
+    }
+    
+    @Test
+    public void canSerializeDeserializeStringArray() {
+        PreparedParameter expected = new PreparedParameter();
+        expected.setType(String.class);
+        expected.setArrayType(true);
+        String[] expectedArray = new String[] {
+                "one", "two", "three"      
+        };
+        expected.setValue(expectedArray);
         String jsonStr = gson.toJson(expected, PreparedParameter.class);
         assertParameterEquals(expected, jsonStr);
     }
@@ -194,29 +281,108 @@
         jsonStr = gson.toJson(expected, PreparedParameter.class);
         assertParameterEquals(expected, jsonStr);
     }
-
+    
+    @Test
+    public void canSerializeDeserializeBooleanArray() {
+        PreparedParameter expected = new PreparedParameter();
+        expected.setType(Boolean.class);
+        // it's important for the expected type to be of primitive array type,
+        // rather than Boolean[]. we want the serializer to deserialize to
+        // primitive types if possible. asserted in method assertParameterEquals()
+        // Note that model classes use primitive array types as well.
+        expected.setValue(new boolean[] { true, false, false, true });
+        expected.setArrayType(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);
+        expected.setArrayType(false);
+        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);
+        expected.setArrayType(false);
+        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);
+        expected.setArrayType(false);
+        String jsonStr = gson.toJson(expected, PreparedParameter.class);
+        
+        PreparedParameter actual = gson.fromJson(jsonStr, PreparedParameter.class);
+        
+        assertEquals(expected.getType(), actual.getType());
+        assertTrue(expected.isArrayType() == actual.isArrayType());
+        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.setArrayType(true);
+        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());
-        assertEquals(expected.getValue(), actual.getValue());
-    }
-    
-    @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);
-        PreparedParameter actual = gson.fromJson(jsonStr, PreparedParameter.class);
-        assertEquals(expected.getType(), actual.getType());
-        String[] actualArray = (String[])actual.getValue();
-        for (int i = 0; i < expectedArray.length; i++) {
-            assertEquals(expectedArray[i], actualArray[i]);
+        assertEquals(expected.isArrayType(), actual.isArrayType());
+        if (actual.isArrayType()) {
+            // compare element by element
+            Object values = actual.getValue();
+            Object expectedVals = expected.getValue();
+            Class<?> expectedType = expectedVals.getClass();
+            int expectedLength = Array.getLength(expectedVals);
+            int actualLength = Array.getLength(values);
+            assertEquals(expectedLength, actualLength);
+            // Make sure the deserialized array is of the correct expected type
+            assertTrue(values.getClass() == expectedType);
+            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	Mon Sep 16 15:28:13 2013 +0200
+++ b/web/common/src/test/java/com/redhat/thermostat/web/common/PreparedParametersSerializerTest.java	Wed Sep 25 19:09:58 2013 +0200
@@ -45,6 +45,8 @@
 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 {
 
@@ -52,13 +54,16 @@
     
     @Before
     public void setup() {
-        gson = new GsonBuilder().registerTypeAdapter(
-                PreparedParameter.class,
-                new PreparedParameterSerializer()).create();
+        gson = new GsonBuilder()
+                .registerTypeHierarchyAdapter(Pojo.class,
+                        new ThermostatGSONConverter())
+                .registerTypeAdapter(
+                        PreparedParameter.class,
+                        new PreparedParameterSerializer()).create();
     }
     
     @Test
-    public void canSerializeDeserialize() {
+    public void canSerializeDeserializeBasic() {
         PreparedParameters params = new PreparedParameters(5);
         params.setBoolean(0, true);
         params.setInt(1, 2300);
@@ -85,4 +90,55 @@
             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());
+        assertEquals(false, param1.isArrayType());
+        
+        PreparedParameter param2 = actual[1];
+        assertEquals(Long.class, param2.getType());
+        assertEquals(true, param2.isArrayType());
+        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());
+        assertEquals(true, param3.isArrayType());
+        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/WebPreparedStatementSerializerTest.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/web/common/src/test/java/com/redhat/thermostat/web/common/WebPreparedStatementSerializerTest.java	Wed Sep 25 19:09:58 2013 +0200
@@ -37,7 +37,9 @@
 package com.redhat.thermostat.web.common;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -46,6 +48,8 @@
 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 {
 
@@ -56,6 +60,7 @@
         gson = new GsonBuilder()
                 .registerTypeAdapter(WebPreparedStatement.class,
                         new WebPreparedStatementSerializer())
+                .registerTypeHierarchyAdapter(Pojo.class, new ThermostatGSONConverter())
                 .registerTypeAdapter(PreparedParameter.class,
                         new PreparedParameterSerializer()).create();
     }
@@ -81,15 +86,46 @@
         assertEquals(Integer.class, parameters[0].getType());
         assertEquals("testing", parameters[1].getValue());
         assertEquals(String.class, parameters[1].getType());
+        assertFalse(parameters[1].isArrayType());
         assertEquals(222L, parameters[2].getValue());
         assertEquals(Long.class, parameters[2].getType());
+        assertFalse(parameters[2].isArrayType());
         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(String.class, parameters[3].getType());
+        assertTrue(parameters[3].isArrayType());
         assertEquals(true, parameters[4].getValue());
         assertEquals(Boolean.class, parameters[4].getType());
+        assertFalse(parameters[4].isArrayType());
         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	Mon Sep 16 15:28:13 2013 +0200
+++ b/web/common/src/test/java/com/redhat/thermostat/web/common/WebQueryResponseSerializerTest.java	Wed Sep 25 19:09:58 2013 +0200
@@ -75,7 +75,7 @@
         // create the query response
         WebQueryResponse<AgentInformation> response = new WebQueryResponse<>();
         response.setResultList(resultList);
-        response.setResponseCode(WebQueryResponse.ILLEGAL_PATCH);
+        response.setResponseCode(PreparedStatementResponseCode.ILLEGAL_PATCH);
         
         String jsonStr = gson.toJson(response);
         String expectedJson = "{\"payload\":[{\"startTime\":0,\"stopTime\":0,\"alive\":false,\"backends\":[],\"agentId\":\"testing\"}],\"errno\":-1}";
@@ -90,7 +90,7 @@
         
         AgentInformation[] actualList = actual.getResultList();
         
-        assertEquals(WebQueryResponse.ILLEGAL_PATCH, actual.getResponseCode());
+        assertEquals(PreparedStatementResponseCode.ILLEGAL_PATCH, actual.getResponseCode());
         assertEquals(1, actualList.length);
         AgentInformation actualInfo = actualList[0];
         assertEquals(true, actualInfo.isAlive());
@@ -109,7 +109,7 @@
         // create the query response
         WebQueryResponse<AgentInformation> response = new WebQueryResponse<>();
         response.setResultList(resultList);
-        response.setResponseCode(WebQueryResponse.ILLEGAL_PATCH);
+        response.setResponseCode(PreparedStatementResponseCode.ILLEGAL_PATCH);
         
         String jsonStr = gson.toJson(response);
 
@@ -120,7 +120,7 @@
         
         AgentInformation[] actualList = actual.getResultList();
         
-        assertEquals(WebQueryResponse.ILLEGAL_PATCH, actual.getResponseCode());
+        assertEquals(PreparedStatementResponseCode.ILLEGAL_PATCH, actual.getResponseCode());
         assertEquals(1, actualList.length);
         AgentInformation actualInfo = actualList[0];
         assertEquals(false, actualInfo.isAlive());
@@ -139,7 +139,7 @@
         // create the query response
         WebQueryResponse<AgentInformation> response = new WebQueryResponse<>();
         response.setResultList(resultList);
-        response.setResponseCode(WebQueryResponse.ILLEGAL_PATCH);
+        response.setResponseCode(PreparedStatementResponseCode.ILLEGAL_PATCH);
         
         String jsonStr = gson.toJson(response);
         String expectedJson = "{\"payload\":[{\"startTime\":0,\"stopTime\":0,\"alive\":false,\"backends\":[],\"agentId\":\"testing\"}],\"errno\":-1}";
@@ -152,7 +152,7 @@
         
         AgentInformation[] actualList = actual.getResultList();
         
-        assertEquals(WebQueryResponse.ILLEGAL_PATCH, actual.getResponseCode());
+        assertEquals(PreparedStatementResponseCode.ILLEGAL_PATCH, actual.getResponseCode());
         assertEquals(1, actualList.length);
         AgentInformation actualInfo = actualList[0];
         assertEquals(false, actualInfo.isAlive());
@@ -170,13 +170,13 @@
         
         WebQueryResponse<HostInfo> expected = new WebQueryResponse<>();
         expected.setResultList(hostInfoResults);
-        expected.setResponseCode(WebQueryResponse.SUCCESS);
+        expected.setResponseCode(PreparedStatementResponseCode.QUERY_SUCCESS);
         
         jsonStr = gson.toJson(expected);
         Type hostinfoQueryResponseType = new TypeToken<WebQueryResponse<HostInfo>>() {}.getType();
         WebQueryResponse<HostInfo> actualResp = gson.fromJson(jsonStr, hostinfoQueryResponseType);
         
-        assertEquals(WebQueryResponse.SUCCESS, actualResp.getResponseCode());
+        assertEquals(PreparedStatementResponseCode.QUERY_SUCCESS, actualResp.getResponseCode());
         HostInfo[] hostInfoList = actualResp.getResultList();
         assertEquals(1, hostInfoList.length);
         assertEquals("something", hostInfoList[0].getAgentId());
--- a/web/server/src/main/java/com/redhat/thermostat/web/server/WebStorageEndPoint.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/web/server/src/main/java/com/redhat/thermostat/web/server/WebStorageEndPoint.java	Wed Sep 25 19:09:58 2013 +0200
@@ -69,17 +69,15 @@
 
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonParser;
 import com.redhat.thermostat.common.utils.LoggingUtils;
 import com.redhat.thermostat.shared.config.Configuration;
 import com.redhat.thermostat.shared.config.InvalidConfigurationException;
-import com.redhat.thermostat.storage.core.Add;
 import com.redhat.thermostat.storage.core.Categories;
 import com.redhat.thermostat.storage.core.Category;
 import com.redhat.thermostat.storage.core.CategoryAdapter;
 import com.redhat.thermostat.storage.core.Connection;
 import com.redhat.thermostat.storage.core.Cursor;
+import com.redhat.thermostat.storage.core.DataModifyingStatement;
 import com.redhat.thermostat.storage.core.DescriptorParsingException;
 import com.redhat.thermostat.storage.core.IllegalPatchException;
 import com.redhat.thermostat.storage.core.Key;
@@ -88,11 +86,8 @@
 import com.redhat.thermostat.storage.core.PreparedParameters;
 import com.redhat.thermostat.storage.core.PreparedStatement;
 import com.redhat.thermostat.storage.core.Query;
-import com.redhat.thermostat.storage.core.Remove;
-import com.redhat.thermostat.storage.core.Replace;
 import com.redhat.thermostat.storage.core.StatementDescriptor;
 import com.redhat.thermostat.storage.core.Storage;
-import com.redhat.thermostat.storage.core.Update;
 import com.redhat.thermostat.storage.core.auth.DescriptorMetadata;
 import com.redhat.thermostat.storage.core.auth.StatementDescriptorMetadataFactory;
 import com.redhat.thermostat.storage.model.AggregateResult;
@@ -100,21 +95,15 @@
 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.storage.query.Operator;
-import com.redhat.thermostat.web.common.ExpressionSerializer;
-import com.redhat.thermostat.web.common.OperatorSerializer;
 import com.redhat.thermostat.web.common.PreparedParameterSerializer;
+import com.redhat.thermostat.web.common.PreparedStatementResponseCode;
 import com.redhat.thermostat.web.common.StorageWrapper;
 import com.redhat.thermostat.web.common.ThermostatGSONConverter;
-import com.redhat.thermostat.web.common.WebAdd;
 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.WebRemove;
-import com.redhat.thermostat.web.common.WebReplace;
-import com.redhat.thermostat.web.common.WebUpdate;
 import com.redhat.thermostat.web.server.auth.FilterResult;
 import com.redhat.thermostat.web.server.auth.Roles;
 import com.redhat.thermostat.web.server.auth.UserPrincipal;
@@ -174,10 +163,6 @@
         gson = new GsonBuilder()
                 .registerTypeHierarchyAdapter(Pojo.class,
                         new ThermostatGSONConverter())
-                .registerTypeHierarchyAdapter(Expression.class,
-                        new ExpressionSerializer())
-                .registerTypeHierarchyAdapter(Operator.class,
-                        new OperatorSerializer())
                 .registerTypeAdapter(WebQueryResponse.class, new WebQueryResponseSerializer<>())
                 .registerTypeAdapter(PreparedParameter.class, new PreparedParameterSerializer())
                 .registerTypeAdapter(WebPreparedStatement.class, new WebPreparedStatementSerializer())
@@ -237,16 +222,10 @@
             prepareStatement(req, resp);
         } else if (cmd.equals("query-execute")) {
             queryExecute(req, resp);
-        } else if (cmd.equals("add-pojo")) {
-            addPojo(req, resp);
-        } else if (cmd.equals("replace-pojo")) {
-            replacePojo(req, resp);
+        } else if (cmd.equals("write-execute")) {
+            writeExecute(req, resp);
         } else if (cmd.equals("register-category")) {
             registerCategory(req, resp);
-        } else if (cmd.equals("remove-pojo")) {
-            removePojo(req, resp);
-        } else if (cmd.equals("update-pojo")) {
-            updatePojo(req, resp);
         } else if (cmd.equals("save-file")) {
             saveFile(req, resp);
         } else if (cmd.equals("load-file")) {
@@ -559,102 +538,6 @@
         }
     }
 
-    @WebStoragePathHandler( path = "add-pojo" )
-    private void addPojo(HttpServletRequest req, HttpServletResponse resp) {
-        if (! isAuthorized(req, resp, Roles.APPEND)) {
-            return;
-        }
-        String addParam = req.getParameter("add");
-        WebAdd<?> add = gson.fromJson(addParam, WebAdd.class);
-        int categoryId = add.getCategoryId();
-        Category<?> category = getCategoryFromId(categoryId);
-        Add<?> targetAdd = storage.createAdd(category);
-        Class<? extends Pojo> pojoCls = category.getDataClass();
-        String pojoParam = req.getParameter("pojo");
-        Pojo pojo = gson.fromJson(pojoParam, pojoCls);
-        targetAdd.setPojo(pojo);
-        targetAdd.apply();
-        resp.setStatus(HttpServletResponse.SC_OK);
-    }
-    
-    @WebStoragePathHandler( path = "replace-pojo" )
-    private void replacePojo(HttpServletRequest req, HttpServletResponse resp) {
-        if (! isAuthorized(req, resp, Roles.REPLACE)) {
-            return;
-        }
-        String replaceParam = req.getParameter("replace");
-        WebReplace<?> replace = gson.fromJson(replaceParam, WebReplace.class);
-        int categoryId = replace.getCategoryId();
-        Category<?> category = getCategoryFromId(categoryId);
-        Replace<?> targetReplace = storage.createReplace(category);
-        Class<? extends Pojo> pojoCls = category.getDataClass();
-        String pojoParam = req.getParameter("pojo");
-        Pojo pojo = gson.fromJson(pojoParam, pojoCls);
-        targetReplace.setPojo(pojo);
-        Expression expr = replace.getWhereExpression();
-        targetReplace.where(expr);
-        targetReplace.apply();
-        resp.setStatus(HttpServletResponse.SC_OK);
-    }
-
-    @WebStoragePathHandler( path = "remove-pojo" )
-    private void removePojo(HttpServletRequest req, HttpServletResponse resp) {
-        if (! isAuthorized(req, resp, Roles.DELETE)) {
-            return;
-        }
-        
-        String removeParam = req.getParameter("remove");
-        WebRemove<?> remove = gson.fromJson(removeParam, WebRemove.class);
-        Category<?> targetCategory = getCategoryFromId(remove.getCategoryId());
-        Remove<?> targetRemove = storage.createRemove(targetCategory);
-        Expression expr = remove.getWhereExpression();
-        if (expr != null) {
-            targetRemove.where(expr);
-        }
-        targetRemove.apply();
-        resp.setStatus(HttpServletResponse.SC_OK);
-    }
-
-    @WebStoragePathHandler( path = "update-pojo" )
-    @SuppressWarnings({ "rawtypes", "unchecked" })
-    private void updatePojo(HttpServletRequest req, HttpServletResponse resp) {
-        if (! isAuthorized(req, resp, Roles.UPDATE)) {
-            return;
-        }
-        
-        try {
-            String updateParam = req.getParameter("update");
-            WebUpdate update = gson.fromJson(updateParam, WebUpdate.class);
-            Update targetUpdate = storage.createUpdate(getCategoryFromId(update.getCategoryId()));
-            Expression expr = update.getWhereExpression();
-            if (expr != null) {
-                targetUpdate.where(expr);
-            }
-            List<WebUpdate.UpdateValue> updates = update.getUpdates();
-            if (updates != null) {
-                String valuesParam = req.getParameter("values");
-                JsonParser parser = new JsonParser();
-                JsonArray jsonArray = parser.parse(valuesParam)
-                        .getAsJsonArray();
-                int index = 0;
-                for (WebUpdate.UpdateValue updateValue : updates) {
-                    Class valueClass = Class.forName(updateValue
-                            .getValueClass());
-                    Object value = gson.fromJson(jsonArray.get(index),
-                            valueClass);
-                    index++;
-                    Key key = updateValue.getKey();
-                    targetUpdate.set(key, value);
-                }
-            }
-            targetUpdate.apply();
-            resp.setStatus(HttpServletResponse.SC_OK);
-        } catch (ClassNotFoundException ex) {
-            ex.printStackTrace();
-            resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
-        }
-    }
-
     @SuppressWarnings("unchecked")
     @WebStoragePathHandler( path = "query-execute" )
     private <T extends Pojo> void queryExecute(HttpServletRequest req, HttpServletResponse resp) throws IOException {
@@ -674,9 +557,10 @@
         WebQueryResponse<T> response = new WebQueryResponse<>();
         try {
             targetQuery = (Query<T>)parsed.patchStatement(params);
-            response.setResponseCode(WebQueryResponse.SUCCESS);
+            response.setResponseCode(PreparedStatementResponseCode.QUERY_SUCCESS);
         } catch (IllegalPatchException e) {
-            response.setResponseCode(WebQueryResponse.ILLEGAL_PATCH);
+            logger.log(Level.INFO, "Failed to execute query", e);
+            response.setResponseCode(PreparedStatementResponseCode.ILLEGAL_PATCH);
             writeResponse(resp, response, WebQueryResponse.class);
             return;
         }
@@ -699,6 +583,36 @@
         writeResponse(resp, response, WebQueryResponse.class);
     }
     
+    @SuppressWarnings("unchecked")
+    @WebStoragePathHandler( path = "write-execute" )
+    private <T extends Pojo> void writeExecute(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        if (! isAuthorized(req, resp, Roles.WRITE)) {
+            return;
+        }
+        String queryParam = req.getParameter("prepared-stmt");
+        WebPreparedStatement<T> stmt = gson.fromJson(queryParam, WebPreparedStatement.class);
+        
+        PreparedParameters p = stmt.getParams();
+        PreparedParameter[] params = p.getParams();
+        PreparedStatementHolder<T> targetStmtHolder = getStatementHolderFromId(stmt.getStatementId());
+        PreparedStatement<T> targetStmt = targetStmtHolder.getStmt();
+        ParsedStatement<T> parsed = targetStmt.getParsedStatement();
+        
+        DataModifyingStatement<T> targetStatement = null;
+        try {
+            // perform the patching of the target statement.
+            targetStatement = (DataModifyingStatement<T>)parsed.patchStatement(params);
+        } catch (IllegalPatchException e) {
+            logger.log(Level.INFO, "Failed to execute write", e);
+            writeResponse(resp, PreparedStatementResponseCode.ILLEGAL_PATCH, int.class);
+            return;
+        }
+        
+        // executes statement
+        int response = targetStatement.apply();
+        writeResponse(resp, response, int.class);
+    }
+    
     private UserPrincipal getUserPrincipal(HttpServletRequest req) {
         // Since we use our own JAAS based auth module this cast is safe
         // for Tomcat and JBoss AS 7 since they allow for configuration of
--- a/web/server/src/main/java/com/redhat/thermostat/web/server/auth/Roles.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/web/server/src/main/java/com/redhat/thermostat/web/server/auth/Roles.java	Wed Sep 25 19:09:58 2013 +0200
@@ -72,12 +72,17 @@
      */
     final String GRANT_READ_ALL = "thermostat-grant-read-ALL";
 
+    /*
+     * TODO: Not sure if we still want to use the following 4 stop-gap roles. 
+     */
     final String APPEND = "thermostat-add";
     final String REPLACE = "thermostat-replace";
     final String UPDATE = "thermostat-update";
     final String DELETE = "thermostat-remove";
+    
     final String PREPARE_STATEMENT = "thermostat-prepare-statement";
     final String READ = "thermostat-query";
+    final String WRITE = "thermostat-write";
     final String LOAD_FILE = "thermostat-load-file";
     final String SAVE_FILE = "thermostat-save-file";
     final String PURGE = "thermostat-purge";
--- a/web/server/src/test/java/com/redhat/thermostat/web/server/KnownDescriptorRegistryTest.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/web/server/src/test/java/com/redhat/thermostat/web/server/KnownDescriptorRegistryTest.java	Wed Sep 25 19:09:58 2013 +0200
@@ -78,10 +78,10 @@
         KnownDescriptorRegistry reg = new KnownDescriptorRegistry();
         Set<String> trustedDescs = reg.getRegisteredDescriptors();
         assertNotNull(trustedDescs);
-        // storage-core registers 9 queries; this module has
+        // storage-core registers 22 descriptors; this module has
         // only storage-core as maven dep which registers queries.
         // see DAOImplStatementDescriptorRegistration
-        assertEquals(13, trustedDescs.size());
+        assertEquals(22, trustedDescs.size());
     }
     
     @Test
--- a/web/server/src/test/java/com/redhat/thermostat/web/server/WebStorageEndpointTest.java	Mon Sep 16 15:28:13 2013 +0200
+++ b/web/server/src/test/java/com/redhat/thermostat/web/server/WebStorageEndpointTest.java	Wed Sep 25 19:09:58 2013 +0200
@@ -109,10 +109,7 @@
 import com.redhat.thermostat.storage.core.PreparedParameter;
 import com.redhat.thermostat.storage.core.PreparedStatement;
 import com.redhat.thermostat.storage.core.Query;
-import com.redhat.thermostat.storage.core.Remove;
-import com.redhat.thermostat.storage.core.Replace;
 import com.redhat.thermostat.storage.core.StatementDescriptor;
-import com.redhat.thermostat.storage.core.Update;
 import com.redhat.thermostat.storage.core.auth.CategoryRegistration;
 import com.redhat.thermostat.storage.core.auth.DescriptorMetadata;
 import com.redhat.thermostat.storage.core.auth.StatementDescriptorRegistration;
@@ -124,23 +121,17 @@
 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.storage.query.Operator;
 import com.redhat.thermostat.test.FreePortFinder;
 import com.redhat.thermostat.test.FreePortFinder.TryPort;
-import com.redhat.thermostat.web.common.ExpressionSerializer;
-import com.redhat.thermostat.web.common.OperatorSerializer;
 import com.redhat.thermostat.web.common.PreparedParameterSerializer;
+import com.redhat.thermostat.web.common.PreparedStatementResponseCode;
 import com.redhat.thermostat.web.common.StorageWrapper;
 import com.redhat.thermostat.web.common.ThermostatGSONConverter;
-import com.redhat.thermostat.web.common.WebAdd;
 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.WebRemove;
-import com.redhat.thermostat.web.common.WebReplace;
-import com.redhat.thermostat.web.common.WebUpdate;
 import com.redhat.thermostat.web.server.auth.BasicRole;
 import com.redhat.thermostat.web.server.auth.RolePrincipal;
 import com.redhat.thermostat.web.server.auth.Roles;
@@ -192,7 +183,6 @@
     private static Key<Integer> key2;
     private static Category<TestClass> category;
     private static String categoryName = "test";
-    private ExpressionFactory factory;
 
     @BeforeClass
     public static void setupCategory() {
@@ -221,8 +211,6 @@
         
         mockStorage = mock(BackingStorage.class);
         StorageWrapper.setStorage(mockStorage);
-        
-        factory = new ExpressionFactory();
     }
 
     private void startServer(int port, LoginService loginService) throws Exception {
@@ -257,9 +245,8 @@
         // manually maintained list of path handlers which should include
         // authorization checks
         final String[] authPaths = new String[] {
-                "prepare-statement", "query-execute", "add-pojo", "replace-pojo", "register-category", "remove-pojo",
-                "update-pojo", "save-file", "load-file",
-                "purge", "ping", "generate-token", "verify-token"
+                "prepare-statement", "query-execute", "write-execute", "register-category",
+                "save-file", "load-file", "purge", "ping", "generate-token", "verify-token"
         };
         Map<String, Boolean> checkedAutPaths = new HashMap<>();
         for (String path: authPaths) {
@@ -736,6 +723,110 @@
         KnownDescriptorRegistryFactory.setKnownDescriptorRegistry(registry);
     }
     
+    @SuppressWarnings("unchecked")
+    @Test
+    public void authorizedPreparedWrite() throws Exception {
+        Category<TestClass> oldCategory = category;
+        String categoryName = "test-authorizedPreparedWrite";
+        // redefine category to include the agentId key in the category.
+        // undone via a the try-finally block.
+        category = new Category<>(categoryName, TestClass.class, key1, key2, Key.AGENT_ID);
+        try {
+            String strDescriptor = "ADD " + category.getName() + " SET '" +
+                    key1.getName() + "' = ?s , '" + key2.getName() + "' = ?s";
+            DescriptorMetadata metadata = new DescriptorMetadata();
+            setupTrustedStatementRegistry(strDescriptor, metadata);
+            
+            Set<BasicRole> roles = new HashSet<>();
+            roles.add(new RolePrincipal(Roles.REGISTER_CATEGORY));
+            roles.add(new RolePrincipal(Roles.PREPARE_STATEMENT));
+            roles.add(new RolePrincipal(Roles.WRITE));
+            roles.add(new RolePrincipal(Roles.ACCESS_REALM));
+            UserPrincipal testUser = new UserPrincipal("ignored1");
+            testUser.setRoles(roles);
+            
+            final LoginService loginService = new TestJAASLoginService(testUser);
+            port = FreePortFinder.findFreePort(new TryPort() {
+                
+                @Override
+                public void tryPort(int port) throws Exception {
+                    startServer(port, loginService);
+                }
+            });
+            // This makes register category work for the "test" category.
+            // Undone via @After
+            setupTrustedCategory(categoryName);
+            registerCategory("ignored1", "ignored2");
+            
+            // prepare-statement does this under the hood
+            Add<TestClass> mockMongoAdd = mock(Add.class);
+            
+            when(mockStorage.createAdd(eq(category))).thenReturn(mockMongoAdd);
+    
+            PreparedStatement<TestClass> mockPreparedQuery = mock(PreparedStatement.class);
+            when(mockStorage.prepareStatement(any(StatementDescriptor.class))).thenReturn(mockPreparedQuery);
+            
+            ParsedStatement<TestClass> mockParsedStatement = mock(ParsedStatement.class);
+            when(mockParsedStatement.getNumParams()).thenReturn(2);
+            when(mockParsedStatement.patchStatement(any(PreparedParameter[].class))).thenReturn(mockMongoAdd);
+            when(mockPreparedQuery.getParsedStatement()).thenReturn(mockParsedStatement);
+            
+            // The web layer
+            when(mockPreparedQuery.execute()).thenReturn(PreparedStatementResponseCode.WRITE_GENERIC_FAILURE);
+            // And the mongo layer
+            when(mockMongoAdd.apply()).thenReturn(PreparedStatementResponseCode.WRITE_GENERIC_FAILURE);
+    
+            String endpoint = getEndpoint();
+            URL url = new URL(endpoint + "/prepare-statement");
+            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+            conn.setRequestMethod("POST");
+            sendAuthentication(conn, "ignored1", "ignored2");
+            conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
+            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();
+            OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream());
+            String body = "query-descriptor=" + URLEncoder.encode(strDescriptor, "UTF-8") + "&category-id=" + categoryId;
+            out.write(body + "\n");
+            out.flush();
+    
+            Reader in = new InputStreamReader(conn.getInputStream());
+            WebPreparedStatementResponse response = gson.fromJson(in, WebPreparedStatementResponse.class);
+            assertEquals(2, response.getNumFreeVariables());
+            assertEquals(0, response.getStatementId());
+            assertEquals("application/json; charset=UTF-8", conn.getContentType());
+            
+            
+            
+            // now execute the ADD we've just prepared
+            WebPreparedStatement<TestClass> stmt = new WebPreparedStatement<>(2, 0);
+            stmt.setString(0, "fluff");
+            stmt.setString(1, "test2");
+            
+            url = new URL(endpoint + "/write-execute");
+            HttpURLConnection conn2 = (HttpURLConnection) url.openConnection();
+            conn2.setRequestMethod("POST");
+            sendAuthentication(conn2, "ignored1", "ignored2");
+            conn2.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
+            conn2.setDoInput(true);
+            conn2.setDoOutput(true);
+            
+            out = new OutputStreamWriter(conn2.getOutputStream());
+            body = "prepared-stmt=" + gson.toJson(stmt, WebPreparedStatement.class);
+            out.write(body + "\n");
+            out.flush();
+    
+            in = new InputStreamReader(conn2.getInputStream());
+            int result = gson.fromJson(in, int.class);
+            assertEquals(PreparedStatementResponseCode.WRITE_GENERIC_FAILURE, result);
+        } finally {
+            category = oldCategory; 
+        }
+    }
+    
     @Test
     public void cannotRegisterCategoryWithoutRegistrationOnInit() throws Exception {
         // need this in order to pass basic permissions.
@@ -878,215 +969,6 @@
         return id;
     }
 
-    @Test
-    public void authorizedReplacePojo() throws Exception {
-        String[] roleNames = new String[] {
-                Roles.REPLACE,
-                Roles.REGISTER_CATEGORY,
-                Roles.ACCESS_REALM
-        };
-        String testuser = "testuser";
-        String password = "testpassword";
-        final LoginService loginService = new TestLoginService(testuser, password, roleNames); 
-        port = FreePortFinder.findFreePort(new TryPort() {
-            
-            @Override
-            public void tryPort(int port) throws Exception {
-                startServer(port, loginService);
-            }
-        });
-        // This makes register category work for the "test" category.
-        // Undone via @After
-        setupTrustedCategory(categoryName);
-        registerCategory(testuser, password);
-        
-        @SuppressWarnings("unchecked")
-        Replace<TestClass> replace = mock(Replace.class);
-        when(mockStorage.createReplace(eq(category))).thenReturn(replace);
-
-        TestClass expected1 = new TestClass();
-        expected1.setKey1("fluff1");
-        expected1.setKey2(42);
-        Expression expectedExpression = new ExpressionFactory().equalTo(key1, "fluff1");
-
-        String endpoint = getEndpoint();
-
-        URL url = new URL(endpoint + "/replace-pojo");
-        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
-        conn.setRequestMethod("POST");
-        sendAuthentication(conn, testuser, password);
-
-        conn.setDoOutput(true);
-        conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
-        WebReplace<TestClass> webReplace = new WebReplace<>(categoryId);
-        webReplace.where(expectedExpression);
-        Gson gson = new GsonBuilder()
-            .registerTypeHierarchyAdapter(Expression.class,
-                    new ExpressionSerializer())
-            .registerTypeHierarchyAdapter(Operator.class,
-                    new OperatorSerializer()).create();
-        OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream());
-        out.write("replace=");
-        gson.toJson(webReplace, out);
-        out.flush();
-        out.write("&pojo=");
-        gson.toJson(expected1, out);
-        out.write("\n");
-        out.flush();
-        assertEquals(200, conn.getResponseCode());
-        verify(mockStorage).createReplace(category);
-        verify(replace).setPojo(expected1);
-        verify(replace).where(eq(expectedExpression));
-        verify(replace).apply();
-    }    
-    
-    @Test
-    public void unauthorizedReplacePojo() throws Exception {
-        String[] insufficientRoleNames = new String[] {
-                Roles.REGISTER_CATEGORY,
-                Roles.ACCESS_REALM
-        };
-        String testuser = "testuser";
-        String password = "testpassword";
-        final LoginService loginService = new TestLoginService(testuser, password, insufficientRoleNames); 
-        port = FreePortFinder.findFreePort(new TryPort() {
-            
-            @Override
-            public void tryPort(int port) throws Exception {
-                startServer(port, loginService);
-            }
-        });
-        // This makes register category work for the "test" category.
-        // Undone via @After
-        setupTrustedCategory(categoryName);
-        registerCategory(testuser, password);
-        
-        String endpoint = getEndpoint();
-        URL url = new URL(endpoint + "/replace-pojo");
-        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
-        conn.setRequestMethod("POST");
-        sendAuthentication(conn, testuser, password);
-        conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
-        conn.setDoOutput(true);
-        conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
-        WebReplace<TestClass> webReplace = new WebReplace<>(categoryId);
-        Gson gson = new GsonBuilder()
-            .registerTypeHierarchyAdapter(Expression.class,
-                new ExpressionSerializer())
-            .registerTypeHierarchyAdapter(Operator.class,
-                new OperatorSerializer()).create();
-        OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream());
-        out.write("replace=");
-        gson.toJson(webReplace, out);
-        out.flush();
-        out.write("&pojo=");
-        TestClass expected1 = new TestClass();
-        gson.toJson(expected1, out);
-        out.write("\n");
-        out.flush();
-        
-        assertEquals("thermostat-replace role missing", HttpServletResponse.SC_FORBIDDEN, conn.getResponseCode());
-    }
-
-    @Test
-    public void authorizedAddPojo() throws Exception {
-        String[] roleNames = new String[] {
-                Roles.APPEND,
-                Roles.REGISTER_CATEGORY,
-                Roles.ACCESS_REALM
-        };
-        String testuser = "testuser";
-        String password = "testpassword";
-        final LoginService loginService = new TestLoginService(testuser, password, roleNames); 
-        port = FreePortFinder.findFreePort(new TryPort() {
-            
-            @Override
-            public void tryPort(int port) throws Exception {
-                startServer(port, loginService);
-            }
-        });
-        // This makes register category work for the "test" category.
-        // Undone via @After
-        setupTrustedCategory(categoryName);
-        registerCategory(testuser, password);
-        
-        @SuppressWarnings("unchecked")
-        Add<TestClass> insert = mock(Add.class);
-        when(mockStorage.createAdd(eq(category))).thenReturn(insert);
-
-        TestClass expected1 = new TestClass();
-        expected1.setKey1("fluff1");
-        expected1.setKey2(42);
-
-        String endpoint = getEndpoint();
-
-        URL url = new URL(endpoint + "/add-pojo");
-        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
-        conn.setRequestMethod("POST");
-        sendAuthentication(conn, testuser, password);
-
-        conn.setDoOutput(true);
-        conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
-        WebAdd<TestClass> ins = new WebAdd<>(categoryId);
-        Gson gson = new Gson();
-        OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream());
-        out.write("add=");
-        gson.toJson(ins, out);
-        out.flush();
-        out.write("&pojo=");
-        gson.toJson(expected1, out);
-        out.write("\n");
-        out.flush();
-        assertEquals(200, conn.getResponseCode());
-        verify(mockStorage).createAdd(category);
-        verify(insert).setPojo(expected1);
-        verify(insert).apply();
-    }
-    
-    @Test
-    public void unauthorizedAddPojo() throws Exception {
-        String[] insufficientRoleNames = new String[] {
-                Roles.REGISTER_CATEGORY,
-                Roles.ACCESS_REALM
-        };
-        String testuser = "testuser";
-        String password = "testpassword";
-        final LoginService loginService = new TestLoginService(testuser, password, insufficientRoleNames); 
-        port = FreePortFinder.findFreePort(new TryPort() {
-            
-            @Override
-            public void tryPort(int port) throws Exception {
-                startServer(port, loginService);
-            }
-        });
-        // This makes register category work for the "test" category.
-        // Undone via @After
-        setupTrustedCategory(categoryName);
-        registerCategory(testuser, password);
-        
-        String endpoint = getEndpoint();
-        URL url = new URL(endpoint + "/add-pojo");
-        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
-        conn.setRequestMethod("POST");
-        sendAuthentication(conn, testuser, password);
-        conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
-        conn.setDoOutput(true);
-        conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
-        WebAdd<TestClass> insert = new WebAdd<>(categoryId);
-        Gson gson = new Gson();
-        OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream());
-        out.write("add=");
-        gson.toJson(insert, out);
-        out.flush();
-        out.write("&pojo=");
-        TestClass expected1 = new TestClass();
-        gson.toJson(expected1, out);
-        out.write("\n");
-        out.flush();
-        
-        assertEquals("thermostat-add role missing", HttpServletResponse.SC_FORBIDDEN, conn.getResponseCode());
-    }
-    
     private void sendAuthentication(HttpURLConnection conn, String username, String passwd) {
         String userpassword = username + ":" + passwd;
         String encodedAuthorization = Base64.encodeBase64String(userpassword.getBytes());
@@ -1094,138 +976,6 @@
     }
 
     @Test
-    public void authorizedRemovePojo() throws Exception {
-        String[] roleNames = new String[] {
-                Roles.DELETE,
-                Roles.REGISTER_CATEGORY,
-                Roles.ACCESS_REALM
-        };
-        String testuser = "testuser";
-        String password = "testpassword";
-        final LoginService loginService = new TestLoginService(testuser, password, roleNames); 
-        port = FreePortFinder.findFreePort(new TryPort() {
-            
-            @Override
-            public void tryPort(int port) throws Exception {
-                startServer(port, loginService);
-            }
-        });
-        // This makes register category work for the "test" category.
-        // Undone via @After
-        setupTrustedCategory(categoryName);
-        registerCategory(testuser, password);
-        
-        
-        @SuppressWarnings("unchecked")
-        Remove<TestClass> mockRemove = mock(Remove.class);
-
-        when(mockStorage.createRemove(category)).thenReturn(mockRemove);
-
-        String endpoint = getEndpoint();
-
-        URL url = new URL(endpoint + "/remove-pojo");
-        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
-        conn.setRequestMethod("POST");
-        sendAuthentication(conn, testuser, password);
-        conn.setDoOutput(true);
-        conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
-        Expression expr = factory.equalTo(key1, "test");
-        WebRemove<?> remove = new WebRemove<>(categoryId);
-        remove.where(expr);
-        Gson gson = new GsonBuilder()
-                .registerTypeHierarchyAdapter(Expression.class,
-                        new ExpressionSerializer())
-                .registerTypeHierarchyAdapter(Operator.class,
-                        new OperatorSerializer()).create();
-        OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream());
-        out.write("remove=");
-        gson.toJson(remove, out);
-        out.write("\n");
-        out.flush();
-
-        assertEquals(200, conn.getResponseCode());
-        verify(mockStorage).createRemove(eq(category));
-        verify(mockRemove).where(eq(expr));
-        verify(mockRemove).apply();
-    }
-    
-    @Test
-    public void unauthorizedRemovePojo() throws Exception {
-        String failMsg = "thermostat-remove role missing, expected Forbidden!";
-        doUnauthorizedTest("remove-pojo", failMsg);
-    }
-
-    @Test
-    public void authorizedUpdatePojo() throws Exception {
-        String[] roleNames = new String[] {
-                Roles.UPDATE,
-                Roles.REGISTER_CATEGORY,
-                Roles.ACCESS_REALM
-        };
-        String testuser = "testuser";
-        String password = "testpassword";
-        final LoginService loginService = new TestLoginService(testuser, password, roleNames); 
-        port = FreePortFinder.findFreePort(new TryPort() {
-            
-            @Override
-            public void tryPort(int port) throws Exception {
-                startServer(port, loginService);
-            }
-        });
-        // This makes register category work for the "test" category.
-        // Undone via @After
-        setupTrustedCategory(categoryName);
-        registerCategory(testuser, password);
-        
-        @SuppressWarnings("unchecked")
-        Update<TestClass> mockUpdate = mock(Update.class);
-        when(mockStorage.createUpdate(eq(category))).thenReturn(mockUpdate);
-
-        String endpoint = getEndpoint();
-
-        URL url = new URL(endpoint + "/update-pojo");
-        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
-        conn.setRequestMethod("POST");
-        sendAuthentication(conn, testuser, password);
-        conn.setDoOutput(true);
-        conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
-
-        WebUpdate<?> update = new WebUpdate<>();
-        update.setCategoryId(categoryId);
-        Expression expr = factory.equalTo(key1, "test");
-        update.where(expr);
-        update.set(key1, "fluff");
-        update.set(key2, 42);
-
-        Gson gson = new GsonBuilder()
-                .registerTypeHierarchyAdapter(Expression.class,
-                        new ExpressionSerializer())
-                .registerTypeHierarchyAdapter(Operator.class,
-                        new OperatorSerializer()).create();
-        OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream());
-        out.write("update=");
-        gson.toJson(update, out);
-        out.write("&values=");
-        gson.toJson(new Object[] {"fluff", 42 }, out);
-        out.write("\n");
-        out.flush();
-
-        assertEquals(200, conn.getResponseCode());
-        verify(mockStorage).createUpdate(category);
-        verify(mockUpdate).where(eq(expr));
-        verify(mockUpdate).set(key1, "fluff");
-        verify(mockUpdate).set(key2, 42);
-        verify(mockUpdate).apply();
-        verifyNoMoreInteractions(mockUpdate);
-    }
-    
-    @Test
-    public void unauthorizedUpdatePojo() throws Exception {
-        String failMsg = "thermostat-update role missing, expected Forbidden!";
-        doUnauthorizedTest("update-pojo", failMsg);
-    }
-
-    @Test
     public void authorizedSaveFile() throws Exception {
         String filename = "fluff";
         String[] roleNames = new String[] {