changeset 1179:03ed49a50413

Implement PreparedStatement in WebStorage (Part 1). Reviewed-by: ebaron Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2013-July/007565.html
author Severin Gehwolf <sgehwolf@redhat.com>
date Thu, 18 Jul 2013 16:52:29 +0200
parents 28e5e0f88e71
children 7690396a1f72
files storage/core/src/main/java/com/redhat/thermostat/storage/core/IllegalDescriptorException.java storage/core/src/main/java/com/redhat/thermostat/storage/core/IllegalPatchException.java storage/core/src/main/java/com/redhat/thermostat/storage/core/ParsedStatement.java storage/core/src/main/java/com/redhat/thermostat/storage/core/PreparedParameter.java storage/core/src/main/java/com/redhat/thermostat/storage/core/PreparedParameters.java storage/core/src/main/java/com/redhat/thermostat/storage/core/PreparedStatement.java storage/core/src/main/java/com/redhat/thermostat/storage/core/PreparedStatementSetter.java storage/core/src/main/java/com/redhat/thermostat/storage/core/Query.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/dao/AgentInfoDAOImpl.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/BinaryExpressionNode.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/IllegalPatchException.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/LimitExpression.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/Node.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/NotBooleanExpressionNode.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/ParsedStatement.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/ParsedStatementImpl.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/Patchable.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/PreparedParameter.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/PreparedStatementImpl.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/SortExpression.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/SortMember.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/StatementDescriptorParser.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/TerminalNode.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/WhereExpression.java storage/mongo/src/main/java/com/redhat/thermostat/storage/mongodb/internal/MongoQuery.java web/client/src/main/java/com/redhat/thermostat/web/client/internal/WebStorage.java web/common/src/main/java/com/redhat/thermostat/web/common/PreparedParameterSerializer.java web/common/src/main/java/com/redhat/thermostat/web/common/ThermostatGSONConverter.java web/common/src/main/java/com/redhat/thermostat/web/common/WebPreparedStatement.java web/common/src/main/java/com/redhat/thermostat/web/common/WebPreparedStatementResponse.java web/common/src/main/java/com/redhat/thermostat/web/common/WebPreparedStatementSerializer.java web/common/src/main/java/com/redhat/thermostat/web/common/WebQuery.java web/common/src/main/java/com/redhat/thermostat/web/common/WebQueryResponse.java web/common/src/main/java/com/redhat/thermostat/web/common/WebQueryResponseSerializer.java web/server/src/main/java/com/redhat/thermostat/web/server/PreparedStatementHolder.java web/server/src/main/java/com/redhat/thermostat/web/server/WebStorageEndPoint.java web/server/src/main/java/com/redhat/thermostat/web/server/auth/Roles.java
diffstat 37 files changed, 1593 insertions(+), 441 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/IllegalDescriptorException.java	Thu Jul 18 16:52:29 2013 +0200
@@ -0,0 +1,56 @@
+/*
+ * 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.core;
+
+/**
+ * Thrown if a prepared statement descriptor was refused by the storage endpoint.
+ */
+@SuppressWarnings("serial")
+public class IllegalDescriptorException extends DescriptorParsingException {
+
+    private final String failedDescriptor;
+    
+    public IllegalDescriptorException(String errorMsg, String strDescriptor) {
+        super(errorMsg);
+        this.failedDescriptor = strDescriptor;
+    }
+
+    public String getFailedDescriptor() {
+        return failedDescriptor;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/IllegalPatchException.java	Thu Jul 18 16:52:29 2013 +0200
@@ -0,0 +1,50 @@
+/*
+ * 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.core;
+
+/**
+ * Thrown if a prepared statement was attempted to patch, which resulted in
+ * NPEs or a type mismatch for some of the free variables.
+ *
+ */
+@SuppressWarnings("serial")
+public class IllegalPatchException extends Exception {
+
+    public IllegalPatchException(Throwable cause) {
+        super(cause);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/ParsedStatement.java	Thu Jul 18 16:52:29 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.storage.core;
+
+import com.redhat.thermostat.storage.model.Pojo;
+
+/**
+ * An intermediary representation of a {@link PreparedStatement}.
+ *
+ */
+public interface ParsedStatement<T extends Pojo> {
+
+    /**
+     * Patches a statement, setting free variables. After patching, the
+     * statement is ready for execution.
+     * 
+     * @param params
+     *            The ordered list of values/types of free parameters.
+     * @return The statement ready for execution.
+     * @throws IllegalPatchException
+     *             If the patching fails for some reason.
+     */
+    Statement<T> patchStatement(PreparedParameter[] params)
+            throws IllegalPatchException;
+    
+    /**
+     * 
+     * @return The number of free variables.
+     */
+    int getNumParams();
+    
+    /**
+     * 
+     * @return The raw statement.
+     */
+    Statement<T> getRawStatement();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/PreparedParameter.java	Thu Jul 18 16:52:29 2013 +0200
@@ -0,0 +1,73 @@
+/*
+ * 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.core;
+
+/**
+ * Represents a prepared parameter.
+ *
+ * @see PreparedParameters
+ */
+public class PreparedParameter {
+
+    private Object value;
+    private Class<?> type;
+    
+    PreparedParameter(Object value, Class<?> type) {
+        this.value = value;
+        this.type = type;
+    }
+    
+    public PreparedParameter() {
+        // nothing. Exists for serialization purposes.
+    }
+
+    public Object getValue() {
+        return value;
+    }
+
+    public void setValue(Object value) {
+        this.value = value;
+    }
+
+    public Class<?> getType() {
+        return type;
+    }
+
+    public void setType(Class<?> type) {
+        this.type = type;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/PreparedParameters.java	Thu Jul 18 16:52:29 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.storage.core;
+
+/**
+ * 
+ * Shared class for setting prepared parameters.
+ *
+ */
+public final class PreparedParameters implements PreparedStatementSetter {
+
+    private PreparedParameter[] params;
+    
+    public PreparedParameters(int numParams) {
+        params = new PreparedParameter[numParams];
+    }
+    
+    @Override
+    public void setLong(int paramIndex, long paramValue) {
+        setType(paramIndex, paramValue, Long.class);
+    }
+
+    @Override
+    public void setInt(int paramIndex, int paramValue) {
+        setType(paramIndex, paramValue, Integer.class);
+    }
+
+    @Override
+    public void setStringList(int paramIndex, String[] paramValue) {
+        setType(paramIndex, paramValue, String[].class);
+    }
+    
+    @Override
+    public void setBoolean(int paramIndex, boolean paramValue) {
+        setType(paramIndex, paramValue, Boolean.class);
+    }
+
+    @Override
+    public void setString(int paramIndex, String paramValue) {
+        setType(paramIndex, paramValue, String.class);
+    }
+
+    private void setType(int paramIndex, Object paramValue, Class<?> paramType) {
+        if (paramIndex >= params.length) {
+            throw new IllegalArgumentException("Parameter index '" + paramIndex + "' out of range.");
+        }
+        PreparedParameter param = new PreparedParameter(paramValue, paramType);
+        params[paramIndex] = param;
+    }
+    
+    public PreparedParameter[] getParams() {
+        return params;
+    }
+}
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/core/PreparedStatement.java	Fri Jul 19 16:37:43 2013 +0200
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/PreparedStatement.java	Thu Jul 18 16:52:29 2013 +0200
@@ -11,7 +11,7 @@
  * @see Storage#prepareStatement(StatementDescriptor)
  *
  */
-public interface PreparedStatement<T extends Pojo> {
+public interface PreparedStatement<T extends Pojo> extends PreparedStatementSetter {
     
     void setBoolean(int paramIndex, boolean paramValue);
     
@@ -44,10 +44,8 @@
     Cursor<T> executeQuery() throws StatementExecutionException;
     
     /**
-     * 
-     * @return The unique ID of this predefined statement for the underlying
-     *         {@link Storage}.
+     * @return An intermediary representation of this prepared statement.
      */
-    int getId();
-    
+    ParsedStatement<T> getParsedStatement();
+
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/PreparedStatementSetter.java	Thu Jul 18 16:52:29 2013 +0200
@@ -0,0 +1,56 @@
+/*
+ * 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.core;
+
+/**
+ * Package private interface in order to ensure consistency of
+ * setters between {@link PreparedStatement} and {@link PreparedParamenters}.
+ *
+ */
+interface PreparedStatementSetter {
+
+    void setBoolean(int paramIndex, boolean paramValue);
+    
+    void setLong(int paramIndex, long paramValue);
+    
+    void setInt(int paramIndex, int paramValue);
+    
+    void setString(int paramIndex, String paramValue);
+    
+    void setStringList(int paramIndex, String[] paramValue);
+    
+}
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/core/Query.java	Fri Jul 19 16:37:43 2013 +0200
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/Query.java	Thu Jul 18 16:52:29 2013 +0200
@@ -66,6 +66,8 @@
     void limit(int n);
 
     Cursor<T> execute();
+    
+    Expression getWhereExpression();
 
 }
 
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/dao/AgentInfoDAOImpl.java	Fri Jul 19 16:37:43 2013 +0200
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/dao/AgentInfoDAOImpl.java	Thu Jul 18 16:52:29 2013 +0200
@@ -189,6 +189,6 @@
         update.set(CONFIG_LISTEN_ADDRESS, agentInfo.getConfigListenAddress());
         update.apply();
     }
-    
+
 }
 
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/BinaryExpressionNode.java	Fri Jul 19 16:37:43 2013 +0200
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/BinaryExpressionNode.java	Thu Jul 18 16:52:29 2013 +0200
@@ -38,7 +38,9 @@
 
 import java.util.Objects;
 
+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.query.BinaryComparisonExpression;
 import com.redhat.thermostat.storage.query.BinaryComparisonOperator;
 import com.redhat.thermostat.storage.query.BinaryLogicalExpression;
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/IllegalPatchException.java	Fri Jul 19 16:37:43 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +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;
-
-/**
- * Thrown if a prepared statement was attempted to patch, which resulted in
- * NPEs or a type mismatch for some of the free variables.
- *
- */
-@SuppressWarnings("serial")
-class IllegalPatchException extends Exception {
-
-    IllegalPatchException(Throwable cause) {
-        super(cause);
-    }
-}
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/LimitExpression.java	Fri Jul 19 16:37:43 2013 +0200
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/LimitExpression.java	Thu Jul 18 16:52:29 2013 +0200
@@ -36,6 +36,9 @@
 
 package com.redhat.thermostat.storage.internal.statement;
 
+import com.redhat.thermostat.storage.core.IllegalPatchException;
+import com.redhat.thermostat.storage.core.PreparedParameter;
+
 /**
  * Represents a limit expression in the prepared statement's parse tree.
  *
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/Node.java	Fri Jul 19 16:37:43 2013 +0200
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/Node.java	Thu Jul 18 16:52:29 2013 +0200
@@ -38,6 +38,9 @@
 
 import java.util.Objects;
 
+import com.redhat.thermostat.storage.core.IllegalPatchException;
+import com.redhat.thermostat.storage.core.PreparedParameter;
+
 /**
  * A basic node in the prepared statement parse tree.
  *
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/NotBooleanExpressionNode.java	Fri Jul 19 16:37:43 2013 +0200
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/NotBooleanExpressionNode.java	Thu Jul 18 16:52:29 2013 +0200
@@ -38,6 +38,8 @@
 
 import java.util.Objects;
 
+import com.redhat.thermostat.storage.core.IllegalPatchException;
+import com.redhat.thermostat.storage.core.PreparedParameter;
 import com.redhat.thermostat.storage.query.ComparisonExpression;
 import com.redhat.thermostat.storage.query.UnaryLogicalExpression;
 import com.redhat.thermostat.storage.query.UnaryLogicalOperator;
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/ParsedStatement.java	Fri Jul 19 16:37:43 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,156 +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 com.redhat.thermostat.storage.core.Query;
-import com.redhat.thermostat.storage.core.Statement;
-import com.redhat.thermostat.storage.model.Pojo;
-import com.redhat.thermostat.storage.query.Expression;
-
-/**
- * Result object as returned by {@link StatementDescriptorParser#parse()}.
- * An instance of this plus an {@link PreparedStatementImpl} instance should
- * be sufficient to patch up a prepared statement with its real values.
- *
- * @see PreparedStatementImpl#executeQuery()
- */
-class ParsedStatement<T extends Pojo> {
-
-    private int numParams;
-    private SuffixExpression suffixExpn;
-    private final Statement<T> statement;
-    
-    ParsedStatement(Statement<T> statement) {
-        this.statement = statement;
-    }
-    
-    public int getNumParams() {
-        return numParams;
-    }
-
-    public Statement<T> getRawStatement() {
-        return statement;
-    }
-    
-    public Statement<T> patchQuery(PreparedStatementImpl<T> stmt) throws IllegalPatchException {
-        if (suffixExpn == null) {
-            String msg = "Suffix expression must be set before patching!";
-            IllegalStateException expn = new IllegalStateException(msg);
-            throw new IllegalPatchException(expn);
-        }
-        PreparedParameter[] params = stmt.getParams();
-        patchWhere(params);
-        patchSort(params);
-        patchLimit(params);
-        // TODO count actual patches and throw an exception if not all vars
-        // have been patched up.
-        return statement;
-    }
-
-    private void patchLimit(PreparedParameter[] params) throws IllegalPatchException {
-        LimitExpression expn = suffixExpn.getLimitExpn();
-        if (expn == null) {
-            // no limit expn, nothing to do
-            return;
-        }
-        PatchedLimitExpression patchedExp = expn.patch(params);
-        if (statement instanceof Query) {
-            Query<T> query = (Query<T>) statement;
-            query.limit(patchedExp.getLimitValue());
-        } else {
-            String msg = "Patching of non-query types not (yet) supported! Class was:"
-                    + statement.getClass().getName();
-            IllegalStateException invalid = new IllegalStateException(msg);
-            throw new IllegalPatchException(invalid);
-        }
-    }
-
-    private void patchSort(PreparedParameter[] params) throws IllegalPatchException {
-        SortExpression expn = suffixExpn.getSortExpn();
-        if (expn == null) {
-            // no sort expn, nothing to do
-            return;
-        }
-        PatchedSortExpression patchedExp = expn.patch(params);
-        if (statement instanceof Query) {
-            Query<T> query = (Query<T>) statement;
-            PatchedSortMember[] members = patchedExp.getSortMembers();
-            for (int i = 0; i < members.length; i++) {
-                query.sort(members[i].getSortKey(), members[i].getDirection());
-            }
-        } else {
-            String msg = "Patching of non-query types not (yet) supported! Class was:"
-                    + statement.getClass().getName();
-            IllegalStateException invalid = new IllegalStateException(msg);
-            throw new IllegalPatchException(invalid);
-        }
-    }
-
-    private void patchWhere(PreparedParameter[] params) throws IllegalPatchException {
-        WhereExpression expn = suffixExpn.getWhereExpn();
-        if (expn == null) {
-            // no where, nothing to do
-            return;
-        }
-        // walk the tree, create actual expressions and patch values along
-        // the way.
-        PatchedWhereExpression patchedExp = expn.patch(params);
-        Expression whereClause = patchedExp.getExpression();
-        if (statement instanceof Query) {
-            Query<T> query = (Query<T>) statement;
-            query.where(whereClause);
-        } else {
-            String msg = "Patching of non-query types not (yet) supported! Class was:"
-                    + statement.getClass().getName();
-            IllegalStateException invalid = new IllegalStateException(msg);
-            throw new IllegalPatchException(invalid);
-        }
-    }
-
-    public void setNumFreeParams(int num) {
-        this.numParams = num;
-    }
-
-    public void setSuffixExpression(SuffixExpression tree) {
-        this.suffixExpn = tree;
-    }
-
-    public SuffixExpression getSuffixExpression() {
-        return suffixExpn;
-    }
-
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/ParsedStatementImpl.java	Thu Jul 18 16:52:29 2013 +0200
@@ -0,0 +1,161 @@
+/*
+ * 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 com.redhat.thermostat.storage.core.IllegalPatchException;
+import com.redhat.thermostat.storage.core.ParsedStatement;
+import com.redhat.thermostat.storage.core.PreparedParameter;
+import com.redhat.thermostat.storage.core.Query;
+import com.redhat.thermostat.storage.core.Statement;
+import com.redhat.thermostat.storage.model.Pojo;
+import com.redhat.thermostat.storage.query.Expression;
+
+/**
+ * Result object as returned by {@link StatementDescriptorParser#parse()}.
+ * An instance of this plus a list of {@link PreparedParameter} should
+ * be sufficient to patch up a prepared statement with its real values.
+ *
+ * @see PreparedStatementImpl#executeQuery()
+ */
+class ParsedStatementImpl<T extends Pojo> implements ParsedStatement<T> {
+
+    private int numParams;
+    private SuffixExpression suffixExpn;
+    private final Statement<T> statement;
+
+    ParsedStatementImpl(Statement<T> statement) {
+        this.statement = statement;
+    }
+    
+    @Override
+    public int getNumParams() {
+        return numParams;
+    }
+
+    @Override
+    public Statement<T> getRawStatement() {
+        return statement;
+    }
+    
+    @Override
+    public Statement<T> patchStatement(PreparedParameter[] params) throws IllegalPatchException {
+        if (suffixExpn == null) {
+            String msg = "Suffix expression must be set before patching!";
+            IllegalStateException expn = new IllegalStateException(msg);
+            throw new IllegalPatchException(expn);
+        }
+        patchWhere(params);
+        patchSort(params);
+        patchLimit(params);
+        // TODO count actual patches and throw an exception if not all vars
+        // have been patched up.
+        return statement;
+    }
+
+    private void patchLimit(PreparedParameter[] params) throws IllegalPatchException {
+        LimitExpression expn = suffixExpn.getLimitExpn();
+        if (expn == null) {
+            // no limit expn, nothing to do
+            return;
+        }
+        PatchedLimitExpression patchedExp = expn.patch(params);
+        if (statement instanceof Query) {
+            Query<T> query = (Query<T>) statement;
+            query.limit(patchedExp.getLimitValue());
+        } else {
+            String msg = "Patching of non-query types not (yet) supported! Class was:"
+                    + statement.getClass().getName();
+            IllegalStateException invalid = new IllegalStateException(msg);
+            throw new IllegalPatchException(invalid);
+        }
+    }
+
+    private void patchSort(PreparedParameter[] params) throws IllegalPatchException {
+        SortExpression expn = suffixExpn.getSortExpn();
+        if (expn == null) {
+            // no sort expn, nothing to do
+            return;
+        }
+        PatchedSortExpression patchedExp = expn.patch(params);
+        if (statement instanceof Query) {
+            Query<T> query = (Query<T>) statement;
+            PatchedSortMember[] members = patchedExp.getSortMembers();
+            for (int i = 0; i < members.length; i++) {
+                query.sort(members[i].getSortKey(), members[i].getDirection());
+            }
+        } else {
+            String msg = "Patching of non-query types not (yet) supported! Class was:"
+                    + statement.getClass().getName();
+            IllegalStateException invalid = new IllegalStateException(msg);
+            throw new IllegalPatchException(invalid);
+        }
+    }
+
+    private void patchWhere(PreparedParameter[] params) throws IllegalPatchException {
+        WhereExpression expn = suffixExpn.getWhereExpn();
+        if (expn == null) {
+            // no where, nothing to do
+            return;
+        }
+        // walk the tree, create actual expressions and patch values along
+        // the way.
+        PatchedWhereExpression patchedExp = expn.patch(params);
+        Expression whereClause = patchedExp.getExpression();
+        if (statement instanceof Query) {
+            Query<T> query = (Query<T>) statement;
+            query.where(whereClause);
+        } else {
+            String msg = "Patching of non-query types not (yet) supported! Class was:"
+                    + statement.getClass().getName();
+            IllegalStateException invalid = new IllegalStateException(msg);
+            throw new IllegalPatchException(invalid);
+        }
+    }
+
+    void setNumFreeParams(int num) {
+        this.numParams = num;
+    }
+
+    void setSuffixExpression(SuffixExpression tree) {
+        this.suffixExpn = tree;
+    }
+
+    SuffixExpression getSuffixExpression() {
+        return suffixExpn;
+    }
+
+}
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/Patchable.java	Fri Jul 19 16:37:43 2013 +0200
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/Patchable.java	Thu Jul 18 16:52:29 2013 +0200
@@ -36,6 +36,9 @@
 
 package com.redhat.thermostat.storage.internal.statement;
 
+import com.redhat.thermostat.storage.core.IllegalPatchException;
+import com.redhat.thermostat.storage.core.PreparedParameter;
+
 /**
  * Interface for patchable objects in a ParsedStatement.
  *
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/PreparedParameter.java	Fri Jul 19 16:37:43 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +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;
-
-/**
- * Represents a prepared parameter.
- *
- * @see PreparedStatementImpl
- */
-class PreparedParameter {
-
-    private final Object value;
-    private final Class<?> type;
-    
-    PreparedParameter(Object value, Class<?> type) {
-        this.value = value;
-        this.type = type;
-    }
-
-    public Object getValue() {
-        return value;
-    }
-
-    public Class<?> getType() {
-        return type;
-    }
-}
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/PreparedStatementImpl.java	Fri Jul 19 16:37:43 2013 +0200
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/PreparedStatementImpl.java	Thu Jul 18 16:52:29 2013 +0200
@@ -39,6 +39,10 @@
 import com.redhat.thermostat.storage.core.Cursor;
 import com.redhat.thermostat.storage.core.DataModifyingStatement;
 import com.redhat.thermostat.storage.core.DescriptorParsingException;
+import com.redhat.thermostat.storage.core.IllegalPatchException;
+import com.redhat.thermostat.storage.core.ParsedStatement;
+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.Query;
 import com.redhat.thermostat.storage.core.Statement;
@@ -56,15 +60,15 @@
     private StatementDescriptor<T> desc;
     private Query<T> query;
     private DataModifyingStatement<T> dmlStatement;
-    private final PreparedParameter[] params;
-    private final ParsedStatement<T> parsedStatement;
+    private final PreparedParameters params;
+    private final ParsedStatementImpl<T> parsedStatement;
     
     public PreparedStatementImpl(Storage storage, StatementDescriptor<T> desc) throws DescriptorParsingException {
         this.desc = desc;
         StatementDescriptorParser<T> parser = new StatementDescriptorParser<>(storage, desc);
-        this.parsedStatement = parser.parse();
+        this.parsedStatement = (ParsedStatementImpl<T>)parser.parse();
         int numParams = parsedStatement.getNumParams();
-        params = new PreparedParameter[numParams];
+        params = new PreparedParameters(numParams);
         Statement<T> statement = parsedStatement.getRawStatement();
         if (statement instanceof DataModifyingStatement) {
             this.dmlStatement = (DataModifyingStatement<T>) statement;
@@ -75,36 +79,28 @@
     
     // used for testing ParsedStatements
     PreparedStatementImpl(int numParams) {
-        params = new PreparedParameter[numParams];
+        params = new PreparedParameters(numParams);
         this.parsedStatement = null;
     }
     
     @Override
     public void setLong(int paramIndex, long paramValue) {
-        setType(paramIndex, paramValue, Long.class);
+        params.setLong(paramIndex, paramValue);
     }
 
     @Override
     public void setInt(int paramIndex, int paramValue) {
-        setType(paramIndex, paramValue, Integer.class);
+        params.setInt(paramIndex, paramValue);
     }
 
     @Override
     public void setStringList(int paramIndex, String[] paramValue) {
-        setType(paramIndex, paramValue, String[].class);
+        params.setStringList(paramIndex, paramValue);
     }
     
     @Override
     public void setBoolean(int paramIndex, boolean paramValue) {
-        setType(paramIndex, paramValue, boolean.class);
-    }
-
-    private void setType(int paramIndex, Object paramValue, Class<?> paramType) {
-        if (paramIndex >= params.length) {
-            throw new IllegalArgumentException("Parameter index '" + paramIndex + "' out of range.");
-        }
-        PreparedParameter param = new PreparedParameter(paramValue, paramType);
-        params[paramIndex] = param;
+        params.setBoolean(paramIndex, paramValue);
     }
 
     @Override
@@ -115,7 +111,7 @@
                             + DataModifyingStatement.class.getName());
         }
         try {
-            dmlStatement = (DataModifyingStatement<T>) parsedStatement.patchQuery(this);
+            dmlStatement = (DataModifyingStatement<T>)parsedStatement.patchStatement(params.getParams());
         } catch (Exception e) {
             throw new StatementExecutionException(e);
         }
@@ -133,7 +129,7 @@
             // FIXME: I'm sure we can improve on this. We should avoid walking the
             // tree each time. Some cache with unfinished nodes and a reference
             // to the matching expression should be sufficient.
-            query = (Query<T>) parsedStatement.patchQuery(this);
+            query = (Query<T>)parsedStatement.patchStatement(params.getParams());
         } catch (IllegalPatchException e) {
             throw new StatementExecutionException(e);
         }
@@ -141,18 +137,18 @@
     }
 
     @Override
-    public int getId() {
-        // not implemented for Mongo
-        return -1;
+    public void setString(int paramIndex, String paramValue) {
+        params.setString(paramIndex, paramValue);
+    }
+    
+    // For testing only
+    PreparedParameter[] getParams() {
+        return params.getParams();
     }
 
     @Override
-    public void setString(int paramIndex, String paramValue) {
-        setType(paramIndex, paramValue, String.class);
-    }
-
-    PreparedParameter[] getParams() {
-        return params;
+    public ParsedStatement<T> getParsedStatement() {
+        return parsedStatement;
     }
 
     @Override
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/SortExpression.java	Fri Jul 19 16:37:43 2013 +0200
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/SortExpression.java	Thu Jul 18 16:52:29 2013 +0200
@@ -39,6 +39,9 @@
 import java.util.ArrayList;
 import java.util.List;
 
+import com.redhat.thermostat.storage.core.IllegalPatchException;
+import com.redhat.thermostat.storage.core.PreparedParameter;
+
 /**
  * Represents a sort expression of a prepared statement's parse tree.
  *
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/SortMember.java	Fri Jul 19 16:37:43 2013 +0200
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/SortMember.java	Thu Jul 18 16:52:29 2013 +0200
@@ -36,7 +36,9 @@
 
 package com.redhat.thermostat.storage.internal.statement;
 
+import com.redhat.thermostat.storage.core.IllegalPatchException;
 import com.redhat.thermostat.storage.core.Key;
+import com.redhat.thermostat.storage.core.PreparedParameter;
 import com.redhat.thermostat.storage.core.Query.SortDirection;
 
 /**
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/StatementDescriptorParser.java	Fri Jul 19 16:37:43 2013 +0200
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/StatementDescriptorParser.java	Thu Jul 18 16:52:29 2013 +0200
@@ -43,6 +43,7 @@
 import com.redhat.thermostat.storage.core.Category;
 import com.redhat.thermostat.storage.core.DescriptorParsingException;
 import com.redhat.thermostat.storage.core.Key;
+import com.redhat.thermostat.storage.core.ParsedStatement;
 import com.redhat.thermostat.storage.core.Query;
 import com.redhat.thermostat.storage.core.Query.SortDirection;
 import com.redhat.thermostat.storage.core.StatementDescriptor;
@@ -120,7 +121,7 @@
     private int currTokenIndex;
     private int placeHolderCount;
     // the parsed statement
-    private ParsedStatement<T> parsedStatement;
+    private ParsedStatementImpl<T> parsedStatement;
     private SuffixExpression tree;
     
     StatementDescriptorParser(Storage storage, StatementDescriptor<T> desc) {
@@ -480,7 +481,7 @@
             break;
         }
         case 'b': {
-            type = boolean.class;
+            type = Boolean.class;
             break;
         }
         default:
@@ -571,7 +572,7 @@
         if (tokens[0].equals(KNOWN_STATEMENT_TYPES[0])) {
             // query case
             Query<T> query = storage.createQuery(desc.getCategory());
-            this.parsedStatement = new ParsedStatement<>(query);
+            this.parsedStatement = new ParsedStatementImpl<>(query);
         } else {
             throw new IllegalStateException("Don't know how to create statement type '" + tokens[0] + "'");
         }
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/TerminalNode.java	Fri Jul 19 16:37:43 2013 +0200
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/TerminalNode.java	Thu Jul 18 16:52:29 2013 +0200
@@ -38,7 +38,9 @@
 
 import java.util.Objects;
 
+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.query.LiteralExpression;
 
 /**
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/WhereExpression.java	Fri Jul 19 16:37:43 2013 +0200
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/WhereExpression.java	Thu Jul 18 16:52:29 2013 +0200
@@ -36,6 +36,9 @@
 
 package com.redhat.thermostat.storage.internal.statement;
 
+import com.redhat.thermostat.storage.core.IllegalPatchException;
+import com.redhat.thermostat.storage.core.PreparedParameter;
+
 /**
  * Data structure representing a where expression.
  *
--- a/storage/mongo/src/main/java/com/redhat/thermostat/storage/mongodb/internal/MongoQuery.java	Fri Jul 19 16:37:43 2013 +0200
+++ b/storage/mongo/src/main/java/com/redhat/thermostat/storage/mongodb/internal/MongoQuery.java	Thu Jul 18 16:52:29 2013 +0200
@@ -55,6 +55,7 @@
     private Category<T> category;
     private Class<T> resultClass;
     private MongoExpressionParser parser;
+    private Expression expression = null;
 
     MongoQuery(MongoStorage storage, Category<T> category) {
         this(storage, category, new MongoExpressionParser());
@@ -77,6 +78,7 @@
 
     @Override
     public void where(Expression expr) {
+        expression = expr;
         query = parser.parse(expr);
         hasClauses = true;
     }
@@ -114,5 +116,10 @@
         return storage.findAllPojos(this, resultClass);
     }
 
+    @Override
+    public Expression getWhereExpression() {
+        return expression;
+    }
+
 }
 
--- a/web/client/src/main/java/com/redhat/thermostat/web/client/internal/WebStorage.java	Fri Jul 19 16:37:43 2013 +0200
+++ b/web/client/src/main/java/com/redhat/thermostat/web/client/internal/WebStorage.java	Thu Jul 18 16:52:29 2013 +0200
@@ -42,8 +42,8 @@
 import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.io.Reader;
-import java.lang.reflect.Array;
 import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Type;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.security.SecureRandom;
@@ -96,13 +96,17 @@
 import com.redhat.thermostat.storage.core.Connection;
 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.IllegalPatchException;
 import com.redhat.thermostat.storage.core.Key;
+import com.redhat.thermostat.storage.core.PreparedParameter;
 import com.redhat.thermostat.storage.core.PreparedStatement;
 import com.redhat.thermostat.storage.core.Query;
 import com.redhat.thermostat.storage.core.Remove;
 import com.redhat.thermostat.storage.core.Replace;
 import com.redhat.thermostat.storage.core.SecureStorage;
 import com.redhat.thermostat.storage.core.StatementDescriptor;
+import com.redhat.thermostat.storage.core.StatementExecutionException;
 import com.redhat.thermostat.storage.core.Storage;
 import com.redhat.thermostat.storage.core.StorageException;
 import com.redhat.thermostat.storage.core.Update;
@@ -111,9 +115,14 @@
 import com.redhat.thermostat.storage.query.Operator;
 import com.redhat.thermostat.web.common.ExpressionSerializer;
 import com.redhat.thermostat.web.common.OperatorSerializer;
+import com.redhat.thermostat.web.common.PreparedParameterSerializer;
 import com.redhat.thermostat.web.common.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;
 
@@ -318,20 +327,29 @@
             updatePojo(this);
         }
     }
-
-    private class WebQueryImpl<T extends Pojo> extends WebQuery<T> {
+    
+    private class WebPreparedStatementImpl<T extends Pojo> extends WebPreparedStatement<T> {
 
-        private transient Class<T> dataClass;
-
-        WebQueryImpl(int categoryId, Class<T> dataClass) {
-            super(categoryId);
-            this.dataClass = dataClass;
+        // The type of the query result objects we'd get back upon
+        // statement execution
+        private final transient Type parametrizedTypeToken;
+        
+        public WebPreparedStatementImpl(Type parametrizedTypeToken, int numParams, int statementId) {
+            super(numParams, statementId);
+            this.parametrizedTypeToken = parametrizedTypeToken;
+        }
+        
+        @Override
+        public int execute() throws StatementExecutionException {
+            throw new IllegalStateException("Not yet implemented!");
         }
 
         @Override
-        public Cursor<T> execute() {
-            return findAllPojos(this, dataClass);
+        public Cursor<T> executeQuery()
+                throws StatementExecutionException {
+            return doExecuteQuery(this, parametrizedTypeToken);
         }
+        
     }
 
     private String endpoint;
@@ -363,7 +381,11 @@
                         new ThermostatGSONConverter())
                 .registerTypeHierarchyAdapter(Expression.class,
                         new ExpressionSerializer())
-                .registerTypeHierarchyAdapter(Operator.class, new OperatorSerializer()).create();
+                .registerTypeHierarchyAdapter(Operator.class, new OperatorSerializer())
+                .registerTypeAdapter(WebPreparedStatement.class, new WebPreparedStatementSerializer())
+                .registerTypeAdapter(WebQueryResponse.class, new WebQueryResponseSerializer<>())
+                .registerTypeAdapter(PreparedParameter.class, new PreparedParameterSerializer())
+                .create();
         httpClient = client;
         random = new SecureRandom();
         conn = new WebConnection();
@@ -492,7 +514,10 @@
 
     @Override
     public <T extends Pojo> Query<T> createQuery(Category<T> category) {
-        return new WebQueryImpl<>(categoryIds.get(category), category.getDataClass());
+        // There isn't going to be a query end-point on server side.
+        String msg = "createQuery() not supported for Web storage. " +
+                "Please use prepareStatement() instead.";
+        throw new IllegalStateException(msg);
     }
 
     @Override
@@ -507,14 +532,47 @@
         return updateImpl;
     }
 
-    @SuppressWarnings("unchecked")
-    private <T extends Pojo> Cursor<T> findAllPojos(WebQuery<T> query, Class<T> resultClass) throws StorageException {
-        NameValuePair queryParam = new BasicNameValuePair("query", gson.toJson(query));
+    /**
+     * Executes a prepared query
+     * 
+     * @param stmt
+     *            The prepared statement to execute
+     * @param parametrizedTypeToken
+     *            The parametrized type token to use for deserialization.
+     *            Example as to how this was created:
+     *            <pre>
+     *            Type parametrizedTypeToken = new
+     *            TypeToken&lt;WebQueryResponse&lt;AgentInformation&gt;&gt;().getType();
+     *            </pre>
+     * @return A cursor for the generic type.
+     * @throws StatementExecutionException
+     *             If execution of the statement failed.
+     */
+    private <T extends Pojo> Cursor<T> doExecuteQuery(WebPreparedStatement<T> stmt, Type parametrizedTypeToken) throws StatementExecutionException {
+        NameValuePair queryParam = new BasicNameValuePair("prepared-stmt", gson.toJson(stmt, WebPreparedStatement.class));
         List<NameValuePair> formparams = Arrays.asList(queryParam);
-        try (CloseableHttpEntity entity = post(endpoint + "/find-all", formparams)) {
+        WebQueryResponse<T> qResp = null;
+        try (CloseableHttpEntity entity = post(endpoint + "/query-execute", formparams)) {
             Reader reader = getContentAsReader(entity);
-            T[] result = (T[]) gson.fromJson(reader, Array.newInstance(resultClass, 0).getClass());
+            qResp = gson.fromJson(reader, parametrizedTypeToken);
+        } catch (Exception e) {
+            throw new StatementExecutionException(e);
+        }
+        if (qResp.getResponseCode() == WebQueryResponse.SUCCESS) {
+            T[] result = qResp.getResultList();
             return new WebCursor<T>(result);
+        } else if (qResp.getResponseCode() == WebQueryResponse.ILLEGAL_PATCH) {
+            String msg = "Illegal statement argument. See server logs for details.";
+            IllegalArgumentException iae = new IllegalArgumentException(msg);
+            IllegalPatchException e = new IllegalPatchException(iae);
+            throw new StatementExecutionException(e);
+        } else {
+            // We only handle success responses and illegal patches, like
+            // we do for other storages. This is just a defensive measure in
+            // order to fail early in case something unexpected comes back.
+            String msg = "Unknown response from storage endpoint!";
+            IllegalStateException ise = new IllegalStateException(msg);
+            throw new StatementExecutionException(ise);
         }
     }
 
@@ -709,8 +767,38 @@
     @Override
     public <T extends Pojo> PreparedStatement<T> prepareStatement(StatementDescriptor<T> desc)
             throws DescriptorParsingException {
-        // TODO Implement
-        return null;
+        String strDesc = desc.getQueryDescriptor();
+        int categoryId = getCategoryId(desc.getCategory());
+        NameValuePair nameParam = new BasicNameValuePair("query-descriptor",
+                strDesc);
+        NameValuePair categoryParam = new BasicNameValuePair("category-id",
+                gson.toJson(categoryId, Integer.class));
+        List<NameValuePair> formparams = Arrays
+                .asList(nameParam, categoryParam);
+        try (CloseableHttpEntity entity = post(endpoint + "/prepare-statement",
+                formparams)) {
+            Reader reader = getContentAsReader(entity);
+            WebPreparedStatementResponse result = gson.fromJson(reader, WebPreparedStatementResponse.class);
+            int numParams = result.getNumFreeVariables();
+            int statementId = result.getStatementId();
+            if (statementId == WebPreparedStatementResponse.ILLEGAL_STATEMENT) {
+                // we've got a descriptor the endpoint doesn't know about or
+                // refuses to accept for security reasons.
+                String msg = "Unknown query descriptor which endpoint of " + WebStorage.class.getName() + "refused to accept!";
+                throw new IllegalDescriptorException(msg, desc.getQueryDescriptor());
+            } else if (statementId == WebPreparedStatementResponse.DESCRIPTOR_PARSE_FAILED) {
+                String msg = "Statement descriptor failed to parse. " +
+                             "Please check server logs for details!";
+                throw new DescriptorParsingException(msg);
+            } else {
+                // We need this ugly trick in order for WebQueryResponse
+                // deserialization to work properly. I.e. GSON needs this type
+                // info hint.
+                Class<T> dataClass = desc.getCategory().getDataClass();
+                Type typeToken = new WebQueryResponse<T>().getRuntimeParametrizedType(dataClass);
+                return new WebPreparedStatementImpl<T>(typeToken, numParams, statementId);
+            }
+        }
     }
 
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/common/src/main/java/com/redhat/thermostat/web/common/PreparedParameterSerializer.java	Thu Jul 18 16:52:29 2013 +0200
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.web.common;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+import com.redhat.thermostat.storage.core.PreparedParameter;
+
+
+/**
+ * GSON type adapter for {@link PreparedParameter}.
+ *
+ */
+public class PreparedParameterSerializer implements JsonDeserializer<PreparedParameter>, JsonSerializer<PreparedParameter>{
+
+    private static final String PROP_TYPE = "type";
+    private static final String PROP_VALUE = "value";
+    // The set of valid classes for types
+    private static final Set<String> VALID_CLASSNAMES;
+    
+    static {
+        VALID_CLASSNAMES = new HashSet<>();
+        VALID_CLASSNAMES.add(String.class.getCanonicalName());
+        VALID_CLASSNAMES.add(String[].class.getCanonicalName());
+        VALID_CLASSNAMES.add(Integer.class.getCanonicalName());
+        VALID_CLASSNAMES.add(Long.class.getCanonicalName());
+        VALID_CLASSNAMES.add(Boolean.class.getCanonicalName());
+    }
+    
+    @Override
+    public JsonElement serialize(PreparedParameter param, Type type,
+            JsonSerializationContext ctxt) {
+        JsonObject result = new JsonObject();
+        JsonElement valueElem = serializeValue(param.getValue());
+        result.add(PROP_VALUE, valueElem);
+        JsonPrimitive typeElem = new JsonPrimitive(param.getType().getCanonicalName());
+        result.add(PROP_TYPE, typeElem);
+        return result;
+    }
+
+    private JsonElement serializeValue(Object value) {
+        JsonElement element;
+        if (value instanceof Integer) {
+            int val = ((Integer)value).intValue();
+            element = new JsonPrimitive(val);
+        } else if (value instanceof Long) {
+            long val = ((Long)value).longValue();
+            element = new JsonPrimitive(val);
+        } else if (value instanceof String) {
+            String val = (String)value;
+            element = new JsonPrimitive(val);
+        } else if (value instanceof String[]) {
+            String[] val = (String[])value;
+            JsonArray array = new JsonArray();
+            for (int i = 0; i < val.length; i++) {
+                array.add(new JsonPrimitive(val[i]));
+            }
+            element = array;
+        } else if (value instanceof Boolean) {
+            Boolean val = (Boolean)value;
+            element = new JsonPrimitive(val.booleanValue());
+        } else {
+            throw new IllegalStateException("Unexpected value for serialization '" + value + "'");
+        }
+        return element;
+    }
+
+    @Override
+    public PreparedParameter deserialize(JsonElement jsonElem, Type type,
+            JsonDeserializationContext ctxt) throws JsonParseException {
+        JsonElement typeElem = jsonElem.getAsJsonObject().get(PROP_TYPE);
+        String className = typeElem.getAsString();
+        // perform some sanity checking on which classes we do forName() :)
+        validateSaneClassName(className);
+        Class<?> typeVal = deserializeTypeVal(className);
+        JsonElement valueElement = jsonElem.getAsJsonObject().get(PROP_VALUE);
+        Object value = deserializeValue(ctxt, valueElement, typeVal);
+        PreparedParameter param = new PreparedParameter();
+        param.setType(typeVal);
+        param.setValue(value);
+        return param;
+    }
+
+    private Class<?> deserializeTypeVal(String className) {
+        Class<?> typeVal = null;
+        if (className.equals(String[].class.getCanonicalName())) {
+            typeVal = String[].class;
+        } else {
+            try {
+                typeVal = Class.forName(className);
+            } catch (ClassNotFoundException ignored) {
+                // we only load valid classes that way
+            };
+        }
+        return typeVal;
+    }
+
+    private Object deserializeValue(JsonDeserializationContext ctxt,
+            JsonElement valueElement, Class<?> valType) {
+        if (valueElement.isJsonPrimitive()) {
+            // By telling GSON the type, we get the rightly casted
+            // value back.
+            return ctxt.deserialize(valueElement, valType);
+        } else if (valueElement.isJsonArray()) {
+            // Only string arrays are supported
+            List<String> values = new ArrayList<>();
+            JsonArray jsonArray = (JsonArray)valueElement;
+            Iterator<JsonElement> it = jsonArray.iterator();
+            while (it.hasNext()) {
+                JsonElement elem = it.next();
+                String strElem = ctxt.deserialize(elem, String.class);
+                values.add(strElem);
+            }
+            return values.toArray(new String[0]);
+        } else {
+            throw new IllegalStateException("Illegal json for parameter value");
+        }
+    }
+
+    private void validateSaneClassName(String className) {
+        if (!VALID_CLASSNAMES.contains(className)) {
+            throw new IllegalStateException("Illegal type of parameter " + className);
+        }
+    }
+
+}
--- a/web/common/src/main/java/com/redhat/thermostat/web/common/ThermostatGSONConverter.java	Fri Jul 19 16:37:43 2013 +0200
+++ b/web/common/src/main/java/com/redhat/thermostat/web/common/ThermostatGSONConverter.java	Thu Jul 18 16:52:29 2013 +0200
@@ -81,7 +81,7 @@
 
     @Override
     public JsonElement serialize(Pojo src, Type typeOfSrc, JsonSerializationContext context) {
-        Class cls = (Class) typeOfSrc;
+        Class<?> cls = (Class<?>) typeOfSrc;
         if (! cls.isAnnotationPresent(Entity.class)) {
             System.err.println("attempt to serialize non-Entity class: " + cls.getName());
             throw new IllegalArgumentException("attempt to serialize non-Entity class: " + cls.getName());
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/common/src/main/java/com/redhat/thermostat/web/common/WebPreparedStatement.java	Thu Jul 18 16:52:29 2013 +0200
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.web.common;
+
+import com.redhat.thermostat.storage.core.Cursor;
+import com.redhat.thermostat.storage.core.ParsedStatement;
+import com.redhat.thermostat.storage.core.PreparedParameters;
+import com.redhat.thermostat.storage.core.PreparedStatement;
+import com.redhat.thermostat.storage.core.StatementExecutionException;
+import com.redhat.thermostat.storage.model.Pojo;
+
+public class WebPreparedStatement<T extends Pojo> implements
+        PreparedStatement<T> {
+    
+    private PreparedParameters params;
+    private int statementId;
+    
+    public WebPreparedStatement(int numParams, int statementId) {
+        this.params = new PreparedParameters(numParams);
+        this.statementId = statementId;
+    }
+    
+    public WebPreparedStatement() {
+        // nothing. used for serialization
+    }
+
+    public int getStatementId() {
+        return statementId;
+    }
+
+    public void setStatementId(int statementId) {
+        this.statementId = statementId;
+    }
+
+    public PreparedParameters getParams() {
+        return params;
+    }
+
+    public void setParams(PreparedParameters params) {
+        this.params = params;
+    }
+
+    @Override
+    public void setBoolean(int paramIndex, boolean paramValue) {
+        params.setBoolean(paramIndex, paramValue);
+    }
+
+    @Override
+    public void setLong(int paramIndex, long paramValue) {
+        params.setLong(paramIndex, paramValue);
+    }
+
+    @Override
+    public void setInt(int paramIndex, int paramValue) {
+        params.setInt(paramIndex, paramValue);
+    }
+
+    @Override
+    public void setString(int paramIndex, String paramValue) {
+        params.setString(paramIndex, paramValue);
+    }
+
+    @Override
+    public void setStringList(int paramIndex, String[] paramValue) {
+        params.setStringList(paramIndex, paramValue);
+    }
+
+    @Override
+    public int execute() throws StatementExecutionException {
+        // actual implementation should override this
+        throw new IllegalStateException();
+    }
+
+    @Override
+    public Cursor<T> executeQuery()
+            throws StatementExecutionException {
+        // actual implementation should override this
+        throw new IllegalStateException();
+    }
+
+    @Override
+    public ParsedStatement<T> getParsedStatement() {
+        // Should never be called on WebPreparedStatement
+        // It should use the implementation of the backing
+        // storage implementation instead.
+        throw new IllegalStateException();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/common/src/main/java/com/redhat/thermostat/web/common/WebPreparedStatementResponse.java	Thu Jul 18 16:52:29 2013 +0200
@@ -0,0 +1,73 @@
+/*
+ * 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;
+
+/**
+ * Model class as returned upon preparing statements.
+ */
+public class WebPreparedStatementResponse {
+    
+    public static final int ILLEGAL_STATEMENT = -1;
+    public static final int DESCRIPTOR_PARSE_FAILED = -2;
+    
+    public WebPreparedStatementResponse() {
+        // Should always be set using the setter before it
+        // is retrieved. Since 0 is a bad default for this,
+        // we set it to -1 in order to make this an invalid
+        // value right away.
+        this.numFreeVariables = -1;
+    }
+    
+    private int numFreeVariables;
+    private int statementId;
+    
+    public int getStatementId() {
+        return statementId;
+    }
+
+    public void setStatementId(int statementId) {
+        this.statementId = statementId;
+    }
+
+    public int getNumFreeVariables() {
+        return numFreeVariables;
+    }
+
+    public void setNumFreeVariables(int freeVars) {
+        this.numFreeVariables = freeVars;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/common/src/main/java/com/redhat/thermostat/web/common/WebPreparedStatementSerializer.java	Thu Jul 18 16:52:29 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 java.lang.reflect.Type;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+import com.redhat.thermostat.storage.core.PreparedParameters;
+
+/**
+ * GSON type adapter for {@link WebPreparedStatement}.
+ * Depends on {@link PreparedParameterSerializer} being registered as
+ * type adapter as well.
+ *
+ */
+public class WebPreparedStatementSerializer implements
+        JsonDeserializer<WebPreparedStatement<?>>,
+        JsonSerializer<WebPreparedStatement<?>> {
+    
+    private static final String PROP_PARAMS = "p";
+    private static final String PROP_STMT_ID = "sid";
+
+    @Override
+    public JsonElement serialize(WebPreparedStatement<?> stmt, Type type,
+            JsonSerializationContext ctxt) {
+        JsonObject result = new JsonObject();
+        JsonElement parameters = ctxt.serialize(stmt.getParams(), PreparedParameters.class);
+        result.add(PROP_PARAMS, parameters);
+        JsonPrimitive stmtIdElem = new JsonPrimitive(stmt.getStatementId());
+        result.add(PROP_STMT_ID, stmtIdElem);
+        return result;
+    }
+
+    @Override
+    public WebPreparedStatement<?> deserialize(JsonElement jsonElem, Type type,
+            JsonDeserializationContext ctxt) throws JsonParseException {
+        JsonElement paramsElem = jsonElem.getAsJsonObject().get(PROP_PARAMS);
+        JsonElement stmtIdElem = jsonElem.getAsJsonObject().get(PROP_STMT_ID);
+        PreparedParameters params = ctxt.deserialize(paramsElem, PreparedParameters.class);
+        int stmtId = ctxt.deserialize(stmtIdElem, int.class);
+        WebPreparedStatement<?> stmt = new WebPreparedStatement<>();
+        stmt.setStatementId(stmtId);
+        stmt.setParams(params);
+        return stmt;
+    }
+
+}
--- a/web/common/src/main/java/com/redhat/thermostat/web/common/WebQuery.java	Fri Jul 19 16:37:43 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,82 +0,0 @@
-/*
- * Copyright 2012, 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-
-package com.redhat.thermostat.web.common;
-
-import com.redhat.thermostat.storage.core.AbstractQuery;
-import com.redhat.thermostat.storage.core.Cursor;
-import com.redhat.thermostat.storage.model.Pojo;
-import com.redhat.thermostat.storage.query.Expression;
-
-public class WebQuery<T extends Pojo> extends AbstractQuery<T> {
-
-    private Expression expr;
-    private int categoryId;
-
-    public WebQuery() {
-        this(-1);
-    }
-
-    public WebQuery(int categoryId) {
-        this.categoryId = categoryId;
-    }
-
-    public int getCategoryId() {
-        return categoryId;
-    }
-
-    public void setCategoryId(int categoryId) {
-        this.categoryId = categoryId;
-    }
-
-    @Override
-    public void where(Expression expr) {
-        this.expr = expr;
-    }
-    
-    public Expression getExpression() {
-        return expr;
-    }
-
-    @Override
-    public Cursor<T> execute() {
-        // This should only ever be called when created from WebStorage, which provides its own subclass.
-        throw new IllegalStateException();
-    }
-
-}
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/common/src/main/java/com/redhat/thermostat/web/common/WebQueryResponse.java	Thu Jul 18 16:52:29 2013 +0200
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.web.common;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+
+import com.redhat.thermostat.storage.model.Pojo;
+
+/**
+ * Model object for prepared query execution responses.
+ *
+ */
+public class WebQueryResponse<T extends Pojo> {
+    
+    public static final int SUCCESS = 0;
+    public static final int ILLEGAL_PATCH = -1;
+
+    private int responseCode;
+    private T[] resultList;
+    
+    public int getResponseCode() {
+        return responseCode;
+    }
+
+    public void setResponseCode(int responseCode) {
+        this.responseCode = responseCode;
+    }
+
+    public T[] getResultList() {
+        return resultList;
+    }
+
+    public void setResultList(T[] resultList) {
+        this.resultList = resultList;
+    }
+    
+    public ParameterizedType getRuntimeParametrizedType(final Class<T> dataClass) {
+        ParameterizedType webQueryResponseType = new ParameterizedType() {
+            
+            @Override
+            public Type getRawType() {
+                return WebQueryResponse.class;
+            }
+            
+            @Override
+            public Type getOwnerType() {
+                // top-level type, must return null
+                return null;
+            }
+            
+            @Override
+            public Type[] getActualTypeArguments() {
+                // WebQueryResponse has only one type parameter, which
+                // is the actual data class
+                return new Type[] {
+                        dataClass
+                };
+            }
+        };
+        return webQueryResponseType;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/common/src/main/java/com/redhat/thermostat/web/common/WebQueryResponseSerializer.java	Thu Jul 18 16:52:29 2013 +0200
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.web.common;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+import com.redhat.thermostat.storage.model.Pojo;
+
+/**
+ * GSON type adapter for {@link QueryResponse}.
+ * Depends on {@link ThermostatGSONConverter} being registered as
+ * type adapter as well.
+ *
+ */
+public class WebQueryResponseSerializer<T extends Pojo> implements
+        JsonDeserializer<WebQueryResponse<T>>, JsonSerializer<WebQueryResponse<T>> {
+
+    private static final String PROP_RESULT = "payload";
+    private static final String PROP_ERROR_CODE = "errno";
+    
+    @Override
+    public JsonElement serialize(WebQueryResponse<T> qResponse, Type type,
+            JsonSerializationContext ctxt) {
+        JsonObject result = new JsonObject();
+        JsonElement resultsElem = ctxt.serialize(qResponse.getResultList());
+        result.add(PROP_RESULT, resultsElem);
+        JsonPrimitive errnoElem = new JsonPrimitive(qResponse.getResponseCode());
+        result.add(PROP_ERROR_CODE, errnoElem);
+        return result;
+    }
+    
+    @SuppressWarnings("unchecked")
+    @Override
+    public WebQueryResponse<T> deserialize(JsonElement jsonElem, Type type,
+            JsonDeserializationContext ctxt) throws JsonParseException {
+        // fromJson() calls need to pass in the right *parameterized* type token:
+        // example for AgentInformation as T:
+        //   Type queryResponseType = new TypeToken<WebQueryResponse<AgentInformation>>() {}.getType();
+        //   gson.fromJson(jsonStr, queryResponseType)
+        Type[] typeParameters = ((ParameterizedType)type).getActualTypeArguments();
+        Type queryResponseTypeParam = typeParameters[0]; // WebQueryResponse has only one parameterized type T
+        JsonArray resultElem = jsonElem.getAsJsonObject().get(PROP_RESULT).getAsJsonArray();
+        @SuppressWarnings("rawtypes")
+        Class typeOfGeneric = (Class)queryResponseTypeParam;
+        T[] array = (T[])Array.newInstance(typeOfGeneric, resultElem.size());
+        for (int i = 0; i < resultElem.size(); i++) {
+            array[i] = ctxt.deserialize(resultElem.get(i), queryResponseTypeParam);
+        }
+        JsonElement errorCodeElem = jsonElem.getAsJsonObject().get(PROP_ERROR_CODE);
+        int errorCode = ctxt.deserialize(errorCodeElem, int.class);
+        WebQueryResponse<T> qResponse = new WebQueryResponse<>();
+        qResponse.setResponseCode(errorCode);
+        qResponse.setResultList(array);
+        return qResponse;
+    }
+    
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/server/src/main/java/com/redhat/thermostat/web/server/PreparedStatementHolder.java	Thu Jul 18 16:52:29 2013 +0200
@@ -0,0 +1,65 @@
+/*
+ * 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.server;
+
+import com.redhat.thermostat.storage.core.PreparedStatement;
+import com.redhat.thermostat.storage.model.Pojo;
+
+class PreparedStatementHolder<T extends Pojo> {
+
+    private final int id;
+    private final PreparedStatement<T> stmt;
+    private final Class<T> dataClass;
+    
+    PreparedStatementHolder(int id, PreparedStatement<T> stmt, Class<T> dataClass) {
+        this.id = id;
+        this.stmt = stmt;
+        this.dataClass = dataClass;
+    }
+
+    int getId() {
+        return id;
+    }
+
+    PreparedStatement<T> getStmt() {
+        return stmt;
+    }
+    
+    Class<T> getDataClass() {
+        return dataClass;
+    }
+}
--- a/web/server/src/main/java/com/redhat/thermostat/web/server/WebStorageEndPoint.java	Fri Jul 19 16:37:43 2013 +0200
+++ b/web/server/src/main/java/com/redhat/thermostat/web/server/WebStorageEndPoint.java	Thu Jul 18 16:52:29 2013 +0200
@@ -41,6 +41,7 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.Writer;
+import java.lang.reflect.Array;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -68,14 +69,20 @@
 import com.redhat.thermostat.common.utils.LoggingUtils;
 import com.redhat.thermostat.shared.config.Configuration;
 import com.redhat.thermostat.shared.config.InvalidConfigurationException;
-import com.redhat.thermostat.storage.core.AbstractQuery.Sort;
 import com.redhat.thermostat.storage.core.Category;
 import com.redhat.thermostat.storage.core.Connection;
 import com.redhat.thermostat.storage.core.Cursor;
+import com.redhat.thermostat.storage.core.DescriptorParsingException;
+import com.redhat.thermostat.storage.core.IllegalPatchException;
 import com.redhat.thermostat.storage.core.Key;
+import com.redhat.thermostat.storage.core.ParsedStatement;
+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.Storage;
 import com.redhat.thermostat.storage.core.Update;
 import com.redhat.thermostat.storage.model.Pojo;
@@ -83,10 +90,15 @@
 import com.redhat.thermostat.storage.query.Operator;
 import com.redhat.thermostat.web.common.ExpressionSerializer;
 import com.redhat.thermostat.web.common.OperatorSerializer;
+import com.redhat.thermostat.web.common.PreparedParameterSerializer;
 import com.redhat.thermostat.web.common.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;
@@ -117,6 +129,12 @@
 
     private Map<String, Integer> categoryIds;
     private Map<Integer, Category<?>> categories;
+    
+    private Map<StatementDescriptor<?>, PreparedStatementHolder<?>> preparedStmts;
+    private Map<Integer, PreparedStatementHolder<?>> preparedStatementIds;
+    // Lock to be held for setting/getting prepared queries in the above maps
+    private Object preparedStmtLock = new Object();
+    private int currentPreparedStmtId;
 
     @Override
     public void init(ServletConfig config) throws ServletException {
@@ -132,9 +150,15 @@
                 .registerTypeHierarchyAdapter(Expression.class,
                         new ExpressionSerializer())
                 .registerTypeHierarchyAdapter(Operator.class,
-                        new OperatorSerializer()).create();
+                        new OperatorSerializer())
+                .registerTypeAdapter(WebQueryResponse.class, new WebQueryResponseSerializer<>())
+                .registerTypeAdapter(PreparedParameter.class, new PreparedParameterSerializer())
+                .registerTypeAdapter(WebPreparedStatement.class, new WebPreparedStatementSerializer())
+                .create();
         categoryIds = new HashMap<>();
         categories = new HashMap<>();
+        preparedStatementIds = new HashMap<>();
+        preparedStmts = new HashMap<>();
         TokenManager tokenManager = new TokenManager();
         String timeoutParam = getInitParameter(TOKEN_MANAGER_TIMEOUT_PARAM);
         if (timeoutParam != null) {
@@ -174,8 +198,11 @@
         String uri = req.getRequestURI();
         int lastPartIdx = uri.lastIndexOf("/");
         String cmd = uri.substring(lastPartIdx + 1);
-        if (cmd.equals("find-all")) {
-            findAll(req, resp);
+        if (cmd.equals("prepare-statement")) {
+            prepareStatement(req, resp);
+        }
+        else if (cmd.equals("query-execute")) {
+            queryExecute(req, resp);
         } else if (cmd.equals("put-pojo")) {
             putPojo(req, resp);
         } else if (cmd.equals("register-category")) {
@@ -243,6 +270,69 @@
         }
     }
 
+    @SuppressWarnings("unchecked")
+    @WebStoragePathHandler( path = "prepare-statement" )
+    private <T extends Pojo> void prepareStatement(HttpServletRequest req,
+            HttpServletResponse resp) throws IOException {
+        if (! isAuthorized(req, resp, Roles.PREPARE_STATEMENT)) {
+            return;
+        }
+        String queryDescrParam = req.getParameter("query-descriptor");
+        String categoryIdParam = req.getParameter("category-id");
+        Integer catId = gson.fromJson(categoryIdParam, Integer.class);
+        Category<?> cat = getCategoryFromId(catId);
+        WebPreparedStatementResponse response = new WebPreparedStatementResponse();
+        if (cat == null) {
+            // bad category? we refuse to accept this
+            logger.log(Level.WARNING, "Attepted to prepare a statement with an illegal category id");
+            response.setStatementId(WebPreparedStatementResponse.ILLEGAL_STATEMENT);
+            writeResponse(resp, response, WebPreparedStatementResponse.class);
+            return;
+        }
+        StatementDescriptor<?> desc = new StatementDescriptor<>(cat, queryDescrParam);
+        synchronized (preparedStmtLock) {
+            // see if we've prepared this query already
+            if (preparedStmts.containsKey(desc)) {
+                PreparedStatementHolder<T> holder = (PreparedStatementHolder<T>) preparedStmts
+                        .get(desc);
+                ParsedStatement<T> parsed = holder.getStmt()
+                        .getParsedStatement();
+                int freeVars = parsed.getNumParams();
+                response.setNumFreeVariables(freeVars);
+                response.setStatementId(holder.getId());
+                writeResponse(resp, response,
+                        WebPreparedStatementResponse.class);
+                return;
+            }
+            // TODO: Check if descriptor is trusted (i.e. known)
+            
+            // Prepare the target statement and put it into our prepared statement
+            // maps.
+            PreparedStatement<T> targetPreparedStatement;
+            try {
+                targetPreparedStatement = (PreparedStatement<T>) storage
+                        .prepareStatement(desc);
+            } catch (DescriptorParsingException e) {
+                logger.log(Level.WARNING, "Descriptor parse error!", e);
+                response.setStatementId(WebPreparedStatementResponse.DESCRIPTOR_PARSE_FAILED);
+                writeResponse(resp, response,
+                        WebPreparedStatementResponse.class);
+                return;
+            }
+            PreparedStatementHolder<T> holder = new PreparedStatementHolder<T>(
+                    currentPreparedStmtId, targetPreparedStatement,
+                    (Class<T>) cat.getDataClass());
+            preparedStmts.put(desc, holder);
+            preparedStatementIds.put(currentPreparedStmtId, holder);
+            ParsedStatement<?> parsed = targetPreparedStatement
+                    .getParsedStatement();
+            response.setNumFreeVariables(parsed.getNumParams());
+            response.setStatementId(currentPreparedStmtId);
+            writeResponse(resp, response, WebPreparedStatementResponse.class);
+            currentPreparedStmtId++;
+        }
+    }
+
     @WebStoragePathHandler( path = "ping" )
     private void ping(HttpServletRequest req, HttpServletResponse resp) {
         if (! isAuthorized(req, resp, Roles.LOGIN)) {
@@ -449,37 +539,54 @@
         }
     }
 
-    @WebStoragePathHandler( path = "find-all" )
-    @SuppressWarnings({ "rawtypes", "unchecked" })
-    private void findAll(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+    @SuppressWarnings("unchecked")
+    @WebStoragePathHandler( path = "query-execute" )
+    private <T extends Pojo> void queryExecute(HttpServletRequest req, HttpServletResponse resp) throws IOException {
         if (! isAuthorized(req, resp, Roles.READ)) {
             return;
         }
-        String queryParam = req.getParameter("query");
-        WebQuery query = gson.fromJson(queryParam, WebQuery.class);
-        Query targetQuery = constructTargetQuery(query);
-        ArrayList resultList = new ArrayList();
-        Cursor result = targetQuery.execute();
-        while (result.hasNext()) {
-            resultList.add(result.next());
+        String queryParam = req.getParameter("prepared-stmt");
+        WebPreparedStatement<T> stmt = gson.fromJson(queryParam, WebPreparedStatement.class);
+        
+        PreparedParameters p = stmt.getParams();
+        PreparedStatementHolder<T> targetStmtHolder = getStatementHolderFromId(stmt.getStatementId());
+        PreparedStatement<T> targetStmt = targetStmtHolder.getStmt();
+        ParsedStatement<T> parsed = targetStmt.getParsedStatement();
+        Query<T> targetQuery = null;
+        ArrayList<T> resultList = new ArrayList<>();
+        WebQueryResponse<T> response = new WebQueryResponse<>();
+        try {
+            targetQuery = (Query<T>)parsed.patchStatement(p.getParams());
+            response.setResponseCode(WebQueryResponse.SUCCESS);
+        } catch (IllegalPatchException e) {
+            response.setResponseCode(WebQueryResponse.ILLEGAL_PATCH);
+            writeResponse(resp, response, WebQueryResponse.class);
+            return;
         }
-        writeResponse(resp, resultList.toArray());
+        // TODO: Do proper query filtering
+        targetQuery = fixQuery(targetQuery, stmt.getStatementId());
+        Cursor<T> cursor = targetQuery.execute();
+        while (cursor.hasNext()) {
+            resultList.add(cursor.next());
+        }
+        T[] results = (T[])Array.newInstance(targetStmtHolder.getDataClass(), resultList.size());
+        for (int i = 0; i < resultList.size(); i++) {
+            results[i] = resultList.get(i);
+        }
+        response.setResultList(results);
+        writeResponse(resp, response, WebQueryResponse.class);
     }
 
-    private Query<?> constructTargetQuery(WebQuery<? extends Pojo> query) {
-        int categoryId = query.getCategoryId();
-        Category<?> category = getCategoryFromId(categoryId);
+    @SuppressWarnings("rawtypes")
+    private <T extends Pojo> Query fixQuery(Query<T> targetQuery, int statementId) {
+        // TODO: Change the expression so as to perform proper filtering.
+        return targetQuery;
+    }
 
-        Query<?> targetQuery = storage.createQuery(category);
-        Expression whereExpr = query.getExpression();
-        if (whereExpr != null) {
-            targetQuery.where(whereExpr);
-        }
-        for (Sort s : query.getSorts()) {
-            targetQuery.sort(s.getKey(), s.getDirection());
-        }
-        targetQuery.limit(query.getLimit());
-        return targetQuery;
+    private <T extends Pojo> PreparedStatementHolder<T> getStatementHolderFromId(int statementId) {
+        @SuppressWarnings("unchecked") // we are the only ones adding them
+        PreparedStatementHolder<T> holder = (PreparedStatementHolder<T>)preparedStatementIds.get(statementId);
+        return holder;
     }
 
     private Category<?> getCategoryFromId(int categoryId) {
@@ -487,10 +594,11 @@
         return category;
     }
 
-    private void writeResponse(HttpServletResponse resp, Object result) throws IOException {
+    private void writeResponse(HttpServletResponse resp,
+            Object responseObj, Class<?> typeOfResponseObj) throws IOException {
         resp.setStatus(HttpServletResponse.SC_OK);
         resp.setContentType(RESPONSE_JSON_CONTENT_TYPE);
-        gson.toJson(result, resp.getWriter());
+        gson.toJson(responseObj, typeOfResponseObj, resp.getWriter());
         resp.flushBuffer();
     }
 
--- a/web/server/src/main/java/com/redhat/thermostat/web/server/auth/Roles.java	Fri Jul 19 16:37:43 2013 +0200
+++ b/web/server/src/main/java/com/redhat/thermostat/web/server/auth/Roles.java	Thu Jul 18 16:52:29 2013 +0200
@@ -46,6 +46,7 @@
     final String REPLACE = "thermostat-replace";
     final String UPDATE = "thermostat-update";
     final String DELETE = "thermostat-remove";
+    final String PREPARE_STATEMENT = "thermostat-prepare-statement";
     final String READ = "thermostat-query";
     final String GET_COUNT = "thermostat-get-count";
     final String LOAD_FILE = "thermostat-load-file";
@@ -60,7 +61,7 @@
     final String[] ALL_ROLES = {
             APPEND, REPLACE, UPDATE, DELETE, READ, GET_COUNT, LOAD_FILE,
             SAVE_FILE, PURGE, REGISTER_CATEGORY, CMD_CHANNEL_GENERATE,
-            CMD_CHANNEL_VERIFY, LOGIN, ACCESS_REALM
+            CMD_CHANNEL_VERIFY, LOGIN, ACCESS_REALM, PREPARE_STATEMENT
     };
     
     final String[] AGENT_ROLES = {
@@ -71,7 +72,7 @@
     
     final String[] CLIENT_ROLES = {
             ACCESS_REALM, LOGIN, CMD_CHANNEL_GENERATE, LOAD_FILE,
-            GET_COUNT, READ, REGISTER_CATEGORY
+            GET_COUNT, READ, REGISTER_CATEGORY, PREPARE_STATEMENT
     };
     
 }