changeset 1193:e810fb4143b5

Move createQuery to new BackingStorage interface In order to enforce that clients must use prepared statements in their queries, this commit removes createQuery from the Storage interface. Since it is still necessary for prepared statement parsers to create queries, this commit makes a distinction from storages that are intermediaries (e.g. WebStorage) and backend storage implementations (e.g. MongoStorage). MongoStorage now implements a BackingStorage interface which contains the old createQuery method. Thus, it is only possible to create queries directly when interacting with a BackingStorage interface and not with the web layer. This is secure since when running the Thermostat web service, the backend storage is never accessible to the client. Once write operations are moved to using prepared statements, their create* methods can be moved to BackingStorage and removed from the web API. Reviewed-by: omajid, jerboaa Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2013-July/007633.html
author Elliott Baron <ebaron@redhat.com>
date Wed, 31 Jul 2013 15:50:35 -0400
parents e7bdfcace2b0
children b525881b4f8a
files integration-tests/src/test/java/com/redhat/thermostat/itest/MongoQueriesTest.java storage/core/src/main/java/com/redhat/thermostat/storage/core/BackingStorage.java storage/core/src/main/java/com/redhat/thermostat/storage/core/PreparedStatementFactory.java storage/core/src/main/java/com/redhat/thermostat/storage/core/QueuedBackingStorage.java storage/core/src/main/java/com/redhat/thermostat/storage/core/QueuedStorage.java storage/core/src/main/java/com/redhat/thermostat/storage/core/Storage.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/PreparedStatementImpl.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/StatementDescriptorParser.java storage/core/src/test/java/com/redhat/thermostat/storage/core/QueuedBackingStorageTest.java storage/core/src/test/java/com/redhat/thermostat/storage/core/QueuedStorageTest.java storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/PreparedStatementImplTest.java storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/StatementDescriptorParserTest.java storage/mongo/src/main/java/com/redhat/thermostat/storage/mongodb/MongoStorageProvider.java storage/mongo/src/main/java/com/redhat/thermostat/storage/mongodb/internal/MongoStorage.java storage/mongo/src/test/java/com/redhat/thermostat/storage/mongodb/internal/MongoStorageProviderTest.java web/client/src/main/java/com/redhat/thermostat/web/client/internal/WebStorage.java web/client/src/test/java/com/redhat/thermostat/web/client/internal/WebStorageProviderTest.java web/client/src/test/java/com/redhat/thermostat/web/client/internal/WebStorageTest.java web/server/src/test/java/com/redhat/thermostat/web/server/WebStorageEndpointTest.java
diffstat 19 files changed, 390 insertions(+), 103 deletions(-) [+]
line wrap: on
line diff
--- a/integration-tests/src/test/java/com/redhat/thermostat/itest/MongoQueriesTest.java	Wed Jul 31 21:07:28 2013 +0200
+++ b/integration-tests/src/test/java/com/redhat/thermostat/itest/MongoQueriesTest.java	Wed Jul 31 15:50:35 2013 -0400
@@ -57,6 +57,7 @@
 import com.redhat.thermostat.host.cpu.common.model.CpuStat;
 import com.redhat.thermostat.storage.config.StartupConfiguration;
 import com.redhat.thermostat.storage.core.Add;
+import com.redhat.thermostat.storage.core.BackingStorage;
 import com.redhat.thermostat.storage.core.Connection.ConnectionListener;
 import com.redhat.thermostat.storage.core.Connection.ConnectionStatus;
 import com.redhat.thermostat.storage.core.Cursor;
@@ -131,7 +132,7 @@
      * Make a connection to mongo storage (returning the Storage object). Before
      * initiating the connection, add the ConnectionListener to Storage.
      */
-    private static Storage getAndConnectStorage(ConnectionListener listener) {
+    private static BackingStorage getAndConnectStorage(ConnectionListener listener) {
         final String url = "mongodb://127.0.0.1:27518";
         StartupConfiguration config = new StartupConfiguration() {
 
@@ -141,7 +142,7 @@
             }
             
         };
-        Storage storage = new MongoStorage(config);
+        BackingStorage storage = new MongoStorage(config);
         if (listener != null) {
             storage.getConnection().addListener(listener);
         }
@@ -241,7 +242,7 @@
     public void canQueryNoWhere() throws Exception {
         CountDownLatch latch = new CountDownLatch(1);
         ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
-        Storage mongoStorage = getAndConnectStorage(listener);
+        BackingStorage mongoStorage = getAndConnectStorage(listener);
         latch.await();
         mongoStorage.getConnection().removeListener(listener);
         mongoStorage.registerCategory(CpuStatDAO.cpuStatCategory);
@@ -258,7 +259,7 @@
     public void canQueryEqualTo() throws Exception {
         CountDownLatch latch = new CountDownLatch(1);
         ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
-        Storage mongoStorage = getAndConnectStorage(listener);
+        BackingStorage mongoStorage = getAndConnectStorage(listener);
         latch.await();
         mongoStorage.getConnection().removeListener(listener);
         mongoStorage.registerCategory(CpuStatDAO.cpuStatCategory);
@@ -277,7 +278,7 @@
     public void canQueryNotEqualTo() throws Exception {
         CountDownLatch latch = new CountDownLatch(1);
         ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
-        Storage mongoStorage = getAndConnectStorage(listener);
+        BackingStorage mongoStorage = getAndConnectStorage(listener);
         latch.await();
         mongoStorage.getConnection().removeListener(listener);
         mongoStorage.registerCategory(CpuStatDAO.cpuStatCategory);
@@ -296,7 +297,7 @@
     public void canQueryGreaterThan() throws Exception {
         CountDownLatch latch = new CountDownLatch(1);
         ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
-        Storage mongoStorage = getAndConnectStorage(listener);
+        BackingStorage mongoStorage = getAndConnectStorage(listener);
         latch.await();
         mongoStorage.getConnection().removeListener(listener);
         mongoStorage.registerCategory(CpuStatDAO.cpuStatCategory);
@@ -315,7 +316,7 @@
     public void canQueryGreaterThanOrEqualTo() throws Exception {
         CountDownLatch latch = new CountDownLatch(1);
         ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
-        Storage mongoStorage = getAndConnectStorage(listener);
+        BackingStorage mongoStorage = getAndConnectStorage(listener);
         latch.await();
         mongoStorage.getConnection().removeListener(listener);
         mongoStorage.registerCategory(CpuStatDAO.cpuStatCategory);
@@ -334,7 +335,7 @@
     public void canQueryLessThan() throws Exception {
         CountDownLatch latch = new CountDownLatch(1);
         ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
-        Storage mongoStorage = getAndConnectStorage(listener);
+        BackingStorage mongoStorage = getAndConnectStorage(listener);
         latch.await();
         mongoStorage.getConnection().removeListener(listener);
         mongoStorage.registerCategory(CpuStatDAO.cpuStatCategory);
@@ -353,7 +354,7 @@
     public void canQueryLessThanOrEqualTo() throws Exception {
         CountDownLatch latch = new CountDownLatch(1);
         ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
-        Storage mongoStorage = getAndConnectStorage(listener);
+        BackingStorage mongoStorage = getAndConnectStorage(listener);
         latch.await();
         mongoStorage.getConnection().removeListener(listener);
         mongoStorage.registerCategory(CpuStatDAO.cpuStatCategory);
@@ -372,7 +373,7 @@
     public void canQueryIn() throws Exception {
         CountDownLatch latch = new CountDownLatch(1);
         ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
-        Storage mongoStorage = getAndConnectStorage(listener);
+        BackingStorage mongoStorage = getAndConnectStorage(listener);
         latch.await();
         mongoStorage.getConnection().removeListener(listener);
         mongoStorage.registerCategory(CpuStatDAO.cpuStatCategory);
@@ -392,7 +393,7 @@
     public void canQueryNotIn() throws Exception {
         CountDownLatch latch = new CountDownLatch(1);
         ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
-        Storage mongoStorage = getAndConnectStorage(listener);
+        BackingStorage mongoStorage = getAndConnectStorage(listener);
         latch.await();
         mongoStorage.getConnection().removeListener(listener);
         mongoStorage.registerCategory(CpuStatDAO.cpuStatCategory);
@@ -411,7 +412,7 @@
     public void canQueryNot() throws Exception {
         CountDownLatch latch = new CountDownLatch(1);
         ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
-        Storage mongoStorage = getAndConnectStorage(listener);
+        BackingStorage mongoStorage = getAndConnectStorage(listener);
         latch.await();
         mongoStorage.getConnection().removeListener(listener);
         mongoStorage.registerCategory(CpuStatDAO.cpuStatCategory);
@@ -430,7 +431,7 @@
     public void canQueryAnd() throws Exception {
         CountDownLatch latch = new CountDownLatch(1);
         ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
-        Storage mongoStorage = getAndConnectStorage(listener);
+        BackingStorage mongoStorage = getAndConnectStorage(listener);
         latch.await();
         mongoStorage.getConnection().removeListener(listener);
         mongoStorage.registerCategory(CpuStatDAO.cpuStatCategory);
@@ -450,7 +451,7 @@
     public void canQueryOr() throws Exception {
         CountDownLatch latch = new CountDownLatch(1);
         ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
-        Storage mongoStorage = getAndConnectStorage(listener);
+        BackingStorage mongoStorage = getAndConnectStorage(listener);
         latch.await();
         mongoStorage.getConnection().removeListener(listener);
         mongoStorage.registerCategory(CpuStatDAO.cpuStatCategory);
@@ -504,7 +505,7 @@
     public void setDefaultAgentID() throws Exception {
         CountDownLatch latch = new CountDownLatch(1);
         ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
-        Storage mongoStorage = getAndConnectStorage(listener);
+        BackingStorage mongoStorage = getAndConnectStorage(listener);
         latch.await();
         mongoStorage.getConnection().removeListener(listener);
         UUID uuid = new UUID(42, 24);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/BackingStorage.java	Wed Jul 31 15:50:35 2013 -0400
@@ -0,0 +1,57 @@
+/*
+ * 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;
+
+/**
+ * This subclass of {@link Storage} should be implemented by classes that
+ * interact directly with a form of storage (e.g. a database).
+ * 
+ * This interface contains methods which provide capabilities we do not want to
+ * expose directly to clients. Clients should instead prepare statements using
+ * {@link Storage#prepareStatement(StatementDescriptor)}. The methods in this
+ * interface then provide the mechanisms to execute the prepared statement, and
+ * should only be used by the prepared statement implementations.
+ */
+public interface BackingStorage extends Storage {
+    
+    <T extends Pojo> Query<T> createQuery(Category<T> category);
+    
+    // TODO Move createUpdate and createRemove here
+
+}
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/core/PreparedStatementFactory.java	Wed Jul 31 21:07:28 2013 +0200
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/PreparedStatementFactory.java	Wed Jul 31 15:50:35 2013 -0400
@@ -9,7 +9,7 @@
  */
 public class PreparedStatementFactory {
 
-    public static <T extends Pojo> PreparedStatement<T> getInstance(Storage storage,
+    public static <T extends Pojo> PreparedStatement<T> getInstance(BackingStorage storage,
             StatementDescriptor<T> desc) throws DescriptorParsingException {
         // This is the sole method in order to avoid leaking impl details of
         // this OSGi module. Storage implementations will have to use this
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/QueuedBackingStorage.java	Wed Jul 31 15:50:35 2013 -0400
@@ -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;
+
+import java.util.concurrent.ExecutorService;
+
+import com.redhat.thermostat.storage.model.Pojo;
+
+public class QueuedBackingStorage extends QueuedStorage implements
+        BackingStorage {
+    
+    public QueuedBackingStorage(BackingStorage delegate) {
+        super(delegate);
+    }
+
+    QueuedBackingStorage(BackingStorage delegate, ExecutorService executor,
+            ExecutorService fileExecutor) {
+        super(delegate, executor, fileExecutor);
+    }
+
+    @Override
+    public <T extends Pojo> Query<T> createQuery(Category<T> category) {
+        return ((BackingStorage) delegate).createQuery(category);
+    }
+    
+    @Override
+    public <T extends Pojo> PreparedStatement<T> prepareStatement(
+            StatementDescriptor<T> desc) throws DescriptorParsingException {
+        // FIXME: Use some kind of cache in order to avoid parsing of
+        // descriptors each time this is called. At least if the descriptor
+        // class is the same we should be able to do something here.
+        
+        // Don't just defer to the delegate, since we want statements
+        // prepared by this method to create queries using the
+        // createQuery method in this class.
+        return PreparedStatementFactory.getInstance(this, desc);
+    }
+
+}
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/core/QueuedStorage.java	Wed Jul 31 21:07:28 2013 +0200
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/QueuedStorage.java	Wed Jul 31 15:50:35 2013 -0400
@@ -229,11 +229,6 @@
     }
 
     @Override
-    public <T extends Pojo> Query<T> createQuery(Category<T> category) {
-        return delegate.createQuery(category);
-    }
-
-    @Override
     public Update createUpdate(Category<?> category) {
         QueuedUpdate update = new QueuedUpdate(delegate.createUpdate(category));
         return update;
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/core/Storage.java	Wed Jul 31 21:07:28 2013 +0200
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/Storage.java	Wed Jul 31 15:50:35 2013 -0400
@@ -96,8 +96,6 @@
 
     InputStream loadFile(String filename);
 
-    <T extends Pojo> Query<T> createQuery(Category<T> category);
-
     Update createUpdate(Category<?> category);
     Remove createRemove();
 
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/PreparedStatementImpl.java	Wed Jul 31 21:07:28 2013 +0200
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/PreparedStatementImpl.java	Wed Jul 31 15:50:35 2013 -0400
@@ -36,6 +36,7 @@
 
 package com.redhat.thermostat.storage.internal.statement;
 
+import com.redhat.thermostat.storage.core.BackingStorage;
 import com.redhat.thermostat.storage.core.Cursor;
 import com.redhat.thermostat.storage.core.DataModifyingStatement;
 import com.redhat.thermostat.storage.core.DescriptorParsingException;
@@ -48,7 +49,6 @@
 import com.redhat.thermostat.storage.core.Statement;
 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.model.Pojo;
 
 /**
@@ -63,7 +63,7 @@
     private final PreparedParameters params;
     private final ParsedStatementImpl<T> parsedStatement;
     
-    public PreparedStatementImpl(Storage storage, StatementDescriptor<T> desc) throws DescriptorParsingException {
+    public PreparedStatementImpl(BackingStorage storage, StatementDescriptor<T> desc) throws DescriptorParsingException {
         this.desc = desc;
         StatementDescriptorParser<T> parser = new StatementDescriptorParser<>(storage, desc);
         this.parsedStatement = (ParsedStatementImpl<T>)parser.parse();
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/StatementDescriptorParser.java	Wed Jul 31 21:07:28 2013 +0200
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/StatementDescriptorParser.java	Wed Jul 31 15:50:35 2013 -0400
@@ -40,6 +40,7 @@
 import java.util.List;
 import java.util.StringTokenizer;
 
+import com.redhat.thermostat.storage.core.BackingStorage;
 import com.redhat.thermostat.storage.core.Category;
 import com.redhat.thermostat.storage.core.DescriptorParsingException;
 import com.redhat.thermostat.storage.core.Key;
@@ -47,7 +48,6 @@
 import com.redhat.thermostat.storage.core.Query;
 import com.redhat.thermostat.storage.core.Query.SortDirection;
 import com.redhat.thermostat.storage.core.StatementDescriptor;
-import com.redhat.thermostat.storage.core.Storage;
 import com.redhat.thermostat.storage.model.Pojo;
 import com.redhat.thermostat.storage.query.BinaryComparisonOperator;
 import com.redhat.thermostat.storage.query.BinaryLogicalOperator;
@@ -117,14 +117,14 @@
     
     private final String[] tokens;
     private final StatementDescriptor<T> desc;
-    private final Storage storage;
+    private final BackingStorage storage;
     private int currTokenIndex;
     private int placeHolderCount;
     // the parsed statement
     private ParsedStatementImpl<T> parsedStatement;
     private SuffixExpression tree;
     
-    StatementDescriptorParser(Storage storage, StatementDescriptor<T> desc) {
+    StatementDescriptorParser(BackingStorage storage, StatementDescriptor<T> desc) {
         this.tokens = getTokens(desc.getQueryDescriptor());
         this.currTokenIndex = 0;
         this.placeHolderCount = 0;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/test/java/com/redhat/thermostat/storage/core/QueuedBackingStorageTest.java	Wed Jul 31 15:50:35 2013 -0400
@@ -0,0 +1,214 @@
+/*
+ * 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 static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.redhat.thermostat.storage.model.Pojo;
+
+
+public class QueuedBackingStorageTest {
+
+    private static class TestExecutor implements ExecutorService {
+
+        private Runnable task;
+        private boolean shutdown;
+
+        @Override
+        public void execute(Runnable task) {
+            this.task = task;
+        }
+
+        Runnable getTask() {
+            return task;
+        }
+
+        @Override
+        public void shutdown() {
+            shutdown = true;
+        }
+
+        @Override
+        public List<Runnable> shutdownNow() {
+            // Not used.
+            shutdown = true;
+            return null;
+        }
+
+        @Override
+        public boolean isShutdown() {
+            return shutdown;
+        }
+
+        @Override
+        public boolean isTerminated() {
+            // Not used.
+            return shutdown;
+        }
+
+        @Override
+        public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
+            return true;
+        }
+
+        @Override
+        public <T> Future<T> submit(Callable<T> task) {
+            // Not used.
+            return null;
+        }
+
+        @Override
+        public <T> Future<T> submit(Runnable task, T result) {
+            // Not used.
+            return null;
+        }
+
+        @Override
+        public Future<?> submit(Runnable task) {
+            // Not used.
+            return null;
+        }
+
+        @Override
+        public <T> List<Future<T>> invokeAll(
+                Collection<? extends Callable<T>> tasks)
+                throws InterruptedException {
+            // Not used.
+            return null;
+        }
+
+        @Override
+        public <T> List<Future<T>> invokeAll(
+                Collection<? extends Callable<T>> tasks, long timeout,
+                TimeUnit unit) throws InterruptedException {
+            // Not used.
+            return null;
+        }
+
+        @Override
+        public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
+                throws InterruptedException, ExecutionException {
+            // Not used.
+            return null;
+        }
+
+        @Override
+        public <T> T invokeAny(Collection<? extends Callable<T>> tasks,
+                long timeout, TimeUnit unit) throws InterruptedException,
+                ExecutionException, TimeoutException {
+            // Not used.
+            return null;
+        }
+
+    }
+    
+    private QueuedBackingStorage queuedStorage;
+    private BackingStorage delegateStorage;
+    private Query<TestPojo> delegateQuery;
+
+    private TestExecutor executor;
+    private TestExecutor fileExecutor;
+
+    private Cursor<TestPojo> expectedResults;
+
+    @SuppressWarnings("unchecked")
+    @Before
+    public void setUp() {
+        executor = new TestExecutor();
+        fileExecutor = new TestExecutor();
+        delegateStorage = mock(BackingStorage.class);
+
+        delegateQuery = (Query<TestPojo>) mock(Query.class);
+        expectedResults = (Cursor<TestPojo>) mock(Cursor.class);
+        when(delegateStorage.createQuery(any(Category.class))).thenReturn(delegateQuery);
+        when(delegateQuery.execute()).thenReturn(expectedResults);
+        
+        queuedStorage = new QueuedBackingStorage(delegateStorage, executor, fileExecutor);
+    }
+
+    @After
+    public void tearDown() {
+        expectedResults = null;
+        queuedStorage = null;
+        delegateStorage = null;
+        fileExecutor = null;
+        executor = null;
+        delegateQuery = null;
+    }
+    
+    @Test
+    public void testCreateQuery() {
+        @SuppressWarnings("unchecked")
+        Category<TestPojo> category = (Category<TestPojo>) mock(Category.class);
+        Query<TestPojo> query = queuedStorage.createQuery(category);
+        verify(delegateStorage).createQuery(category);
+        verifyNoMoreInteractions(delegateStorage);
+
+        Cursor<TestPojo> result = query.execute();
+        verify(delegateQuery).execute();
+        assertSame(expectedResults, result);
+
+        assertNull(executor.getTask());
+        assertNull(fileExecutor.getTask());
+    }
+    
+    private static class TestPojo implements Pojo {
+        
+    }
+
+}
+
--- a/storage/core/src/test/java/com/redhat/thermostat/storage/core/QueuedStorageTest.java	Wed Jul 31 21:07:28 2013 +0200
+++ b/storage/core/src/test/java/com/redhat/thermostat/storage/core/QueuedStorageTest.java	Wed Jul 31 15:50:35 2013 -0400
@@ -194,24 +194,16 @@
         }
     }
 
-    private static class TestPojo implements Pojo {
-        
-    }
-
     private QueuedStorage queuedStorage;
     private Storage delegateStorage;
     private Add delegateAdd;
     private Replace delegateReplace;
-    private Query<?> delegateQuery;
 
     private TestExecutor executor;
     private TestExecutor fileExecutor;
 
-    @SuppressWarnings("rawtypes")
-    private Cursor expectedResults;
     private InputStream expectedFile;
 
-    @SuppressWarnings("unchecked")
     @Before
     public void setUp() {
         executor = new TestExecutor();
@@ -222,30 +214,23 @@
         delegateReplace = mock(Replace.class);
 
         Remove remove = mock(Remove.class);
-        delegateQuery = mock(Query.class);
         when(delegateStorage.createAdd(any(Category.class))).thenReturn(delegateAdd);
         when(delegateStorage.createReplace(any(Category.class))).thenReturn(delegateReplace);
         when(delegateStorage.createRemove()).thenReturn(remove);
-        when(delegateStorage.createQuery(any(Category.class))).thenReturn(delegateQuery);
-        expectedResults = mock(Cursor.class);
-        when(delegateQuery.execute()).thenReturn(expectedResults);
         when(delegateStorage.getCount(any(Category.class))).thenReturn(42l);
         expectedFile = mock(InputStream.class);
         when(delegateStorage.loadFile(anyString())).thenReturn(expectedFile);
         when(delegateStorage.getAgentId()).thenReturn("huzzah");
         queuedStorage = new QueuedStorage(delegateStorage, executor, fileExecutor);
-        
     }
 
     @After
     public void tearDown() {
         expectedFile = null;
-        expectedResults = null;
         queuedStorage = null;
         delegateStorage = null;
         fileExecutor = null;
         executor = null;
-        delegateQuery = null;
     }
 
     @Test
@@ -329,22 +314,6 @@
     }
 
     @Test
-    public void testFindAllPojos() {
-        @SuppressWarnings("unchecked")
-        Category<TestPojo> category = mock(Category.class);
-        Query<TestPojo> query = queuedStorage.createQuery(category);
-        verify(delegateStorage).createQuery(category);
-        verifyNoMoreInteractions(delegateStorage);
-
-        Cursor<TestPojo> result = query.execute();
-        verify(delegateQuery).execute();
-        assertSame(expectedResults, result);
-
-        assertNull(executor.getTask());
-        assertNull(fileExecutor.getTask());
-    }
-
-    @Test
     public void testGetCount() {
         Category<?> category = mock(Category.class);
 
@@ -535,12 +504,6 @@
         }
 
         @Override
-        public <T extends Pojo> Query<T> createQuery(Category<T> category) {
-            // not implemented
-            throw new AssertionError();
-        }
-
-        @Override
         public Update createUpdate(Category<?> category) {
             // not implemented
             throw new AssertionError();
@@ -564,7 +527,7 @@
         }
 
         @Override
-        public PreparedStatement prepareStatement(StatementDescriptor desc)
+        public <T extends Pojo> PreparedStatement<T> prepareStatement(StatementDescriptor<T> desc)
                 throws DescriptorParsingException {
             // not implemented
             return null;
--- a/storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/PreparedStatementImplTest.java	Wed Jul 31 21:07:28 2013 +0200
+++ b/storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/PreparedStatementImplTest.java	Wed Jul 31 15:50:35 2013 -0400
@@ -44,13 +44,13 @@
 
 import org.junit.Test;
 
+import com.redhat.thermostat.storage.core.BackingStorage;
 import com.redhat.thermostat.storage.core.Category;
 import com.redhat.thermostat.storage.core.Cursor;
 import com.redhat.thermostat.storage.core.Key;
 import com.redhat.thermostat.storage.core.Query;
 import com.redhat.thermostat.storage.core.StatementDescriptor;
 import com.redhat.thermostat.storage.core.StatementExecutionException;
-import com.redhat.thermostat.storage.core.Storage;
 import com.redhat.thermostat.storage.model.Pojo;
 import com.redhat.thermostat.storage.query.BinaryComparisonExpression;
 import com.redhat.thermostat.storage.query.BinaryComparisonOperator;
@@ -100,7 +100,7 @@
         Category<Pojo> mockCategory = (Category<Pojo>) mock(Category.class);
         when(desc.getCategory()).thenReturn(mockCategory);
         when(mockCategory.getName()).thenReturn("foo");
-        Storage storage = mock(Storage.class);
+        BackingStorage storage = mock(BackingStorage.class);
         StubQuery stmt = new StubQuery();
         when(storage.createQuery(mockCategory)).thenReturn(stmt);
         PreparedStatementImpl<Pojo> preparedStatement = new PreparedStatementImpl<>(storage, desc);
@@ -124,7 +124,7 @@
         Category<Pojo> mockCategory = (Category<Pojo>) mock(Category.class);
         when(desc.getCategory()).thenReturn(mockCategory);
         when(mockCategory.getName()).thenReturn("foo");
-        Storage storage = mock(Storage.class);
+        BackingStorage storage = mock(BackingStorage.class);
         StubQuery stmt = new StubQuery();
         when(storage.createQuery(mockCategory)).thenReturn(stmt);
         PreparedStatementImpl<Pojo> preparedStatement = new PreparedStatementImpl<>(storage, desc);
--- a/storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/StatementDescriptorParserTest.java	Wed Jul 31 21:07:28 2013 +0200
+++ b/storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/StatementDescriptorParserTest.java	Wed Jul 31 15:50:35 2013 -0400
@@ -51,12 +51,12 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import com.redhat.thermostat.storage.core.BackingStorage;
 import com.redhat.thermostat.storage.core.DescriptorParsingException;
 import com.redhat.thermostat.storage.core.Key;
 import com.redhat.thermostat.storage.core.Query;
 import com.redhat.thermostat.storage.core.Query.SortDirection;
 import com.redhat.thermostat.storage.core.StatementDescriptor;
-import com.redhat.thermostat.storage.core.Storage;
 import com.redhat.thermostat.storage.dao.AgentInfoDAO;
 import com.redhat.thermostat.storage.model.AgentInformation;
 import com.redhat.thermostat.storage.internal.statement.BinaryExpressionNode;
@@ -77,14 +77,14 @@
 
 public class StatementDescriptorParserTest {
 
-    private Storage storage;
+    private BackingStorage storage;
     private Query<AgentInformation> mockQuery;
     private StatementDescriptorParser<AgentInformation> parser;
     
     @SuppressWarnings("unchecked")
     @Before
     public void setup() {
-        storage = mock(Storage.class);
+        storage = mock(BackingStorage.class);
         mockQuery = mock(Query.class);
         when(storage.createQuery(any(AgentInfoDAO.CATEGORY.getClass()))).thenReturn(mockQuery);
     }
--- a/storage/mongo/src/main/java/com/redhat/thermostat/storage/mongodb/MongoStorageProvider.java	Wed Jul 31 21:07:28 2013 +0200
+++ b/storage/mongo/src/main/java/com/redhat/thermostat/storage/mongodb/MongoStorageProvider.java	Wed Jul 31 15:50:35 2013 -0400
@@ -37,7 +37,7 @@
 package com.redhat.thermostat.storage.mongodb;
 
 import com.redhat.thermostat.storage.config.StartupConfiguration;
-import com.redhat.thermostat.storage.core.QueuedStorage;
+import com.redhat.thermostat.storage.core.QueuedBackingStorage;
 import com.redhat.thermostat.storage.core.Storage;
 import com.redhat.thermostat.storage.core.StorageProvider;
 import com.redhat.thermostat.storage.mongodb.internal.MongoStorage;
@@ -53,7 +53,7 @@
     @Override
     public Storage createStorage() {
         MongoStorage storage = new MongoStorage(configuration);
-        return new QueuedStorage(storage);
+        return new QueuedBackingStorage(storage);
     }
 
     @Override
--- a/storage/mongo/src/main/java/com/redhat/thermostat/storage/mongodb/internal/MongoStorage.java	Wed Jul 31 21:07:28 2013 +0200
+++ b/storage/mongo/src/main/java/com/redhat/thermostat/storage/mongodb/internal/MongoStorage.java	Wed Jul 31 15:50:35 2013 -0400
@@ -55,6 +55,7 @@
 import com.redhat.thermostat.storage.config.StartupConfiguration;
 import com.redhat.thermostat.storage.core.AbstractQuery.Sort;
 import com.redhat.thermostat.storage.core.Add;
+import com.redhat.thermostat.storage.core.BackingStorage;
 import com.redhat.thermostat.storage.core.BasePut;
 import com.redhat.thermostat.storage.core.Category;
 import com.redhat.thermostat.storage.core.Connection;
@@ -69,7 +70,6 @@
 import com.redhat.thermostat.storage.core.Remove;
 import com.redhat.thermostat.storage.core.Replace;
 import com.redhat.thermostat.storage.core.StatementDescriptor;
-import com.redhat.thermostat.storage.core.Storage;
 import com.redhat.thermostat.storage.core.Update;
 import com.redhat.thermostat.storage.model.Pojo;
 
@@ -78,7 +78,7 @@
  *
  * In this implementation, each CATEGORY is given a distinct collection.
  */
-public class MongoStorage implements Storage {
+public class MongoStorage implements BackingStorage {
 
     private class MongoAdd extends BasePut implements Add {
 
--- a/storage/mongo/src/test/java/com/redhat/thermostat/storage/mongodb/internal/MongoStorageProviderTest.java	Wed Jul 31 21:07:28 2013 +0200
+++ b/storage/mongo/src/test/java/com/redhat/thermostat/storage/mongodb/internal/MongoStorageProviderTest.java	Wed Jul 31 15:50:35 2013 -0400
@@ -36,14 +36,15 @@
 
 package com.redhat.thermostat.storage.mongodb.internal;
 
-import org.junit.Test;
-
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import org.junit.Test;
+
 import com.redhat.thermostat.storage.config.StartupConfiguration;
+import com.redhat.thermostat.storage.core.BackingStorage;
 import com.redhat.thermostat.storage.core.QueuedStorage;
 import com.redhat.thermostat.storage.core.SecureStorage;
 import com.redhat.thermostat.storage.core.Storage;
@@ -59,6 +60,7 @@
         provider.setConfig(config);
         Storage result = provider.createStorage();
         assertTrue(result instanceof QueuedStorage);
+        assertTrue(result instanceof BackingStorage);
         assertFalse(result instanceof SecureStorage);
     }
 }
--- a/web/client/src/main/java/com/redhat/thermostat/web/client/internal/WebStorage.java	Wed Jul 31 21:07:28 2013 +0200
+++ b/web/client/src/main/java/com/redhat/thermostat/web/client/internal/WebStorage.java	Wed Jul 31 15:50:35 2013 -0400
@@ -101,7 +101,6 @@
 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;
@@ -513,14 +512,6 @@
     }
 
     @Override
-    public <T extends Pojo> Query<T> createQuery(Category<T> category) {
-        // 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
     public Remove createRemove() {
         return new WebRemove(categoryIds);
     }
--- a/web/client/src/test/java/com/redhat/thermostat/web/client/internal/WebStorageProviderTest.java	Wed Jul 31 21:07:28 2013 +0200
+++ b/web/client/src/test/java/com/redhat/thermostat/web/client/internal/WebStorageProviderTest.java	Wed Jul 31 15:50:35 2013 -0400
@@ -36,16 +36,18 @@
 
 package com.redhat.thermostat.web.client.internal;
 
-import org.junit.Test;
-import org.mockito.Mockito;
-
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import org.junit.Test;
+import org.mockito.Mockito;
+
 import com.redhat.thermostat.storage.config.AuthenticationConfiguration;
 import com.redhat.thermostat.storage.config.StartupConfiguration;
+import com.redhat.thermostat.storage.core.BackingStorage;
 import com.redhat.thermostat.storage.core.QueuedStorage;
 import com.redhat.thermostat.storage.core.SecureStorage;
 import com.redhat.thermostat.storage.core.Storage;
@@ -61,6 +63,7 @@
         Storage storage = provider.createStorage();
         assertTrue(storage instanceof SecureStorage);
         assertTrue(storage instanceof QueuedStorage);
+        assertFalse(storage instanceof BackingStorage);
         verify(config, Mockito.atLeastOnce()).getUsername();
         verify(config, Mockito.atLeastOnce()).getPassword();
     }
--- a/web/client/src/test/java/com/redhat/thermostat/web/client/internal/WebStorageTest.java	Wed Jul 31 21:07:28 2013 +0200
+++ b/web/client/src/test/java/com/redhat/thermostat/web/client/internal/WebStorageTest.java	Wed Jul 31 15:50:35 2013 -0400
@@ -391,17 +391,6 @@
     }
 
     @Test
-    public void createQueryFailsCorrectly() throws UnsupportedEncodingException, IOException {
-        try {
-            storage.createQuery(category);
-            fail("createQuery() should fail for WebStorage.");
-        } catch (IllegalStateException e) {
-            // pass
-            assertTrue(e.getMessage().contains("createQuery() not supported"));
-        }
-    }
-
-    @Test
     public void testPut() throws IOException, JsonSyntaxException, ClassNotFoundException {
 
         TestObj obj = new TestObj();
--- a/web/server/src/test/java/com/redhat/thermostat/web/server/WebStorageEndpointTest.java	Wed Jul 31 21:07:28 2013 +0200
+++ b/web/server/src/test/java/com/redhat/thermostat/web/server/WebStorageEndpointTest.java	Wed Jul 31 15:50:35 2013 -0400
@@ -92,6 +92,7 @@
 import com.google.gson.GsonBuilder;
 import com.google.gson.reflect.TypeToken;
 import com.redhat.thermostat.storage.core.Add;
+import com.redhat.thermostat.storage.core.BackingStorage;
 import com.redhat.thermostat.storage.core.Categories;
 import com.redhat.thermostat.storage.core.Category;
 import com.redhat.thermostat.storage.core.Cursor;
@@ -164,7 +165,7 @@
 
     private Server server;
     private int port;
-    private Storage mockStorage;
+    private BackingStorage mockStorage;
     private Integer categoryId;
 
     private static Key<String> key1;
@@ -197,7 +198,7 @@
         assertTrue(fakeHome.canRead());
         System.setProperty("THERMOSTAT_HOME", fakeHome.getAbsolutePath());
         
-        mockStorage = mock(Storage.class);
+        mockStorage = mock(BackingStorage.class);
         StorageWrapper.setStorage(mockStorage);
         
         factory = new ExpressionFactory();