changeset 1180:7690396a1f72

Implement PreparedStatement in WebStorage (Part 2). Reviewed-by: ebaron Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2013-July/007536.html
author Severin Gehwolf <sgehwolf@redhat.com>
date Thu, 18 Jul 2013 16:56:05 +0200
parents 03ed49a50413
children 468d91f69d67
files storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/ParsedStatementImplTest.java storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/ParsedStatementTest.java storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/PreparedStatementImplTest.java storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/StatementDescriptorParserTest.java web/client/src/test/java/com/redhat/thermostat/web/client/internal/WebStorageTest.java web/common/src/test/java/com/redhat/thermostat/web/common/PreparedParameterSerializerTest.java web/common/src/test/java/com/redhat/thermostat/web/common/PreparedParametersSerializerTest.java web/common/src/test/java/com/redhat/thermostat/web/common/ThermostatGSONConverterTest.java web/common/src/test/java/com/redhat/thermostat/web/common/WebPreparedStatementResponseSerializerTest.java web/common/src/test/java/com/redhat/thermostat/web/common/WebPreparedStatementSerializerTest.java web/common/src/test/java/com/redhat/thermostat/web/common/WebQueryResponseSerializerTest.java web/common/src/test/java/com/redhat/thermostat/web/common/WebQueryResponseTest.java web/common/src/test/java/com/redhat/thermostat/web/common/WebQueryTest.java web/server/src/test/java/com/redhat/thermostat/web/server/WebStorageEndpointTest.java
diffstat 14 files changed, 1555 insertions(+), 625 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/ParsedStatementImplTest.java	Thu Jul 18 16:56:05 2013 +0200
@@ -0,0 +1,483 @@
+/*
+ * 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.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.redhat.thermostat.common.Pair;
+import com.redhat.thermostat.storage.core.Cursor;
+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;
+import com.redhat.thermostat.storage.core.Query.SortDirection;
+import com.redhat.thermostat.storage.model.Pojo;
+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.Expression;
+import com.redhat.thermostat.storage.query.LiteralExpression;
+
+public class ParsedStatementImplTest {
+
+    private Query<Pojo> statement;
+    
+    @Before
+    public void setup() {
+        statement = new TestQuery();
+    }
+    
+    @After
+    public void tearDown() {
+        statement = null;
+    }
+    
+    @Test
+    public void canPatchWhereAndExpr() throws IllegalPatchException {
+        // create the parsedStatementImpl we are going to use
+        ParsedStatementImpl<Pojo> parsedStmt = new ParsedStatementImpl<>(statement);
+        SuffixExpression suffixExpn = new SuffixExpression();
+        suffixExpn.setLimitExpn(null);
+        suffixExpn.setSortExpn(null);
+        // WHERE a = ? AND c = ?
+        WhereExpression expn = new WhereExpression();
+        BinaryExpressionNode and = new BinaryExpressionNode(expn.getRoot());
+        expn.getRoot().setValue(and);
+        and.setOperator(BinaryLogicalOperator.AND);
+        BinaryExpressionNode leftEqual = new BinaryExpressionNode(and);
+        BinaryExpressionNode rightEqual = new BinaryExpressionNode(and);
+        leftEqual.setOperator(BinaryComparisonOperator.EQUALS);
+        rightEqual.setOperator(BinaryComparisonOperator.EQUALS);
+        and.setLeftChild(leftEqual);
+        and.setRightChild(rightEqual);
+        TerminalNode a = new TerminalNode(leftEqual);
+        Key<String> aKey = new Key<>("a", false);
+        a.setValue(aKey);
+        TerminalNode b = new TerminalNode(leftEqual);
+        UnfinishedValueNode patchB = new UnfinishedValueNode();
+        patchB.setParameterIndex(0);
+        patchB.setType(String.class);
+        b.setValue(patchB);
+        leftEqual.setLeftChild(a);
+        leftEqual.setRightChild(b);
+        TerminalNode c = new TerminalNode(rightEqual);
+        c.setValue("c");
+        rightEqual.setLeftChild(c);
+        TerminalNode d = new TerminalNode(rightEqual);
+        UnfinishedValueNode dPatch = new UnfinishedValueNode();
+        dPatch.setParameterIndex(1);
+        dPatch.setType(Integer.class);
+        d.setValue(dPatch);
+        rightEqual.setRightChild(d);
+        suffixExpn.setWhereExpn(expn);
+        parsedStmt.setNumFreeParams(1);
+        parsedStmt.setSuffixExpression(suffixExpn);
+        // next, create the PreparedStatement we are going to use for
+        // patching.
+        PreparedStatementImpl<Pojo> preparedStatement = new PreparedStatementImpl<>(2);
+        preparedStatement.setString(0, "test1");
+        preparedStatement.setInt(1, 2);
+        PreparedParameter[] params = preparedStatement.getParams();
+        // finally test the patching
+        Query<Pojo> query = (Query<Pojo>)parsedStmt.patchStatement(params);
+        assertTrue(query instanceof TestQuery);
+        TestQuery q = (TestQuery)query;
+        Expression expectedExpression = q.expr;
+        assertTrue(expectedExpression instanceof BinaryLogicalExpression);
+        BinaryLogicalExpression<?, ?> andFinal = (BinaryLogicalExpression<?, ?>) expectedExpression;
+        assertEquals(BinaryLogicalOperator.AND, andFinal.getOperator());
+        assertTrue(andFinal.getLeftOperand() instanceof BinaryComparisonExpression);
+        assertTrue(andFinal.getRightOperand() instanceof BinaryComparisonExpression);
+        BinaryComparisonExpression<?> left = (BinaryComparisonExpression<?>)andFinal.getLeftOperand();
+        BinaryComparisonExpression<?> right = (BinaryComparisonExpression<?>)andFinal.getRightOperand();
+        assertEquals(BinaryComparisonOperator.EQUALS, left.getOperator());
+        assertEquals(BinaryComparisonOperator.EQUALS, right.getOperator());
+        assertTrue(left.getLeftOperand() instanceof LiteralExpression);
+        assertTrue(left.getRightOperand() instanceof LiteralExpression);
+        LiteralExpression<?> leftLiteral1 = (LiteralExpression<?>)left.getLeftOperand();
+        LiteralExpression<?> rightLiteral1 = (LiteralExpression<?>)left.getRightOperand();
+        assertEquals(aKey, leftLiteral1.getValue());
+        assertEquals("test1", rightLiteral1.getValue());
+        LiteralExpression<?> leftLiteral2 = (LiteralExpression<?>)right.getLeftOperand();
+        LiteralExpression<?> rightLiteral2 = (LiteralExpression<?>)right.getRightOperand();
+        assertEquals("c", leftLiteral2.getValue());
+        // right literal value should have been patched to a "d"
+        assertEquals(2, rightLiteral2.getValue());
+    }
+    
+    @Test
+    public void canPatchBasicWhereEquals() throws IllegalPatchException {
+        // create the parsedStatementImpl we are going to use
+        ParsedStatementImpl<Pojo> parsedStmt = new ParsedStatementImpl<>(statement);
+        SuffixExpression suffixExpn = new SuffixExpression();
+        suffixExpn.setLimitExpn(null);
+        suffixExpn.setSortExpn(null);
+        // WHERE a = ?
+        WhereExpression expn = new WhereExpression();
+        BinaryExpressionNode and = new BinaryExpressionNode(expn.getRoot());
+        expn.getRoot().setValue(and);
+        and.setOperator(BinaryComparisonOperator.EQUALS);
+        TerminalNode a = new TerminalNode(and);
+        a.setValue(new Key<>("a", false));
+        TerminalNode b = new TerminalNode(and);
+        UnfinishedValueNode bPatch = new UnfinishedValueNode();
+        bPatch.setParameterIndex(0);
+        bPatch.setType(Boolean.class);
+        b.setValue(bPatch);
+        and.setLeftChild(a);
+        and.setRightChild(b);
+        suffixExpn.setWhereExpn(expn);
+        parsedStmt.setNumFreeParams(1);
+        parsedStmt.setSuffixExpression(suffixExpn);
+        // next, create the PreparedStatement we are going to use for
+        // patching.
+        PreparedStatementImpl<Pojo> preparedStatement = new PreparedStatementImpl<>(1);
+        preparedStatement.setBoolean(0, true);
+        PreparedParameter[] params = preparedStatement.getParams();
+        // finally test the patching
+        Query<?> query = (Query<?>)parsedStmt.patchStatement(params);
+        assertTrue(query instanceof TestQuery);
+        TestQuery q = (TestQuery)query;
+        Expression expectedExpression = q.expr;
+        assertTrue(expectedExpression instanceof BinaryComparisonExpression);
+        BinaryComparisonExpression<?> root = (BinaryComparisonExpression<?>)expectedExpression;
+        assertEquals(BinaryComparisonOperator.EQUALS, root.getOperator());
+        assertTrue(root.getLeftOperand() instanceof LiteralExpression);
+        assertTrue(root.getRightOperand() instanceof LiteralExpression);
+        LiteralExpression<?> leftLiteral1 = (LiteralExpression<?>)root.getLeftOperand();
+        LiteralExpression<?> rightLiteral1 = (LiteralExpression<?>)root.getRightOperand();
+        assertEquals(new Key<>("a", false), leftLiteral1.getValue());
+        // this should have gotten patched to a "b"
+        assertEquals(true, rightLiteral1.getValue());
+        // now do it again with a different value
+        preparedStatement = new PreparedStatementImpl<>(1);
+        preparedStatement.setBoolean(0, false);
+        params = preparedStatement.getParams();
+        query = (Query<?>)parsedStmt.patchStatement(params);
+        assertTrue(query instanceof TestQuery);
+        q = (TestQuery)query;
+        expectedExpression = q.expr;
+        assertTrue(expectedExpression instanceof BinaryComparisonExpression);
+        root = (BinaryComparisonExpression<?>)expectedExpression;
+        assertEquals(BinaryComparisonOperator.EQUALS, root.getOperator());
+        assertTrue(root.getLeftOperand() instanceof LiteralExpression);
+        assertTrue(root.getRightOperand() instanceof LiteralExpression);
+        leftLiteral1 = (LiteralExpression<?>)root.getLeftOperand();
+        rightLiteral1 = (LiteralExpression<?>)root.getRightOperand();
+        assertEquals(new Key<>("a", false), leftLiteral1.getValue());
+        assertEquals(false, rightLiteral1.getValue());
+    }
+    
+    @Test
+    public void canPatchBasicWhereEqualsLHSKeyAndRHSValue() throws IllegalPatchException {
+        // create the parsedStatementImpl we are going to use
+        ParsedStatementImpl<Pojo> parsedStmt = new ParsedStatementImpl<>(statement);
+        SuffixExpression suffixExpn = new SuffixExpression();
+        suffixExpn.setLimitExpn(null);
+        suffixExpn.setSortExpn(null);
+        // WHERE ?s = ?b
+        WhereExpression expn = new WhereExpression();
+        BinaryExpressionNode and = new BinaryExpressionNode(expn.getRoot());
+        expn.getRoot().setValue(and);
+        and.setOperator(BinaryComparisonOperator.EQUALS);
+        TerminalNode a = new TerminalNode(and);
+        UnfinishedValueNode aPatch = new UnfinishedValueNode();
+        aPatch.setLHS(true);
+        aPatch.setType(String.class);
+        aPatch.setParameterIndex(0);
+        a.setValue(aPatch);
+        TerminalNode b = new TerminalNode(and);
+        UnfinishedValueNode bPatch = new UnfinishedValueNode();
+        bPatch.setParameterIndex(1);
+        bPatch.setType(Boolean.class);
+        b.setValue(bPatch);
+        and.setLeftChild(a);
+        and.setRightChild(b);
+        suffixExpn.setWhereExpn(expn);
+        parsedStmt.setNumFreeParams(1);
+        parsedStmt.setSuffixExpression(suffixExpn);
+        // next, create the PreparedStatement we are going to use for
+        // patching.
+        PreparedStatementImpl<Pojo> preparedStatement = new PreparedStatementImpl<>(2);
+        preparedStatement.setString(0, "a");
+        preparedStatement.setBoolean(1, true);
+        PreparedParameter[] params = preparedStatement.getParams();
+        // finally test the patching
+        Query<?> query = (Query<?>)parsedStmt.patchStatement(params);
+        assertTrue(query instanceof TestQuery);
+        TestQuery q = (TestQuery)query;
+        Expression expectedExpression = q.expr;
+        assertTrue(expectedExpression instanceof BinaryComparisonExpression);
+        BinaryComparisonExpression<?> root = (BinaryComparisonExpression<?>)expectedExpression;
+        assertEquals(BinaryComparisonOperator.EQUALS, root.getOperator());
+        assertTrue(root.getLeftOperand() instanceof LiteralExpression);
+        assertTrue(root.getRightOperand() instanceof LiteralExpression);
+        LiteralExpression<?> leftLiteral1 = (LiteralExpression<?>)root.getLeftOperand();
+        LiteralExpression<?> rightLiteral1 = (LiteralExpression<?>)root.getRightOperand();
+        assertEquals(new Key<>("a", false), leftLiteral1.getValue());
+        // this should have gotten patched to a "b"
+        assertEquals(true, rightLiteral1.getValue());
+        // now do it again with a different value
+        preparedStatement = new PreparedStatementImpl<>(2);
+        preparedStatement.setString(0, "a");
+        preparedStatement.setBoolean(1, false);
+        params = preparedStatement.getParams();
+        query = (Query<?>)parsedStmt.patchStatement(params);
+        assertTrue(query instanceof TestQuery);
+        q = (TestQuery)query;
+        expectedExpression = q.expr;
+        assertTrue(expectedExpression instanceof BinaryComparisonExpression);
+        root = (BinaryComparisonExpression<?>)expectedExpression;
+        assertEquals(BinaryComparisonOperator.EQUALS, root.getOperator());
+        assertTrue(root.getLeftOperand() instanceof LiteralExpression);
+        assertTrue(root.getRightOperand() instanceof LiteralExpression);
+        leftLiteral1 = (LiteralExpression<?>)root.getLeftOperand();
+        rightLiteral1 = (LiteralExpression<?>)root.getRightOperand();
+        assertEquals(new Key<>("a", false), leftLiteral1.getValue());
+        assertEquals(false, rightLiteral1.getValue());
+    }
+    
+    @Test
+    public void canPatchBasicLimit() throws IllegalPatchException {
+        // create the parsedStatementImpl we are going to use
+        ParsedStatementImpl<Pojo> parsedStmt = new ParsedStatementImpl<>(statement);
+        SuffixExpression suffixExpn = new SuffixExpression();
+        LimitExpression limitExpnToPatch = new LimitExpression();
+        UnfinishedLimitValue unfinished = new UnfinishedLimitValue();
+        unfinished.setParameterIndex(0);
+        limitExpnToPatch.setValue(unfinished);
+        suffixExpn.setLimitExpn(limitExpnToPatch);
+        suffixExpn.setSortExpn(null);
+        suffixExpn.setWhereExpn(null);
+        parsedStmt.setSuffixExpression(suffixExpn);
+        // set the value for the one unfinished param
+        PreparedStatementImpl<Pojo> preparedStatement = new PreparedStatementImpl<>(1);
+        preparedStatement.setInt(0, 3);
+        PreparedParameter[] params = preparedStatement.getParams();
+        // finally test the patching
+        Query<?> query = (Query<?>)parsedStmt.patchStatement(params);
+        assertTrue(query instanceof TestQuery);
+        TestQuery q = (TestQuery)query;
+        assertEquals(3, q.limitVal);
+    }
+    
+    @Test
+    public void canPatchBasicSort() throws IllegalPatchException {
+        // create the parsedStatementImpl we are going to use
+        ParsedStatementImpl<Pojo> parsedStmt = new ParsedStatementImpl<>(statement);
+        SuffixExpression suffixExpn = new SuffixExpression();
+        // SORT ? ASC, b DSC
+        SortExpression sortExpn = new SortExpression();
+        SortMember member = new SortMember();
+        member.setDirection(SortDirection.ASCENDING);
+        UnfinishedSortKey unfinished = new UnfinishedSortKey();
+        unfinished.setParameterIndex(0);
+        member.setSortKey(unfinished);
+        SortMember member2 = new SortMember();
+        member2.setDirection(SortDirection.DESCENDING);
+        member2.setSortKey("b");
+        sortExpn.addMember(member);
+        sortExpn.addMember(member2);
+        suffixExpn.setLimitExpn(null);
+        suffixExpn.setSortExpn(sortExpn);
+        suffixExpn.setWhereExpn(null);
+        parsedStmt.setSuffixExpression(suffixExpn);
+        // set the value for the one unfinished param
+        PreparedStatementImpl<Pojo> preparedStatement = new PreparedStatementImpl<>(1);
+        preparedStatement.setString(0, "a");
+        PreparedParameter[] params = preparedStatement.getParams();
+        // finally test the patching
+        Query<Pojo> query = (Query<Pojo>)parsedStmt.patchStatement(params);
+        assertTrue(query instanceof TestQuery);
+        TestQuery q = (TestQuery)query;
+        List<Pair<Key<?>, SortDirection>> actualSorts = q.sorts;
+        assertEquals(2, actualSorts.size());
+        Pair<Key<?>, SortDirection> first = actualSorts.get(0);
+        Key<?> firstKeyActual = (Key<?>)first.getFirst();
+        Key<?> expectedFirst = new Key<>("a", false);
+        assertEquals(expectedFirst, firstKeyActual);
+        assertEquals(SortDirection.ASCENDING, first.getSecond());
+        Pair<Key<?>, SortDirection> second = actualSorts.get(1);
+        Key<?> secondKeyActual = (Key<?>)second.getFirst();
+        Key<?> expectedSecond = new Key<>("b", false);
+        assertEquals(expectedSecond, secondKeyActual);
+        assertEquals(SortDirection.DESCENDING, second.getSecond());
+    }
+    
+    @Test
+    public void failPatchWithWrongType() throws IllegalPatchException {
+        // create the parsedStatementImpl we are going to use
+        ParsedStatementImpl<Pojo> parsedStmt = new ParsedStatementImpl<>(statement);
+        SuffixExpression suffixExpn = new SuffixExpression();
+        suffixExpn.setLimitExpn(null);
+        suffixExpn.setSortExpn(null);
+        // WHERE 'a' = ?s AND 'c' = ?i
+        WhereExpression expn = new WhereExpression();
+        BinaryExpressionNode and = new BinaryExpressionNode(expn.getRoot());
+        expn.getRoot().setValue(and);
+        and.setOperator(BinaryLogicalOperator.AND);
+        BinaryExpressionNode leftEqual = new BinaryExpressionNode(and);
+        BinaryExpressionNode rightEqual = new BinaryExpressionNode(and);
+        leftEqual.setOperator(BinaryComparisonOperator.EQUALS);
+        rightEqual.setOperator(BinaryComparisonOperator.EQUALS);
+        and.setLeftChild(leftEqual);
+        and.setRightChild(rightEqual);
+        TerminalNode a = new TerminalNode(leftEqual);
+        Key<?> aKey = new Key<>("a", false);
+        a.setValue(aKey);
+        TerminalNode b = new TerminalNode(leftEqual);
+        UnfinishedValueNode patchB = new UnfinishedValueNode();
+        patchB.setType(String.class);
+        patchB.setParameterIndex(0);
+        b.setValue(patchB);
+        leftEqual.setLeftChild(a);
+        leftEqual.setRightChild(b);
+        TerminalNode c = new TerminalNode(rightEqual);
+        Key<?> cKey = new Key<>("c", false);
+        c.setValue(cKey);
+        rightEqual.setLeftChild(c);
+        TerminalNode d = new TerminalNode(rightEqual);
+        UnfinishedValueNode dPatch = new UnfinishedValueNode();
+        dPatch.setParameterIndex(1);
+        dPatch.setType(Integer.class);
+        d.setValue(dPatch);
+        rightEqual.setRightChild(d);
+        suffixExpn.setWhereExpn(expn);
+        parsedStmt.setNumFreeParams(1);
+        parsedStmt.setSuffixExpression(suffixExpn);
+        // next, create the PreparedStatement we are going to use for
+        // patching.
+        PreparedStatementImpl<Pojo> preparedStatement = new PreparedStatementImpl<>(2);
+        preparedStatement.setString(0, "test1");
+        preparedStatement.setString(1, "foo");
+        // finally test the patching
+        try {
+            PreparedParameter[] params = preparedStatement.getParams();
+            parsedStmt.patchStatement(params);
+            fail("should have failed to patch param 1 with a string value (expected int)");
+        } catch (IllegalPatchException e) {
+            // pass
+            assertTrue(e.getMessage().contains("invalid type when attempting to patch"));
+        }
+    }
+    
+    @Test
+    public void failPatchBasicEqualsIfIndexOutofBounds() {
+        // create the parsedStatementImpl we are going to use
+        ParsedStatementImpl<Pojo> parsedStmt = new ParsedStatementImpl<>(statement);
+        SuffixExpression suffixExpn = new SuffixExpression();
+        suffixExpn.setLimitExpn(null);
+        suffixExpn.setSortExpn(null);
+        // WHERE a = ?
+        WhereExpression expn = new WhereExpression();
+        BinaryExpressionNode and = new BinaryExpressionNode(expn.getRoot());
+        expn.getRoot().setValue(and);
+        and.setOperator(BinaryComparisonOperator.EQUALS);
+        TerminalNode a = new TerminalNode(and);
+        a.setValue("a");
+        TerminalNode b = new TerminalNode(and);
+        UnfinishedValueNode bPatch = new UnfinishedValueNode();
+        bPatch.setParameterIndex(1); // out of bounds
+        b.setValue(bPatch);
+        and.setLeftChild(a);
+        and.setRightChild(b);
+        suffixExpn.setWhereExpn(expn);
+        parsedStmt.setNumFreeParams(1);
+        parsedStmt.setSuffixExpression(suffixExpn);
+        PreparedStatementImpl<Pojo> preparedStatement = new PreparedStatementImpl<>(1);
+        preparedStatement.setString(0, "b");
+        try {
+            PreparedParameter[] params = preparedStatement.getParams();
+            // this should fail
+            parsedStmt.patchStatement(params);
+            fail("should not reach here");
+        } catch (IllegalPatchException e) {
+            // pass
+            assertTrue(e.getCause() instanceof ArrayIndexOutOfBoundsException);
+        }
+    }
+    
+    private static class TestQuery implements Query<Pojo> {
+
+        private Expression expr;
+        private List<Pair<Key<?>, SortDirection>> sorts;
+        private int limitVal = -1;
+        
+        private TestQuery() {
+            sorts = new ArrayList<>();
+        }
+        
+        @Override
+        public void where(Expression expr) {
+            this.expr = expr;
+        }
+
+        @Override
+        public void sort(Key<?> key, SortDirection direction) {
+            Pair<Key<?>, SortDirection> sortPair = new Pair<Key<?>, SortDirection>(key, direction);
+            sorts.add(sortPair);
+        }
+
+        @Override
+        public void limit(int n) {
+            this.limitVal = n;
+        }
+
+        @Override
+        public Cursor<Pojo> execute() {
+            // Not implemented
+            return null;
+        }
+
+        @Override
+        public Expression getWhereExpression() {
+            // Not implemented
+            return null;
+        }
+        
+    }
+}
--- a/storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/ParsedStatementTest.java	Thu Jul 18 16:52:29 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,463 +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.storage.internal.statement;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import com.redhat.thermostat.common.Pair;
-import com.redhat.thermostat.storage.core.Cursor;
-import com.redhat.thermostat.storage.core.Key;
-import com.redhat.thermostat.storage.core.Query;
-import com.redhat.thermostat.storage.core.Query.SortDirection;
-import com.redhat.thermostat.storage.model.Pojo;
-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.Expression;
-import com.redhat.thermostat.storage.query.LiteralExpression;
-
-public class ParsedStatementTest {
-
-    private Query<Pojo> statement;
-    
-    @Before
-    public void setup() {
-        statement = new TestQuery();
-    }
-    
-    @After
-    public void tearDown() {
-        statement = null;
-    }
-    
-    @Test
-    public void canPatchWhereAndExpr() throws IllegalPatchException {
-        // create the parsedStatementImpl we are going to use
-        ParsedStatement<Pojo> parsedStmt = new ParsedStatement<>(statement);
-        SuffixExpression suffixExpn = new SuffixExpression();
-        suffixExpn.setLimitExpn(null);
-        suffixExpn.setSortExpn(null);
-        // WHERE a = ? AND c = ?
-        WhereExpression expn = new WhereExpression();
-        BinaryExpressionNode and = new BinaryExpressionNode(expn.getRoot());
-        expn.getRoot().setValue(and);
-        and.setOperator(BinaryLogicalOperator.AND);
-        BinaryExpressionNode leftEqual = new BinaryExpressionNode(and);
-        BinaryExpressionNode rightEqual = new BinaryExpressionNode(and);
-        leftEqual.setOperator(BinaryComparisonOperator.EQUALS);
-        rightEqual.setOperator(BinaryComparisonOperator.EQUALS);
-        and.setLeftChild(leftEqual);
-        and.setRightChild(rightEqual);
-        TerminalNode a = new TerminalNode(leftEqual);
-        Key<String> aKey = new Key<>("a", false);
-        a.setValue(aKey);
-        TerminalNode b = new TerminalNode(leftEqual);
-        UnfinishedValueNode patchB = new UnfinishedValueNode();
-        patchB.setParameterIndex(0);
-        patchB.setType(String.class);
-        b.setValue(patchB);
-        leftEqual.setLeftChild(a);
-        leftEqual.setRightChild(b);
-        TerminalNode c = new TerminalNode(rightEqual);
-        c.setValue("c");
-        rightEqual.setLeftChild(c);
-        TerminalNode d = new TerminalNode(rightEqual);
-        UnfinishedValueNode dPatch = new UnfinishedValueNode();
-        dPatch.setParameterIndex(1);
-        dPatch.setType(Integer.class);
-        d.setValue(dPatch);
-        rightEqual.setRightChild(d);
-        suffixExpn.setWhereExpn(expn);
-        parsedStmt.setNumFreeParams(1);
-        parsedStmt.setSuffixExpression(suffixExpn);
-        // next, create the PreparedStatement we are going to use for
-        // patching.
-        PreparedStatementImpl<Pojo> preparedStatement = new PreparedStatementImpl<>(2);
-        preparedStatement.setString(0, "test1");
-        preparedStatement.setInt(1, 2);
-        // finally test the patching
-        Query<Pojo> query = (Query<Pojo>)parsedStmt.patchQuery(preparedStatement);
-        assertTrue(query instanceof TestQuery);
-        TestQuery q = (TestQuery)query;
-        Expression expectedExpression = q.expr;
-        assertTrue(expectedExpression instanceof BinaryLogicalExpression);
-        BinaryLogicalExpression<?, ?> andFinal = (BinaryLogicalExpression<?, ?>) expectedExpression;
-        assertEquals(BinaryLogicalOperator.AND, andFinal.getOperator());
-        assertTrue(andFinal.getLeftOperand() instanceof BinaryComparisonExpression);
-        assertTrue(andFinal.getRightOperand() instanceof BinaryComparisonExpression);
-        BinaryComparisonExpression<?> left = (BinaryComparisonExpression<?>)andFinal.getLeftOperand();
-        BinaryComparisonExpression<?> right = (BinaryComparisonExpression<?>)andFinal.getRightOperand();
-        assertEquals(BinaryComparisonOperator.EQUALS, left.getOperator());
-        assertEquals(BinaryComparisonOperator.EQUALS, right.getOperator());
-        assertTrue(left.getLeftOperand() instanceof LiteralExpression);
-        assertTrue(left.getRightOperand() instanceof LiteralExpression);
-        LiteralExpression<?> leftLiteral1 = (LiteralExpression<?>)left.getLeftOperand();
-        LiteralExpression<?> rightLiteral1 = (LiteralExpression<?>)left.getRightOperand();
-        assertEquals(aKey, leftLiteral1.getValue());
-        assertEquals("test1", rightLiteral1.getValue());
-        LiteralExpression<?> leftLiteral2 = (LiteralExpression<?>)right.getLeftOperand();
-        LiteralExpression<?> rightLiteral2 = (LiteralExpression<?>)right.getRightOperand();
-        assertEquals("c", leftLiteral2.getValue());
-        // right literal value should have been patched to a "d"
-        assertEquals(2, rightLiteral2.getValue());
-    }
-    
-    @Test
-    public void canPatchBasicWhereEquals() throws IllegalPatchException {
-        // create the parsedStatementImpl we are going to use
-        ParsedStatement<Pojo> parsedStmt = new ParsedStatement<>(statement);
-        SuffixExpression suffixExpn = new SuffixExpression();
-        suffixExpn.setLimitExpn(null);
-        suffixExpn.setSortExpn(null);
-        // WHERE a = ?
-        WhereExpression expn = new WhereExpression();
-        BinaryExpressionNode and = new BinaryExpressionNode(expn.getRoot());
-        expn.getRoot().setValue(and);
-        and.setOperator(BinaryComparisonOperator.EQUALS);
-        TerminalNode a = new TerminalNode(and);
-        a.setValue(new Key<>("a", false));
-        TerminalNode b = new TerminalNode(and);
-        UnfinishedValueNode bPatch = new UnfinishedValueNode();
-        bPatch.setParameterIndex(0);
-        bPatch.setType(boolean.class);
-        b.setValue(bPatch);
-        and.setLeftChild(a);
-        and.setRightChild(b);
-        suffixExpn.setWhereExpn(expn);
-        parsedStmt.setNumFreeParams(1);
-        parsedStmt.setSuffixExpression(suffixExpn);
-        // next, create the PreparedStatement we are going to use for
-        // patching.
-        PreparedStatementImpl<Pojo> preparedStatement = new PreparedStatementImpl<>(1);
-        preparedStatement.setBoolean(0, true);
-        // finally test the patching
-        Query<?> query = (Query<?>)parsedStmt.patchQuery(preparedStatement);
-        assertTrue(query instanceof TestQuery);
-        TestQuery q = (TestQuery)query;
-        Expression expectedExpression = q.expr;
-        assertTrue(expectedExpression instanceof BinaryComparisonExpression);
-        BinaryComparisonExpression<?> root = (BinaryComparisonExpression<?>)expectedExpression;
-        assertEquals(BinaryComparisonOperator.EQUALS, root.getOperator());
-        assertTrue(root.getLeftOperand() instanceof LiteralExpression);
-        assertTrue(root.getRightOperand() instanceof LiteralExpression);
-        LiteralExpression<?> leftLiteral1 = (LiteralExpression<?>)root.getLeftOperand();
-        LiteralExpression<?> rightLiteral1 = (LiteralExpression<?>)root.getRightOperand();
-        assertEquals(new Key<>("a", false), leftLiteral1.getValue());
-        // this should have gotten patched to a "b"
-        assertEquals(true, rightLiteral1.getValue());
-        // now do it again with a different value
-        preparedStatement = new PreparedStatementImpl<>(1);
-        preparedStatement.setBoolean(0, false);
-        query = (Query<?>)parsedStmt.patchQuery(preparedStatement);
-        assertTrue(query instanceof TestQuery);
-        q = (TestQuery)query;
-        expectedExpression = q.expr;
-        assertTrue(expectedExpression instanceof BinaryComparisonExpression);
-        root = (BinaryComparisonExpression<?>)expectedExpression;
-        assertEquals(BinaryComparisonOperator.EQUALS, root.getOperator());
-        assertTrue(root.getLeftOperand() instanceof LiteralExpression);
-        assertTrue(root.getRightOperand() instanceof LiteralExpression);
-        leftLiteral1 = (LiteralExpression<?>)root.getLeftOperand();
-        rightLiteral1 = (LiteralExpression<?>)root.getRightOperand();
-        assertEquals(new Key<>("a", false), leftLiteral1.getValue());
-        assertEquals(false, rightLiteral1.getValue());
-    }
-    
-    @Test
-    public void canPatchBasicWhereEqualsLHSKeyAndRHSValue() throws IllegalPatchException {
-        // create the parsedStatementImpl we are going to use
-        ParsedStatement<Pojo> parsedStmt = new ParsedStatement<>(statement);
-        SuffixExpression suffixExpn = new SuffixExpression();
-        suffixExpn.setLimitExpn(null);
-        suffixExpn.setSortExpn(null);
-        // WHERE ?s = ?b
-        WhereExpression expn = new WhereExpression();
-        BinaryExpressionNode and = new BinaryExpressionNode(expn.getRoot());
-        expn.getRoot().setValue(and);
-        and.setOperator(BinaryComparisonOperator.EQUALS);
-        TerminalNode a = new TerminalNode(and);
-        UnfinishedValueNode aPatch = new UnfinishedValueNode();
-        aPatch.setLHS(true);
-        aPatch.setType(String.class);
-        aPatch.setParameterIndex(0);
-        a.setValue(aPatch);
-        TerminalNode b = new TerminalNode(and);
-        UnfinishedValueNode bPatch = new UnfinishedValueNode();
-        bPatch.setParameterIndex(1);
-        bPatch.setType(boolean.class);
-        b.setValue(bPatch);
-        and.setLeftChild(a);
-        and.setRightChild(b);
-        suffixExpn.setWhereExpn(expn);
-        parsedStmt.setNumFreeParams(1);
-        parsedStmt.setSuffixExpression(suffixExpn);
-        // next, create the PreparedStatement we are going to use for
-        // patching.
-        PreparedStatementImpl<Pojo> preparedStatement = new PreparedStatementImpl<>(2);
-        preparedStatement.setString(0, "a");
-        preparedStatement.setBoolean(1, true);
-        // finally test the patching
-        Query<?> query = (Query<?>)parsedStmt.patchQuery(preparedStatement);
-        assertTrue(query instanceof TestQuery);
-        TestQuery q = (TestQuery)query;
-        Expression expectedExpression = q.expr;
-        assertTrue(expectedExpression instanceof BinaryComparisonExpression);
-        BinaryComparisonExpression<?> root = (BinaryComparisonExpression<?>)expectedExpression;
-        assertEquals(BinaryComparisonOperator.EQUALS, root.getOperator());
-        assertTrue(root.getLeftOperand() instanceof LiteralExpression);
-        assertTrue(root.getRightOperand() instanceof LiteralExpression);
-        LiteralExpression<?> leftLiteral1 = (LiteralExpression<?>)root.getLeftOperand();
-        LiteralExpression<?> rightLiteral1 = (LiteralExpression<?>)root.getRightOperand();
-        assertEquals(new Key<>("a", false), leftLiteral1.getValue());
-        // this should have gotten patched to a "b"
-        assertEquals(true, rightLiteral1.getValue());
-        // now do it again with a different value
-        preparedStatement = new PreparedStatementImpl<>(2);
-        preparedStatement.setString(0, "a");
-        preparedStatement.setBoolean(1, false);
-        query = (Query<?>)parsedStmt.patchQuery(preparedStatement);
-        assertTrue(query instanceof TestQuery);
-        q = (TestQuery)query;
-        expectedExpression = q.expr;
-        assertTrue(expectedExpression instanceof BinaryComparisonExpression);
-        root = (BinaryComparisonExpression<?>)expectedExpression;
-        assertEquals(BinaryComparisonOperator.EQUALS, root.getOperator());
-        assertTrue(root.getLeftOperand() instanceof LiteralExpression);
-        assertTrue(root.getRightOperand() instanceof LiteralExpression);
-        leftLiteral1 = (LiteralExpression<?>)root.getLeftOperand();
-        rightLiteral1 = (LiteralExpression<?>)root.getRightOperand();
-        assertEquals(new Key<>("a", false), leftLiteral1.getValue());
-        assertEquals(false, rightLiteral1.getValue());
-    }
-    
-    @Test
-    public void canPatchBasicLimit() throws IllegalPatchException {
-        // create the parsedStatementImpl we are going to use
-        ParsedStatement<Pojo> parsedStmt = new ParsedStatement<>(statement);
-        SuffixExpression suffixExpn = new SuffixExpression();
-        LimitExpression limitExpnToPatch = new LimitExpression();
-        UnfinishedLimitValue unfinished = new UnfinishedLimitValue();
-        unfinished.setParameterIndex(0);
-        limitExpnToPatch.setValue(unfinished);
-        suffixExpn.setLimitExpn(limitExpnToPatch);
-        suffixExpn.setSortExpn(null);
-        suffixExpn.setWhereExpn(null);
-        parsedStmt.setSuffixExpression(suffixExpn);
-        // set the value for the one unfinished param
-        PreparedStatementImpl<Pojo> preparedStatement = new PreparedStatementImpl<>(1);
-        preparedStatement.setInt(0, 3);
-        Query<?> query = (Query<?>)parsedStmt.patchQuery(preparedStatement);
-        assertTrue(query instanceof TestQuery);
-        TestQuery q = (TestQuery)query;
-        assertEquals(3, q.limitVal);
-    }
-    
-    @Test
-    public void canPatchBasicSort() throws IllegalPatchException {
-        // create the parsedStatementImpl we are going to use
-        ParsedStatement<Pojo> parsedStmt = new ParsedStatement<>(statement);
-        SuffixExpression suffixExpn = new SuffixExpression();
-        // SORT ? ASC, b DSC
-        SortExpression sortExpn = new SortExpression();
-        SortMember member = new SortMember();
-        member.setDirection(SortDirection.ASCENDING);
-        UnfinishedSortKey unfinished = new UnfinishedSortKey();
-        unfinished.setParameterIndex(0);
-        member.setSortKey(unfinished);
-        SortMember member2 = new SortMember();
-        member2.setDirection(SortDirection.DESCENDING);
-        member2.setSortKey("b");
-        sortExpn.addMember(member);
-        sortExpn.addMember(member2);
-        suffixExpn.setLimitExpn(null);
-        suffixExpn.setSortExpn(sortExpn);
-        suffixExpn.setWhereExpn(null);
-        parsedStmt.setSuffixExpression(suffixExpn);
-        // set the value for the one unfinished param
-        PreparedStatementImpl<Pojo> preparedStatement = new PreparedStatementImpl<>(1);
-        preparedStatement.setString(0, "a");
-        Query<Pojo> query = (Query<Pojo>)parsedStmt.patchQuery(preparedStatement);
-        assertTrue(query instanceof TestQuery);
-        TestQuery q = (TestQuery)query;
-        List<Pair<Key<?>, SortDirection>> actualSorts = q.sorts;
-        assertEquals(2, actualSorts.size());
-        Pair<Key<?>, SortDirection> first = actualSorts.get(0);
-        Key<?> firstKeyActual = (Key<?>)first.getFirst();
-        Key<?> expectedFirst = new Key<>("a", false);
-        assertEquals(expectedFirst, firstKeyActual);
-        assertEquals(SortDirection.ASCENDING, first.getSecond());
-        Pair<Key<?>, SortDirection> second = actualSorts.get(1);
-        Key<?> secondKeyActual = (Key<?>)second.getFirst();
-        Key<?> expectedSecond = new Key<>("b", false);
-        assertEquals(expectedSecond, secondKeyActual);
-        assertEquals(SortDirection.DESCENDING, second.getSecond());
-    }
-    
-    @Test
-    public void failPatchWithWrongType() throws IllegalPatchException {
-        // create the parsedStatementImpl we are going to use
-        ParsedStatement<Pojo> parsedStmt = new ParsedStatement<>(statement);
-        SuffixExpression suffixExpn = new SuffixExpression();
-        suffixExpn.setLimitExpn(null);
-        suffixExpn.setSortExpn(null);
-        // WHERE 'a' = ?s AND 'c' = ?i
-        WhereExpression expn = new WhereExpression();
-        BinaryExpressionNode and = new BinaryExpressionNode(expn.getRoot());
-        expn.getRoot().setValue(and);
-        and.setOperator(BinaryLogicalOperator.AND);
-        BinaryExpressionNode leftEqual = new BinaryExpressionNode(and);
-        BinaryExpressionNode rightEqual = new BinaryExpressionNode(and);
-        leftEqual.setOperator(BinaryComparisonOperator.EQUALS);
-        rightEqual.setOperator(BinaryComparisonOperator.EQUALS);
-        and.setLeftChild(leftEqual);
-        and.setRightChild(rightEqual);
-        TerminalNode a = new TerminalNode(leftEqual);
-        Key<?> aKey = new Key<>("a", false);
-        a.setValue(aKey);
-        TerminalNode b = new TerminalNode(leftEqual);
-        UnfinishedValueNode patchB = new UnfinishedValueNode();
-        patchB.setType(String.class);
-        patchB.setParameterIndex(0);
-        b.setValue(patchB);
-        leftEqual.setLeftChild(a);
-        leftEqual.setRightChild(b);
-        TerminalNode c = new TerminalNode(rightEqual);
-        Key<?> cKey = new Key<>("c", false);
-        c.setValue(cKey);
-        rightEqual.setLeftChild(c);
-        TerminalNode d = new TerminalNode(rightEqual);
-        UnfinishedValueNode dPatch = new UnfinishedValueNode();
-        dPatch.setParameterIndex(1);
-        dPatch.setType(Integer.class);
-        d.setValue(dPatch);
-        rightEqual.setRightChild(d);
-        suffixExpn.setWhereExpn(expn);
-        parsedStmt.setNumFreeParams(1);
-        parsedStmt.setSuffixExpression(suffixExpn);
-        // next, create the PreparedStatement we are going to use for
-        // patching.
-        PreparedStatementImpl<Pojo> preparedStatement = new PreparedStatementImpl<>(2);
-        preparedStatement.setString(0, "test1");
-        preparedStatement.setString(1, "foo");
-        // finally test the patching
-        try {
-            parsedStmt.patchQuery(preparedStatement);
-            fail("should have failed to patch param 1 with a string value (expected int)");
-        } catch (IllegalPatchException e) {
-            // pass
-            assertTrue(e.getMessage().contains("invalid type when attempting to patch"));
-        }
-    }
-    
-    @Test
-    public void failPatchBasicEqualsIfIndexOutofBounds() {
-        // create the parsedStatementImpl we are going to use
-        ParsedStatement<Pojo> parsedStmt = new ParsedStatement<>(statement);
-        SuffixExpression suffixExpn = new SuffixExpression();
-        suffixExpn.setLimitExpn(null);
-        suffixExpn.setSortExpn(null);
-        // WHERE a = ?
-        WhereExpression expn = new WhereExpression();
-        BinaryExpressionNode and = new BinaryExpressionNode(expn.getRoot());
-        expn.getRoot().setValue(and);
-        and.setOperator(BinaryComparisonOperator.EQUALS);
-        TerminalNode a = new TerminalNode(and);
-        a.setValue("a");
-        TerminalNode b = new TerminalNode(and);
-        UnfinishedValueNode bPatch = new UnfinishedValueNode();
-        bPatch.setParameterIndex(1); // out of bounds
-        b.setValue(bPatch);
-        and.setLeftChild(a);
-        and.setRightChild(b);
-        suffixExpn.setWhereExpn(expn);
-        parsedStmt.setNumFreeParams(1);
-        parsedStmt.setSuffixExpression(suffixExpn);
-        PreparedStatementImpl<Pojo> preparedStatement = new PreparedStatementImpl<>(1);
-        preparedStatement.setString(0, "b");
-        // this should fail
-        try {
-            parsedStmt.patchQuery(preparedStatement);
-        } catch (IllegalPatchException e) {
-            // pass
-            assertTrue(e.getCause() instanceof ArrayIndexOutOfBoundsException);
-        }
-    }
-    
-    private static class TestQuery implements Query<Pojo> {
-
-        private Expression expr;
-        private List<Pair<Key<?>, SortDirection>> sorts;
-        private int limitVal = -1;
-        
-        private TestQuery() {
-            sorts = new ArrayList<>();
-        }
-        
-        @Override
-        public void where(Expression expr) {
-            this.expr = expr;
-        }
-
-        @Override
-        public void sort(Key<?> key, SortDirection direction) {
-            Pair<Key<?>, SortDirection> sortPair = new Pair<Key<?>, SortDirection>(key, direction);
-            sorts.add(sortPair);
-        }
-
-        @Override
-        public void limit(int n) {
-            this.limitVal = n;
-        }
-
-        @Override
-        public Cursor<Pojo> execute() {
-            // Not implemented
-            return null;
-        }
-        
-    }
-}
--- a/storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/PreparedStatementImplTest.java	Thu Jul 18 16:52:29 2013 +0200
+++ b/storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/PreparedStatementImplTest.java	Thu Jul 18 16:56:05 2013 +0200
@@ -163,6 +163,12 @@
             called = true;
             return null;
         }
+
+        @Override
+        public Expression getWhereExpression() {
+            // not implemented
+            return null;
+        }
         
     }
 }
--- a/storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/StatementDescriptorParserTest.java	Thu Jul 18 16:52:29 2013 +0200
+++ b/storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/StatementDescriptorParserTest.java	Thu Jul 18 16:56:05 2013 +0200
@@ -59,6 +59,19 @@
 import com.redhat.thermostat.storage.core.Storage;
 import com.redhat.thermostat.storage.dao.AgentInfoDAO;
 import com.redhat.thermostat.storage.model.AgentInformation;
+import com.redhat.thermostat.storage.internal.statement.BinaryExpressionNode;
+import com.redhat.thermostat.storage.internal.statement.LimitExpression;
+import com.redhat.thermostat.storage.internal.statement.NotBooleanExpressionNode;
+import com.redhat.thermostat.storage.internal.statement.ParsedStatementImpl;
+import com.redhat.thermostat.storage.internal.statement.SortExpression;
+import com.redhat.thermostat.storage.internal.statement.SortMember;
+import com.redhat.thermostat.storage.internal.statement.StatementDescriptorParser;
+import com.redhat.thermostat.storage.internal.statement.SuffixExpression;
+import com.redhat.thermostat.storage.internal.statement.TerminalNode;
+import com.redhat.thermostat.storage.internal.statement.UnfinishedLimitValue;
+import com.redhat.thermostat.storage.internal.statement.UnfinishedSortKey;
+import com.redhat.thermostat.storage.internal.statement.UnfinishedValueNode;
+import com.redhat.thermostat.storage.internal.statement.WhereExpression;
 import com.redhat.thermostat.storage.query.BinaryComparisonOperator;
 import com.redhat.thermostat.storage.query.BinaryLogicalOperator;
 
@@ -87,7 +100,7 @@
         String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName();
         StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
         parser = new StatementDescriptorParser<>(storage, desc);
-        ParsedStatement<AgentInformation> statement = (ParsedStatement<AgentInformation>)parser.parse();
+        ParsedStatementImpl<AgentInformation> statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
         assertEquals(0, statement.getNumParams());
         assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
         SuffixExpression tree = statement.getSuffixExpression();
@@ -101,7 +114,7 @@
         String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' != 'b'";
         StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
         parser = new StatementDescriptorParser<>(storage, desc);
-        ParsedStatement<AgentInformation> statement = parser.parse();
+        ParsedStatementImpl<AgentInformation> statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
         assertEquals(0, statement.getNumParams());
         assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
         SuffixExpression tree = statement.getSuffixExpression();
@@ -128,7 +141,7 @@
         String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " LIMIT ?i";
         StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
         parser = new StatementDescriptorParser<>(storage, desc);
-        ParsedStatement<AgentInformation> statement = (ParsedStatement<AgentInformation>)parser.parse();
+        ParsedStatementImpl<AgentInformation> statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
         assertEquals(1, statement.getNumParams());
         assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
         SuffixExpression expn = statement.getSuffixExpression();
@@ -146,7 +159,7 @@
         String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " SORT 'a' ASC , 'b' DSC , 'c' ASC";
         StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
         parser = new StatementDescriptorParser<>(storage, desc);
-        ParsedStatement<AgentInformation> statement = (ParsedStatement<AgentInformation>)parser.parse();
+        ParsedStatementImpl<AgentInformation> statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
         assertEquals(0, statement.getNumParams());
         assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
         SuffixExpression suffixExpn = statement.getSuffixExpression();
@@ -170,7 +183,7 @@
         String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' = 'b' AND 'c' = 'd' AND 'e' < ?i";
         StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
         parser = new StatementDescriptorParser<>(storage, desc);
-        ParsedStatement<AgentInformation> statement = parser.parse();
+        ParsedStatementImpl<AgentInformation> statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
         assertEquals(1, statement.getNumParams());
         assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
         SuffixExpression expn = statement.getSuffixExpression();
@@ -229,7 +242,7 @@
         String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' = 'b' AND 'c' = 'd' AND 'e' < 'f' AND 'g' >= 'h'";
         StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
         parser = new StatementDescriptorParser<>(storage, desc);
-        ParsedStatement<AgentInformation> statement = parser.parse();
+        ParsedStatementImpl<AgentInformation> statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
         assertEquals(0, statement.getNumParams());
         assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
         SuffixExpression expn = statement.getSuffixExpression();
@@ -297,7 +310,7 @@
         String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' = 'b' OR 'c' = 'd' OR 'e' < ?i";
         StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
         parser = new StatementDescriptorParser<>(storage, desc);
-        ParsedStatement<AgentInformation> statement = parser.parse();
+        ParsedStatementImpl<AgentInformation> statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
         assertEquals(1, statement.getNumParams());
         assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
         SuffixExpression expn = statement.getSuffixExpression();
@@ -357,7 +370,7 @@
         String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' = 'b' OR 'c' = 'd' OR 'e' < 'f' OR 'g' >= 'h'";
         StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
         parser = new StatementDescriptorParser<>(storage, desc);
-        ParsedStatement<AgentInformation> statement = parser.parse();
+        ParsedStatementImpl<AgentInformation> statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
         assertEquals(0, statement.getNumParams());
         assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
         SuffixExpression expn = statement.getSuffixExpression();
@@ -426,7 +439,7 @@
         String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' = 'b' OR 'c' = 'd' OR 'e' < 'f' OR 'g' >= 'h' AND 'x' = 'y' AND 'u' = 'w' AND 's' = 't'";
         StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
         parser = new StatementDescriptorParser<>(storage, desc);
-        ParsedStatement<AgentInformation> statement = parser.parse();
+        ParsedStatementImpl<AgentInformation> statement = (ParsedStatementImpl<AgentInformation>) parser.parse();
         assertEquals(0, statement.getNumParams());
         assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
         SuffixExpression expn = statement.getSuffixExpression();
@@ -533,7 +546,7 @@
         String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' = 'b' OR NOT 'c' = 'd' OR 'e' < 'f' OR 'g' >= 'h' AND NOT 'x' = 'y' AND 'u' = 'w' AND 's' = 't'";
         StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
         parser = new StatementDescriptorParser<>(storage, desc);
-        ParsedStatement<AgentInformation> statement = parser.parse();
+        ParsedStatementImpl<AgentInformation> statement = (ParsedStatementImpl<AgentInformation>) parser.parse();
         assertEquals(0, statement.getNumParams());
         assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
         SuffixExpression expn = statement.getSuffixExpression();
@@ -644,7 +657,7 @@
         String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' < 'b' AND 'c' = ?s OR NOT 'x' >= ?i SORT 'a' ASC , 'b' DSC , 'c' ASC";
         StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
         parser = new StatementDescriptorParser<>(storage, desc);
-        ParsedStatement<AgentInformation> statement = (ParsedStatement<AgentInformation>) parser.parse();
+        ParsedStatementImpl<AgentInformation> statement = (ParsedStatementImpl<AgentInformation>) parser.parse();
         assertEquals(2, statement.getNumParams());
         assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
         SuffixExpression suffixExpn = statement.getSuffixExpression();
@@ -717,7 +730,7 @@
         String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' < 'b' OR 'c' = ?s SORT 'a' ASC , ?s DSC , 'c' ASC";
         StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
         parser = new StatementDescriptorParser<>(storage, desc);
-        ParsedStatement<AgentInformation> statement = (ParsedStatement<AgentInformation>) parser.parse();
+        ParsedStatementImpl<AgentInformation> statement = (ParsedStatementImpl<AgentInformation>) parser.parse();
         assertEquals(2, statement.getNumParams());
         assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
         SuffixExpression suffixExpn = statement.getSuffixExpression();
@@ -773,7 +786,7 @@
         String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' < 'b' SORT 'a' DSC";
         StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
         parser = new StatementDescriptorParser<>(storage, desc);
-        ParsedStatement<AgentInformation> statement = (ParsedStatement<AgentInformation>)parser.parse();
+        ParsedStatementImpl<AgentInformation> statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
         assertEquals(0, statement.getNumParams());
         assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
         SuffixExpression suffixExpn = statement.getSuffixExpression();
@@ -807,9 +820,9 @@
         String descString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE '" + Key.AGENT_ID.getName() + "' = ?s";
         StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
         parser = new StatementDescriptorParser<>(storage, desc);
-        ParsedStatement<AgentInformation> statement = null; 
+        ParsedStatementImpl<AgentInformation> statement = null; 
         try {
-            statement = (ParsedStatement<AgentInformation>)parser.parse();
+            statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
         } catch (DescriptorParsingException e) {
             fail(e.getMessage());
         }
@@ -839,16 +852,15 @@
         assertTrue(WhereExpressions.equals(where, suffixExpn.getWhereExpn()));
     }
 
-    @SuppressWarnings("rawtypes")
     @Test
     public void testParseSimpleWithAndOr() {
         String descString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE '" + Key.AGENT_ID.getName() + "' = ?s" +
                             " AND ?s < ?b OR 'a' = 'b'";
         StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
         parser = new StatementDescriptorParser<>(storage, desc);
-        ParsedStatement statement = null; 
+        ParsedStatementImpl<AgentInformation> statement = null; 
         try {
-            statement = (ParsedStatement)parser.parse();
+            statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
         } catch (DescriptorParsingException e) {
             e.printStackTrace();
             fail(e.getMessage());
@@ -870,7 +882,7 @@
         BinaryExpressionNode equality = new BinaryExpressionNode(and);
         equality.setOperator(BinaryComparisonOperator.EQUALS);
         TerminalNode a = new TerminalNode(equality);
-        Key aKey = new Key("a", false);
+        Key<String> aKey = new Key<>("a", false);
         a.setValue(aKey);
         equality.setLeftChild(a);
         TerminalNode b = new TerminalNode(equality);
@@ -880,7 +892,7 @@
         BinaryExpressionNode equality2 = new BinaryExpressionNode(and);
         equality2.setOperator(BinaryComparisonOperator.EQUALS);
         TerminalNode c = new TerminalNode(equality2);
-        Key cKey = new Key(Key.AGENT_ID.getName(), false);
+        Key<String> cKey = new Key<>(Key.AGENT_ID.getName(), false);
         c.setValue(cKey);
         equality2.setLeftChild(c);
         UnfinishedValueNode patch1 = new UnfinishedValueNode();
@@ -903,7 +915,7 @@
         UnfinishedValueNode patch2 = new UnfinishedValueNode();
         patch2.setLHS(false);
         patch2.setParameterIndex(2);
-        patch2.setType(boolean.class);
+        patch2.setType(Boolean.class);
         TerminalNode y = new TerminalNode(lessThan);
         y.setValue(patch2);
         lessThan.setRightChild(y);
@@ -916,9 +928,9 @@
         String descString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' = ?s AND ?s = 'd'";
         StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
         parser = new StatementDescriptorParser<>(storage, desc);
-        ParsedStatement<AgentInformation> statement = null; 
+        ParsedStatementImpl<AgentInformation> statement = null; 
         try {
-            statement = (ParsedStatement<AgentInformation>)parser.parse();
+            statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
         } catch (DescriptorParsingException e) {
             e.printStackTrace();
             fail(e.getMessage());
@@ -970,9 +982,9 @@
         String descString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE NOT 'a' = ?s OR ?s = 'd'";
         StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
         parser = new StatementDescriptorParser<>(storage, desc);
-        ParsedStatement<AgentInformation> statement = null; 
+        ParsedStatementImpl<AgentInformation> statement = null; 
         try {
-            statement = (ParsedStatement<AgentInformation>)parser.parse();
+            statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
         } catch (DescriptorParsingException e) {
             e.printStackTrace();
             fail(e.getMessage());
@@ -1026,9 +1038,9 @@
         String descString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' = ?s OR ?s = 'd'";
         StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
         parser = new StatementDescriptorParser<>(storage, desc);
-        ParsedStatement<AgentInformation> statement = null; 
+        ParsedStatementImpl<AgentInformation> statement = null; 
         try {
-            statement = (ParsedStatement<AgentInformation>)parser.parse();
+            statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
         } catch (DescriptorParsingException e) {
             e.printStackTrace();
             fail(e.getMessage());
@@ -1080,9 +1092,9 @@
         String descString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " LIMIT 1";
         StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
         parser = new StatementDescriptorParser<>(storage, desc);
-        ParsedStatement<AgentInformation> statement = null; 
+        ParsedStatementImpl<AgentInformation> statement = null; 
         try {
-            statement = (ParsedStatement<AgentInformation>)parser.parse();
+            statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
         } catch (DescriptorParsingException e) {
             e.printStackTrace();
             fail(e.getMessage());
--- a/web/client/src/test/java/com/redhat/thermostat/web/client/internal/WebStorageTest.java	Thu Jul 18 16:52:29 2013 +0200
+++ b/web/client/src/test/java/com/redhat/thermostat/web/client/internal/WebStorageTest.java	Thu Jul 18 16:56:05 2013 +0200
@@ -96,11 +96,18 @@
 import com.redhat.thermostat.storage.core.Connection.ConnectionListener;
 import com.redhat.thermostat.storage.core.Connection.ConnectionStatus;
 import com.redhat.thermostat.storage.core.Cursor;
+import com.redhat.thermostat.storage.core.DescriptorParsingException;
+import com.redhat.thermostat.storage.core.IllegalDescriptorException;
 import com.redhat.thermostat.storage.core.Key;
+import com.redhat.thermostat.storage.core.PreparedParameter;
+import com.redhat.thermostat.storage.core.PreparedParameters;
+import com.redhat.thermostat.storage.core.PreparedStatement;
 import com.redhat.thermostat.storage.core.Put;
-import com.redhat.thermostat.storage.core.Query;
 import com.redhat.thermostat.storage.core.Remove;
+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;
@@ -108,8 +115,14 @@
 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.ThermostatGSONConverter;
 import com.redhat.thermostat.web.common.WebInsert;
-import com.redhat.thermostat.web.common.WebQuery;
+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.WebUpdate;
 
@@ -262,37 +275,108 @@
 
         storage.registerCategory(category);
     }
+    
+    @Test
+    public void preparingFaultyDescriptorThrowsException() throws UnsupportedEncodingException, IOException {
+        Gson gson = new GsonBuilder()
+                .registerTypeAdapter(WebPreparedStatement.class, new WebPreparedStatementSerializer())
+                .create();
 
+        // missing quotes for LHS key
+        String strDesc = "QUERY test WHERE a = ?s";
+        StatementDescriptor<TestObj> desc = new StatementDescriptor<>(category, strDesc);
+        
+        WebPreparedStatementResponse fakeResponse = new WebPreparedStatementResponse();
+        fakeResponse.setStatementId(WebPreparedStatementResponse.DESCRIPTOR_PARSE_FAILED);
+        prepareServer(gson.toJson(fakeResponse));
+        try {
+            storage.prepareStatement(desc);
+            fail("Should have refused to prepare the statement");
+        } catch (IllegalDescriptorException e) {
+            // should have thrown superclass DescriptorParsingException
+            fail(e.getMessage());
+        } catch (DescriptorParsingException e) {
+            // pass
+        }
+    }
+    
     @Test
-    public void testFindAllPojos() throws UnsupportedEncodingException, IOException {
+    public void preparingUnknownDescriptorThrowsException() throws UnsupportedEncodingException, IOException {
+        Gson gson = new GsonBuilder()
+                .registerTypeAdapter(WebPreparedStatement.class, new WebPreparedStatementSerializer())
+                .create();
 
+        String strDesc = "QUERY test WHERE 'property1' = ?s";
+        StatementDescriptor<TestObj> desc = new StatementDescriptor<>(category, strDesc);
+        
+        WebPreparedStatementResponse fakeResponse = new WebPreparedStatementResponse();
+        fakeResponse.setStatementId(WebPreparedStatementResponse.ILLEGAL_STATEMENT);
+        prepareServer(gson.toJson(fakeResponse));
+        try {
+            storage.prepareStatement(desc);
+            fail("Should have refused to prepare the statement");
+        } catch (IllegalDescriptorException e) {
+            // pass
+            assertEquals(strDesc, e.getFailedDescriptor());
+        } catch (DescriptorParsingException e) {
+            // should have thrown IllegalDescriptorException
+            fail(e.getMessage());
+        }
+    }
+    
+    @Test
+    public void canPrepareAndExecuteQuery() throws UnsupportedEncodingException, IOException {
         TestObj obj1 = new TestObj();
         obj1.setProperty1("fluffor1");
         TestObj obj2 = new TestObj();
         obj2.setProperty1("fluffor2");
-        Gson gson = new GsonBuilder().registerTypeHierarchyAdapter(Expression.class, new ExpressionSerializer())
-                .registerTypeHierarchyAdapter(Operator.class, new OperatorSerializer()).create();
-
-        Key<String> key1 = new Key<>("property1", true);
-        Query<TestObj> query = storage.createQuery(category);
-        ExpressionFactory factory = new ExpressionFactory();
-        Expression expr = factory.equalTo(key1, "fluff");
-        query.where(expr);
-
-        prepareServer(gson.toJson(Arrays.asList(obj1, obj2)));
-        Cursor<TestObj> results = query.execute();
+        Gson gson = new GsonBuilder().registerTypeHierarchyAdapter(PreparedParameter.class, new PreparedParameterSerializer())
+                .registerTypeAdapter(WebPreparedStatement.class, new WebPreparedStatementSerializer())
+                .registerTypeAdapter(WebQueryResponse.class, new WebQueryResponseSerializer<>())
+                .registerTypeAdapter(Pojo.class, new ThermostatGSONConverter())
+                .create();
 
-        StringReader reader = new StringReader(requestBody);
-        BufferedReader bufRead = new BufferedReader(reader);
-        String line = URLDecoder.decode(bufRead.readLine(), "UTF-8");
-        String[] parts = line.split("=");
-        assertEquals("query", parts[0]);
-        WebQuery<?> restQuery = gson.fromJson(parts[1], WebQuery.class);
-
-        assertEquals(42, restQuery.getCategoryId());
-        Expression restExpr = restQuery.getExpression();
-        assertEquals(expr, restExpr);
-
+        String strDesc = "QUERY test WHERE 'property1' = ?s";
+        StatementDescriptor<TestObj> desc = new StatementDescriptor<>(category, strDesc);
+        PreparedStatement<TestObj> stmt = null;
+        
+        int fakePrepStmtId = 5;
+        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());
+        
+        WebQueryResponse<TestObj> fakeQueryResponse = new WebQueryResponse<>();
+        fakeQueryResponse.setResponseCode(WebQueryResponse.SUCCESS);
+        fakeQueryResponse.setResultList(new TestObj[] { obj1, obj2 });
+        prepareServer(gson.toJson(fakeQueryResponse));
+        Cursor<TestObj> results = null;
+        try {
+            results = stmt.executeQuery();
+        } catch (StatementExecutionException e) {
+            // should execute fine
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+        assertNotNull(results);
+        assertTrue(results instanceof WebCursor);
         assertTrue(results.hasNext());
         assertEquals("fluffor1", results.next().getProperty1());
         assertTrue(results.hasNext());
@@ -307,6 +391,17 @@
     }
 
     @Test
+    public void createQueryFailsCorrectly() throws UnsupportedEncodingException, IOException {
+        try {
+            storage.createQuery(category);
+            fail("createQuery() should fail for WebStorage.");
+        } catch (IllegalStateException e) {
+            // pass
+            assertTrue(e.getMessage().contains("createQuery() not supported"));
+        }
+    }
+
+    @Test
     public void testPut() throws IOException, JsonSyntaxException, ClassNotFoundException {
 
         TestObj obj = new TestObj();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/common/src/test/java/com/redhat/thermostat/web/common/PreparedParameterSerializerTest.java	Thu Jul 18 16:56:05 2013 +0200
@@ -0,0 +1,222 @@
+/*
+ * 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 static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+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;
+
+public class PreparedParameterSerializerTest {
+
+    private Gson gson;
+    
+    @Before
+    public void setup() {
+        gson = new GsonBuilder().registerTypeAdapter(
+                PreparedParameter.class,
+                new PreparedParameterSerializer()).create();
+    }
+    
+    @Test
+    public void canDeserializeBasic() {
+        // String
+        String jsonStr = "{ \"type\": \"java.lang.String\" , \"value\": \"testing\"}";
+        PreparedParameter param = gson.fromJson(jsonStr, PreparedParameter.class);
+        assertEquals(String.class, param.getType());
+        assertEquals("testing", param.getValue());
+        // Integer
+        jsonStr = "{ \"type\": \"java.lang.Integer\" , \"value\": -1}";
+        param = gson.fromJson(jsonStr, PreparedParameter.class);
+        assertEquals(Integer.class, param.getType());
+        assertTrue(param.getValue() instanceof Integer);
+        assertEquals(-1, param.getValue());
+        // Long
+        jsonStr = "{ \"type\": \"java.lang.Long\" , \"value\": -10}";
+        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}";
+        param = gson.fromJson(jsonStr, PreparedParameter.class);
+        assertEquals(Long.class, param.getType());
+        assertTrue(param.getValue() instanceof Long);
+        assertEquals(30000000003L, param.getValue());
+        // Boolean
+        jsonStr = "{ \"type\": \"java.lang.Boolean\" , \"value\": true}";
+        param = gson.fromJson(jsonStr, PreparedParameter.class);
+        assertEquals(Boolean.class, param.getType());
+        assertTrue(param.getValue() instanceof Boolean);
+        assertEquals(true, param.getValue());
+        // String[]
+        String strArrayVal = "[ \"testing1\", \"testing2\", \"3\" ]";
+        jsonStr = "{ \"type\": \"java.lang.String[]\" , \"value\": " + strArrayVal + "}";
+        param = gson.fromJson(jsonStr, PreparedParameter.class);
+        assertEquals(String[].class, param.getType());
+        assertTrue(param.getValue() instanceof String[]);
+        String[] vals = (String[])param.getValue();
+        assertEquals(3, vals.length);
+        assertEquals("testing1", vals[0]);
+        assertEquals("testing2", vals[1]);
+        assertEquals("3", vals[2]);
+    }
+    
+    @Test
+    public void failsDeserializationWrongTypeClass() {
+        String jsonStr = "{ \"type\": \"java.io.File\" , \"value\": true}";
+        try {
+            gson.fromJson(jsonStr, PreparedParameter.class);
+            fail("should have failed to serialize");
+        } catch (Exception e) {
+            // pass
+            Throwable cause = e.getCause();
+            assertTrue(cause.getMessage().contains("Illegal type of parameter"));
+        }
+    }
+    
+    @Test
+    public void canSerializeBasic() {
+        // String
+        String expected = "{\"value\":\"testing\",\"type\":\"java.lang.String\"}";
+        PreparedParameter param = new PreparedParameter();
+        param.setType(String.class);
+        param.setValue("testing");
+        String actual = gson.toJson(param);
+        assertEquals(expected, actual);
+        // Integer
+        expected = "{\"value\":-1,\"type\":\"java.lang.Integer\"}";
+        param.setType(Integer.class);
+        param.setValue(-1);
+        actual = gson.toJson(param);
+        assertEquals(expected, actual);
+        // Long
+        expected = "{\"value\":30000000003,\"type\":\"java.lang.Long\"}";
+        param.setType(Long.class);
+        param.setValue(30000000003L);
+        actual = gson.toJson(param);
+        assertEquals(expected, actual);
+        // boolean
+        expected = "{\"value\":true,\"type\":\"java.lang.Boolean\"}";
+        param.setType(Boolean.class);
+        param.setValue(true);
+        actual = gson.toJson(param);
+        assertEquals(expected, actual);
+        // String[]
+        String strArrayVal = "[\"testing1\",\"testing2\",\"3\"]";
+        expected = "{\"value\":" + strArrayVal + ",\"type\":\"java.lang.String[]\"}";
+        param.setType(String[].class);
+        String[] array = new String[] {
+                "testing1", "testing2", "3"
+        };
+        param.setValue(array);
+        actual = gson.toJson(param);
+        assertEquals(expected, actual);
+    }
+    
+    @Test
+    public void canSerializeDeserializeInteger() {
+        PreparedParameter expected = new PreparedParameter();
+        expected.setType(Integer.class);
+        expected.setValue(3);
+        String jsonStr = gson.toJson(expected, PreparedParameter.class);
+        assertParameterEquals(expected, jsonStr);
+    }
+
+    @Test
+    public void canSerializeDeserializeLong() {
+        PreparedParameter expected = new PreparedParameter();
+        expected.setType(Long.class);
+        expected.setValue(30000000003L);
+        String jsonStr = gson.toJson(expected, PreparedParameter.class);
+        assertParameterEquals(expected, jsonStr);
+    }
+    
+    @Test
+    public void canSerializeDeserializeString() {
+        PreparedParameter expected = new PreparedParameter();
+        expected.setType(String.class);
+        expected.setValue("testing");
+        String jsonStr = gson.toJson(expected, PreparedParameter.class);
+        assertParameterEquals(expected, jsonStr);
+    }
+    
+    @Test
+    public void canSerializeDeserializeBoolean() {
+        PreparedParameter expected = new PreparedParameter();
+        expected.setType(Boolean.class);
+        expected.setValue(false);
+        String jsonStr = gson.toJson(expected, PreparedParameter.class);
+        assertParameterEquals(expected, jsonStr);
+        
+        expected = new PreparedParameter();
+        expected.setType(Boolean.class);
+        expected.setValue(true);
+        jsonStr = gson.toJson(expected, PreparedParameter.class);
+        assertParameterEquals(expected, jsonStr);
+    }
+
+    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]);
+        }
+    }
+    
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/common/src/test/java/com/redhat/thermostat/web/common/PreparedParametersSerializerTest.java	Thu Jul 18 16:56:05 2013 +0200
@@ -0,0 +1,88 @@
+/*
+ * 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.redhat.thermostat.storage.core.PreparedParameter;
+import com.redhat.thermostat.storage.core.PreparedParameters;
+
+public class PreparedParametersSerializerTest {
+
+    private Gson gson;
+    
+    @Before
+    public void setup() {
+        gson = new GsonBuilder().registerTypeAdapter(
+                PreparedParameter.class,
+                new PreparedParameterSerializer()).create();
+    }
+    
+    @Test
+    public void canSerializeDeserialize() {
+        PreparedParameters params = new PreparedParameters(5);
+        params.setBoolean(0, true);
+        params.setInt(1, 2300);
+        params.setLong(2, 2200000000L);
+        params.setString(3, "testing");
+        String[] list = new String[] {
+          "a", "b", "c"      
+        };
+        params.setStringList(4, list);
+        
+        String jsonStr = gson.toJson(params, PreparedParameters.class);
+        PreparedParameters actualParams = gson.fromJson(jsonStr, PreparedParameters.class);
+        
+        PreparedParameter[] expected = params.getParams();
+        PreparedParameter[] actual = actualParams.getParams();
+        
+        // last element is the string array, which we check manually
+        for (int i = 0; i < expected.length - 1; i++) {
+            assertEquals(expected[i].getType(), actual[i].getType());
+            assertEquals(expected[i].getValue(), actual[i].getValue());
+        }
+        String actualList[] = (String[])actual[4].getValue();
+        for (int i = 0; i < list.length; i++) {
+            assertEquals(list[i], actualList[i]);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/common/src/test/java/com/redhat/thermostat/web/common/ThermostatGSONConverterTest.java	Thu Jul 18 16:56:05 2013 +0200
@@ -0,0 +1,92 @@
+/*
+ * 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.redhat.thermostat.storage.model.AgentInformation;
+import com.redhat.thermostat.storage.model.Pojo;
+
+public class ThermostatGSONConverterTest {
+
+ private Gson gson;
+    
+    @Before
+    public void setup() {
+        gson = new GsonBuilder()
+                .registerTypeAdapter(Pojo.class, new ThermostatGSONConverter())
+                .create();
+    }
+    
+    @Test
+    public void canSerializeDeserializeBasic() {
+        // Our test pojo
+        AgentInformation agentInfo = new AgentInformation();
+        agentInfo.setAgentId("testing");
+        agentInfo.setAlive(true);
+        
+        String jsonStr = gson.toJson(agentInfo);
+        
+        AgentInformation actual = gson.fromJson(jsonStr, AgentInformation.class);
+        
+        assertEquals("testing", actual.getAgentId());
+        assertEquals(true, actual.isAlive());
+    }
+    
+    @Test
+    public void canSerializeDeserializeArray() {
+        // Our test pojo
+        AgentInformation agentInfo = new AgentInformation();
+        agentInfo.setAgentId("testing");
+        agentInfo.setAlive(true);
+        AgentInformation[] agentInfos = new AgentInformation[] {
+                agentInfo
+        };
+        
+        String jsonStr = gson.toJson(agentInfos);
+        
+        AgentInformation[] actual = gson.fromJson(jsonStr, AgentInformation[].class);
+        
+        assertEquals("testing", actual[0].getAgentId());
+        assertEquals(true, actual[0].isAlive());
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/common/src/test/java/com/redhat/thermostat/web/common/WebPreparedStatementResponseSerializerTest.java	Thu Jul 18 16:56:05 2013 +0200
@@ -0,0 +1,71 @@
+/*
+ * 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;
+
+public class WebPreparedStatementResponseSerializerTest {
+
+    private Gson gson;
+    
+    @Before
+    public void setup() {
+        gson = new GsonBuilder().create();
+    }
+    
+    @Test
+    public void testSerializationDeserializationBasic() {
+        WebPreparedStatementResponse response = new WebPreparedStatementResponse();
+        response.setNumFreeVariables(6);
+        response.setStatementId(WebPreparedStatementResponse.ILLEGAL_STATEMENT);
+        
+        String jsonStr = gson.toJson(response, WebPreparedStatementResponse.class);
+        String expectedString = "{\"numFreeVariables\":6,\"statementId\":-1}";
+        assertEquals(expectedString, jsonStr);
+        
+        WebPreparedStatementResponse actual = gson.fromJson(jsonStr, WebPreparedStatementResponse.class);
+        
+        assertEquals(6, actual.getNumFreeVariables());
+        assertEquals(WebPreparedStatementResponse.ILLEGAL_STATEMENT, actual.getStatementId());
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/common/src/test/java/com/redhat/thermostat/web/common/WebPreparedStatementSerializerTest.java	Thu Jul 18 16:56:05 2013 +0200
@@ -0,0 +1,95 @@
+/*
+ * 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 static org.junit.Assert.assertNotNull;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.redhat.thermostat.storage.core.PreparedParameter;
+import com.redhat.thermostat.storage.core.PreparedParameters;
+
+public class WebPreparedStatementSerializerTest {
+
+    private Gson gson;
+    
+    @Before
+    public void setup() {
+        gson = new GsonBuilder()
+                .registerTypeAdapter(WebPreparedStatement.class,
+                        new WebPreparedStatementSerializer())
+                .registerTypeAdapter(PreparedParameter.class,
+                        new PreparedParameterSerializer()).create();
+    }
+    
+    @Test
+    public void canSerializeAndDeserialize() {
+        PreparedParameters params = new PreparedParameters(5);
+        params.setInt(0, 2);
+        params.setString(1, "testing");
+        params.setLong(2, 222L);
+        params.setStringList(3, new String[] { "one", "two" });
+        params.setBoolean(4, true);
+        WebPreparedStatement<?> stmt = new WebPreparedStatement<>();
+        stmt.setParams(params);
+        stmt.setStatementId(WebPreparedStatementResponse.DESCRIPTOR_PARSE_FAILED);
+        String jsonString = gson.toJson(stmt, WebPreparedStatement.class);
+        WebPreparedStatement<?> newStmt = gson.fromJson(jsonString, WebPreparedStatement.class);
+        assertNotNull(newStmt);
+        PreparedParameters newParams = newStmt.getParams();
+        PreparedParameter[] parameters = newParams.getParams();
+        assertEquals(5, parameters.length);
+        assertEquals(2, parameters[0].getValue());
+        assertEquals(Integer.class, parameters[0].getType());
+        assertEquals("testing", parameters[1].getValue());
+        assertEquals(String.class, parameters[1].getType());
+        assertEquals(222L, parameters[2].getValue());
+        assertEquals(Long.class, parameters[2].getType());
+        String[] list = (String[])parameters[3].getValue();
+        assertEquals(2, list.length);
+        assertEquals("one", list[0]);
+        assertEquals("two", list[1]);
+        assertEquals(String[].class, parameters[3].getType());
+        assertEquals(true, parameters[4].getValue());
+        assertEquals(Boolean.class, parameters[4].getType());
+        assertEquals(WebPreparedStatementResponse.DESCRIPTOR_PARSE_FAILED, newStmt.getStatementId());
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/common/src/test/java/com/redhat/thermostat/web/common/WebQueryResponseSerializerTest.java	Thu Jul 18 16:56:05 2013 +0200
@@ -0,0 +1,189 @@
+/*
+ * 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.lang.reflect.Type;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.reflect.TypeToken;
+import com.redhat.thermostat.storage.model.AgentInformation;
+import com.redhat.thermostat.storage.model.HostInfo;
+import com.redhat.thermostat.storage.model.Pojo;
+
+public class WebQueryResponseSerializerTest {
+
+    private Gson gson;
+    
+    @SuppressWarnings("rawtypes")
+    @Before
+    public void setup() {
+        gson = new GsonBuilder()
+                .registerTypeAdapter(Pojo.class, new ThermostatGSONConverter())
+                .registerTypeAdapter(WebQueryResponse.class,
+                        new WebQueryResponseSerializer()).create();
+    }
+    
+    @Test
+    public void canSerializeBasic() {
+        // Our test pojo
+        AgentInformation agentInfo = new AgentInformation();
+        agentInfo.setAgentId("testing");
+        agentInfo.setAlive(false);
+        AgentInformation[] resultList = new AgentInformation[] {
+                agentInfo
+        };
+        
+        // create the query response
+        WebQueryResponse<AgentInformation> response = new WebQueryResponse<>();
+        response.setResultList(resultList);
+        response.setResponseCode(WebQueryResponse.ILLEGAL_PATCH);
+        
+        String jsonStr = gson.toJson(response);
+        String expectedJson = "{\"payload\":[{\"startTime\":0,\"stopTime\":0,\"alive\":false,\"backends\":[],\"agentId\":\"testing\"}],\"errno\":-1}";
+        assertEquals(expectedJson, jsonStr);
+    }
+    
+    @Test
+    public void canDeserializeBasic() {
+        String rawJson = "{\"payload\":[{\"startTime\":0,\"stopTime\":0,\"alive\":true,\"backends\":[],\"agentId\":\"testing\"}],\"errno\":-1}";
+        Type queryResponseType = new TypeToken<WebQueryResponse<AgentInformation>>() {}.getType();
+        WebQueryResponse<AgentInformation> actual = gson.fromJson(rawJson, queryResponseType);
+        
+        AgentInformation[] actualList = actual.getResultList();
+        
+        assertEquals(WebQueryResponse.ILLEGAL_PATCH, actual.getResponseCode());
+        assertEquals(1, actualList.length);
+        AgentInformation actualInfo = actualList[0];
+        assertEquals(true, actualInfo.isAlive());
+        assertEquals("testing", actualInfo.getAgentId());
+    }
+    
+    @Test
+    public void canSerializeAndDeserializeBasic() {
+        // Our test pojo
+        AgentInformation agentInfo = new AgentInformation();
+        agentInfo.setAgentId("testing");
+        agentInfo.setAlive(false);
+        AgentInformation[] resultList = new AgentInformation[] {
+                agentInfo
+        };
+        
+        // create the query response
+        WebQueryResponse<AgentInformation> response = new WebQueryResponse<>();
+        response.setResultList(resultList);
+        response.setResponseCode(WebQueryResponse.ILLEGAL_PATCH);
+        
+        String jsonStr = gson.toJson(response);
+
+        // We need to tell GSON which parametrized type we want it to deserialize
+        // it to.
+        Type queryResponseType = new TypeToken<WebQueryResponse<AgentInformation>>() {}.getType();
+        WebQueryResponse<AgentInformation> actual = gson.fromJson(jsonStr, queryResponseType);
+        
+        AgentInformation[] actualList = actual.getResultList();
+        
+        assertEquals(WebQueryResponse.ILLEGAL_PATCH, actual.getResponseCode());
+        assertEquals(1, actualList.length);
+        AgentInformation actualInfo = actualList[0];
+        assertEquals(false, actualInfo.isAlive());
+        assertEquals("testing", actualInfo.getAgentId());
+    }
+    
+    @Test
+    public void canSerializeAndDeserializeVariousPojos() {
+        // Our test pojo
+        AgentInformation agentInfo = new AgentInformation();
+        agentInfo.setAgentId("testing");
+        agentInfo.setAlive(false);
+        AgentInformation[] resultList = new AgentInformation[] {
+                agentInfo
+        };
+        
+        // create the query response
+        WebQueryResponse<AgentInformation> response = new WebQueryResponse<>();
+        response.setResultList(resultList);
+        response.setResponseCode(WebQueryResponse.ILLEGAL_PATCH);
+        
+        String jsonStr = gson.toJson(response);
+        String expectedJson = "{\"payload\":[{\"startTime\":0,\"stopTime\":0,\"alive\":false,\"backends\":[],\"agentId\":\"testing\"}],\"errno\":-1}";
+        assertEquals(expectedJson, jsonStr);
+
+        // We need to tell GSON which parametrized type we want it to deserialize
+        // it to.
+        Type queryResponseType = new TypeToken<WebQueryResponse<AgentInformation>>() {}.getType();
+        WebQueryResponse<AgentInformation> actual = gson.fromJson(jsonStr, queryResponseType);
+        
+        AgentInformation[] actualList = actual.getResultList();
+        
+        assertEquals(WebQueryResponse.ILLEGAL_PATCH, actual.getResponseCode());
+        assertEquals(1, actualList.length);
+        AgentInformation actualInfo = actualList[0];
+        assertEquals(false, actualInfo.isAlive());
+        assertEquals("testing", actualInfo.getAgentId());
+        
+        // Do it again using HostInfo as model
+        HostInfo hostInfo = new HostInfo();
+        hostInfo.setAgentId("something");
+        hostInfo.setCpuCount(56);
+        hostInfo.setHostname("flukebox");
+        
+        HostInfo[] hostInfoResults = new HostInfo[] {
+                hostInfo
+        };
+        
+        WebQueryResponse<HostInfo> expected = new WebQueryResponse<>();
+        expected.setResultList(hostInfoResults);
+        expected.setResponseCode(WebQueryResponse.SUCCESS);
+        
+        jsonStr = gson.toJson(expected);
+        Type hostinfoQueryResponseType = new TypeToken<WebQueryResponse<HostInfo>>() {}.getType();
+        WebQueryResponse<HostInfo> actualResp = gson.fromJson(jsonStr, hostinfoQueryResponseType);
+        
+        assertEquals(WebQueryResponse.SUCCESS, actualResp.getResponseCode());
+        HostInfo[] hostInfoList = actualResp.getResultList();
+        assertEquals(1, hostInfoList.length);
+        assertEquals("something", hostInfoList[0].getAgentId());
+        assertEquals(56, hostInfoList[0].getCpuCount());
+        assertEquals("flukebox", hostInfoList[0].getHostname());
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/common/src/test/java/com/redhat/thermostat/web/common/WebQueryResponseTest.java	Thu Jul 18 16:56:05 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import java.lang.reflect.ParameterizedType;
+
+import org.junit.Test;
+
+import com.redhat.thermostat.storage.model.Pojo;
+
+public class WebQueryResponseTest {
+
+    /**
+     * Having this test is important since we need the type info for deserializing
+     * {@link WebQueryResponse}s.
+     */
+    @Test
+    public void canGetCorrectParameterizedType() {
+        WebQueryResponse<TestPojo> response = new WebQueryResponse<>();
+        ParameterizedType type = response
+                .getRuntimeParametrizedType(TestPojo.class);
+        assertNull(type.getOwnerType());
+        assertEquals(1, type.getActualTypeArguments().length);
+        assertEquals(TestPojo.class, type.getActualTypeArguments()[0]);
+        assertEquals(WebQueryResponse.class, type.getRawType());
+    }
+
+    private static class TestPojo implements Pojo {
+        // nothing
+    }
+}
--- a/web/common/src/test/java/com/redhat/thermostat/web/common/WebQueryTest.java	Thu Jul 18 16:52:29 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,77 +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 static org.junit.Assert.assertNotNull;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import org.junit.Test;
-
-import com.redhat.thermostat.storage.core.Category;
-import com.redhat.thermostat.storage.core.Key;
-import com.redhat.thermostat.storage.model.Pojo;
-import com.redhat.thermostat.storage.query.Expression;
-import com.redhat.thermostat.storage.query.ExpressionFactory;
-
-public class WebQueryTest {
-
-    private static class TestObj implements Pojo {
-        
-    }
-
-    @Test
-    public void test() {
-        Key<String> key1 = new Key<>("testkey", true);
-        Category<TestObj> category = new Category<>("test", TestObj.class, key1);
-        Map<Category,Integer> categoryIdMap = new HashMap<>();
-        categoryIdMap.put(category, 42);
-        WebQuery query = new WebQuery(42);
-        ExpressionFactory factory = new ExpressionFactory();
-        Expression expr = factory.equalTo(key1, "fluff");
-        query.where(expr);
-
-        Expression retExpr = query.getExpression();
-        assertNotNull(retExpr);
-        assertEquals(expr, retExpr);
-
-        assertEquals(42, query.getCategoryId());
-    }
-}
-
--- a/web/server/src/test/java/com/redhat/thermostat/web/server/WebStorageEndpointTest.java	Thu Jul 18 16:52:29 2013 +0200
+++ b/web/server/src/test/java/com/redhat/thermostat/web/server/WebStorageEndpointTest.java	Thu Jul 18 16:56:05 2013 +0200
@@ -56,6 +56,7 @@
 import java.io.OutputStreamWriter;
 import java.io.Reader;
 import java.lang.reflect.Method;
+import java.lang.reflect.Type;
 import java.net.HttpURLConnection;
 import java.net.MalformedURLException;
 import java.net.ProtocolException;
@@ -85,20 +86,25 @@
 
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
+import com.google.gson.reflect.TypeToken;
 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.Cursor;
 import com.redhat.thermostat.storage.core.Entity;
 import com.redhat.thermostat.storage.core.Key;
+import com.redhat.thermostat.storage.core.ParsedStatement;
 import com.redhat.thermostat.storage.core.Persist;
+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.Query.SortDirection;
 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.model.BasePojo;
+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;
@@ -106,9 +112,15 @@
 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.StorageWrapper;
+import com.redhat.thermostat.web.common.ThermostatGSONConverter;
 import com.redhat.thermostat.web.common.WebInsert;
-import com.redhat.thermostat.web.common.WebQuery;
+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.WebUpdate;
 import com.redhat.thermostat.web.server.auth.Roles;
@@ -217,7 +229,7 @@
         // manually maintained list of path handlers which should include
         // authorization checks
         final String[] authPaths = new String[] {
-                "find-all", "put-pojo", "register-category", "remove-pojo",
+                "prepare-statement", "query-execute", "put-pojo", "register-category", "remove-pojo",
                 "update-pojo", "get-count", "save-file", "load-file",
                 "purge", "ping", "generate-token", "verify-token"
         };
@@ -263,9 +275,10 @@
 
     @SuppressWarnings({ "unchecked", "rawtypes" })
     @Test
-    public void authorizedFindAllPojos() throws Exception {
+    public void authorizedPrepareQuery() throws Exception {
         String[] roleNames = new String[] {
                 Roles.REGISTER_CATEGORY,
+                Roles.PREPARE_STATEMENT,
                 Roles.READ
         };
         String testuser = "testuser";
@@ -286,60 +299,96 @@
         TestClass expected2 = new TestClass();
         expected2.setKey1("fluff2");
         expected2.setKey2(43);
+        // prepare-statement does this under the hood
+        Query<TestClass> mockMongoQuery = mock(Query.class);
+        when(mockStorage.createQuery(eq(category))).thenReturn(mockMongoQuery);
+
         Cursor<TestClass> cursor = mock(Cursor.class);
         when(cursor.hasNext()).thenReturn(true).thenReturn(true).thenReturn(false);
         when(cursor.next()).thenReturn(expected1).thenReturn(expected2);
+        
+        PreparedStatement mockPreparedQuery = mock(PreparedStatement.class);
+        when(mockStorage.prepareStatement(any(StatementDescriptor.class))).thenReturn(mockPreparedQuery);
+        
+        ParsedStatement mockParsedStatement = mock(ParsedStatement.class);
+        when(mockParsedStatement.getNumParams()).thenReturn(1);
+        when(mockParsedStatement.patchStatement(any(PreparedParameter[].class))).thenReturn(mockMongoQuery);
+        when(mockPreparedQuery.getParsedStatement()).thenReturn(mockParsedStatement);
+        
+        // The web layer
+        when(mockPreparedQuery.executeQuery()).thenReturn(cursor);
+        // And the mongo layer
+        when(mockMongoQuery.execute()).thenReturn(cursor);
 
-        Query mockQuery = mock(Query.class);
-        when(mockStorage.createQuery(any(Category.class))).thenReturn(mockQuery);
-        when(mockQuery.execute()).thenReturn(cursor);
-
+        String strDescriptor = "QUERY " + category.getName() + " WHERE '" + key1.getName() + "' = ?s SORT '" + key1.getName() + "' DSC LIMIT 42";
         String endpoint = getEndpoint();
-        URL url = new URL(endpoint + "/find-all");
+        URL url = new URL(endpoint + "/prepare-statement");
         HttpURLConnection conn = (HttpURLConnection) url.openConnection();
         conn.setRequestMethod("POST");
         sendAuthentication(conn, testuser, password);
         conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
         conn.setDoInput(true);
         conn.setDoOutput(true);
-        Map<Category,Integer> categoryIdMap = new HashMap<>();
-        categoryIdMap.put(category, categoryId);
-        WebQuery query = new WebQuery(categoryId);
-        Expression expr = factory.equalTo(key1, "fluff");
-        query.where(expr);
-        query.sort(key1, SortDirection.DESCENDING);
-        query.limit(42);
         Gson gson = new GsonBuilder()
-                .registerTypeHierarchyAdapter(Expression.class,
-                        new ExpressionSerializer())
-                .registerTypeHierarchyAdapter(Operator.class,
-                        new OperatorSerializer()).create();
+                .registerTypeHierarchyAdapter(WebQueryResponse.class, new WebQueryResponseSerializer<>())
+                .registerTypeAdapter(Pojo.class, new ThermostatGSONConverter())
+                .registerTypeAdapter(WebPreparedStatement.class, new WebPreparedStatementSerializer())
+                .registerTypeAdapter(PreparedParameter.class, new PreparedParameterSerializer()).create();
         OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream());
-        String body = "query=" + URLEncoder.encode(gson.toJson(query), "UTF-8");
+        String body = "query-descriptor=" + URLEncoder.encode(strDescriptor, "UTF-8") + "&category-id=" + categoryId;
         out.write(body + "\n");
         out.flush();
 
         Reader in = new InputStreamReader(conn.getInputStream());
-        TestClass[] results = gson.fromJson(in, TestClass[].class);
+        WebPreparedStatementResponse response = gson.fromJson(in, WebPreparedStatementResponse.class);
+        assertEquals(1, response.getNumFreeVariables());
+        assertEquals(0, response.getStatementId());
+        assertEquals("application/json; charset=UTF-8", conn.getContentType());
+        
+        
+        
+        // now execute the query we've just prepared
+        WebPreparedStatement<TestClass> stmt = new WebPreparedStatement<>(1, 0);
+        stmt.setString(0, "fluff");
+        
+        url = new URL(endpoint + "/query-execute");
+        HttpURLConnection conn2 = (HttpURLConnection) url.openConnection();
+        conn2.setRequestMethod("POST");
+        sendAuthentication(conn2, testuser, password);
+        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());
+        Type typeToken = new TypeToken<WebQueryResponse<TestClass>>(){}.getType();
+        WebQueryResponse<TestClass> result = gson.fromJson(in, typeToken);
+        TestClass[] results = result.getResultList();
         assertEquals(2, results.length);
         assertEquals("fluff1", results[0].getKey1());
         assertEquals(42, results[0].getKey2());
         assertEquals("fluff2", results[1].getKey1());
         assertEquals(43, results[1].getKey2());
 
-        assertEquals("application/json; charset=UTF-8", conn.getContentType());
-
-        verify(mockQuery).where(eq(expr));
-        verify(mockQuery).sort(key1, SortDirection.DESCENDING);
-        verify(mockQuery).limit(42);
-        verify(mockQuery).execute();
-        verifyNoMoreInteractions(mockQuery);
+        assertEquals("application/json; charset=UTF-8", conn2.getContentType());
+        verify(mockMongoQuery).execute();
+        verifyNoMoreInteractions(mockMongoQuery);
     }
     
     @Test
-    public void unauthorizedFindAllPojos() throws Exception {
+    public void unauthorizedPrepareStmt() throws Exception {
+        String failMsg = "thermostat-prepare-statement role missing, expected Forbidden!";
+        doUnauthorizedTest("prepare-statement", failMsg);
+    }
+    
+    @Test
+    public void unauthorizedExecutePreparedQuery() throws Exception {
         String failMsg = "thermostat-read role missing, expected Forbidden!";
-        doUnauthorizedTest("find-all", failMsg);
+        doUnauthorizedTest("query-execute", failMsg);
     }
     
     private void doUnauthorizedTest(String pathForEndPoint, String failMessage) throws Exception {