changeset 631:9eed9fa986ad

Implement findAll() in web storage. Reviewed-by: omajid http://icedtea.classpath.org/pipermail/thermostat/2012-September/003308.html
author Roman Kennke <rkennke@redhat.com>
date Thu, 20 Sep 2012 00:28:12 +0200
parents bd6dab2e9300
children 5e8550c83f37
files common/core/src/main/java/com/redhat/thermostat/common/dao/AgentInfoDAOImpl.java common/core/src/main/java/com/redhat/thermostat/common/dao/HostLatestPojoListGetter.java common/core/src/main/java/com/redhat/thermostat/common/dao/VmMemoryStatDAOImpl.java common/core/src/main/java/com/redhat/thermostat/common/storage/AbstractQuery.java common/core/src/main/java/com/redhat/thermostat/common/storage/Categories.java common/core/src/main/java/com/redhat/thermostat/common/storage/Cursor.java common/core/src/main/java/com/redhat/thermostat/common/storage/MongoCursor.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/test/MockQuery.java common/core/src/test/java/com/redhat/thermostat/common/dao/AgentInfoDAOTest.java common/core/src/test/java/com/redhat/thermostat/common/dao/CpuStatDAOTest.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/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/VmLatestPojoListGetterTest.java common/core/src/test/java/com/redhat/thermostat/common/dao/VmMemoryStatDAOTest.java common/core/src/test/java/com/redhat/thermostat/common/storage/MongoCursorTest.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 web/client/src/main/java/com/redhat/thermostat/web/client/RESTStorage.java web/client/src/main/java/com/redhat/thermostat/web/client/WebCursor.java web/client/src/test/java/com/redhat/thermostat/web/client/RESTStorageTest.java web/common/src/main/java/com/redhat/thermostat/web/common/RESTQuery.java web/server/src/main/java/com/redhat/thermostat/web/server/RESTStorageEndPoint.java web/server/src/test/java/com/redhat/thermostat/web/server/RESTStorageEndpointTest.java web/server/src/test/java/com/redhat/thermostat/web/server/RESTStorageTest.java
diffstat 32 files changed, 660 insertions(+), 337 deletions(-) [+]
line wrap: on
line diff
--- a/common/core/src/main/java/com/redhat/thermostat/common/dao/AgentInfoDAOImpl.java	Tue Sep 18 22:52:53 2012 +0200
+++ b/common/core/src/main/java/com/redhat/thermostat/common/dao/AgentInfoDAOImpl.java	Thu Sep 20 00:28:12 2012 +0200
@@ -64,7 +64,8 @@
 
     @Override
     public List<AgentInformation> getAllAgentInformation() {
-        Cursor<AgentInformation> agentCursor = storage.findAllPojosFromCategory(CATEGORY, AgentInformation.class);
+        Query query = storage.createQuery().from(CATEGORY);
+        Cursor<AgentInformation> agentCursor = storage.findAllPojos(query, AgentInformation.class);
 
         List<AgentInformation> results = new ArrayList<>();
 
--- a/common/core/src/main/java/com/redhat/thermostat/common/dao/HostLatestPojoListGetter.java	Tue Sep 18 22:52:53 2012 +0200
+++ b/common/core/src/main/java/com/redhat/thermostat/common/dao/HostLatestPojoListGetter.java	Thu Sep 20 00:28:12 2012 +0200
@@ -42,7 +42,6 @@
 import com.redhat.thermostat.common.model.TimeStampedPojo;
 import com.redhat.thermostat.common.storage.Category;
 import com.redhat.thermostat.common.storage.Cursor;
-import com.redhat.thermostat.common.storage.Cursor.SortDirection;
 import com.redhat.thermostat.common.storage.Key;
 import com.redhat.thermostat.common.storage.Query;
 import com.redhat.thermostat.common.storage.Query.Criteria;
@@ -70,7 +69,6 @@
 
     private List<T> getLatest(Query query) {
         Cursor<T> cursor = storage.findAllPojos(query, resultClass);
-        cursor = cursor.sort(Key.TIMESTAMP, SortDirection.DESCENDING);
         List<T> result = new ArrayList<>();
         while (cursor.hasNext()) {
             T pojo = cursor.next();
@@ -82,8 +80,10 @@
     protected Query buildQuery(long since) {
         Query query = storage.createQuery()
                 .from(cat)
-                .where(Key.AGENT_ID, Criteria.EQUALS, ref.getAgentId());
-        query.where(Key.TIMESTAMP, Criteria.GREATER_THAN, since);
+                .where(Key.AGENT_ID, Criteria.EQUALS, ref.getAgentId())
+                .where(Key.TIMESTAMP, Criteria.GREATER_THAN, since)
+                .sort(Key.TIMESTAMP, Query.SortDirection.DESCENDING);
+        
         return query;
     }
 }
--- a/common/core/src/main/java/com/redhat/thermostat/common/dao/VmMemoryStatDAOImpl.java	Tue Sep 18 22:52:53 2012 +0200
+++ b/common/core/src/main/java/com/redhat/thermostat/common/dao/VmMemoryStatDAOImpl.java	Thu Sep 20 00:28:12 2012 +0200
@@ -62,8 +62,10 @@
         Query query = storage.createQuery()
                 .from(vmMemoryStatsCategory)
                 .where(Key.AGENT_ID, Criteria.EQUALS, ref.getAgent().getAgentId())
-                .where(Key.VM_ID, Criteria.EQUALS, ref.getId());
-        Cursor<VmMemoryStat> cursor = storage.findAllPojos(query, VmMemoryStat.class).sort(Key.TIMESTAMP, Cursor.SortDirection.DESCENDING).limit(1);
+                .where(Key.VM_ID, Criteria.EQUALS, ref.getId())
+                .sort(Key.TIMESTAMP, Query.SortDirection.DESCENDING)
+                .limit(1);;
+        Cursor<VmMemoryStat> cursor = storage.findAllPojos(query, VmMemoryStat.class);
         if (cursor.hasNext()) {
             return cursor.next();
         }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/core/src/main/java/com/redhat/thermostat/common/storage/AbstractQuery.java	Thu Sep 20 00:28:12 2012 +0200
@@ -0,0 +1,137 @@
+/*
+ * 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.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+public abstract class AbstractQuery implements Query {
+
+    public static class Sort {
+        private Key<?> key;
+        private SortDirection direction;
+        public Sort() {
+            this(null, null);
+        }
+
+        public Sort(Key<?> key, SortDirection direction) {
+            this.key = key;
+            this.direction = direction;
+        }
+        public Key<?> getKey() {
+            return key;
+        }
+        public void setKey(Key<?> key) {
+            this.key = key;
+        }
+        public SortDirection getDirection() {
+            return direction;
+        }
+        public void setDirection(SortDirection direction) {
+            this.direction = direction;
+        }
+
+        public int hashCode() {
+            return Objects.hash(key, direction);
+        }
+        public boolean equals(Object obj) {
+            if (obj == null) {
+                return false;
+            }
+            if (obj == this) {
+                return true;
+            }
+            if (!(obj instanceof Sort)) {
+                return false;
+            }
+            Sort other = (Sort) obj;
+            return Objects.equals(key, other.key) && Objects.equals(direction, other.direction);
+        }
+    }
+
+    private Category category;
+    private List<Sort> sorts;
+    private int limit = -1;
+
+    public AbstractQuery() {
+        sorts = new ArrayList<>();
+    }
+
+    @Override
+    public Query from(Category category) {
+        this.category = category;
+        return this;
+    }
+
+    public Category getCategory() {
+        return category;
+    }
+
+    public void setCategory(Category category) {
+        this.category = category;
+    }
+
+    @Override
+    public <T> Query sort(Key<T> key, SortDirection direction) {
+        sorts.add(new Sort(key, direction));
+        return this;
+    }
+
+    public List<Sort> getSorts() {
+        return sorts;
+    }
+
+    public void setSorts(List<Sort> sorts) {
+        this.sorts = sorts;
+    }
+
+    @Override
+    public Query limit(int limit) {
+        this.limit  = limit;
+        return this;
+    }
+
+    public int getLimit() {
+        return limit;
+    }
+
+    public void setLimit(int limit) {
+        this.limit = limit;
+    }
+}
--- a/common/core/src/main/java/com/redhat/thermostat/common/storage/Categories.java	Tue Sep 18 22:52:53 2012 +0200
+++ b/common/core/src/main/java/com/redhat/thermostat/common/storage/Categories.java	Thu Sep 20 00:28:12 2012 +0200
@@ -54,6 +54,10 @@
         namesToCategories.put(category.getName(), category);
     }
 
+    public static synchronized void remove(Category category) {
+        namesToCategories.remove(category.getName());
+    }
+
     public static synchronized Category getByName(String categoryName) {
         return namesToCategories.get(categoryName);
     }
--- a/common/core/src/main/java/com/redhat/thermostat/common/storage/Cursor.java	Tue Sep 18 22:52:53 2012 +0200
+++ b/common/core/src/main/java/com/redhat/thermostat/common/storage/Cursor.java	Thu Sep 20 00:28:12 2012 +0200
@@ -40,26 +40,8 @@
 
 public interface Cursor<T extends Pojo> {
 
-    public enum SortDirection {
-        ASCENDING(1),
-        DESCENDING(-1);
-
-        private int value;
-
-        private SortDirection(int value) {
-            this.value = value;
-        }
-
-        public int getValue() {
-            return value;
-        }
-    }
-
     boolean hasNext();
 
     T next();
 
-    Cursor<T> sort(Key<?> orderBy, SortDirection direction);
-
-    Cursor<T> limit(int i);
 }
--- a/common/core/src/main/java/com/redhat/thermostat/common/storage/MongoCursor.java	Tue Sep 18 22:52:53 2012 +0200
+++ b/common/core/src/main/java/com/redhat/thermostat/common/storage/MongoCursor.java	Thu Sep 20 00:28:12 2012 +0200
@@ -74,23 +74,4 @@
         return ChunkToPojoConverter.convertChunkToPojo(resultChunk, resultClass, converters);
     }
 
-    @Override
-    public Cursor<T> sort(Key<?> orderBy, SortDirection direction) {
-        if (!category.getKeys().contains(orderBy)) {
-            throw new IllegalArgumentException("Key not present in this Cursor's category.");
-        }   /* TODO: There are other possible error conditions.  Once there is API to configure
-             * indexing/optimization, we may want to prevent or log predictably bad performance
-             * sorting requests.
-             */
-        DBObject dbOrderBy = new BasicDBObject(orderBy.getName(), direction.getValue());
-        DBCursor sorted = cursor.sort(dbOrderBy);
-        return new MongoCursor<T>(sorted, category, resultClass, converters);
-    }
-
-    @Override
-    public Cursor<T> limit(int i) {
-        DBCursor limited = cursor.limit(i);
-        return new MongoCursor<T>(limited, category, resultClass, converters);
-    }
-
 }
--- a/common/core/src/main/java/com/redhat/thermostat/common/storage/MongoQuery.java	Tue Sep 18 22:52:53 2012 +0200
+++ b/common/core/src/main/java/com/redhat/thermostat/common/storage/MongoQuery.java	Thu Sep 20 00:28:12 2012 +0200
@@ -41,14 +41,14 @@
 import com.mongodb.BasicDBObject;
 import com.mongodb.DBObject;
 
-public class MongoQuery implements Query {
+public class MongoQuery extends AbstractQuery {
 
-    private Category category;
     private BasicDBObject query = new BasicDBObject();
+    private boolean hasClauses = false;
 
     @Override
     public MongoQuery from(Category category) {
-        this.category = category;
+        setCategory(category);
         return this;
     }
 
@@ -84,22 +84,18 @@
         default:
             throw new IllegalArgumentException("MongoQuery can not handle " + operator);
         }
-
+        hasClauses = true;
         return this;
     }
 
     String getCollectionName() {
-        return category.getName();
+        return getCategory().getName();
     }
 
     DBObject getGeneratedQuery() {
         return query;
     }
 
-    Category getCategory() {
-        return category;
-    }
-
     @Override
     public boolean equals(Object obj) {
         if (obj == this) {
@@ -112,11 +108,16 @@
             return false;
         }
         MongoQuery other = (MongoQuery) obj;
-        return Objects.equals(this.category, other.category) && Objects.equals(this.query, other.query);
+        return Objects.equals(getCategory(), other.getCategory()) && Objects.equals(this.query, other.query);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(this.category, this.query);
+        return Objects.hash(getCategory(), this.query);
     }
+
+    boolean hasClauses() {
+        return hasClauses ;
+    }
+
 }
--- a/common/core/src/main/java/com/redhat/thermostat/common/storage/MongoStorage.java	Tue Sep 18 22:52:53 2012 +0200
+++ b/common/core/src/main/java/com/redhat/thermostat/common/storage/MongoStorage.java	Thu Sep 20 00:28:12 2012 +0200
@@ -38,6 +38,7 @@
 
 import java.io.InputStream;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.UUID;
@@ -55,6 +56,7 @@
 import com.redhat.thermostat.common.dao.VmMemoryStatConverter;
 import com.redhat.thermostat.common.model.Pojo;
 import com.redhat.thermostat.common.model.VmMemoryStat;
+import com.redhat.thermostat.common.storage.AbstractQuery.Sort;
 import com.redhat.thermostat.common.storage.Connection.ConnectionListener;
 import com.redhat.thermostat.common.storage.Connection.ConnectionStatus;
 
@@ -347,10 +349,30 @@
     public <T extends Pojo> Cursor<T> findAllPojos(Query query, Class<T> resultClass) {
         MongoQuery mongoQuery =  checkAndCastQuery(query);
         DBCollection coll = getCachedCollection(mongoQuery.getCollectionName());
-        DBCursor dbCursor = coll.find(mongoQuery.getGeneratedQuery());
+        DBCursor dbCursor;
+        if (mongoQuery.hasClauses()) {
+            dbCursor = coll.find(mongoQuery.getGeneratedQuery());
+        } else {
+            dbCursor = coll.find();
+        }
+        dbCursor = applySortAndLimit(mongoQuery, dbCursor);
         return new MongoCursor<T>(dbCursor, mongoQuery.getCategory(), resultClass, converters);
     }
 
+    private DBCursor applySortAndLimit(MongoQuery query, DBCursor dbCursor) {
+        BasicDBObject orderBy = new BasicDBObject();
+        List<Sort> sorts = query.getSorts();
+        for (Sort sort : sorts) {
+            orderBy.append(sort.getKey().getName(), sort.getDirection().getValue());
+        }
+        dbCursor.sort(orderBy);
+        int limit = query.getLimit();
+        if (limit > 0) {
+            dbCursor.limit(limit);
+        }
+        return dbCursor;
+    }
+
     @Override
     public <T extends Pojo> T findPojo(Query query, Class<T> resultClass) {
         Chunk resultChunk = find(query);
@@ -379,13 +401,6 @@
     }
     
     @Override
-    public <T extends Pojo> Cursor<T> findAllPojosFromCategory(Category category, Class<T> resultClass) {
-        DBCollection coll = getCachedCollection(category.getName());
-        DBCursor dbCursor = coll.find();
-        return new MongoCursor<T>(dbCursor, category, resultClass, converters);
-    }
-
-    @Override
     public long getCount(Category category) {
         DBCollection coll = getCachedCollection(category.getName());
         if (coll != null) {
--- a/common/core/src/main/java/com/redhat/thermostat/common/storage/Query.java	Tue Sep 18 22:52:53 2012 +0200
+++ b/common/core/src/main/java/com/redhat/thermostat/common/storage/Query.java	Thu Sep 20 00:28:12 2012 +0200
@@ -47,8 +47,26 @@
         LESS_THAN_OR_EQUAL_TO,
     }
 
+    enum SortDirection {
+        ASCENDING(1),
+        DESCENDING(-1);
+
+        private int value;
+
+        private SortDirection(int value) {
+            this.value = value;
+        }
+
+        public int getValue() {
+            return value;
+        }
+    }
+
     Query from(Category category);
 
     <T> Query where(Key<T> key, Criteria criteria, T value);
 
+    <T> Query sort(Key<T> key, SortDirection direction);
+
+    Query limit(int n);
 }
--- a/common/core/src/main/java/com/redhat/thermostat/common/storage/Storage.java	Tue Sep 18 22:52:53 2012 +0200
+++ b/common/core/src/main/java/com/redhat/thermostat/common/storage/Storage.java	Thu Sep 20 00:28:12 2012 +0200
@@ -74,8 +74,6 @@
 
     public abstract <T extends Pojo> T findPojo(Query query, Class<T> resultClass);
 
-    public abstract <T extends Pojo> Cursor<T> findAllPojosFromCategory(Category category, Class<T> resultClass);
-    
     public abstract long getCount(Category category);
 
     public abstract void saveFile(String filename, InputStream data);
--- a/common/core/src/main/java/com/redhat/thermostat/test/MockQuery.java	Tue Sep 18 22:52:53 2012 +0200
+++ b/common/core/src/main/java/com/redhat/thermostat/test/MockQuery.java	Thu Sep 20 00:28:12 2012 +0200
@@ -40,11 +40,11 @@
 import java.util.List;
 import java.util.Objects;
 
+import com.redhat.thermostat.common.storage.AbstractQuery;
 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 class MockQuery extends AbstractQuery {
 
     public static class WhereClause <T> {
         public final Key<T> key;
@@ -79,11 +79,10 @@
     }
 
     private final List<WhereClause<?>> whereClauses = new ArrayList<>();
-    private Category category;
 
     @Override
     public MockQuery from(Category category) {
-        this.category = category;
+        setCategory(category);
         return this;
     }
 
@@ -93,10 +92,6 @@
         return this;
     }
 
-    public Category getCategory() {
-        return category;
-    }
-
     public List<WhereClause<?>> getWhereClauses() {
         return whereClauses;
     }
@@ -135,12 +130,17 @@
             return false;
         }
         MockQuery other = (MockQuery) obj;
-        return Objects.equals(category, other.category) && Objects.equals(whereClauses, other.whereClauses);
+        return Objects.equals(getCategory(), other.getCategory()) && Objects.equals(whereClauses, other.whereClauses);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(category, whereClauses);
+        return Objects.hash(getCategory(), whereClauses);
+    }
+
+    public boolean hasSort(Key<?> key, SortDirection direction) {
+        
+        return getSorts().contains(new Sort(key, direction));
     }
 
 }
--- a/common/core/src/test/java/com/redhat/thermostat/common/dao/AgentInfoDAOTest.java	Tue Sep 18 22:52:53 2012 +0200
+++ b/common/core/src/test/java/com/redhat/thermostat/common/dao/AgentInfoDAOTest.java	Thu Sep 20 00:28:12 2012 +0200
@@ -39,6 +39,8 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.same;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -49,15 +51,14 @@
 
 import org.junit.Before;
 import org.junit.Test;
-import org.mockito.ArgumentCaptor;
 
 import com.redhat.thermostat.common.model.AgentInformation;
 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.QueryTestHelper;
-import com.redhat.thermostat.common.storage.Query.Criteria;
 import com.redhat.thermostat.common.storage.Remove;
 import com.redhat.thermostat.common.storage.Storage;
 import com.redhat.thermostat.common.storage.Update;
@@ -119,8 +120,9 @@
         when(agentCursor.next()).thenReturn(agent1).thenReturn(null);
 
         Storage storage = mock(Storage.class);
-        when(storage.findAllPojosFromCategory(AgentInfoDAO.CATEGORY, AgentInformation.class)).thenReturn(agentCursor);
-
+        when(storage.findAllPojos(any(Query.class), same(AgentInformation.class))).thenReturn(agentCursor);
+        MockQuery query = new MockQuery();
+        when(storage.createQuery()).thenReturn(query);
         AgentInfoDAOImpl dao = new AgentInfoDAOImpl(storage);
 
         List<AgentInformation> allAgentInfo = dao.getAllAgentInformation();
--- a/common/core/src/test/java/com/redhat/thermostat/common/dao/CpuStatDAOTest.java	Tue Sep 18 22:52:53 2012 +0200
+++ b/common/core/src/test/java/com/redhat/thermostat/common/dao/CpuStatDAOTest.java	Thu Sep 20 00:28:12 2012 +0200
@@ -38,7 +38,6 @@
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.same;
@@ -56,7 +55,6 @@
 import com.redhat.thermostat.common.model.CpuStat;
 import com.redhat.thermostat.common.storage.Category;
 import com.redhat.thermostat.common.storage.Cursor;
-import com.redhat.thermostat.common.storage.Cursor.SortDirection;
 import com.redhat.thermostat.common.storage.Key;
 import com.redhat.thermostat.common.storage.Query;
 import com.redhat.thermostat.common.storage.Query.Criteria;
@@ -93,7 +91,6 @@
 
         when(cursor.hasNext()).thenReturn(true).thenReturn(false);
         when(cursor.next()).thenReturn(cpuStat);
-        when(cursor.sort(any(Key.class), any(SortDirection.class))).thenReturn(cursor);
 
         when(storage.createQuery()).thenReturn(query);
         when(storage.findAllPojos(query, CpuStat.class)).thenReturn(cursor);
@@ -125,7 +122,6 @@
 
         when(cursor.hasNext()).thenReturn(true).thenReturn(false);
         when(cursor.next()).thenReturn(cpuStat);
-        when(cursor.sort(any(Key.class), any(SortDirection.class))).thenReturn(cursor);
 
         when(storage.createQuery()).thenReturn(query);
         when(storage.findAllPojos(any(Query.class), same(CpuStat.class))).thenReturn(cursor);
--- a/common/core/src/test/java/com/redhat/thermostat/common/dao/HostInfoDAOTest.java	Tue Sep 18 22:52:53 2012 +0200
+++ b/common/core/src/test/java/com/redhat/thermostat/common/dao/HostInfoDAOTest.java	Thu Sep 20 00:28:12 2012 +0200
@@ -147,7 +147,6 @@
                 return new MockQuery();
             }
         });
-        when(storage.findAllPojosFromCategory(HostInfoDAO.hostInfoCategory, HostInfo.class)).thenReturn(cursor);
         when(storage.findAllPojos(any(Query.class), same(HostInfo.class))).thenReturn(cursor);
         
         return storage;
@@ -190,7 +189,6 @@
                 return new MockQuery();
             }
         });
-        when(storage.findAllPojosFromCategory(HostInfoDAO.hostInfoCategory, HostInfo.class)).thenReturn(cursor);
         when(storage.findAllPojos(any(Query.class), same(HostInfo.class))).thenReturn(cursor);
         
         return storage;
--- a/common/core/src/test/java/com/redhat/thermostat/common/dao/HostLatestPojoListGetterTest.java	Tue Sep 18 22:52:53 2012 +0200
+++ b/common/core/src/test/java/com/redhat/thermostat/common/dao/HostLatestPojoListGetterTest.java	Thu Sep 20 00:28:12 2012 +0200
@@ -38,14 +38,9 @@
 
 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.Matchers.any;
-import static org.mockito.Matchers.same;
 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 java.util.Arrays;
@@ -59,8 +54,6 @@
 import com.redhat.thermostat.common.storage.Category;
 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.Cursor.SortDirection;
 import com.redhat.thermostat.common.storage.Query.Criteria;
 import com.redhat.thermostat.common.storage.Storage;
 import com.redhat.thermostat.common.utils.ArrayUtils;
@@ -142,7 +135,6 @@
         Cursor<CpuStat> cursor = mock(Cursor.class);
         when(cursor.hasNext()).thenReturn(true).thenReturn(true).thenReturn(false);
         when(cursor.next()).thenReturn(result1).thenReturn(result2).thenReturn(null);
-        when(cursor.sort(any(Key.class), any(SortDirection.class))).thenReturn(cursor);
 
         Storage storage = mock(Storage.class);
         MockQuery query = new MockQuery();
--- a/common/core/src/test/java/com/redhat/thermostat/common/dao/MemoryStatDAOTest.java	Tue Sep 18 22:52:53 2012 +0200
+++ b/common/core/src/test/java/com/redhat/thermostat/common/dao/MemoryStatDAOTest.java	Thu Sep 20 00:28:12 2012 +0200
@@ -55,7 +55,6 @@
 import com.redhat.thermostat.common.model.MemoryStat;
 import com.redhat.thermostat.common.storage.Category;
 import com.redhat.thermostat.common.storage.Cursor;
-import com.redhat.thermostat.common.storage.Cursor.SortDirection;
 import com.redhat.thermostat.common.storage.Key;
 import com.redhat.thermostat.common.storage.Query;
 import com.redhat.thermostat.common.storage.Query.Criteria;
@@ -98,7 +97,6 @@
         Cursor<MemoryStat> cursor = mock(Cursor.class);
         when(cursor.hasNext()).thenReturn(true).thenReturn(false);
         when(cursor.next()).thenReturn(memStat1);
-        when(cursor.sort(any(Key.class), any(SortDirection.class))).thenReturn(cursor);
 
         Storage storage = mock(Storage.class);
         when(storage.createQuery()).then(new Answer<Query>() {
--- a/common/core/src/test/java/com/redhat/thermostat/common/dao/VmClassStatDAOTest.java	Tue Sep 18 22:52:53 2012 +0200
+++ b/common/core/src/test/java/com/redhat/thermostat/common/dao/VmClassStatDAOTest.java	Thu Sep 20 00:28:12 2012 +0200
@@ -56,7 +56,6 @@
 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.Cursor.SortDirection;
 import com.redhat.thermostat.common.storage.Query.Criteria;
 import com.redhat.thermostat.common.storage.Storage;
 import com.redhat.thermostat.test.MockQuery;
@@ -87,7 +86,6 @@
         Cursor<VmClassStat> cursor = mock(Cursor.class);
         when(cursor.hasNext()).thenReturn(true).thenReturn(false);
         when(cursor.next()).thenReturn(vmClassStat);
-        when(cursor.sort(any(Key.class), any(SortDirection.class))).thenReturn(cursor);
 
         Storage storage = mock(Storage.class);
         when(storage.createQuery()).then(new Answer<Query>() {
--- a/common/core/src/test/java/com/redhat/thermostat/common/dao/VmCpuStatDAOTest.java	Tue Sep 18 22:52:53 2012 +0200
+++ b/common/core/src/test/java/com/redhat/thermostat/common/dao/VmCpuStatDAOTest.java	Thu Sep 20 00:28:12 2012 +0200
@@ -55,7 +55,6 @@
 
 import com.redhat.thermostat.common.model.VmCpuStat;
 import com.redhat.thermostat.common.storage.Cursor;
-import com.redhat.thermostat.common.storage.Cursor.SortDirection;
 import com.redhat.thermostat.common.storage.Key;
 import com.redhat.thermostat.common.storage.Query;
 import com.redhat.thermostat.common.storage.Query.Criteria;
@@ -93,7 +92,6 @@
         Cursor<VmCpuStat> cursor = mock(Cursor.class);
         when(cursor.hasNext()).thenReturn(true).thenReturn(false);
         when(cursor.next()).thenReturn(cpuStat);
-        when(cursor.sort(any(Key.class), any(SortDirection.class))).thenReturn(cursor);
 
         Storage storage = mock(Storage.class);
         when(storage.createQuery()).then(new Answer<Query>() {
--- a/common/core/src/test/java/com/redhat/thermostat/common/dao/VmGcStatDAOTest.java	Tue Sep 18 22:52:53 2012 +0200
+++ b/common/core/src/test/java/com/redhat/thermostat/common/dao/VmGcStatDAOTest.java	Thu Sep 20 00:28:12 2012 +0200
@@ -37,12 +37,10 @@
 package com.redhat.thermostat.common.dao;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.same;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -51,12 +49,9 @@
 
 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.Cursor;
-import com.redhat.thermostat.common.storage.Cursor.SortDirection;
 import com.redhat.thermostat.common.storage.Key;
 import com.redhat.thermostat.common.storage.Query;
 import com.redhat.thermostat.common.storage.Query.Criteria;
@@ -93,7 +88,6 @@
         Cursor<VmGcStat> cursor = mock(Cursor.class);
         when(cursor.hasNext()).thenReturn(true).thenReturn(false);
         when(cursor.next()).thenReturn(vmGcStat);
-        when(cursor.sort(any(Key.class), any(SortDirection.class))).thenReturn(cursor);
 
         Storage storage = mock(Storage.class);
         when(storage.createQuery()).thenReturn(new MockQuery());
--- a/common/core/src/test/java/com/redhat/thermostat/common/dao/VmLatestPojoListGetterTest.java	Tue Sep 18 22:52:53 2012 +0200
+++ b/common/core/src/test/java/com/redhat/thermostat/common/dao/VmLatestPojoListGetterTest.java	Thu Sep 20 00:28:12 2012 +0200
@@ -42,7 +42,6 @@
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.same;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -54,7 +53,6 @@
 import com.redhat.thermostat.common.model.VmClassStat;
 import com.redhat.thermostat.common.storage.Category;
 import com.redhat.thermostat.common.storage.Cursor;
-import com.redhat.thermostat.common.storage.Cursor.SortDirection;
 import com.redhat.thermostat.common.storage.Key;
 import com.redhat.thermostat.common.storage.Query;
 import com.redhat.thermostat.common.storage.Query.Criteria;
@@ -134,7 +132,6 @@
         Cursor<VmClassStat> cursor = mock(Cursor.class);
         when(cursor.hasNext()).thenReturn(true).thenReturn(true).thenReturn(false);
         when(cursor.next()).thenReturn(result1).thenReturn(result2).thenReturn(null);
-        when(cursor.sort(any(Key.class), any(SortDirection.class))).thenReturn(cursor);
 
         Storage storage = mock(Storage.class);
         MockQuery query = new MockQuery();
--- a/common/core/src/test/java/com/redhat/thermostat/common/dao/VmMemoryStatDAOTest.java	Tue Sep 18 22:52:53 2012 +0200
+++ b/common/core/src/test/java/com/redhat/thermostat/common/dao/VmMemoryStatDAOTest.java	Thu Sep 20 00:28:12 2012 +0200
@@ -51,13 +51,11 @@
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
-import org.mockito.ArgumentCaptor;
 
 import com.redhat.thermostat.common.model.VmMemoryStat;
 import com.redhat.thermostat.common.model.VmMemoryStat.Generation;
 import com.redhat.thermostat.common.model.VmMemoryStat.Space;
 import com.redhat.thermostat.common.storage.Cursor;
-import com.redhat.thermostat.common.storage.Cursor.SortDirection;
 import com.redhat.thermostat.common.storage.Key;
 import com.redhat.thermostat.common.storage.Query;
 import com.redhat.thermostat.common.storage.Query.Criteria;
@@ -94,8 +92,6 @@
         cursor = mock(Cursor.class);
         when(storage.findAllPojos(any(Query.class), same(VmMemoryStat.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);
 
     }
@@ -164,16 +160,10 @@
     }
 
     private void verifyQuery() {
-        @SuppressWarnings("rawtypes")
-        ArgumentCaptor<Key> sortKey = ArgumentCaptor.forClass(Key.class);
-        ArgumentCaptor<SortDirection> sortDirection = ArgumentCaptor.forClass(SortDirection.class);
-        verify(cursor).sort(sortKey.capture(), sortDirection.capture());
 
         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));
+        assertTrue(query.hasSort(Key.TIMESTAMP, Query.SortDirection.DESCENDING));
     }
 
     @Test
--- a/common/core/src/test/java/com/redhat/thermostat/common/storage/MongoCursorTest.java	Tue Sep 18 22:52:53 2012 +0200
+++ b/common/core/src/test/java/com/redhat/thermostat/common/storage/MongoCursorTest.java	Thu Sep 20 00:28:12 2012 +0200
@@ -150,34 +150,4 @@
         assertNull(cursor.next());
     }
 
-    @Test
-    public void verifyCursorSort() {
-        ArgumentCaptor<DBObject> arg = ArgumentCaptor.forClass(DBObject.class);
-        Cursor<TestClass> sorted = cursor.sort(key1, Cursor.SortDirection.ASCENDING);
-
-        verify(dbCursor).sort(arg.capture());
-        DBObject orderByDBObject = arg.getValue();
-        assertEquals(1, orderByDBObject.keySet().size());
-        assertEquals((Integer) Cursor.SortDirection.ASCENDING.getValue(), orderByDBObject.get(key1.getName()));
-        // Verify that the sorted cursor is still return the same number of items. We leave the actual
-        // sorting to Mongo and won't check it here.
-        assertTrue(sorted.hasNext());
-        sorted.next();
-        assertTrue(sorted.hasNext());
-        sorted.next();
-        assertFalse(sorted.hasNext());
-    }
-
-    @Test
-    public void verifyCursorLimit() {
-
-        Cursor<TestClass> sorted = cursor.limit(1);
-
-        verify(dbCursor).limit(1);
-
-        // We cannot really test if the cursor really got limited, this is up to the mongo implementation.
-        // In any case, we can verify that the returned cursor actually is 'active'.
-        assertTrue(sorted.hasNext());
-        sorted.next();
-    }
 }
--- a/common/core/src/test/java/com/redhat/thermostat/common/storage/MongoStorageTest.java	Tue Sep 18 22:52:53 2012 +0200
+++ b/common/core/src/test/java/com/redhat/thermostat/common/storage/MongoStorageTest.java	Thu Sep 20 00:28:12 2012 +0200
@@ -144,6 +144,7 @@
     private Mongo m;
     private DB db;
     private DBCollection testCollection, emptyTestCollection, mockedCollection;
+    private DBCursor cursor;
 
     private MongoStorage makeStorage() {
         MongoStorage storage = new MongoStorage(conf);
@@ -177,7 +178,7 @@
         multiKeyQuery.put(key2, "test4");
         multiKeyQuery.put(key1, "test5");
 
-        DBCursor cursor = mock(DBCursor.class);
+        cursor = mock(DBCursor.class);
         when(cursor.hasNext()).thenReturn(true).thenReturn(true).thenReturn(false);
         when(cursor.next()).thenReturn(value1).thenReturn(value2).thenReturn(null);
 
@@ -200,6 +201,7 @@
         testCollection = null;
         emptyTestCollection = null;
         multiKeyQuery = null;
+        cursor = null;
     }
 
     @Test
@@ -248,7 +250,7 @@
     public void verifyFindAllCallsDBCollectionFind() throws Exception {
         PowerMockito.whenNew(Mongo.class).withParameterTypes(MongoURI.class).withArguments(any(MongoURI.class)).thenReturn(m);
         MongoStorage storage = makeStorage();
-        Query query = storage.createQuery().from(testCategory);
+        Query query = storage.createQuery().from(testCategory).where(key1, Criteria.EQUALS, "fluff");
         storage.findAllPojos(query, TestClass.class);
         verify(testCollection).find(any(DBObject.class));
     }
@@ -268,6 +270,7 @@
         MongoStorage storage = makeStorage();
 
         MongoQuery query = mock(MongoQuery.class);
+        when(query.hasClauses()).thenReturn(true);
         DBObject generatedQuery = mock(DBObject.class);
         when(query.getGeneratedQuery()).thenReturn(generatedQuery);
         when(query.getCollectionName()).thenReturn(testCategory.getName());
@@ -329,10 +332,29 @@
     }
 
     @Test
+    public void verifyFindAllWithSortAndLimit() throws Exception {
+        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
+        // Because we mock the DBCollection, the contents of this query don't actually determine the result.
+        MongoQuery query = (MongoQuery) new MongoQuery().from(testCategory).sort(key1, Query.SortDirection.ASCENDING).limit(3);
+
+        Cursor<TestClass> cursor = storage.findAllPojos(query, TestClass.class);
+
+        verifyDefaultCursor(cursor);
+        ArgumentCaptor<DBObject> orderBy = ArgumentCaptor.forClass(DBObject.class);
+        verify(this.cursor).sort(orderBy.capture());
+        assertTrue(orderBy.getValue().containsField("key1"));
+        assertEquals(1, orderBy.getValue().get("key1"));
+        verify(this.cursor).limit(3);
+    }
+
+    @Test
     public void verifyFindAllFromCategoryCallsDBCollectionFindAll() throws Exception {
         PowerMockito.whenNew(Mongo.class).withParameterTypes(MongoURI.class).withArguments(any(MongoURI.class)).thenReturn(m);
         MongoStorage storage = makeStorage();
-        storage.findAllPojosFromCategory(testCategory, TestClass.class);
+        Query query = storage.createQuery().from(testCategory);
+        storage.findAllPojos(query, TestClass.class);
         verify(testCollection).find();
     }
 
@@ -340,7 +362,8 @@
     public void verifyFindAllFromCategoryReturnsCorrectCursor() throws Exception {
         PowerMockito.whenNew(Mongo.class).withParameterTypes(MongoURI.class).withArguments(any(MongoURI.class)).thenReturn(m);
         MongoStorage storage = makeStorage();
-        Cursor<TestClass> cursor = storage.findAllPojosFromCategory(testCategory, TestClass.class);
+        Query query = storage.createQuery().from(testCategory);
+        Cursor<TestClass> cursor = storage.findAllPojos(query, TestClass.class);
 
         verifyDefaultCursor(cursor);
     }
--- a/thread/collector/src/main/java/com/redhat/thermostat/thread/dao/impl/ThreadDaoImpl.java	Tue Sep 18 22:52:53 2012 +0200
+++ b/thread/collector/src/main/java/com/redhat/thermostat/thread/dao/impl/ThreadDaoImpl.java	Thu Sep 20 00:28:12 2012 +0200
@@ -92,8 +92,8 @@
     public ThreadSummary loadLastestSummary(VmRef ref) {
         ThreadSummary summary = null;
 
-        Query query = prepareQuery(THREAD_SUMMARY, ref);
-        Cursor<ThreadSummary> cursor = storage.findAllPojos(query, ThreadSummary.class).sort(Key.TIMESTAMP, Cursor.SortDirection.DESCENDING).limit(1);
+        Query query = prepareQuery(THREAD_SUMMARY, ref).sort(Key.TIMESTAMP, Query.SortDirection.DESCENDING).limit(1);
+        Cursor<ThreadSummary> cursor = storage.findAllPojos(query, ThreadSummary.class);
         if (cursor.hasNext()) {
             summary = cursor.next();
         }
@@ -106,10 +106,10 @@
         
         List<ThreadSummary> result = new ArrayList<>();
         
-        Query query = prepareQuery(THREAD_SUMMARY, ref);
+        Query query = prepareQuery(THREAD_SUMMARY, ref).sort(Key.TIMESTAMP, Query.SortDirection.DESCENDING);
         query.where(Key.TIMESTAMP, Criteria.GREATER_THAN, since);
 
-        Cursor<ThreadSummary> cursor = storage.findAllPojos(query, ThreadSummary.class).sort(Key.TIMESTAMP, Cursor.SortDirection.DESCENDING);
+        Cursor<ThreadSummary> cursor = storage.findAllPojos(query, ThreadSummary.class);
         while (cursor.hasNext()) {
             ThreadSummary summary = cursor.next();
             result.add(summary);
@@ -128,9 +128,10 @@
         List<ThreadInfoData> result = new ArrayList<>();
         
         Query query = prepareQuery(THREAD_INFO, ref)
-                .where(Key.TIMESTAMP, Criteria.GREATER_THAN, since);
+                .where(Key.TIMESTAMP, Criteria.GREATER_THAN, since)
+                .sort(Key.TIMESTAMP, Query.SortDirection.DESCENDING);
         
-        Cursor<ThreadInfoData> cursor = storage.findAllPojos(query, ThreadInfoData.class).sort(Key.TIMESTAMP, Cursor.SortDirection.DESCENDING);
+        Cursor<ThreadInfoData> cursor = storage.findAllPojos(query, ThreadInfoData.class);
         while (cursor.hasNext()) {
             ThreadInfoData info = cursor.next();
             result.add(info);
--- a/web/client/src/main/java/com/redhat/thermostat/web/client/RESTStorage.java	Tue Sep 18 22:52:53 2012 +0200
+++ b/web/client/src/main/java/com/redhat/thermostat/web/client/RESTStorage.java	Thu Sep 20 00:28:12 2012 +0200
@@ -42,6 +42,7 @@
 import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.io.OutputStreamWriter;
+import java.lang.reflect.Array;
 import java.net.HttpURLConnection;
 import java.net.URL;
 import java.util.UUID;
@@ -85,16 +86,26 @@
     }
 
     @Override
-    public <T extends Pojo> Cursor<T> findAllPojos(Query arg0, Class<T> arg1) {
-        // TODO Auto-generated method stub
-        return null;
-    }
+    public <T extends Pojo> Cursor<T> findAllPojos(Query query, Class<T> resultClass) {
+        try {
+            URL url = new URL(endpoint + "/find-all");
+            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+            conn.setDoInput(true);
+            conn.setDoOutput(true);
+            conn.setRequestMethod("POST");
+            OutputStream out = conn.getOutputStream();
+            Gson gson = new Gson();
+            OutputStreamWriter writer = new OutputStreamWriter(out);
+            ((RESTQuery) query).setResultClassName(resultClass.getName());
+            gson.toJson(query, writer);
+            writer.flush();
 
-    @Override
-    public <T extends Pojo> Cursor<T> findAllPojosFromCategory(Category arg0,
-            Class<T> arg1) {
-        // TODO Auto-generated method stub
-        return null;
+            InputStream in = conn.getInputStream();
+            T[] result = (T[]) gson.fromJson(new InputStreamReader(in), Array.newInstance(resultClass, 0).getClass());
+            return new WebCursor<T>(result);
+        } catch (IOException ex) {
+            throw new RuntimeException(ex);
+        }
     }
 
     @Override
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/client/src/main/java/com/redhat/thermostat/web/client/WebCursor.java	Thu Sep 20 00:28:12 2012 +0200
@@ -0,0 +1,67 @@
+/*
+ * 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.web.client;
+
+import com.redhat.thermostat.common.NotImplementedException;
+import com.redhat.thermostat.common.model.Pojo;
+import com.redhat.thermostat.common.storage.Cursor;
+import com.redhat.thermostat.common.storage.Key;
+
+class WebCursor<T extends Pojo> implements Cursor<T> {
+
+    private T[] data;
+    private int index;
+
+    WebCursor(T[] data) {
+        this.data = data;
+        index = 0;
+    }
+
+    @Override
+    public boolean hasNext() {
+        return index < data.length;
+    }
+
+    @Override
+    public T next() {
+        T result = data[index];
+        index++;
+        return result;
+    }
+
+}
--- a/web/client/src/test/java/com/redhat/thermostat/web/client/RESTStorageTest.java	Tue Sep 18 22:52:53 2012 +0200
+++ b/web/client/src/test/java/com/redhat/thermostat/web/client/RESTStorageTest.java	Thu Sep 20 00:28:12 2012 +0200
@@ -38,10 +38,12 @@
 package com.redhat.thermostat.web.client;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import java.io.IOException;
 import java.io.Reader;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 
@@ -53,11 +55,15 @@
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.handler.AbstractHandler;
 import org.junit.After;
+import org.junit.AfterClass;
 import org.junit.Before;
+import org.junit.BeforeClass;
 import org.junit.Test;
 
 import com.google.gson.Gson;
+import com.redhat.thermostat.common.storage.Categories;
 import com.redhat.thermostat.common.storage.Category;
+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;
@@ -76,6 +82,22 @@
 
     private String responseBody;
 
+    private static Category category;
+    private static Key<String> key1;
+
+    @BeforeClass
+    public static void setupCategory() {
+        key1 = new Key<>("property1", true);
+        category = new Category("test", key1);
+    }
+
+    @AfterClass
+    public static void cleanupCategory() {
+        Categories.remove(category);
+        category = null;
+        key1 = null;
+    }
+
     @Before
     public void setUp() throws Exception {
         port = FreePortFinder.findFreePort(new TryPort() {
@@ -134,8 +156,7 @@
         Gson gson = new Gson();
         responseBody = gson.toJson(obj);
 
-        Key<String> key1 = new Key<>("property1", true);
-        Query query = storage.createQuery().from(new Category("test", key1)).where(key1, Criteria.EQUALS, "fluff");
+        Query query = storage.createQuery().from(category).where(key1, Criteria.EQUALS, "fluff");
 
         TestObj result = storage.findPojo(query, TestObj.class);
         RESTQuery restQuery = gson.fromJson(requestBody, RESTQuery.class);
@@ -154,4 +175,42 @@
 
         assertEquals("fluffor", result.getProperty1());
     }
+
+    @Test
+    public void testFindAllPojos() {
+        RESTStorage storage = new RESTStorage();
+        storage.setEndpoint("http://localhost:" + port + "/");
+
+        TestObj obj1 = new TestObj();
+        obj1.setProperty1("fluffor1");
+        TestObj obj2 = new TestObj();
+        obj2.setProperty1("fluffor2");
+        Gson gson = new Gson();
+        responseBody = gson.toJson(Arrays.asList(obj1, obj2));
+
+        Key<String> key1 = new Key<>("property1", true);
+        Query query = storage.createQuery().from(category).where(key1, Criteria.EQUALS, "fluff");
+
+        Cursor<TestObj> results = storage.findAllPojos(query, TestObj.class);
+        RESTQuery restQuery = gson.fromJson(requestBody, RESTQuery.class);
+
+        Category actualCategory = restQuery.getCategory();
+        assertEquals("test", actualCategory.getName());
+        Collection<Key<?>> keys = actualCategory.getKeys();
+        assertEquals(1, keys.size());
+        assertTrue(keys.contains(new Key<String>("property1", true)));
+        List<Qualifier<?>> qualifiers = restQuery.getQualifiers();
+        assertEquals(1, qualifiers.size());
+        Qualifier<?> qual = qualifiers.get(0);
+        assertEquals(new Key<String>("property1", true), qual.getKey());
+        assertEquals(Criteria.EQUALS, qual.getCriteria());
+        assertEquals("fluff", qual.getValue());
+
+        assertTrue(results.hasNext());
+        assertEquals("fluffor1", results.next().getProperty1());
+        assertTrue(results.hasNext());
+        assertEquals("fluffor2", results.next().getProperty1());
+        assertFalse(results.hasNext());
+    }
+
 }
--- a/web/common/src/main/java/com/redhat/thermostat/web/common/RESTQuery.java	Tue Sep 18 22:52:53 2012 +0200
+++ b/web/common/src/main/java/com/redhat/thermostat/web/common/RESTQuery.java	Thu Sep 20 00:28:12 2012 +0200
@@ -40,13 +40,12 @@
 import java.util.ArrayList;
 import java.util.List;
 
-import com.redhat.thermostat.common.storage.Category;
+import com.redhat.thermostat.common.storage.AbstractQuery;
 import com.redhat.thermostat.common.storage.Key;
 import com.redhat.thermostat.common.storage.Query;
 
-public class RESTQuery implements Query {
+public class RESTQuery extends AbstractQuery {
 
-    private Category category;
     private List<Qualifier<?>> qualifiers;
     private String resultClassName;
 
@@ -55,25 +54,11 @@
     }
 
     @Override
-    public Query from(Category category) {
-        this.category = category;
-        return this;
-    }
-
-    @Override
     public <T> Query where(Key<T> key, Criteria criteria, T value) {
         qualifiers.add(new Qualifier<>(key, criteria, value));
         return this;
     }
 
-    public Category getCategory() {
-        return category;
-    }
-
-    public void setCategory(Category category) {
-        this.category = category;
-    }
-
     public List<Qualifier<?>> getQualifiers() {
         return qualifiers;
     }
@@ -89,4 +74,5 @@
     public void setResultClassName(String resultClassName) {
         this.resultClassName = resultClassName;
     }
+
 }
--- a/web/server/src/main/java/com/redhat/thermostat/web/server/RESTStorageEndPoint.java	Tue Sep 18 22:52:53 2012 +0200
+++ b/web/server/src/main/java/com/redhat/thermostat/web/server/RESTStorageEndPoint.java	Thu Sep 20 00:28:12 2012 +0200
@@ -2,6 +2,7 @@
 
 import java.io.IOException;
 import java.io.Reader;
+import java.util.ArrayList;
 import java.util.List;
 
 import javax.servlet.http.HttpServlet;
@@ -9,6 +10,7 @@
 import javax.servlet.http.HttpServletResponse;
 
 import com.google.gson.Gson;
+import com.redhat.thermostat.common.storage.Cursor;
 import com.redhat.thermostat.common.storage.Query;
 import com.redhat.thermostat.common.storage.Storage;
 import com.redhat.thermostat.web.common.Qualifier;
@@ -19,9 +21,11 @@
 public class RESTStorageEndPoint extends HttpServlet {
 
     private Storage storage;
+    private Gson gson;
 
     public void init() {
         storage = StorageWrapper.getStorage();
+        gson = new Gson();
     }
 
     @Override
@@ -32,6 +36,8 @@
         String cmd = uri.substring(lastPartIdx + 1);
         if (cmd.equals("find-pojo")) {
             findPojo(req, resp);
+        } else if (cmd.equals("find-all")) {
+            findAll(req, resp);
         }
     }
 
@@ -39,23 +45,49 @@
     private void findPojo(HttpServletRequest req, HttpServletResponse resp) throws IOException {
         try {
             Reader in = req.getReader();
-            Gson gson = new Gson();
             RESTQuery query = gson.fromJson(in, RESTQuery.class);
             Class resultClass = Class.forName(query.getResultClassName());
-            Query targetQuery = storage.createQuery();
-            targetQuery = targetQuery.from(query.getCategory());
-            List<Qualifier<?>> qualifiers = query.getQualifiers();
-            for (Qualifier q : qualifiers) {
-                targetQuery = targetQuery.where(q.getKey(), q.getCriteria(), q.getValue());
-            }
+            Query targetQuery = constructTargetQuery(query);
             Object result = storage.findPojo(targetQuery, resultClass);
-            resp.setStatus(HttpServletResponse.SC_OK);
-            resp.setContentType("application/json");
-            gson.toJson(result, resp.getWriter());
-            resp.flushBuffer();
+            writeResponse(resp, result);
         } catch (ClassNotFoundException e) {
             resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "result class not found");
         }
     }
 
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    private void findAll(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        try {
+            Reader in = req.getReader();
+            RESTQuery query = gson.fromJson(in, RESTQuery.class);
+            Class resultClass = Class.forName(query.getResultClassName());
+            Query targetQuery = constructTargetQuery(query);
+            ArrayList resultList = new ArrayList();
+            Cursor result = storage.findAllPojos(targetQuery, resultClass);
+            while (result.hasNext()) {
+                resultList.add(result.next());
+            }
+            writeResponse(resp, resultList.toArray());
+        } catch (ClassNotFoundException e) {
+            resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "result class not found");
+        }
+    }
+
+    private Query constructTargetQuery(RESTQuery query) {
+        Query targetQuery = storage.createQuery();
+        targetQuery = targetQuery.from(query.getCategory());
+        List<Qualifier<?>> qualifiers = query.getQualifiers();
+        for (Qualifier q : qualifiers) {
+            targetQuery = targetQuery.where(q.getKey(), q.getCriteria(), q.getValue());
+        }
+        return targetQuery;
+    }
+
+    private void writeResponse(HttpServletResponse resp, Object result) throws IOException {
+        resp.setStatus(HttpServletResponse.SC_OK);
+        resp.setContentType("application/json");
+        gson.toJson(result, resp.getWriter());
+        resp.flushBuffer();
+    }
+
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/server/src/test/java/com/redhat/thermostat/web/server/RESTStorageEndpointTest.java	Thu Sep 20 00:28:12 2012 +0200
@@ -0,0 +1,210 @@
+/*
+ * 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.web.server;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.google.gson.Gson;
+import com.redhat.thermostat.common.model.Pojo;
+import com.redhat.thermostat.common.storage.Categories;
+import com.redhat.thermostat.common.storage.Category;
+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.FreePortFinder;
+import com.redhat.thermostat.test.FreePortFinder.TryPort;
+import com.redhat.thermostat.web.client.RESTStorage;
+import com.redhat.thermostat.web.common.RESTQuery;
+import com.redhat.thermostat.web.common.StorageWrapper;
+
+public class RESTStorageEndpointTest {
+
+    public static class TestClass implements Pojo {
+        private String key1;
+        private int key2;
+        public String getKey1() {
+            return key1;
+        }
+        public void setKey1(String key1) {
+            this.key1 = key1;
+        }
+        public int getKey2() {
+            return key2;
+        }
+        public void setKey2(int key2) {
+            this.key2 = key2;
+        }
+    }
+
+    private Server server;
+    private int port;
+    private Storage mockStorage;
+
+    private static Key<String> key1;
+    private static Key<Integer> key2;
+    private static Category category;
+
+    @BeforeClass
+    public static void setupCategory() {
+        key1 = new Key<>("key1", true);
+        key2 = new Key<>("key2", false);
+        category = new Category("test", key1, key2);
+    }
+
+    @AfterClass
+    public static void cleanupCategory() {
+        Categories.remove(category);
+        category = null;
+        key2 = null;
+        key1 = null;
+    }
+
+    @Before
+    public void setUp() throws Exception {
+
+        mockStorage = mock(Storage.class);
+        StorageWrapper.setStorage(mockStorage);
+
+        port = FreePortFinder.findFreePort(new TryPort() {
+            
+            @Override
+            public void tryPort(int port) throws Exception {
+                startServer(port);
+            }
+        });
+    }
+
+    private void startServer(int port) throws Exception {
+        server = new Server(port);
+        server.setHandler(new WebAppContext("src/main/webapp", "/"));
+        server.start();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        server.stop();
+        server.join();
+    }
+
+    @Test
+    public void testFind() {
+        // Configure mock storage.
+        TestClass expected = new TestClass();
+        expected.setKey1("fluff");
+        expected.setKey2(42);
+        when(mockStorage.findPojo(any(Query.class), same(TestClass.class))).thenReturn(expected);
+
+        Query mockQuery = QueryTestHelper.createMockQuery();
+        when(mockStorage.createQuery()).thenReturn(mockQuery);
+
+        RESTStorage restStorage = new RESTStorage();
+        restStorage.setEndpoint(getEndpoint());
+        Query query = restStorage.createQuery();
+        query.from(category).where(key1, Criteria.EQUALS, "fluff");
+
+        TestClass result = restStorage.findPojo(query, TestClass.class);
+
+        assertEquals("fluff", result.getKey1());
+        assertEquals(42, result.getKey2());
+        verify(mockStorage).createQuery();
+        verify(mockStorage).findPojo(any(Query.class), same(TestClass.class));
+    }
+
+    @Test
+    public void testFindAllPojos() throws IOException {
+        TestClass expected1 = new TestClass();
+        expected1.setKey1("fluff1");
+        expected1.setKey2(42);
+        TestClass expected2 = new TestClass();
+        expected2.setKey1("fluff2");
+        expected2.setKey2(43);
+        @SuppressWarnings("unchecked")
+        Cursor<TestClass> cursor = mock(Cursor.class);
+        when(cursor.hasNext()).thenReturn(true).thenReturn(true).thenReturn(false);
+        when(cursor.next()).thenReturn(expected1).thenReturn(expected2);
+
+        when(mockStorage.findAllPojos(any(Query.class), same(TestClass.class))).thenReturn(cursor);
+        Query mockQuery = QueryTestHelper.createMockQuery();
+        when(mockStorage.createQuery()).thenReturn(mockQuery);
+
+        String endpoint = getEndpoint();
+        URL url = new URL(endpoint + "/find-all");
+        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+        conn.setDoInput(true);
+        conn.setDoOutput(true);
+        RESTQuery query = (RESTQuery) new RESTQuery().from(category).where(key1, Criteria.EQUALS, "fluff");
+        query.setResultClassName(TestClass.class.getName());
+        Gson gson = new Gson();
+        OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream());
+        gson.toJson(query, out);
+        out.flush();
+
+        Reader in = new InputStreamReader(conn.getInputStream());
+        TestClass[] results = gson.fromJson(in, TestClass[].class);
+        assertEquals(2, results.length);
+        assertEquals("fluff1", results[0].getKey1());
+        assertEquals(42, results[0].getKey2());
+        assertEquals("fluff2", results[1].getKey1());
+        assertEquals(43, results[1].getKey2());
+    }
+
+    private String getEndpoint() {
+        return "http://localhost:" + port + "/storage";
+    }
+}
--- a/web/server/src/test/java/com/redhat/thermostat/web/server/RESTStorageTest.java	Tue Sep 18 22:52:53 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,138 +0,0 @@
-/*
- * 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.web.server;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.same;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.webapp.WebAppContext;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import com.redhat.thermostat.common.model.Pojo;
-import com.redhat.thermostat.common.storage.Category;
-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.FreePortFinder;
-import com.redhat.thermostat.test.FreePortFinder.TryPort;
-import com.redhat.thermostat.web.client.RESTStorage;
-import com.redhat.thermostat.web.common.StorageWrapper;
-
-public class RESTStorageTest {
-
-    public static class TestClass implements Pojo {
-        private String key1;
-        private int key2;
-        public String getKey1() {
-            return key1;
-        }
-        public void setKey1(String key1) {
-            this.key1 = key1;
-        }
-        public int getKey2() {
-            return key2;
-        }
-        public void setKey2(int key2) {
-            this.key2 = key2;
-        }
-    }
-
-    private Server server;
-    private int port;
-    private Storage mockStorage;
-
-    @Before
-    public void setUp() throws Exception {
-        mockStorage = mock(Storage.class);
-        StorageWrapper.setStorage(mockStorage);
-
-        port = FreePortFinder.findFreePort(new TryPort() {
-            
-            @Override
-            public void tryPort(int port) throws Exception {
-                startServer(port);
-            }
-        });
-    }
-
-    private void startServer(int port) throws Exception {
-        server = new Server(port);
-        server.setHandler(new WebAppContext("src/main/webapp", "/"));
-        server.start();
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        server.stop();
-        server.join();
-    }
-
-    @Test
-    public void testFind() {
-        // Configure mock storage.
-        TestClass expected = new TestClass();
-        expected.setKey1("fluff");
-        expected.setKey2(42);
-        when(mockStorage.findPojo(any(Query.class), same(TestClass.class))).thenReturn(expected);
-
-        Query mockQuery = QueryTestHelper.createMockQuery();
-        when(mockStorage.createQuery()).thenReturn(mockQuery);
-
-        RESTStorage restStorage = new RESTStorage();
-        restStorage.setEndpoint("http://localhost:" + port + "/storage");
-        Query query = restStorage.createQuery();
-        Key<String> key1 = new Key<>("key1", true);
-        Key<Integer> key2 = new Key<>("key2", false);
-        Category category = new Category("test", key1, key2);
-        query.from(category).where(key1, Criteria.EQUALS, "fluff");
-
-        TestClass result = restStorage.findPojo(query, TestClass.class);
-
-        assertEquals("fluff", result.getKey1());
-        assertEquals(42, result.getKey2());
-        verify(mockStorage).createQuery();
-        verify(mockStorage).findPojo(any(Query.class), same(TestClass.class));
-    }
-}