changeset 589:f7953e6ce321

Separate api for querying Introduce a new type, Query, for representing queries (SELECT in sql-speak). This limits the Chunk type to just representing data rather than both data and queries. Reviewed-by: neugens, rkennke Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2012-August/002662.html
author Omair Majid <omajid@redhat.com>
date Fri, 07 Sep 2012 11:42:51 -0400
parents 591c74ae68d5
children 549a5e6562c6
files common/core/src/main/java/com/redhat/thermostat/common/dao/HeapDAOImpl.java common/core/src/main/java/com/redhat/thermostat/common/dao/HostInfoDAOImpl.java common/core/src/main/java/com/redhat/thermostat/common/dao/HostLatestPojoListGetter.java common/core/src/main/java/com/redhat/thermostat/common/dao/NetworkInterfaceInfoDAOImpl.java common/core/src/main/java/com/redhat/thermostat/common/dao/VmInfoDAOImpl.java common/core/src/main/java/com/redhat/thermostat/common/dao/VmLatestPojoListGetter.java common/core/src/main/java/com/redhat/thermostat/common/dao/VmMemoryStatDAOImpl.java common/core/src/main/java/com/redhat/thermostat/common/storage/Key.java common/core/src/main/java/com/redhat/thermostat/common/storage/MongoQuery.java common/core/src/main/java/com/redhat/thermostat/common/storage/MongoStorage.java common/core/src/main/java/com/redhat/thermostat/common/storage/Query.java common/core/src/main/java/com/redhat/thermostat/common/storage/Storage.java common/core/src/main/java/com/redhat/thermostat/common/storage/StorageProvider.java common/core/src/main/java/com/redhat/thermostat/test/MockQuery.java common/core/src/test/java/com/redhat/thermostat/common/dao/CpuStatDAOTest.java common/core/src/test/java/com/redhat/thermostat/common/dao/HeapDAOTest.java common/core/src/test/java/com/redhat/thermostat/common/dao/HostInfoDAOTest.java common/core/src/test/java/com/redhat/thermostat/common/dao/HostLatestPojoListGetterTest.java common/core/src/test/java/com/redhat/thermostat/common/dao/MemoryStatDAOTest.java common/core/src/test/java/com/redhat/thermostat/common/dao/NetworkInterfaceInfoDAOTest.java common/core/src/test/java/com/redhat/thermostat/common/dao/VmClassStatDAOTest.java common/core/src/test/java/com/redhat/thermostat/common/dao/VmCpuStatDAOTest.java common/core/src/test/java/com/redhat/thermostat/common/dao/VmGcStatDAOTest.java common/core/src/test/java/com/redhat/thermostat/common/dao/VmInfoDAOTest.java common/core/src/test/java/com/redhat/thermostat/common/dao/VmLatestPojoListGetterTest.java common/core/src/test/java/com/redhat/thermostat/common/dao/VmMemoryStatDAOTest.java common/core/src/test/java/com/redhat/thermostat/common/storage/MongoQueryTest.java common/core/src/test/java/com/redhat/thermostat/common/storage/MongoStorageTest.java thread/collector/src/main/java/com/redhat/thermostat/thread/dao/impl/ThreadDaoImpl.java thread/collector/src/test/java/com/redhat/thermostat/thread/dao/impl/ThreadDaoImplTest.java
diffstat 30 files changed, 942 insertions(+), 300 deletions(-) [+]
line wrap: on
line diff
--- a/common/core/src/main/java/com/redhat/thermostat/common/dao/HeapDAOImpl.java	Fri Sep 07 17:22:36 2012 +0200
+++ b/common/core/src/main/java/com/redhat/thermostat/common/dao/HeapDAOImpl.java	Fri Sep 07 11:42:51 2012 -0400
@@ -55,13 +55,15 @@
 import com.redhat.thermostat.common.storage.Chunk;
 import com.redhat.thermostat.common.storage.Cursor;
 import com.redhat.thermostat.common.storage.Key;
+import com.redhat.thermostat.common.storage.Query;
+import com.redhat.thermostat.common.storage.Query.Criteria;
 import com.redhat.thermostat.common.storage.Storage;
 
 class HeapDAOImpl implements HeapDAO {
 
     private static final Logger log = Logger.getLogger(HeapDAOImpl.class.getName());
 
-    private Storage storage;
+    private final Storage storage;
 
     HeapDAOImpl(Storage storage) {
         this.storage = storage;
@@ -73,6 +75,8 @@
         int vmId = heapInfo.getVmId();
         Chunk chunk = new Chunk(heapInfoCategory, false);
 
+        // We dont add a Key.AGENT_ID here explicitly. Storage takes care of that.
+
         chunk.put(Key.VM_ID, vmId);
         chunk.put(Key.TIMESTAMP, heapInfo.getTimestamp());
         String id = heapInfo.getHeapId();
@@ -103,16 +107,27 @@
             }
         }
 
-        Chunk entry = storage.find(chunk);
+        Query justInsertedHeap = storage.createQuery()
+                .from(heapInfoCategory)
+                .where(Key.VM_ID, Criteria.EQUALS, vmId)
+                .where(Key.TIMESTAMP, Criteria.EQUALS, heapInfo.getTimestamp());
+        if (heapDumpData != null) {
+            justInsertedHeap.where(heapDumpIdKey, Criteria.EQUALS, heapDumpId);
+        }
+        if (histogramData != null) {
+            justInsertedHeap.where(histogramIdKey, Criteria.EQUALS, histogramId);
+        }
+
+        Chunk entry = storage.find(justInsertedHeap);
         heapInfo.setHeapId(entry.get(Key.ID));
     }
 
     @Override
     public Collection<HeapInfo> getAllHeapInfo(VmRef vm) {
-
-        Chunk query = new Chunk(heapInfoCategory, false);
-        query.put(Key.AGENT_ID, vm.getAgent().getAgentId());
-        query.put(Key.VM_ID, vm.getId());
+        Query query = storage.createQuery()
+                .from(heapInfoCategory)
+                .where(Key.AGENT_ID, Criteria.EQUALS, vm.getAgent().getAgentId())
+                .where(Key.VM_ID, Criteria.EQUALS, vm.getId());
         Cursor cursor = storage.findAll(query);
         Collection<HeapInfo> heapInfos = new ArrayList<>();
         while (cursor.hasNext()) {
@@ -149,8 +164,9 @@
 
     @Override
     public HeapInfo getHeapInfo(String heapId) {
-        Chunk query = new Chunk(heapInfoCategory, false);
-        query.put(Key.ID, heapId);
+        Query query = storage.createQuery()
+                .from(heapInfoCategory)
+                .where(Key.ID, Criteria.EQUALS, heapId);
         Chunk found = null;
         try {
             found = storage.find(query);
--- a/common/core/src/main/java/com/redhat/thermostat/common/dao/HostInfoDAOImpl.java	Fri Sep 07 17:22:36 2012 +0200
+++ b/common/core/src/main/java/com/redhat/thermostat/common/dao/HostInfoDAOImpl.java	Fri Sep 07 11:42:51 2012 -0400
@@ -44,10 +44,13 @@
 import com.redhat.thermostat.common.storage.Chunk;
 import com.redhat.thermostat.common.storage.Cursor;
 import com.redhat.thermostat.common.storage.Key;
+import com.redhat.thermostat.common.storage.Query;
 import com.redhat.thermostat.common.storage.Storage;
+import com.redhat.thermostat.common.storage.Query.Criteria;
 
 class HostInfoDAOImpl implements HostInfoDAO {
     private Storage storage;
+
     private HostInfoConverter converter;
 
     public HostInfoDAOImpl(Storage storage) {
@@ -57,8 +60,9 @@
 
     @Override
     public HostInfo getHostInfo(HostRef ref) {
-        Chunk query = new Chunk(hostInfoCategory, false);
-        query.put(Key.AGENT_ID, ref.getAgentId());
+        Query query = storage.createQuery()
+                .from(hostInfoCategory)
+                .where(Key.AGENT_ID, Criteria.EQUALS, ref.getAgentId());
         Chunk result = storage.find(query);
         return result == null ? null : converter.fromChunk(result);
     }
@@ -70,10 +74,11 @@
     
     @Override
     public Collection<HostRef> getHosts() {
-        return getHosts(new Chunk(hostInfoCategory, false));
+        Query allHosts = storage.createQuery().from(hostInfoCategory);
+        return getHosts(allHosts);
     }
     
-    private Collection<HostRef> getHosts(Chunk filter) {
+    private Collection<HostRef> getHosts(Query filter) {
         Collection<HostRef> hosts = new ArrayList<HostRef>();
         
         Cursor hostsCursor = storage.findAll(filter);
@@ -91,14 +96,17 @@
         
         Collection<HostRef> hosts = new ArrayList<HostRef>();
         
-        Chunk agents = new Chunk(AgentInformation.AGENT_INFO_CATEGORY, false);
-        agents.put(AgentInformation.AGENT_ALIVE_KEY, true);
-        Cursor agentCursor = storage.findAll(agents);
+        Query aliveAgents = storage.createQuery()
+                .from(AgentInformation.AGENT_INFO_CATEGORY)
+                .where(AgentInformation.AGENT_ALIVE_KEY, Criteria.EQUALS, true);
+
+        Cursor agentCursor = storage.findAll(aliveAgents);
         while(agentCursor.hasNext()) {
             Chunk chunk = agentCursor.next();
             
-            Chunk filter = new Chunk(hostInfoCategory, false);
-            filter.put(Key.AGENT_ID, chunk.get(Key.AGENT_ID));
+            Query filter = storage.createQuery()
+                    .from(hostInfoCategory)
+                    .where(Key.AGENT_ID, Criteria.EQUALS, chunk.get(Key.AGENT_ID));
             
             hosts.addAll(getHosts(filter));
         }
--- a/common/core/src/main/java/com/redhat/thermostat/common/dao/HostLatestPojoListGetter.java	Fri Sep 07 17:22:36 2012 +0200
+++ b/common/core/src/main/java/com/redhat/thermostat/common/dao/HostLatestPojoListGetter.java	Fri Sep 07 11:42:51 2012 -0400
@@ -46,8 +46,10 @@
 import com.redhat.thermostat.common.storage.Chunk;
 import com.redhat.thermostat.common.storage.Cursor;
 import com.redhat.thermostat.common.storage.Key;
+import com.redhat.thermostat.common.storage.Query;
 import com.redhat.thermostat.common.storage.Storage;
 import com.redhat.thermostat.common.storage.Cursor.SortDirection;
+import com.redhat.thermostat.common.storage.Query.Criteria;
 
 class HostLatestPojoListGetter<T extends TimeStampedPojo> implements LatestPojoListGetter<T> {
 
@@ -74,11 +76,11 @@
 
     @Override
     public List<T> getLatest() {
-        Chunk query = buildQuery();
+        Query query = buildQuery();
         return getLatest(query);
     }
 
-    private List<T> getLatest(Chunk query) {
+    private List<T> getLatest(Query query) {
         // TODO if multiple threads will be using this utility class, there may be some issues
         // with the updateTimes
         Long lastUpdate = lastUpdateTimes.get(ref);
@@ -94,14 +96,13 @@
         return result;
     }
 
-    protected Chunk buildQuery() {
-        Chunk query = new Chunk(cat, false);
-        query.put(Key.AGENT_ID, ref.getAgentId());
+    protected Query buildQuery() {
+        Query query = storage.createQuery()
+                .from(cat)
+                .where(Key.AGENT_ID, Criteria.EQUALS, ref.getAgentId());
         Long lastUpdate = lastUpdateTimes.get(ref);
         if (lastUpdate != null) {
-            // TODO once we have an index and the 'column' is of type long, use
-            // a query which can utilize an index. this one doesn't
-            query.put(Key.WHERE, "this.timestamp > " + lastUpdate);
+            query.where(Key.TIMESTAMP, Criteria.GREATER_THAN, lastUpdate);
         } else {
             lastUpdateTimes.put(ref, Long.MIN_VALUE);
         }
--- a/common/core/src/main/java/com/redhat/thermostat/common/dao/NetworkInterfaceInfoDAOImpl.java	Fri Sep 07 17:22:36 2012 +0200
+++ b/common/core/src/main/java/com/redhat/thermostat/common/dao/NetworkInterfaceInfoDAOImpl.java	Fri Sep 07 11:42:51 2012 -0400
@@ -43,7 +43,9 @@
 import com.redhat.thermostat.common.storage.Chunk;
 import com.redhat.thermostat.common.storage.Cursor;
 import com.redhat.thermostat.common.storage.Key;
+import com.redhat.thermostat.common.storage.Query;
 import com.redhat.thermostat.common.storage.Storage;
+import com.redhat.thermostat.common.storage.Query.Criteria;
 
 class NetworkInterfaceInfoDAOImpl implements NetworkInterfaceInfoDAO {
 
@@ -57,10 +59,11 @@
 
     @Override
     public List<NetworkInterfaceInfo> getNetworkInterfaces(HostRef ref) {
-        Chunk query = new Chunk(networkInfoCategory, false);
-        query.put(Key.AGENT_ID, ref.getAgentId());
+        Query allHostNetworkInterfaces = storage.createQuery()
+                .from(networkInfoCategory)
+                .where(Key.AGENT_ID, Criteria.EQUALS, ref.getAgentId());
 
-        Cursor cursor = storage.findAll(query);
+        Cursor cursor = storage.findAll(allHostNetworkInterfaces);
         List<NetworkInterfaceInfo> result = new ArrayList<>();
         while (cursor.hasNext()) {
             Chunk chunk = cursor.next();
--- a/common/core/src/main/java/com/redhat/thermostat/common/dao/VmInfoDAOImpl.java	Fri Sep 07 17:22:36 2012 +0200
+++ b/common/core/src/main/java/com/redhat/thermostat/common/dao/VmInfoDAOImpl.java	Fri Sep 07 11:42:51 2012 -0400
@@ -44,7 +44,9 @@
 import com.redhat.thermostat.common.storage.Chunk;
 import com.redhat.thermostat.common.storage.Cursor;
 import com.redhat.thermostat.common.storage.Key;
+import com.redhat.thermostat.common.storage.Query;
 import com.redhat.thermostat.common.storage.Storage;
+import com.redhat.thermostat.common.storage.Query.Criteria;
 
 class VmInfoDAOImpl implements VmInfoDAO {
 
@@ -58,10 +60,11 @@
 
     @Override
     public VmInfo getVmInfo(VmRef ref) {
-        Chunk query = new Chunk(vmInfoCategory, false);
-        query.put(Key.AGENT_ID, ref.getAgent().getAgentId());
-        query.put(vmIdKey, ref.getId());
-        Chunk result = storage.find(query);
+        Query findMatchingVm = storage.createQuery()
+                .from(vmInfoCategory)
+                .where(Key.AGENT_ID, Criteria.EQUALS, ref.getAgent().getAgentId())
+                .where(vmIdKey, Criteria.EQUALS, ref.getId());
+        Chunk result = storage.find(findMatchingVm);
         if (result == null) {
             throw new DAOException("Unknown VM: host:" + ref.getAgent().getAgentId() + ";vm:" + ref.getId());
         }
@@ -71,14 +74,15 @@
     @Override
     public Collection<VmRef> getVMs(HostRef host) {
 
-        Chunk query = buildQuery(host);
+        Query query = buildQuery(host);
         Cursor cursor = storage.findAll(query);
         return buildVMsFromQuery(cursor, host);
     }
 
-    private Chunk buildQuery(HostRef host) {
-        Chunk query = new Chunk(vmInfoCategory, false);
-        query.put(Key.AGENT_ID, host.getAgentId());
+    private Query buildQuery(HostRef host) {
+        Query query = storage.createQuery()
+                .from(vmInfoCategory)
+                .where(Key.AGENT_ID, Criteria.EQUALS, host.getAgentId());
         return query;
     }
 
--- a/common/core/src/main/java/com/redhat/thermostat/common/dao/VmLatestPojoListGetter.java	Fri Sep 07 17:22:36 2012 +0200
+++ b/common/core/src/main/java/com/redhat/thermostat/common/dao/VmLatestPojoListGetter.java	Fri Sep 07 11:42:51 2012 -0400
@@ -38,8 +38,9 @@
 
 import com.redhat.thermostat.common.model.TimeStampedPojo;
 import com.redhat.thermostat.common.storage.Category;
-import com.redhat.thermostat.common.storage.Chunk;
 import com.redhat.thermostat.common.storage.Key;
+import com.redhat.thermostat.common.storage.Query;
+import com.redhat.thermostat.common.storage.Query.Criteria;
 import com.redhat.thermostat.common.storage.Storage;
 
 class VmLatestPojoListGetter<T extends TimeStampedPojo> extends HostLatestPojoListGetter<T> {
@@ -56,9 +57,9 @@
     }
 
     @Override
-    protected Chunk buildQuery() {
-        Chunk query = super.buildQuery();
-        query.put(Key.VM_ID, vmRef.getId());
+    protected Query buildQuery() {
+        Query query = super.buildQuery();
+        query.where(Key.VM_ID, Criteria.EQUALS, vmRef.getId());
         return query;
     }
 }
--- a/common/core/src/main/java/com/redhat/thermostat/common/dao/VmMemoryStatDAOImpl.java	Fri Sep 07 17:22:36 2012 +0200
+++ b/common/core/src/main/java/com/redhat/thermostat/common/dao/VmMemoryStatDAOImpl.java	Fri Sep 07 11:42:51 2012 -0400
@@ -41,10 +41,11 @@
 import java.util.Map;
 
 import com.redhat.thermostat.common.model.VmMemoryStat;
-import com.redhat.thermostat.common.storage.Chunk;
 import com.redhat.thermostat.common.storage.Cursor;
 import com.redhat.thermostat.common.storage.Key;
+import com.redhat.thermostat.common.storage.Query;
 import com.redhat.thermostat.common.storage.Storage;
+import com.redhat.thermostat.common.storage.Query.Criteria;
 
 class VmMemoryStatDAOImpl implements VmMemoryStatDAO {
 
@@ -60,9 +61,10 @@
 
     @Override
     public VmMemoryStat getLatestMemoryStat(VmRef ref) {
-        Chunk query = new Chunk(vmMemoryStatsCategory, false);
-        query.put(Key.AGENT_ID, ref.getAgent().getAgentId());
-        query.put(Key.VM_ID, ref.getId());
+        Query query = storage.createQuery()
+                .from(vmMemoryStatsCategory)
+                .where(Key.AGENT_ID, Criteria.EQUALS, ref.getAgent().getAgentId())
+                .where(Key.VM_ID, Criteria.EQUALS, ref.getId());
         Cursor cursor = storage.findAll(query).sort(Key.TIMESTAMP, Cursor.SortDirection.DESCENDING).limit(1);
         if (cursor.hasNext()) {
             return converter.fromChunk(cursor.next());
--- a/common/core/src/main/java/com/redhat/thermostat/common/storage/Key.java	Fri Sep 07 17:22:36 2012 +0200
+++ b/common/core/src/main/java/com/redhat/thermostat/common/storage/Key.java	Fri Sep 07 11:42:51 2012 -0400
@@ -46,7 +46,6 @@
     public static final Key<Long> TIMESTAMP = new Key<>("timestamp", false);
     public static final Key<String> AGENT_ID = new Key<>("agent-id", true);
     public static final Key<Integer> VM_ID = new Key<>("vm-id", true);
-    public static final Key<String> WHERE = new Key<>("$where", false);
     public static final Key<String> ID = new Key<>("_id", false);
 
     private String name;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/core/src/main/java/com/redhat/thermostat/common/storage/MongoQuery.java	Fri Sep 07 11:42:51 2012 -0400
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2012 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.common.storage;
+
+import java.util.Objects;
+
+import com.mongodb.BasicDBObject;
+import com.mongodb.DBObject;
+
+public class MongoQuery implements Query {
+
+    private Category category;
+    private BasicDBObject query = new BasicDBObject();
+
+    @Override
+    public MongoQuery from(Category category) {
+        this.category = category;
+        return this;
+    }
+
+    @Override
+    public <T> MongoQuery where(Key<T> key, Criteria operator, T value) {
+        return where(key.getName(), operator, value);
+    }
+
+    public MongoQuery where(String key, Criteria operator, Object value) {
+        switch (operator) {
+        case EQUALS:
+            query.put(key, value);
+            break;
+
+        case NOT_EQUAL_TO:
+            query.put(key, new BasicDBObject("$ne", value));
+            break;
+
+        case LESS_THAN:
+            query.put(key, new BasicDBObject("$lt", value));
+            break;
+
+        case LESS_THAN_OR_EQUAL_TO:
+            query.put(key, new BasicDBObject("$lte", value));
+            break;
+        case GREATER_THAN:
+            query.put(key, new BasicDBObject("$gt", value));
+            break;
+
+        case GREATER_THAN_OR_EQUAL_TO:
+            query.put(key, new BasicDBObject("$gte", value));
+            break;
+        default:
+            throw new IllegalArgumentException("MongoQuery can not handle " + operator);
+        }
+
+        return this;
+    }
+
+    String getCollectionName() {
+        return category.getName();
+    }
+
+    DBObject getGeneratedQuery() {
+        return query;
+    }
+
+    Category getCategory() {
+        return category;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (!(obj instanceof MongoQuery)) {
+            return false;
+        }
+        MongoQuery other = (MongoQuery) obj;
+        return Objects.equals(this.category, other.category) && Objects.equals(this.query, other.query);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(this.category, this.query);
+    }
+}
--- a/common/core/src/main/java/com/redhat/thermostat/common/storage/MongoStorage.java	Fri Sep 07 17:22:36 2012 +0200
+++ b/common/core/src/main/java/com/redhat/thermostat/common/storage/MongoStorage.java	Fri Sep 07 11:42:51 2012 -0400
@@ -255,6 +255,14 @@
         return coll;
     }
 
+    // TODO: This method is only temporary to enable tests, until we come up with a better design,
+    // in particular, the collection should be stored in the category itself. It must not be called
+    // from production code.
+    void mapCategoryToDBCollection(Category category, DBCollection coll) {
+        collectionCache.put(category.getName(), coll);
+    }
+
+
     private DBObject createConfigDBObject(AgentInformation agentInfo) {
         BasicDBObject result = getAgentQueryKeyFromGlobalAgent();
         result.put(StorageConstants.KEY_AGENT_CONFIG_AGENT_START_TIME, agentInfo.getStartTime());
@@ -312,29 +320,34 @@
     }
 
     @Override
-    public Cursor findAll(Chunk query) {
-        Category cat = query.getCategory();
-        DBCollection coll = getCachedCollection(cat.getName());
-        ChunkConverter converter = new ChunkConverter();
-        DBObject obj = converter.chunkToDBObject(query);
-        DBCursor dbCursor = coll.find(obj);
-        return new MongoCursor(dbCursor, query.getCategory());
+    public Query createQuery() {
+        return new MongoQuery();
     }
 
-    // TODO: This method is only temporary to enable tests, until we come up with a better design,
-    // in particular, the collection should be stored in the category itself. It must not be called
-    // from production code.
-    void mapCategoryToDBCollection(Category category, DBCollection coll) {
-        collectionCache.put(category.getName(), coll);
+    @Override
+    public Cursor findAll(Query query) {
+        MongoQuery mongoQuery =  checkAndCastQuery(query);
+        DBCollection coll = getCachedCollection(mongoQuery.getCollectionName());
+        DBCursor dbCursor = coll.find(mongoQuery.getGeneratedQuery());
+        return new MongoCursor(dbCursor, mongoQuery.getCategory());
     }
 
     @Override
-    public Chunk find(Chunk query) {
-        Category cat = query.getCategory();
-        DBCollection coll = getCachedCollection(cat.getName());
+    public Chunk find(Query query) {
+        MongoQuery mongoQuery = checkAndCastQuery(query);
+        DBCollection coll = getCachedCollection(mongoQuery.getCollectionName());
+        DBObject dbResult = coll.findOne(mongoQuery.getGeneratedQuery());
         ChunkConverter converter = new ChunkConverter();
-        DBObject dbResult = coll.findOne(converter.chunkToDBObject(query));
-        return dbResult == null ? null : converter.dbObjectToChunk(dbResult, cat);
+        return dbResult == null ? null : converter.dbObjectToChunk(dbResult, mongoQuery.getCategory());
+    }
+
+    private MongoQuery checkAndCastQuery(Query query) {
+        if (!(query instanceof MongoQuery)) {
+            throw new IllegalArgumentException("MongoStorage can only handle MongoQuery");
+        }
+
+        return (MongoQuery) query;
+
     }
     
     @Override
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/core/src/main/java/com/redhat/thermostat/common/storage/Query.java	Fri Sep 07 11:42:51 2012 -0400
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2012 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.common.storage;
+
+public interface Query {
+
+    enum Criteria {
+        EQUALS,
+        NOT_EQUAL_TO,
+        GREATER_THAN,
+        GREATER_THAN_OR_EQUAL_TO,
+        LESS_THAN,
+        LESS_THAN_OR_EQUAL_TO,
+    }
+
+    Query from(Category category);
+
+    <T> Query where(Key<T> key, Criteria criteria, T value);
+
+}
--- a/common/core/src/main/java/com/redhat/thermostat/common/storage/Storage.java	Fri Sep 07 17:22:36 2012 +0200
+++ b/common/core/src/main/java/com/redhat/thermostat/common/storage/Storage.java	Fri Sep 07 11:42:51 2012 -0400
@@ -67,9 +67,9 @@
      */
     public abstract void purge();
     
-    public abstract Cursor findAll(Chunk query);
+    public abstract Cursor findAll(Query query);
 
-    public abstract Chunk find(Chunk query);
+    public abstract Chunk find(Query query);
 
     public abstract Cursor findAllFromCategory(Category category);
     
@@ -93,4 +93,6 @@
 
     public abstract InputStream loadFile(String filename);
 
+    public abstract Query createQuery();
+
 }
--- a/common/core/src/main/java/com/redhat/thermostat/common/storage/StorageProvider.java	Fri Sep 07 17:22:36 2012 +0200
+++ b/common/core/src/main/java/com/redhat/thermostat/common/storage/StorageProvider.java	Fri Sep 07 11:42:51 2012 -0400
@@ -39,4 +39,5 @@
 public interface StorageProvider {
 
     Storage createStorage();
+
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/core/src/main/java/com/redhat/thermostat/test/MockQuery.java	Fri Sep 07 11:42:51 2012 -0400
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2012 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.test;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+import com.redhat.thermostat.common.storage.Category;
+import com.redhat.thermostat.common.storage.Key;
+import com.redhat.thermostat.common.storage.Query;
+
+public class MockQuery implements Query {
+
+    public static class WhereClause <T> {
+        public final Key<T> key;
+        public final Criteria criteria;
+        public final T value;
+
+        public WhereClause(Key<T> key, Criteria criteria, T value) {
+            this.key = key;
+            this.criteria = criteria;
+            this.value = value;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj == null) {
+                return false;
+            }
+            if (obj == this) {
+                return true;
+            }
+            if (!(obj instanceof WhereClause)) {
+                return false;
+            }
+            WhereClause<?> other = (WhereClause<?>) obj;
+            return Objects.equals(key, other.key) && Objects.equals(criteria, other.criteria) && Objects.equals(value, other.value);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(key, criteria, value);
+        }
+    }
+
+    private final List<WhereClause<?>> whereClauses = new ArrayList<>();
+    private Category category;
+
+    @Override
+    public MockQuery from(Category category) {
+        this.category = category;
+        return this;
+    }
+
+    @Override
+    public <T> MockQuery where(Key<T> key, Criteria criteria, T value) {
+        whereClauses.add(new WhereClause<>(key, criteria, value));
+        return this;
+    }
+
+    public Category getCategory() {
+        return category;
+    }
+
+    public List<WhereClause<?>> getWhereClauses() {
+        return whereClauses;
+    }
+
+    public int getWhereClausesCount() {
+        return whereClauses.size();
+    }
+
+    public <T> boolean hasWhereClause(Key<T> key, Criteria criteria, T value) {
+        for (WhereClause<?> whereClause: whereClauses) {
+            if (whereClause.equals(new WhereClause<T>(key, criteria, value))) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public boolean hasWhereClauseFor(Key<?> key) {
+        for (WhereClause<?> where : whereClauses) {
+            if (where.key.equals(key)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (obj == this) {
+            return true;
+        }
+        if (!(obj instanceof MockQuery)) {
+            return false;
+        }
+        MockQuery other = (MockQuery) obj;
+        return Objects.equals(category, other.category) && Objects.equals(whereClauses, other.whereClauses);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(category, whereClauses);
+    }
+
+}
--- a/common/core/src/test/java/com/redhat/thermostat/common/dao/CpuStatDAOTest.java	Fri Sep 07 17:22:36 2012 +0200
+++ b/common/core/src/test/java/com/redhat/thermostat/common/dao/CpuStatDAOTest.java	Fri Sep 07 11:42:51 2012 -0400
@@ -38,9 +38,12 @@
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.isA;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -58,8 +61,10 @@
 import com.redhat.thermostat.common.storage.Chunk;
 import com.redhat.thermostat.common.storage.Cursor;
 import com.redhat.thermostat.common.storage.Key;
+import com.redhat.thermostat.common.storage.Query;
 import com.redhat.thermostat.common.storage.Storage;
 import com.redhat.thermostat.common.utils.ArrayUtils;
+import com.redhat.thermostat.test.MockQuery;
 
 public class CpuStatDAOTest {
 
@@ -79,6 +84,7 @@
 
         Cursor cursor = mock(Cursor.class);
         Storage storage = mock(Storage.class);
+        MockQuery query = new MockQuery();
         HostRef hostRef = mock(HostRef.class);
         CpuStatDAO dao = new CpuStatDAOImpl(storage);
 
@@ -90,14 +96,13 @@
 
         when(cursor.hasNext()).thenReturn(true).thenReturn(false);
         when(cursor.next()).thenReturn(chunk);
-        when(storage.findAll(any(Chunk.class))).thenReturn(cursor);
+        when(storage.createQuery()).thenReturn(query);
+        when(storage.findAll(query)).thenReturn(cursor);
         when(hostRef.getAgentId()).thenReturn("system");
 
         List<CpuStat> cpuStats = dao.getLatestCpuStats(hostRef);
 
-        ArgumentCaptor<Chunk> arg = ArgumentCaptor.forClass(Chunk.class);
-        verify(storage).findAll(arg.capture());
-        assertNull(arg.getValue().get(new Key<String>("$where", false)));
+        assertFalse(query.hasWhereClauseFor(Key.TIMESTAMP));
 
         assertEquals(1, cpuStats.size());
         CpuStat stat = cpuStats.get(0);
@@ -111,6 +116,7 @@
 
         Cursor cursor = mock(Cursor.class);
         Storage storage = mock(Storage.class);
+        MockQuery query = new MockQuery();
         HostRef hostRef = mock(HostRef.class);
 
         CpuStatDAO dao = new CpuStatDAOImpl(storage);
@@ -119,18 +125,18 @@
         chunk.put(Key.TIMESTAMP, 1234L);
         chunk.put(CpuStatDAO.cpuLoadKey, Arrays.asList(5.0));
 
-
         when(cursor.hasNext()).thenReturn(true).thenReturn(false);
         when(cursor.next()).thenReturn(chunk);
-        when(storage.findAll(any(Chunk.class))).thenReturn(cursor);
+        when(storage.createQuery()).thenReturn(query);
+        when(storage.findAll(any(Query.class))).thenReturn(cursor);
         when(hostRef.getAgentId()).thenReturn("system");
 
         dao.getLatestCpuStats(hostRef);
         dao.getLatestCpuStats(hostRef);
 
-        ArgumentCaptor<Chunk> arg = ArgumentCaptor.forClass(Chunk.class);
-        verify(storage, times(2)).findAll(arg.capture());
-        assertEquals("this.timestamp > 1234", arg.getValue().get(Key.WHERE));
+        verify(storage, times(2)).findAll(query);
+
+        query.hasWhereClauseFor(Key.TIMESTAMP);
     }
 
     @Test
--- a/common/core/src/test/java/com/redhat/thermostat/common/dao/HeapDAOTest.java	Fri Sep 07 17:22:36 2012 +0200
+++ b/common/core/src/test/java/com/redhat/thermostat/common/dao/HeapDAOTest.java	Fri Sep 07 11:42:51 2012 -0400
@@ -61,6 +61,8 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 
 import com.redhat.thermostat.common.heap.HistogramRecord;
 import com.redhat.thermostat.common.heap.ObjectHistogram;
@@ -69,7 +71,10 @@
 import com.redhat.thermostat.common.storage.Chunk;
 import com.redhat.thermostat.common.storage.Cursor;
 import com.redhat.thermostat.common.storage.Key;
+import com.redhat.thermostat.common.storage.Query;
 import com.redhat.thermostat.common.storage.Storage;
+import com.redhat.thermostat.common.storage.Query.Criteria;
+import com.redhat.thermostat.test.MockQuery;
 import com.sun.tools.hat.internal.model.JavaClass;
 import com.sun.tools.hat.internal.model.JavaHeapObject;
 
@@ -86,6 +91,13 @@
     @Before
     public void setUp() throws IOException {
         storage = mock(Storage.class);
+        when(storage.createQuery()).then(new Answer<Query>() {
+            @Override
+            public Query answer(InvocationOnMock invocation) throws Throwable {
+                return new MockQuery();
+            }
+        });
+
         dao = new HeapDAOImpl(storage);
         
         heapInfo = new HeapInfo(123, 12345);
@@ -98,9 +110,11 @@
         histogramData = createHistogramData();
 
         // Setup for reading data from DB.
-        Chunk findAllQuery = new Chunk(HeapDAO.heapInfoCategory, false);
-        findAllQuery.put(Key.AGENT_ID, "123");
-        findAllQuery.put(Key.VM_ID, 234);
+        MockQuery findAllQuery = new MockQuery()
+            .from(HeapDAO.heapInfoCategory)
+            .where(Key.AGENT_ID, Criteria.EQUALS, "123")
+            .where(Key.VM_ID, Criteria.EQUALS, 234);
+
         Cursor cursor = mock(Cursor.class);
         Chunk info1 = new Chunk(HeapDAO.heapInfoCategory, false);
         info1.put(Key.AGENT_ID, "123");
@@ -124,19 +138,24 @@
         when(storage.loadFile("test-heap")).thenReturn(new ByteArrayInputStream(data));
         when(storage.loadFile("test-histo")).thenReturn(histogramData);
 
+        // We dont check for AGENT_ID. That's enforced/added/checked by Storage
+
         // Prepare queries for read-back of _id in putHeapInfo() tests.
-        Chunk heap1query = new Chunk(HeapDAO.heapInfoCategory, false);
-        heap1query.put(Key.VM_ID, 123);
-        heap1query.put(Key.TIMESTAMP, 12345l);
-        heap1query.put(HeapDAO.heapDumpIdKey, "heapdump-987-123-12345");
-        heap1query.put(HeapDAO.histogramIdKey, "histogram-987-123-12345");
+        MockQuery heap1query = new MockQuery()
+            .from(HeapDAO.heapInfoCategory)
+            .where(Key.VM_ID, Criteria.EQUALS, 123)
+            .where(Key.TIMESTAMP, Criteria.EQUALS, 12345l)
+            .where(HeapDAO.heapDumpIdKey, Criteria.EQUALS, "heapdump-987-123-12345")
+            .where(HeapDAO.histogramIdKey, Criteria.EQUALS, "histogram-987-123-12345");
         Chunk heap1 = new Chunk(HeapDAO.heapInfoCategory, false);
         heap1.put(Key.ID, "id1");
         when(storage.find(heap1query)).thenReturn(heap1);
 
-        Chunk heap2query = new Chunk(HeapDAO.heapInfoCategory, false);
-        heap2query.put(Key.VM_ID, 123);
-        heap2query.put(Key.TIMESTAMP, 12345l);
+        MockQuery heap2query = new MockQuery()
+            .from(HeapDAO.heapInfoCategory)
+            .where(Key.VM_ID, Criteria.EQUALS, 123)
+            .where(Key.TIMESTAMP, Criteria.EQUALS, 12345l);
+
         Chunk heap2 = new Chunk(HeapDAO.heapInfoCategory, false);
         heap2.put(Key.ID, "id2");
         when(storage.find(heap2query)).thenReturn(heap2);
@@ -286,7 +305,8 @@
     @Test
     public void testInvalidHeapId() throws IOException {
         storage = mock(Storage.class);
-        when(storage.find(isA(Chunk.class))).thenThrow(new IllegalArgumentException("invalid ObjectId"));
+        when(storage.createQuery()).thenReturn(new MockQuery());
+        when(storage.find(isA(Query.class))).thenThrow(new IllegalArgumentException("invalid ObjectId"));
         dao = new HeapDAOImpl(storage);
         heapInfo = dao.getHeapInfo("some-random-heap-id");
         assertTrue(heapInfo == null);
--- a/common/core/src/test/java/com/redhat/thermostat/common/dao/HostInfoDAOTest.java	Fri Sep 07 17:22:36 2012 +0200
+++ b/common/core/src/test/java/com/redhat/thermostat/common/dao/HostInfoDAOTest.java	Fri Sep 07 11:42:51 2012 -0400
@@ -44,6 +44,8 @@
 
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.mock;
@@ -57,7 +59,9 @@
 import com.redhat.thermostat.common.storage.Chunk;
 import com.redhat.thermostat.common.storage.Cursor;
 import com.redhat.thermostat.common.storage.Key;
+import com.redhat.thermostat.common.storage.Query;
 import com.redhat.thermostat.common.storage.Storage;
+import com.redhat.thermostat.test.MockQuery;
 
 public class HostInfoDAOTest {
 
@@ -94,7 +98,8 @@
         chunk.put(HostInfoDAO.hostMemoryTotalKey, MEMORY_TOTAL);
 
         Storage storage = mock(Storage.class);
-        when(storage.find(any(Chunk.class))).thenReturn(chunk);
+        when(storage.createQuery()).thenReturn(new MockQuery());
+        when(storage.find(any(Query.class))).thenReturn(chunk);
 
         HostInfo info = new HostInfoDAOImpl(storage).getHostInfo(new HostRef("some uid", HOST_NAME));
         assertNotNull(info);
@@ -129,8 +134,14 @@
         when(cursor.next()).thenReturn(hostConfig);
 
         Storage storage = mock(Storage.class);
+        when(storage.createQuery()).then(new Answer<Query>() {
+            @Override
+            public Query answer(InvocationOnMock invocation) throws Throwable {
+                return new MockQuery();
+            }
+        });
         when(storage.findAllFromCategory(HostInfoDAO.hostInfoCategory)).thenReturn(cursor);
-        when(storage.findAll(any(Chunk.class))).thenReturn(cursor);
+        when(storage.findAll(any(Query.class))).thenReturn(cursor);
         
         return storage;
     }
@@ -166,8 +177,14 @@
         when(cursor.next()).thenReturn(hostConfig1).thenReturn(hostConfig2).thenReturn(hostConfig3);
 
         Storage storage = mock(Storage.class);
+        when(storage.createQuery()).then(new Answer<Query>() {
+            @Override
+            public Query answer(InvocationOnMock invocation) throws Throwable {
+                return new MockQuery();
+            }
+        });
         when(storage.findAllFromCategory(HostInfoDAO.hostInfoCategory)).thenReturn(cursor);
-        when(storage.findAll(any(Chunk.class))).thenReturn(cursor);
+        when(storage.findAll(any(Query.class))).thenReturn(cursor);
         
         return storage;
     }
@@ -211,7 +228,7 @@
         // cursor 3 from the above storage should not be used
         assertEquals(1, hosts.size());
         assertTrue(hosts.contains(new HostRef("123", "fluffhost1")));
-        verify(storage, times(2)).findAll(any(Chunk.class));
+        verify(storage, times(2)).findAll(any(Query.class));
     }
     
     private Storage setupStorageForSingleAliveHost() {
@@ -247,7 +264,13 @@
         // storage
         
         Storage storage = mock(Storage.class);
-        when(storage.findAll(any(Chunk.class))).thenReturn(cursor1).thenReturn(cursor2).thenReturn(cursor3);
+        when(storage.createQuery()).then(new Answer<Query>() {
+            @Override
+            public Query answer(InvocationOnMock invocation) throws Throwable {
+                return new MockQuery();
+            }
+        });
+        when(storage.findAll(any(Query.class))).thenReturn(cursor1).thenReturn(cursor2).thenReturn(cursor3);
         
         return storage;
     }
@@ -264,7 +287,7 @@
         assertTrue(hosts.contains(new HostRef("123", "fluffhost1")));
         assertTrue(hosts.contains(new HostRef("456", "fluffhost2")));
         assertTrue(hosts.contains(new HostRef("678", "fluffhost3")));
-        verify(storage, times(4)).findAll(any(Chunk.class));
+        verify(storage, times(4)).findAll(any(Query.class));
     }
     
     private Storage setupStorageForSingleAliveHost3() {
@@ -316,7 +339,13 @@
         // storage
         
         Storage storage = mock(Storage.class);
-        when(storage.findAll(any(Chunk.class))).thenReturn(cursor1).
+        when(storage.createQuery()).then(new Answer<Query>() {
+            @Override
+            public Query answer(InvocationOnMock invocation) throws Throwable {
+                return new MockQuery();
+            }
+        });
+        when(storage.findAll(any(Query.class))).thenReturn(cursor1).
                                                 thenReturn(cursor2).
                                                 thenReturn(cursor3).
                                                 thenReturn(cursor4);
--- a/common/core/src/test/java/com/redhat/thermostat/common/dao/HostLatestPojoListGetterTest.java	Fri Sep 07 17:22:36 2012 +0200
+++ b/common/core/src/test/java/com/redhat/thermostat/common/dao/HostLatestPojoListGetterTest.java	Fri Sep 07 11:42:51 2012 -0400
@@ -49,18 +49,22 @@
 import com.redhat.thermostat.common.storage.Chunk;
 import com.redhat.thermostat.common.storage.Cursor;
 import com.redhat.thermostat.common.storage.Key;
+import com.redhat.thermostat.common.storage.Query;
 import com.redhat.thermostat.common.storage.Storage;
+import com.redhat.thermostat.common.storage.Query.Criteria;
+import com.redhat.thermostat.test.MockQuery;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.isA;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
-import static org.mockito.Mockito.any;
 
 public class HostLatestPojoListGetterTest {
     private static final String AGENT_ID = "agentid";
@@ -111,44 +115,52 @@
     @Test
     public void testBuildQuery() {
         Storage storage = mock(Storage.class);
+        MockQuery query = new MockQuery();
+        when (storage.createQuery()).thenReturn(query);
+
         HostLatestPojoListGetter<CpuStat> getter = new HostLatestPojoListGetter<>(storage, cat, converter, ref);
-        Chunk query = getter.buildQuery();
+        query = (MockQuery) getter.buildQuery();
 
         assertNotNull(query);
         assertEquals(cat, query.getCategory());
-        assertEquals(1, query.getKeys().size());
-        assertTrue(query.getKeys().contains(Key.AGENT_ID));
-        assertFalse(query.getKeys().contains(Key.WHERE));
-        assertEquals(AGENT_ID, query.get(Key.AGENT_ID));
+        assertEquals(1, query.getWhereClausesCount());
+        assertFalse(query.hasWhereClauseFor(Key.TIMESTAMP));
+        assertTrue(query.hasWhereClause(Key.AGENT_ID, Criteria.EQUALS, AGENT_ID));
     }
 
     @Test
     public void testBuildQueryWithSince() {
         Storage storage = mock(Storage.class);
+        MockQuery query = new MockQuery();
+        when (storage.createQuery()).thenReturn(query);
+
         HostLatestPojoListGetter<CpuStat> getter = new HostLatestPojoListGetter<>(storage, cat, converter, ref, 123);
-        Chunk query = getter.buildQuery();
+        query = (MockQuery) getter.buildQuery();
 
         assertNotNull(query);
         assertEquals(cat, query.getCategory());
-        assertEquals(2, query.getKeys().size());
-        assertTrue(query.getKeys().contains(Key.AGENT_ID));
-        assertEquals("this.timestamp > 123", query.get(Key.WHERE));
-        assertEquals(AGENT_ID, query.get(Key.AGENT_ID));
+        assertEquals(2, query.getWhereClausesCount());
+        assertTrue(query.hasWhereClause(Key.TIMESTAMP, Criteria.GREATER_THAN, 123l));
+        assertTrue(query.hasWhereClause(Key.AGENT_ID, Criteria.EQUALS, AGENT_ID));
     }
 
     @Test
     public void testBuildQueryPopulatesUpdateTimes() {
         Storage storage = mock(Storage.class);
+        MockQuery ignored = new MockQuery();
+        MockQuery query = new MockQuery();
+        when(storage.createQuery()).thenReturn(ignored).thenReturn(query);
+
         HostLatestPojoListGetter<CpuStat> getter = new HostLatestPojoListGetter<>(storage, cat, converter, ref);
-        getter.buildQuery(); // Ignore first return value.
-        Chunk query = getter.buildQuery();
+        ignored = (MockQuery) getter.buildQuery(); // Ignore first return value.
+
+        query = (MockQuery) getter.buildQuery();
 
         assertNotNull(query);
         assertEquals(cat, query.getCategory());
-        assertEquals(2, query.getKeys().size());
-        assertTrue(query.getKeys().contains(Key.AGENT_ID));
-        assertTrue(query.getKeys().contains(Key.WHERE));
-        assertEquals("this.timestamp > " + Long.MIN_VALUE, query.get(Key.WHERE));
+        assertEquals(2, query.getWhereClausesCount());
+        assertTrue(query.hasWhereClause(Key.AGENT_ID, Criteria.EQUALS, AGENT_ID));
+        assertTrue(query.hasWhereClause(Key.TIMESTAMP, Criteria.GREATER_THAN, Long.MIN_VALUE));
     }
 
     @Test
@@ -158,7 +170,9 @@
         when(cursor.next()).thenReturn(result1).thenReturn(result2).thenReturn(null);
 
         Storage storage = mock(Storage.class);
-        when(storage.findAll(any(Chunk.class))).thenReturn(cursor);
+        Query query = new MockQuery();
+        when(storage.createQuery()).thenReturn(query);
+        when(storage.findAll(query)).thenReturn(cursor);
 
         HostLatestPojoListGetter<CpuStat> getter = new HostLatestPojoListGetter<>(storage, cat, converter, ref);
 
@@ -185,21 +199,20 @@
         when(cursor2.next()).thenReturn(result3);
 
         Storage storage = mock(Storage.class);
-        when(storage.findAll(any(Chunk.class))).thenReturn(cursor1);
+        MockQuery firstQuery = new MockQuery();
+        MockQuery secondQuery = new MockQuery();
+        when(storage.createQuery()).thenReturn(firstQuery).thenReturn(secondQuery);
+
+        when(storage.findAll(isA(Query.class))).thenReturn(cursor1);
 
         HostLatestPojoListGetter<CpuStat> getter = new HostLatestPojoListGetter<>(storage, cat, converter, ref);
         getter.getLatest();
         getter.getLatest();
 
-        ArgumentCaptor<Chunk> arg = ArgumentCaptor.forClass(Chunk.class);
-        verify(storage, times(2)).findAll(arg.capture());
-        List<Chunk> queries = arg.getAllValues();
+        verify(storage, times(2)).findAll(isA(Query.class));
 
-        assertEquals(2, queries.size());
-        Chunk query = queries.get(1);
-        assertNotNull(query);
-        assertEquals(AGENT_ID, query.get(Key.AGENT_ID));
-        assertEquals("this.timestamp > " + t2, query.get(Key.WHERE));
+        assertTrue(secondQuery.hasWhereClause(Key.AGENT_ID, Criteria.EQUALS, AGENT_ID));
+        assertTrue(secondQuery.hasWhereClause(Key.TIMESTAMP, Criteria.GREATER_THAN, t2));
     }
 
     @After
--- a/common/core/src/test/java/com/redhat/thermostat/common/dao/MemoryStatDAOTest.java	Fri Sep 07 17:22:36 2012 +0200
+++ b/common/core/src/test/java/com/redhat/thermostat/common/dao/MemoryStatDAOTest.java	Fri Sep 07 11:42:51 2012 -0400
@@ -37,6 +37,7 @@
 package com.redhat.thermostat.common.dao;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
@@ -50,13 +51,18 @@
 
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 
 import com.redhat.thermostat.common.model.MemoryStat;
 import com.redhat.thermostat.common.storage.Category;
 import com.redhat.thermostat.common.storage.Chunk;
 import com.redhat.thermostat.common.storage.Cursor;
 import com.redhat.thermostat.common.storage.Key;
+import com.redhat.thermostat.common.storage.Query;
+import com.redhat.thermostat.common.storage.Query.Criteria;
 import com.redhat.thermostat.common.storage.Storage;
+import com.redhat.thermostat.test.MockQuery;
 
 public class MemoryStatDAOTest {
 
@@ -105,7 +111,13 @@
         when(cursor.next()).thenReturn(chunk);
 
         Storage storage = mock(Storage.class);
-        when(storage.findAll(any(Chunk.class))).thenReturn(cursor);
+        when(storage.createQuery()).then(new Answer<Query>() {
+            @Override
+            public Query answer(InvocationOnMock invocation) throws Throwable {
+                return new MockQuery();
+            }
+        });
+        when(storage.findAll(any(Query.class))).thenReturn(cursor);
 
         HostRef hostRef = mock(HostRef.class);
         when(hostRef.getAgentId()).thenReturn("system");
@@ -113,9 +125,9 @@
         MemoryStatDAO dao = new MemoryStatDAOImpl(storage);
         List<MemoryStat> memoryStats = dao.getLatestMemoryStats(hostRef);
 
-        ArgumentCaptor<Chunk> arg = ArgumentCaptor.forClass(Chunk.class);
+        ArgumentCaptor<MockQuery> arg = ArgumentCaptor.forClass(MockQuery.class);
         verify(storage).findAll(arg.capture());
-        assertNull(arg.getValue().get(new Key<String>("$where", false)));
+        assertFalse(arg.getValue().hasWhereClauseFor(Key.TIMESTAMP));
 
         assertEquals(1, memoryStats.size());
         MemoryStat stat = memoryStats.get(0);
@@ -148,7 +160,13 @@
         when(cursor.next()).thenReturn(chunk);
 
         Storage storage = mock(Storage.class);
-        when(storage.findAll(any(Chunk.class))).thenReturn(cursor);
+        when(storage.createQuery()).then(new Answer<Query>() {
+            @Override
+            public Query answer(InvocationOnMock invocation) throws Throwable {
+                return new MockQuery();
+            }
+        });
+        when(storage.findAll(any(Query.class))).thenReturn(cursor);
 
         HostRef hostRef = mock(HostRef.class);
         when(hostRef.getAgentId()).thenReturn("system");
@@ -157,9 +175,9 @@
         dao.getLatestMemoryStats(hostRef);
         dao.getLatestMemoryStats(hostRef);
 
-        ArgumentCaptor<Chunk> arg = ArgumentCaptor.forClass(Chunk.class);
+        ArgumentCaptor<MockQuery> arg = ArgumentCaptor.forClass(MockQuery.class);
         verify(storage, times(2)).findAll(arg.capture());
-        assertEquals("this.timestamp > 1", arg.getValue().get(new Key<String>("$where", false)));
+        assertTrue(arg.getValue().hasWhereClause(Key.TIMESTAMP, Criteria.GREATER_THAN, 1l));
     }
 
     @Test
--- a/common/core/src/test/java/com/redhat/thermostat/common/dao/NetworkInterfaceInfoDAOTest.java	Fri Sep 07 17:22:36 2012 +0200
+++ b/common/core/src/test/java/com/redhat/thermostat/common/dao/NetworkInterfaceInfoDAOTest.java	Fri Sep 07 11:42:51 2012 -0400
@@ -37,6 +37,7 @@
 package com.redhat.thermostat.common.dao;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
@@ -56,6 +57,7 @@
 import com.redhat.thermostat.common.storage.Cursor;
 import com.redhat.thermostat.common.storage.Key;
 import com.redhat.thermostat.common.storage.Storage;
+import com.redhat.thermostat.test.MockQuery;
 
 public class NetworkInterfaceInfoDAOTest {
 
@@ -90,7 +92,9 @@
         when(cursor.next()).thenReturn(chunk);
 
         Storage storage = mock(Storage.class);
-        when(storage.findAll(any(Chunk.class))).thenReturn(cursor);
+        MockQuery query = new MockQuery();
+        when(storage.createQuery()).thenReturn(query);
+        when(storage.findAll(query)).thenReturn(cursor);
 
         HostRef hostRef = mock(HostRef.class);
         when(hostRef.getAgentId()).thenReturn("system");
@@ -98,9 +102,7 @@
         NetworkInterfaceInfoDAO dao = new NetworkInterfaceInfoDAOImpl(storage);
         List<NetworkInterfaceInfo> netInfo = dao.getNetworkInterfaces(hostRef);
 
-        ArgumentCaptor<Chunk> arg = ArgumentCaptor.forClass(Chunk.class);
-        verify(storage).findAll(arg.capture());
-        assertNull(arg.getValue().get(new Key<String>("$where", false)));
+        assertFalse(query.hasWhereClauseFor(Key.TIMESTAMP));
 
         assertEquals(1, netInfo.size());
 
--- a/common/core/src/test/java/com/redhat/thermostat/common/dao/VmClassStatDAOTest.java	Fri Sep 07 17:22:36 2012 +0200
+++ b/common/core/src/test/java/com/redhat/thermostat/common/dao/VmClassStatDAOTest.java	Fri Sep 07 11:42:51 2012 -0400
@@ -37,6 +37,7 @@
 package com.redhat.thermostat.common.dao;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
@@ -50,12 +51,17 @@
 
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 
 import com.redhat.thermostat.common.model.VmClassStat;
 import com.redhat.thermostat.common.storage.Chunk;
 import com.redhat.thermostat.common.storage.Cursor;
 import com.redhat.thermostat.common.storage.Key;
+import com.redhat.thermostat.common.storage.Query;
+import com.redhat.thermostat.common.storage.Query.Criteria;
 import com.redhat.thermostat.common.storage.Storage;
+import com.redhat.thermostat.test.MockQuery;
 
 public class VmClassStatDAOTest {
 
@@ -85,7 +91,13 @@
         when(cursor.next()).thenReturn(chunk);
 
         Storage storage = mock(Storage.class);
-        when(storage.findAll(any(Chunk.class))).thenReturn(cursor);
+        when(storage.createQuery()).then(new Answer<Query>() {
+            @Override
+            public Query answer(InvocationOnMock invocation) throws Throwable {
+                return new MockQuery();
+            }
+        });
+        when(storage.findAll(any(Query.class))).thenReturn(cursor);
 
         HostRef hostRef = mock(HostRef.class);
         when(hostRef.getAgentId()).thenReturn("system");
@@ -98,9 +110,9 @@
         VmClassStatDAO dao = new VmClassStatDAOImpl(storage);
         List<VmClassStat> vmClassStats = dao.getLatestClassStats(vmRef);
 
-        ArgumentCaptor<Chunk> arg = ArgumentCaptor.forClass(Chunk.class);
+        ArgumentCaptor<MockQuery> arg = ArgumentCaptor.forClass(MockQuery.class);
         verify(storage).findAll(arg.capture());
-        assertNull(arg.getValue().get(new Key<String>("$where", false)));
+        assertFalse(arg.getValue().hasWhereClauseFor(Key.TIMESTAMP));
 
         assertEquals(1, vmClassStats.size());
         VmClassStat stat = vmClassStats.get(0);
@@ -119,7 +131,13 @@
         when(cursor.next()).thenReturn(chunk);
 
         Storage storage = mock(Storage.class);
-        when(storage.findAll(any(Chunk.class))).thenReturn(cursor);
+        when(storage.createQuery()).then(new Answer<Query>() {
+            @Override
+            public Query answer(InvocationOnMock invocation) throws Throwable {
+                return new MockQuery();
+            }
+        });
+        when(storage.findAll(any(Query.class))).thenReturn(cursor);
 
         HostRef hostRef = mock(HostRef.class);
         when(hostRef.getAgentId()).thenReturn("system");
@@ -133,9 +151,9 @@
         dao.getLatestClassStats(vmRef);
 
         dao.getLatestClassStats(vmRef);
-        ArgumentCaptor<Chunk> arg = ArgumentCaptor.forClass(Chunk.class);
+        ArgumentCaptor<MockQuery> arg = ArgumentCaptor.forClass(MockQuery.class);
         verify(storage, times(2)).findAll(arg.capture());
-        assertEquals("this.timestamp > 1234", arg.getValue().get(new Key<String>("$where", false)));
+        assertTrue(arg.getValue().hasWhereClause(Key.TIMESTAMP, Criteria.GREATER_THAN, 1234l));
     }
 
     private Chunk getChunk() {
@@ -159,8 +177,8 @@
         Chunk chunk = arg.getValue();
 
         assertEquals(VmClassStatDAO.vmClassStatsCategory, chunk.getCategory());
-        assertEquals((Long) TIMESTAMP, chunk.get(Key.TIMESTAMP));
-        assertEquals((Integer) VM_ID, chunk.get(Key.VM_ID));
-        assertEquals((Long) LOADED_CLASSES, chunk.get(VmClassStatDAO.loadedClassesKey));
+        assertEquals(TIMESTAMP, chunk.get(Key.TIMESTAMP));
+        assertEquals(VM_ID, chunk.get(Key.VM_ID));
+        assertEquals(LOADED_CLASSES, chunk.get(VmClassStatDAO.loadedClassesKey));
     }
 }
--- a/common/core/src/test/java/com/redhat/thermostat/common/dao/VmCpuStatDAOTest.java	Fri Sep 07 17:22:36 2012 +0200
+++ b/common/core/src/test/java/com/redhat/thermostat/common/dao/VmCpuStatDAOTest.java	Fri Sep 07 11:42:51 2012 -0400
@@ -37,6 +37,7 @@
 package com.redhat.thermostat.common.dao;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
@@ -51,12 +52,17 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 
 import com.redhat.thermostat.common.model.VmCpuStat;
 import com.redhat.thermostat.common.storage.Chunk;
 import com.redhat.thermostat.common.storage.Cursor;
 import com.redhat.thermostat.common.storage.Key;
+import com.redhat.thermostat.common.storage.Query;
 import com.redhat.thermostat.common.storage.Storage;
+import com.redhat.thermostat.common.storage.Query.Criteria;
+import com.redhat.thermostat.test.MockQuery;
 
 public class VmCpuStatDAOTest {
 
@@ -95,7 +101,13 @@
         when(cursor.next()).thenReturn(chunk);
 
         Storage storage = mock(Storage.class);
-        when(storage.findAll(any(Chunk.class))).thenReturn(cursor);
+        when(storage.createQuery()).then(new Answer<Query>() {
+            @Override
+            public Query answer(InvocationOnMock invocation) throws Throwable {
+                return new MockQuery();
+            }
+        });
+        when(storage.findAll(any(Query.class))).thenReturn(cursor);
 
         HostRef hostRef = mock(HostRef.class);
         when(hostRef.getAgentId()).thenReturn("system");
@@ -108,9 +120,9 @@
         VmCpuStatDAO dao = new VmCpuStatDAOImpl(storage);
         List<VmCpuStat> vmCpuStats = dao.getLatestVmCpuStats(vmRef);
 
-        ArgumentCaptor<Chunk> arg = ArgumentCaptor.forClass(Chunk.class);
+        ArgumentCaptor<MockQuery> arg = ArgumentCaptor.forClass(MockQuery.class);
         verify(storage).findAll(arg.capture());
-        assertNull(arg.getValue().get(new Key<String>("$where", false)));
+        assertFalse(arg.getValue().hasWhereClauseFor(Key.TIMESTAMP));
 
         assertEquals(1, vmCpuStats.size());
         VmCpuStat stat = vmCpuStats.get(0);
@@ -127,7 +139,13 @@
         when(cursor.next()).thenReturn(chunk);
 
         Storage storage = mock(Storage.class);
-        when(storage.findAll(any(Chunk.class))).thenReturn(cursor);
+        when(storage.createQuery()).then(new Answer<Query>() {
+            @Override
+            public Query answer(InvocationOnMock invocation) throws Throwable {
+                return new MockQuery();
+            }
+        });
+        when(storage.findAll(any(Query.class))).thenReturn(cursor);
 
         HostRef hostRef = mock(HostRef.class);
         when(hostRef.getAgentId()).thenReturn("system");
@@ -140,9 +158,9 @@
         dao.getLatestVmCpuStats(vmRef);
 
         dao.getLatestVmCpuStats(vmRef);
-        ArgumentCaptor<Chunk> arg = ArgumentCaptor.forClass(Chunk.class);
+        ArgumentCaptor<MockQuery> arg = ArgumentCaptor.forClass(MockQuery.class);
         verify(storage, times(2)).findAll(arg.capture());
-        assertEquals("this.timestamp > 1234", arg.getValue().get(new Key<String>("$where", false)));
+        assertTrue(arg.getValue().hasWhereClause(Key.TIMESTAMP, Criteria.GREATER_THAN, 1234l));
     }
 
     @Test
--- a/common/core/src/test/java/com/redhat/thermostat/common/dao/VmGcStatDAOTest.java	Fri Sep 07 17:22:36 2012 +0200
+++ b/common/core/src/test/java/com/redhat/thermostat/common/dao/VmGcStatDAOTest.java	Fri Sep 07 11:42:51 2012 -0400
@@ -37,6 +37,7 @@
 package com.redhat.thermostat.common.dao;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
@@ -51,12 +52,17 @@
 
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 
 import com.redhat.thermostat.common.model.VmGcStat;
 import com.redhat.thermostat.common.storage.Chunk;
 import com.redhat.thermostat.common.storage.Cursor;
 import com.redhat.thermostat.common.storage.Key;
+import com.redhat.thermostat.common.storage.Query;
 import com.redhat.thermostat.common.storage.Storage;
+import com.redhat.thermostat.common.storage.Query.Criteria;
+import com.redhat.thermostat.test.MockQuery;
 
 public class VmGcStatDAOTest {
 
@@ -94,7 +100,8 @@
         when(cursor.next()).thenReturn(chunk);
 
         Storage storage = mock(Storage.class);
-        when(storage.findAll(any(Chunk.class))).thenReturn(cursor);
+        when(storage.createQuery()).thenReturn(new MockQuery());
+        when(storage.findAll(any(Query.class))).thenReturn(cursor);
 
         HostRef hostRef = mock(HostRef.class);
         when(hostRef.getAgentId()).thenReturn("system");
@@ -107,9 +114,9 @@
         VmGcStatDAO dao = new VmGcStatDAOImpl(storage);
         List<VmGcStat> vmGcStats = dao.getLatestVmGcStats(vmRef);
 
-        ArgumentCaptor<Chunk> arg = ArgumentCaptor.forClass(Chunk.class);
+        ArgumentCaptor<MockQuery> arg = ArgumentCaptor.forClass(MockQuery.class);
         verify(storage).findAll(arg.capture());
-        assertNull(arg.getValue().get(new Key<String>("$where", false)));
+        assertFalse(arg.getValue().hasWhereClauseFor(Key.TIMESTAMP));
 
         assertEquals(1, vmGcStats.size());
         VmGcStat stat = vmGcStats.get(0);
@@ -135,7 +142,14 @@
         when(cursor.next()).thenReturn(chunk);
 
         Storage storage = mock(Storage.class);
-        when(storage.findAll(any(Chunk.class))).thenReturn(cursor);
+
+        when(storage.createQuery()).then(new Answer<MockQuery>() {
+            @Override
+            public MockQuery answer(InvocationOnMock invocation) throws Throwable {
+                return new MockQuery();
+            }
+        });
+        when(storage.findAll(any(Query.class))).thenReturn(cursor);
 
         HostRef hostRef = mock(HostRef.class);
         when(hostRef.getAgentId()).thenReturn("system");
@@ -148,9 +162,10 @@
         dao.getLatestVmGcStats(vmRef);
 
         dao.getLatestVmGcStats(vmRef);
-        ArgumentCaptor<Chunk> arg = ArgumentCaptor.forClass(Chunk.class);
+        ArgumentCaptor<MockQuery> arg = ArgumentCaptor.forClass(MockQuery.class);
         verify(storage, times(2)).findAll(arg.capture());
-        assertEquals("this.timestamp > 456", arg.getValue().get(new Key<String>("$where", false)));
+        MockQuery query = arg.getValue();
+        assertTrue(query.hasWhereClause(Key.TIMESTAMP, Criteria.GREATER_THAN, 456l));
     }
 
     @Test
--- a/common/core/src/test/java/com/redhat/thermostat/common/dao/VmInfoDAOTest.java	Fri Sep 07 17:22:36 2012 +0200
+++ b/common/core/src/test/java/com/redhat/thermostat/common/dao/VmInfoDAOTest.java	Fri Sep 07 11:42:51 2012 -0400
@@ -37,8 +37,6 @@
 package com.redhat.thermostat.common.dao;
 
 import static org.junit.Assert.*;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
@@ -50,7 +48,6 @@
 import java.util.List;
 import java.util.Map;
 
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
@@ -60,7 +57,10 @@
 import com.redhat.thermostat.common.storage.Chunk;
 import com.redhat.thermostat.common.storage.Cursor;
 import com.redhat.thermostat.common.storage.Key;
+import com.redhat.thermostat.common.storage.Query;
+import com.redhat.thermostat.common.storage.Query.Criteria;
 import com.redhat.thermostat.common.storage.Storage;
+import com.redhat.thermostat.test.MockQuery;
 
 public class VmInfoDAOTest {
 
@@ -78,7 +78,6 @@
     private Map<String, String> props;
     private Map<String, String> env;
     private List<String> libs;
-    private VmInfoDAO dao;
 
     @Before
     public void setUp() {
@@ -96,13 +95,6 @@
         props = new HashMap<>();
         env = new HashMap<>();
         libs = new ArrayList<>();
-        Storage storage = setupStorageForSingleVM();
-        dao = new VmInfoDAOImpl(storage);
-    }
-
-    @After
-    public void tearDown() {
-        dao = null;
     }
 
     @Test
@@ -148,7 +140,9 @@
         chunk.put(VmInfoDAO.librariesKey, libs);
 
         Storage storage = mock(Storage.class);
-        when(storage.find(any(Chunk.class))).thenReturn(chunk);
+        Query query = new MockQuery();
+        when(storage.createQuery()).thenReturn(query);
+        when(storage.find(query)).thenReturn(chunk);
 
         HostRef hostRef = mock(HostRef.class);
         when(hostRef.getAgentId()).thenReturn("system");
@@ -181,6 +175,8 @@
     public void testGetVmInfoUnknownVM() {
 
         Storage storage = mock(Storage.class);
+        Query query = new MockQuery();
+        when(storage.createQuery()).thenReturn(query);
 
         HostRef hostRef = mock(HostRef.class);
         when(hostRef.getAgentId()).thenReturn("system");
@@ -199,37 +195,10 @@
 
     }
 
-    private Storage setupStorageForSingleVM() {
-        Chunk query1 = new Chunk(VmInfoDAO.vmInfoCategory, false);
-        query1.put(Key.AGENT_ID, "123");
-
-        Chunk query2 = new Chunk(VmInfoDAO.vmInfoCategory, false);
-        query2.put(Key.AGENT_ID, "456");
-
-        Chunk vm1 = new Chunk(VmInfoDAO.vmInfoCategory, false);
-        vm1.put(VmInfoDAO.vmIdKey, 123);
-        vm1.put(VmInfoDAO.mainClassKey, "mainClass1");
-
-        Chunk vm2 = new Chunk(VmInfoDAO.vmInfoCategory, false);
-        vm2.put(VmInfoDAO.vmIdKey, 456);
-        vm2.put(VmInfoDAO.mainClassKey, "mainClass2");
-
-        Cursor singleVMCursor = mock(Cursor.class);
-        when(singleVMCursor.hasNext()).thenReturn(true).thenReturn(false);
-        when(singleVMCursor.next()).thenReturn(vm1);
-
-        Cursor multiVMsCursor = mock(Cursor.class);
-        when(multiVMsCursor.hasNext()).thenReturn(true).thenReturn(true).thenReturn(false);
-        when(multiVMsCursor.next()).thenReturn(vm1).thenReturn(vm2);
-
-        Storage storage = mock(Storage.class);
-        when(storage.findAll(query1)).thenReturn(singleVMCursor);
-        when(storage.findAll(query2)).thenReturn(multiVMsCursor);
-        return storage;
-    }
-
     @Test
     public void testSingleVM() {
+        Storage storage = setupStorageForSingleVM();
+        VmInfoDAO dao = new VmInfoDAOImpl(storage);
         HostRef host = new HostRef("123", "fluffhost");
 
         Collection<VmRef> vms = dao.getVMs(host);
@@ -237,8 +206,30 @@
         assertCollection(vms, new VmRef(host, 123, "mainClass1"));
     }
 
+    private Storage setupStorageForSingleVM() {
+      Query expectedQuery = new MockQuery()
+          .from(VmInfoDAO.vmInfoCategory)
+          .where(Key.AGENT_ID, Criteria.EQUALS, "123");
+
+      Chunk vm1 = new Chunk(VmInfoDAO.vmInfoCategory, false);
+      vm1.put(VmInfoDAO.vmIdKey, 123);
+      vm1.put(VmInfoDAO.mainClassKey, "mainClass1");
+
+      Cursor singleVMCursor = mock(Cursor.class);
+      when(singleVMCursor.hasNext()).thenReturn(true).thenReturn(false);
+      when(singleVMCursor.next()).thenReturn(vm1);
+
+      Storage storage = mock(Storage.class);
+      when(storage.createQuery()).thenReturn(new MockQuery());
+      when(storage.findAll(expectedQuery)).thenReturn(singleVMCursor);
+      return storage;
+  }
+
     @Test
     public void testMultiVMs() {
+        Storage storage = setupStorageForMultiVM();
+        VmInfoDAO dao = new VmInfoDAOImpl(storage);
+
         HostRef host = new HostRef("456", "fluffhost");
 
         Collection<VmRef> vms = dao.getVMs(host);
@@ -246,6 +237,29 @@
         assertCollection(vms, new VmRef(host, 123, "mainClass1"), new VmRef(host, 456, "mainClass2"));
     }
 
+    private Storage setupStorageForMultiVM() {
+      Query expectedQuery = new MockQuery()
+          .from(VmInfoDAO.vmInfoCategory)
+          .where(Key.AGENT_ID, Criteria.EQUALS, "456");
+
+      Chunk vm1 = new Chunk(VmInfoDAO.vmInfoCategory, false);
+      vm1.put(VmInfoDAO.vmIdKey, 123);
+      vm1.put(VmInfoDAO.mainClassKey, "mainClass1");
+
+      Chunk vm2 = new Chunk(VmInfoDAO.vmInfoCategory, false);
+      vm2.put(VmInfoDAO.vmIdKey, 456);
+      vm2.put(VmInfoDAO.mainClassKey, "mainClass2");
+
+      Cursor multiVMsCursor = mock(Cursor.class);
+      when(multiVMsCursor.hasNext()).thenReturn(true).thenReturn(true).thenReturn(false);
+      when(multiVMsCursor.next()).thenReturn(vm1).thenReturn(vm2);
+
+      Storage storage = mock(Storage.class);
+      when(storage.createQuery()).thenReturn(new MockQuery());
+      when(storage.findAll(expectedQuery)).thenReturn(multiVMsCursor);
+      return storage;
+  }
+
     private void assertCollection(Collection<VmRef> vms, VmRef... expectedVMs) {
         assertEquals(expectedVMs.length, vms.size());
         for (VmRef expectedVM : expectedVMs) {
--- a/common/core/src/test/java/com/redhat/thermostat/common/dao/VmLatestPojoListGetterTest.java	Fri Sep 07 17:22:36 2012 +0200
+++ b/common/core/src/test/java/com/redhat/thermostat/common/dao/VmLatestPojoListGetterTest.java	Fri Sep 07 11:42:51 2012 -0400
@@ -47,13 +47,17 @@
 import com.redhat.thermostat.common.storage.Chunk;
 import com.redhat.thermostat.common.storage.Cursor;
 import com.redhat.thermostat.common.storage.Key;
+import com.redhat.thermostat.common.storage.Query;
+import com.redhat.thermostat.common.storage.Query.Criteria;
 import com.redhat.thermostat.common.storage.Storage;
+import com.redhat.thermostat.test.MockQuery;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.isA;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -107,49 +111,53 @@
     @Test
     public void testBuildQuery() {
         Storage storage = mock(Storage.class);
+        MockQuery query = new MockQuery();
+        when(storage.createQuery()).thenReturn(query);
+
         VmLatestPojoListGetter<VmClassStat> getter = new VmLatestPojoListGetter<>(storage, cat, converter, vmRef);
-        Chunk query = getter.buildQuery();
+        query = (MockQuery) getter.buildQuery();
 
         assertNotNull(query);
         assertEquals(cat, query.getCategory());
-        assertEquals(2, query.getKeys().size());
-        assertTrue(query.getKeys().contains(Key.AGENT_ID));
-        assertTrue(query.getKeys().contains(Key.VM_ID));
-        assertFalse(query.getKeys().contains(Key.WHERE));
-        assertEquals(AGENT_ID, query.get(Key.AGENT_ID));
-        assertEquals((Integer) VM_PID, query.get(Key.VM_ID));
+        assertEquals(2, query.getWhereClausesCount());
+        assertTrue(query.hasWhereClause(Key.AGENT_ID, Criteria.EQUALS, AGENT_ID));
+        assertTrue(query.hasWhereClause(Key.VM_ID, Criteria.EQUALS, VM_PID));
     }
 
     @Test
     public void testBuildQueryWithSince() {
         Storage storage = mock(Storage.class);
+        MockQuery query = new MockQuery();
+        when(storage.createQuery()).thenReturn(query);
+
         VmLatestPojoListGetter<VmClassStat> getter = new VmLatestPojoListGetter<>(storage, cat, converter, vmRef, 123);
-        Chunk query = getter.buildQuery();
+        query = (MockQuery) getter.buildQuery();
 
         assertNotNull(query);
         assertEquals(cat, query.getCategory());
-        assertEquals(3, query.getKeys().size());
-        assertTrue(query.getKeys().contains(Key.AGENT_ID));
-        assertTrue(query.getKeys().contains(Key.VM_ID));
-        assertEquals("this.timestamp > 123" , query.get(Key.WHERE));
-        assertEquals(AGENT_ID, query.get(Key.AGENT_ID));
-        assertEquals((Integer) VM_PID, query.get(Key.VM_ID));
+        assertEquals(3, query.getWhereClausesCount());
+        assertTrue(query.hasWhereClause(Key.AGENT_ID, Criteria.EQUALS, AGENT_ID));
+        assertTrue(query.hasWhereClause(Key.VM_ID, Criteria.EQUALS, VM_PID));
+        assertTrue(query.hasWhereClause(Key.TIMESTAMP, Criteria.GREATER_THAN, 123l));
     }
 
     @Test
     public void testBuildQueryPopulatesUpdateTimes() {
         Storage storage = mock(Storage.class);
+        MockQuery ignored = new MockQuery();
+        MockQuery query = new MockQuery();
+        when(storage.createQuery()).thenReturn(ignored).thenReturn(query);
+
         VmLatestPojoListGetter<VmClassStat> getter = new VmLatestPojoListGetter<>(storage, cat, converter, vmRef);
         getter.buildQuery(); // Ignore first return value.
-        Chunk query = getter.buildQuery();
+        query = (MockQuery) getter.buildQuery();
 
         assertNotNull(query);
         assertEquals(cat, query.getCategory());
-        assertEquals(3, query.getKeys().size());
-        assertTrue(query.getKeys().contains(Key.AGENT_ID));
-        assertTrue(query.getKeys().contains(Key.VM_ID));
-        assertTrue(query.getKeys().contains(Key.WHERE));
-        assertEquals("this.timestamp > " + Long.MIN_VALUE, query.get(Key.WHERE));
+        assertEquals(3, query.getWhereClausesCount());
+        assertTrue(query.hasWhereClauseFor(Key.AGENT_ID));
+        assertTrue(query.hasWhereClauseFor(Key.VM_ID));
+        assertTrue(query.hasWhereClause(Key.TIMESTAMP, Criteria.GREATER_THAN, Long.MIN_VALUE));
     }
 
     @Test
@@ -159,7 +167,8 @@
         when(cursor.next()).thenReturn(result1).thenReturn(result2).thenReturn(null);
 
         Storage storage = mock(Storage.class);
-        when(storage.findAll(any(Chunk.class))).thenReturn(cursor);
+        when(storage.createQuery()).thenReturn(new MockQuery());
+        when(storage.findAll(any(Query.class))).thenReturn(cursor);
 
         VmLatestPojoListGetter<VmClassStat> getter = new VmLatestPojoListGetter<>(storage, cat, converter, vmRef);
 
@@ -186,21 +195,20 @@
         when(cursor2.next()).thenReturn(result3);
 
         Storage storage = mock(Storage.class);
-        when(storage.findAll(any(Chunk.class))).thenReturn(cursor1);
+        MockQuery firstQuery = new MockQuery();
+        MockQuery secondQuery = new MockQuery();
+        when(storage.createQuery()).thenReturn(firstQuery).thenReturn(secondQuery);
+
+        when(storage.findAll(any(Query.class))).thenReturn(cursor1);
 
         VmLatestPojoListGetter<VmClassStat> getter = new VmLatestPojoListGetter<>(storage, cat, converter, vmRef);
         getter.getLatest();
         getter.getLatest();
 
-        ArgumentCaptor<Chunk> arg = ArgumentCaptor.forClass(Chunk.class);
-        verify(storage, times(2)).findAll(arg.capture());
-        List<Chunk> queries = arg.getAllValues();
+        verify(storage, times(2)).findAll(isA(Query.class));
 
-        assertEquals(2, queries.size());
-        Chunk query = queries.get(1);
-        assertNotNull(query);
-        assertEquals(AGENT_ID, query.get(Key.AGENT_ID));
-        assertEquals((Integer) VM_PID, query.get(Key.VM_ID));
-        assertEquals("this.timestamp > " + t2, query.get(Key.WHERE));
+        assertTrue(secondQuery.hasWhereClause(Key.AGENT_ID, Criteria.EQUALS, AGENT_ID));
+        assertTrue(secondQuery.hasWhereClause(Key.VM_ID, Criteria.EQUALS, VM_PID));
+        assertTrue(secondQuery.hasWhereClause(Key.TIMESTAMP, Criteria.GREATER_THAN, t2));
     }
 }
--- a/common/core/src/test/java/com/redhat/thermostat/common/dao/VmMemoryStatDAOTest.java	Fri Sep 07 17:22:36 2012 +0200
+++ b/common/core/src/test/java/com/redhat/thermostat/common/dao/VmMemoryStatDAOTest.java	Fri Sep 07 11:42:51 2012 -0400
@@ -51,8 +51,6 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
 
 import com.redhat.thermostat.common.model.VmMemoryStat;
 import com.redhat.thermostat.common.model.VmMemoryStat.Generation;
@@ -60,8 +58,11 @@
 import com.redhat.thermostat.common.storage.Chunk;
 import com.redhat.thermostat.common.storage.Cursor;
 import com.redhat.thermostat.common.storage.Cursor.SortDirection;
+import com.redhat.thermostat.common.storage.Query.Criteria;
 import com.redhat.thermostat.common.storage.Key;
+import com.redhat.thermostat.common.storage.Query;
 import com.redhat.thermostat.common.storage.Storage;
+import com.redhat.thermostat.test.MockQuery;
 
 public class VmMemoryStatDAOTest {
 
@@ -71,7 +72,7 @@
     private Storage storage;
     private VmRef vmRef;
 
-    private Chunk query;
+    private MockQuery query;
     private Cursor cursor;
 
     @Before
@@ -86,16 +87,12 @@
         when(vmRef.getId()).thenReturn(VM_ID);
 
         storage = mock(Storage.class);
+        query = new MockQuery();
+        when(storage.createQuery()).thenReturn(query);
 
         cursor = mock(Cursor.class);
-        when(storage.findAll(any(Chunk.class))).thenAnswer(new Answer<Cursor>() {
-            @Override
-            public Cursor answer(InvocationOnMock invocation) throws Throwable {
-                query = (Chunk) invocation.getArguments()[0];
-                return cursor;
-            }
+        when(storage.findAll(any(Query.class))).thenReturn(cursor);
 
-        });
         when(cursor.sort(any(Key.class), any(SortDirection.class))).thenReturn(cursor);
         when(cursor.limit(any(Integer.class))).thenReturn(cursor);
         when(cursor.hasNext()).thenReturn(false);
@@ -162,7 +159,7 @@
 
         verifyQuery();
 
-        assertEquals("this.timestamp > 123", query.get(Key.WHERE));
+        assertTrue(query.hasWhereClause(Key.TIMESTAMP, Criteria.GREATER_THAN, 123l));
     }
 
     private void verifyQuery() {
@@ -171,8 +168,8 @@
         ArgumentCaptor<SortDirection> sortDirection = ArgumentCaptor.forClass(SortDirection.class);
         verify(cursor).sort(sortKey.capture(), sortDirection.capture());
 
-        assertEquals(AGENT_ID, query.get(Key.AGENT_ID));
-        assertEquals((Integer)VM_ID, query.get(Key.VM_ID));
+        assertTrue(query.hasWhereClause(Key.AGENT_ID, Criteria.EQUALS, AGENT_ID));
+        assertTrue(query.hasWhereClause(Key.VM_ID, Criteria.EQUALS, VM_ID));
 
         assertTrue(sortKey.getValue().equals(Key.TIMESTAMP));
         assertTrue(sortDirection.getValue().equals(SortDirection.DESCENDING));
@@ -184,7 +181,8 @@
         when(cursor.next()).thenReturn(null);
 
         Storage storage = mock(Storage.class);
-        when(storage.findAll(any(Chunk.class))).thenReturn(cursor);
+        when(storage.createQuery()).thenReturn(new MockQuery());
+        when(storage.findAll(any(Query.class))).thenReturn(cursor);
 
         VmMemoryStatDAO impl = new VmMemoryStatDAOImpl(storage);
         VmMemoryStat latest = impl.getLatestMemoryStat(vmRef);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/core/src/test/java/com/redhat/thermostat/common/storage/MongoQueryTest.java	Fri Sep 07 11:42:51 2012 -0400
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2012 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.common.storage;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+import com.mongodb.BasicDBObject;
+import com.mongodb.DBObject;
+import com.redhat.thermostat.common.storage.Query.Criteria;
+
+public class MongoQueryTest {
+
+    @Test
+    public void testEmptyQuery() {
+        MongoQuery query = new MongoQuery();
+        DBObject mongoQuery = query.getGeneratedQuery();
+        assertTrue(mongoQuery.keySet().isEmpty());
+    }
+
+    @Test
+    public void testCollectionName() {
+        MongoQuery query = new MongoQuery().from(new Category("some-collection"));
+        assertEquals("some-collection", query.getCollectionName());
+    }
+
+    @Test
+    public void testWhereEquals() {
+        DBObject generatedQuery = generateSimpleWhereQuery("key", Criteria.EQUALS, "value");
+        assertEquals("value", generatedQuery.get("key"));
+    }
+
+    @Test
+    public void testWhereNotEquals() {
+        DBObject generatedQuery = generateSimpleWhereQuery("key", Criteria.NOT_EQUAL_TO, "value");
+        assertEquals(new BasicDBObject("$ne", "value"), generatedQuery.get("key"));
+    }
+
+    @Test
+    public void testWhereGreaterThan() {
+        DBObject generatedQuery = generateSimpleWhereQuery("key", Criteria.GREATER_THAN, "value");
+        assertEquals(new BasicDBObject("$gt", "value"), generatedQuery.get("key"));
+    }
+
+    @Test
+    public void testWhereGreaterThanOrEqualTo() {
+        DBObject generatedQuery = generateSimpleWhereQuery("key", Criteria.GREATER_THAN_OR_EQUAL_TO, "value");
+        assertEquals(new BasicDBObject("$gte", "value"), generatedQuery.get("key"));
+    }
+
+    @Test
+    public void testWhereLessThan() {
+        DBObject generatedQuery = generateSimpleWhereQuery("key", Criteria.LESS_THAN, "value");
+        assertEquals(new BasicDBObject("$lt", "value"), generatedQuery.get("key"));
+    }
+
+    @Test
+    public void testWhereLessThanOrEqualTo() {
+        DBObject generatedQuery = generateSimpleWhereQuery("key", Criteria.LESS_THAN_OR_EQUAL_TO, "value");
+        assertEquals(new BasicDBObject("$lte", "value"), generatedQuery.get("key"));
+    }
+
+    private DBObject generateSimpleWhereQuery(String key, Criteria criteria, Object value) {
+        MongoQuery query = new MongoQuery().where(key, criteria, value);
+        return query.getGeneratedQuery();
+    }
+
+}
--- a/common/core/src/test/java/com/redhat/thermostat/common/storage/MongoStorageTest.java	Fri Sep 07 17:22:36 2012 +0200
+++ b/common/core/src/test/java/com/redhat/thermostat/common/storage/MongoStorageTest.java	Fri Sep 07 11:42:51 2012 -0400
@@ -75,6 +75,7 @@
 import com.mongodb.gridfs.GridFSDBFile;
 import com.mongodb.gridfs.GridFSInputFile;
 import com.redhat.thermostat.common.config.StartupConfiguration;
+import com.redhat.thermostat.common.storage.Query.Criteria;
 
 @RunWith(PowerMockRunner.class)
 @PrepareForTest({ DBCollection.class, DB.class, Mongo.class, MongoStorage.class, MongoConnection.class })
@@ -94,7 +95,7 @@
     private DB db;
     private DBCollection testCollection, emptyTestCollection, mockedCollection;
 
-    private MongoStorage makeStorage() throws Exception {
+    private MongoStorage makeStorage() {
         MongoStorage storage = new MongoStorage(conf);
         storage.mapCategoryToDBCollection(testCategory, testCollection);
         storage.mapCategoryToDBCollection(emptyTestCategory, emptyTestCollection);
@@ -161,11 +162,25 @@
         assertNotNull(connKey);
     }
 
-    @Test 
+    @Test (expected=IllegalArgumentException.class)
+    public void verifyFindOnlyAcceptsMongoQuery() {
+        MongoStorage storage = makeStorage();
+        Query query = mock(Query.class);
+        storage.find(query);
+    }
+
+    @Test (expected=IllegalArgumentException.class)
+    public void veirfyFindAllOnlyAcceptsMongoQuery() {
+        MongoStorage storage = makeStorage();
+        Query query = mock(Query.class);
+        storage.findAll(query);
+    }
+
+    @Test
     public void verifyFindAllReturnsCursor() throws Exception {
         PowerMockito.whenNew(Mongo.class).withParameterTypes(MongoURI.class).withArguments(any(MongoURI.class)).thenReturn(m);
         MongoStorage storage = makeStorage();
-        Chunk query = new Chunk(testCategory, false);
+        Query query = storage.createQuery().from(testCategory);
         Cursor cursor = storage.findAll(query);
         assertNotNull(cursor);
     }
@@ -174,8 +189,7 @@
     public void verifyFindReturnsChunk() throws Exception {
         PowerMockito.whenNew(Mongo.class).withParameterTypes(MongoURI.class).withArguments(any(MongoURI.class)).thenReturn(m);
         MongoStorage storage = makeStorage();
-        Chunk query = new Chunk(testCategory, false);
-        query.put(key1, "test1");
+        Query query = storage.createQuery().from(testCategory).where(key1, Criteria.EQUALS, "test1");
         Chunk result = storage.find(query);
         assertNotNull(result);
     }
@@ -184,7 +198,7 @@
     public void verifyFindAllCallsDBCollectionFind() throws Exception {
         PowerMockito.whenNew(Mongo.class).withParameterTypes(MongoURI.class).withArguments(any(MongoURI.class)).thenReturn(m);
         MongoStorage storage = makeStorage();
-        Chunk query = new Chunk(testCategory, false);
+        Query query = storage.createQuery().from(testCategory);
         storage.findAll(query);
         verify(testCollection).find(any(DBObject.class));
     }
@@ -193,7 +207,7 @@
     public void verifyFindCallsDBCollectionFindOne() throws Exception {
         PowerMockito.whenNew(Mongo.class).withParameterTypes(MongoURI.class).withArguments(any(MongoURI.class)).thenReturn(m);
         MongoStorage storage = makeStorage();
-        Chunk query = new Chunk(testCategory, false);
+        Query query = storage.createQuery().from(testCategory);
         storage.find(query);
         verify(testCollection).findOne(any(DBObject.class));
     }
@@ -202,60 +216,37 @@
     public void verifyFindAllCallsDBCollectionFindWithCorrectQuery() throws Exception {
         PowerMockito.whenNew(Mongo.class).withParameterTypes(MongoURI.class).withArguments(any(MongoURI.class)).thenReturn(m);
         MongoStorage storage = makeStorage();
-        Chunk query = new Chunk(testCategory, false);
-        query.put(key1, "test");
+
+        MongoQuery query = mock(MongoQuery.class);
+        DBObject generatedQuery = mock(DBObject.class);
+        when(query.getGeneratedQuery()).thenReturn(generatedQuery);
+        when(query.getCollectionName()).thenReturn(testCategory.getName());
+        when(query.getCategory()).thenReturn(testCategory);
+        ArgumentCaptor<DBObject> findArg = ArgumentCaptor.forClass(DBObject.class);
+
         storage.findAll(query);
 
-        ArgumentCaptor<DBObject> findArg = ArgumentCaptor.forClass(DBObject.class);
         verify(testCollection).find(findArg.capture());
-
-        DBObject arg = findArg.getValue();
-        assertEquals(1, arg.keySet().size());
-        assertTrue(arg.keySet().contains("key1"));
-        assertEquals("test", arg.get("key1"));
+        assertSame(generatedQuery, findArg.getValue());
     }
 
     @Test
     public void verifyFindCallsDBCollectionFindOneWithCorrectQuery() throws Exception {
         PowerMockito.whenNew(Mongo.class).withParameterTypes(MongoURI.class).withArguments(any(MongoURI.class)).thenReturn(m);
         MongoStorage storage = makeStorage();
-        Chunk query = new Chunk(testCategory, false);
-        query.put(key1, "test");
-        storage.find(query);
+
+        MongoQuery query = mock(MongoQuery.class);
+        DBObject generatedQuery = mock(DBObject.class);
+        when(query.getGeneratedQuery()).thenReturn(generatedQuery);
+        when(query.getCollectionName()).thenReturn(testCategory.getName());
+        when(query.getCategory()).thenReturn(testCategory);
 
         ArgumentCaptor<DBObject> findArg = ArgumentCaptor.forClass(DBObject.class);
-        verify(testCollection).findOne(findArg.capture());
 
-        DBObject arg = findArg.getValue();
-        assertEquals(1, arg.keySet().size());
-        assertTrue(arg.keySet().contains("key1"));
-        assertEquals("test", arg.get("key1"));
-    }
-
-    @Test
-    public void verifyFindAllWithMultiKeys() throws Exception {
-        PowerMockito.whenNew(Mongo.class).withParameterTypes(MongoURI.class).withArguments(any(MongoURI.class)).thenReturn(m);
-        MongoStorage storage = makeStorage();
-        storage.findAll(multiKeyQuery);
+        storage.find(query);
 
-        ArgumentCaptor<DBObject> findArg = ArgumentCaptor.forClass(DBObject.class);
-        verify(testCollection).find(findArg.capture());
-
-        DBObject arg = findArg.getValue();
-        assertArrayEquals(new String[]{ "key5", "key4", "key3", "key2", "key1" }, arg.keySet().toArray());
-    }
-
-    @Test
-    public void verifyFindWithMultiKeys() throws Exception {
-        PowerMockito.whenNew(Mongo.class).withParameterTypes(MongoURI.class).withArguments(any(MongoURI.class)).thenReturn(m);
-        MongoStorage storage = makeStorage();
-        storage.find(multiKeyQuery);
-
-        ArgumentCaptor<DBObject> findArg = ArgumentCaptor.forClass(DBObject.class);
         verify(testCollection).findOne(findArg.capture());
-
-        DBObject arg = findArg.getValue();
-        assertArrayEquals(new String[]{ "key5", "key4", "key3", "key2", "key1" }, arg.keySet().toArray());
+        assertSame(generatedQuery, findArg.getValue());
     }
 
     @Test
@@ -263,9 +254,8 @@
         PowerMockito.whenNew(Mongo.class).withParameterTypes(MongoURI.class).withArguments(any(MongoURI.class)).thenReturn(m);
         MongoStorage storage = makeStorage();
         // TODO find a way to test this that isn't just testing mock and converters
-        Chunk query = new Chunk(testCategory, false);
         // Because we mock the DBCollection, the contents of this query don't actually determine the result.
-        query.put(key5, "test1");
+        MongoQuery query = new MongoQuery().from(testCategory);
 
         Chunk result = storage.find(query);
 
@@ -280,9 +270,8 @@
         PowerMockito.whenNew(Mongo.class).withParameterTypes(MongoURI.class).withArguments(any(MongoURI.class)).thenReturn(m);
         MongoStorage storage = makeStorage();
         // TODO find a way to test this that isn't just testing MongoCursor
-        Chunk query = new Chunk(testCategory, false);
         // Because we mock the DBCollection, the contents of this query don't actually determine the result.
-        query.put(key5, "test1");
+        MongoQuery query = new MongoQuery().from(testCategory);
 
         Cursor cursor = storage.findAll(query);
 
--- a/thread/collector/src/main/java/com/redhat/thermostat/thread/dao/impl/ThreadDaoImpl.java	Fri Sep 07 17:22:36 2012 +0200
+++ b/thread/collector/src/main/java/com/redhat/thermostat/thread/dao/impl/ThreadDaoImpl.java	Fri Sep 07 11:42:51 2012 -0400
@@ -44,6 +44,8 @@
 import com.redhat.thermostat.common.storage.Chunk;
 import com.redhat.thermostat.common.storage.Cursor;
 import com.redhat.thermostat.common.storage.Key;
+import com.redhat.thermostat.common.storage.Query;
+import com.redhat.thermostat.common.storage.Query.Criteria;
 import com.redhat.thermostat.common.storage.Storage;
 import com.redhat.thermostat.thread.dao.ThreadDao;
 import com.redhat.thermostat.thread.model.ThreadInfoData;
@@ -65,9 +67,10 @@
         
         VMThreadCapabilities caps = null;
         
-        Chunk query = new Chunk(THREAD_CAPABILITIES, false);
-        query.put(Key.VM_ID, vm.getId());
-        query.put(Key.AGENT_ID, vm.getAgent().getAgentId());
+        Query query = storage.createQuery()
+                .from(THREAD_CAPABILITIES)
+                .where(Key.VM_ID, Query.Criteria.EQUALS, vm.getId())
+                .where(Key.AGENT_ID, Query.Criteria.EQUALS, vm.getAgent().getAgentId());
         
         Chunk found = storage.find(query);
         if (found != null) {
@@ -106,7 +109,7 @@
     public ThreadSummary loadLastestSummary(VmRef ref) {
         ThreadSummary summary = null;
 
-        Chunk query = prepareChunk(THREAD_SUMMARY, false, ref);
+        Query query = prepareQuery(THREAD_SUMMARY, ref);
         Cursor cursor = storage.findAll(query).sort(Key.TIMESTAMP, Cursor.SortDirection.DESCENDING).limit(1);
         if (cursor.hasNext()) {
             Chunk found = cursor.next();
@@ -124,8 +127,8 @@
         
         List<ThreadSummary> result = new ArrayList<>();
         
-        Chunk query = prepareChunk(THREAD_SUMMARY, false, ref);
-        query.put(Key.WHERE, "this.timestamp > " + since);
+        Query query = prepareQuery(THREAD_SUMMARY, ref);
+        query.where(Key.TIMESTAMP, Criteria.GREATER_THAN, since);
 
         Cursor cursor = storage.findAll(query).sort(Key.TIMESTAMP, Cursor.SortDirection.DESCENDING);
         while (cursor.hasNext()) {
@@ -163,8 +166,8 @@
     public List<ThreadInfoData> loadThreadInfo(VmRef ref, long since) {
         List<ThreadInfoData> result = new ArrayList<>();
         
-        Chunk query = prepareChunk(THREAD_INFO, false, ref);
-        query.put(Key.WHERE, "this.timestamp > " + since);
+        Query query = prepareQuery(THREAD_INFO, ref)
+                .where(Key.TIMESTAMP, Criteria.GREATER_THAN, since);
         
         Cursor cursor = storage.findAll(query).sort(Key.TIMESTAMP, Cursor.SortDirection.DESCENDING);
         while (cursor.hasNext()) {
@@ -198,6 +201,18 @@
     private Chunk prepareChunk(Category category, boolean replace, VmRef vm) {
         return prepareChunk(category, replace, vm.getIdString(), vm.getAgent().getAgentId());
     }
+
+    private Query prepareQuery(Category category, VmRef vm) {
+        return prepareQuery(category, vm.getIdString(), vm.getAgent().getAgentId());
+    }
+
+    private Query prepareQuery(Category category, String vmId, String agentId) {
+        Query query = storage.createQuery()
+                .from(category)
+                .where(Key.AGENT_ID, Query.Criteria.EQUALS, agentId)
+                .where(Key.VM_ID, Query.Criteria.EQUALS, Integer.valueOf(vmId));
+        return query;
+    }
     
     @Override
     public Storage getStorage() {
--- a/thread/collector/src/test/java/com/redhat/thermostat/thread/dao/impl/ThreadDaoImplTest.java	Fri Sep 07 17:22:36 2012 +0200
+++ b/thread/collector/src/test/java/com/redhat/thermostat/thread/dao/impl/ThreadDaoImplTest.java	Fri Sep 07 11:42:51 2012 -0400
@@ -51,7 +51,10 @@
 import com.redhat.thermostat.common.dao.VmRef;
 import com.redhat.thermostat.common.storage.Chunk;
 import com.redhat.thermostat.common.storage.Key;
+import com.redhat.thermostat.common.storage.Query;
 import com.redhat.thermostat.common.storage.Storage;
+import com.redhat.thermostat.common.storage.Query.Criteria;
+import com.redhat.thermostat.test.MockQuery;
 import com.redhat.thermostat.thread.dao.ThreadDao;
 import com.redhat.thermostat.thread.model.VMThreadCapabilities;
 
@@ -71,7 +74,9 @@
     
     @Test
     public void testLoadVMCapabilities() {
+        MockQuery query = new MockQuery();
         Storage storage = mock(Storage.class);
+        when(storage.createQuery()).thenReturn(query);
         VmRef ref = mock(VmRef.class);
         when(ref.getId()).thenReturn(42);
         
@@ -85,15 +90,13 @@
         when(answer.get(ThreadDao.CPU_TIME_KEY)).thenReturn(true);
         when(answer.get(ThreadDao.THREAD_ALLOCATED_MEMORY_KEY)).thenReturn(true);
         
-        ArgumentCaptor<Chunk> queryCaptor = ArgumentCaptor.forClass(Chunk.class);
-        when(storage.find(queryCaptor.capture())).thenReturn(answer);
+        when(storage.find(query)).thenReturn(answer);
         
         ThreadDaoImpl dao = new ThreadDaoImpl(storage);
         VMThreadCapabilities caps = dao.loadCapabilities(ref);
 
-        Chunk query = queryCaptor.getValue();
-        assertEquals(42, (int) query.get(Key.VM_ID));
-        assertEquals("0xcafe", query.get(Key.AGENT_ID));
+        assertTrue(query.hasWhereClause(Key.VM_ID, Criteria.EQUALS, 42));
+        assertTrue(query.hasWhereClause(Key.AGENT_ID, Criteria.EQUALS, "0xcafe"));
         
         assertFalse(caps.supportContentionMonitor());
         assertTrue(caps.supportCPUTime());