changeset 1141:2e71b9784316

Convert write database operations to handle boolean formulas This commit follows up from my previous work to support arbitrary boolean formulas in queries. As done with Query, now Update and Remove have been modified to handle these arbitrary Expressions in their "where" methods. One issue I'd like to bring up is that these methods previously accepted conditions using only the EQUALS operator. As far as I can tell this is an unnecessary restriction. Now with the move to simply accept an Expression argument, updates and removes can handle arbitrary where conditions. The Qualifier class and Criteria enum have been removed since they are no longer used. Reviewed-by: jerboaa Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2013-June/007082.html
author Elliott Baron <ebaron@redhat.com>
date Mon, 10 Jun 2013 17:00:25 -0400
parents bf720980510c
children 66b8b266a5dd
files storage/core/src/main/java/com/redhat/thermostat/storage/core/Query.java storage/core/src/main/java/com/redhat/thermostat/storage/core/QueuedStorage.java storage/core/src/main/java/com/redhat/thermostat/storage/core/Remove.java storage/core/src/main/java/com/redhat/thermostat/storage/core/Update.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/dao/AgentInfoDAOImpl.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/dao/BackendInfoDAOImpl.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/dao/VmInfoDAOImpl.java storage/core/src/test/java/com/redhat/thermostat/storage/internal/dao/AgentInfoDAOTest.java storage/core/src/test/java/com/redhat/thermostat/storage/internal/dao/BackendInfoDAOTest.java storage/core/src/test/java/com/redhat/thermostat/storage/internal/dao/QueryTestHelper.java storage/core/src/test/java/com/redhat/thermostat/storage/internal/dao/VmInfoDAOTest.java storage/mongo/src/main/java/com/redhat/thermostat/storage/mongodb/internal/MongoRemove.java storage/mongo/src/main/java/com/redhat/thermostat/storage/mongodb/internal/MongoUpdate.java storage/mongo/src/test/java/com/redhat/thermostat/storage/mongodb/internal/MongoStorageTest.java vm-heap-analysis/common/src/test/java/com/redhat/thermostat/vm/heap/analysis/common/internal/HeapDAOTest.java web/client/src/test/java/com/redhat/thermostat/web/client/internal/WebStorageTest.java web/common/src/main/java/com/redhat/thermostat/web/common/Qualifier.java web/common/src/main/java/com/redhat/thermostat/web/common/WebRemove.java web/common/src/main/java/com/redhat/thermostat/web/common/WebUpdate.java web/server/src/main/java/com/redhat/thermostat/web/server/WebStorageEndPoint.java web/server/src/test/java/com/redhat/thermostat/web/server/QueryTestHelper.java web/server/src/test/java/com/redhat/thermostat/web/server/WebStorageEndpointTest.java
diffstat 22 files changed, 139 insertions(+), 206 deletions(-) [+]
line wrap: on
line diff
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/core/Query.java	Fri Jun 07 13:49:04 2013 -0400
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/Query.java	Mon Jun 10 17:00:25 2013 -0400
@@ -44,15 +44,6 @@
  */
 public interface Query<T extends Pojo> {
 
-    enum Criteria {
-        EQUALS,
-        NOT_EQUAL_TO,
-        GREATER_THAN,
-        GREATER_THAN_OR_EQUAL_TO,
-        LESS_THAN,
-        LESS_THAN_OR_EQUAL_TO,
-    }
-    
     enum SortDirection {
         ASCENDING(1),
         DESCENDING(-1);
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/core/QueuedStorage.java	Fri Jun 07 13:49:04 2013 -0400
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/QueuedStorage.java	Mon Jun 10 17:00:25 2013 -0400
@@ -44,6 +44,7 @@
 import java.util.concurrent.TimeUnit;
 
 import com.redhat.thermostat.storage.model.Pojo;
+import com.redhat.thermostat.storage.query.Expression;
 
 public class QueuedStorage implements Storage {
 
@@ -75,8 +76,8 @@
         }
 
         @Override
-        public <T> void where(Key<T> key, T value) {
-            delegateUpdate.where(key,  value);
+        public void where(Expression expr) {
+            delegateUpdate.where(expr);
             
         }
 
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/core/Remove.java	Fri Jun 07 13:49:04 2013 -0400
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/Remove.java	Mon Jun 10 17:00:25 2013 -0400
@@ -37,6 +37,8 @@
 
 package com.redhat.thermostat.storage.core;
 
+import com.redhat.thermostat.storage.query.Expression;
+
 /**
  * Describes what data should be removed from storage.
  */
@@ -44,7 +46,7 @@
 
     Remove from(Category category);
 
-    <T> Remove where(Key<T> key, T value);
+    Remove where(Expression where);
 
 }
 
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/core/Update.java	Fri Jun 07 13:49:04 2013 -0400
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/Update.java	Mon Jun 10 17:00:25 2013 -0400
@@ -36,24 +36,23 @@
 
 package com.redhat.thermostat.storage.core;
 
+import com.redhat.thermostat.storage.query.Expression;
+
 /**
  * Updates fields of a database entry. 
  */
 public interface Update {
 
     /**
-     * Adds a where clause that denotes the entry to be updated. If more than one
-     * where-clause is declared, they are concatenated as an and-query.
-     * If a clause with the same key is declared more than once, the latter
-     * overrides the former. This is so that an Update object can be reused
-     * for multiple requests. If an update is issued for which no entry can
-     * be found (i.e. the where-clause yields no results), a
+     * Given a boolean expression, this method specifies a where condition for
+     * this update operation. If an update is issued for which no entry can be
+     * found (i.e. the where-clause yields no results), a
      * <code>StorageException</code> may get thrown.
-     *
+     * 
      * @param key the key of the field of the where clause
      * @param value the value of the field of the where clause
      */
-    <T> void where(Key<T> key, T value);
+    void where(Expression expr);
 
     /**
      * Sets a field in a found document to the specified value. If the same key is
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/dao/AgentInfoDAOImpl.java	Fri Jun 07 13:49:04 2013 -0400
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/dao/AgentInfoDAOImpl.java	Mon Jun 10 17:00:25 2013 -0400
@@ -55,10 +55,12 @@
 public class AgentInfoDAOImpl implements AgentInfoDAO {
 
     private final Storage storage;
+    private final ExpressionFactory factory;
 
     public AgentInfoDAOImpl(Storage storage) {
         this.storage = storage;
         storage.registerCategory(CATEGORY);
+        factory = new ExpressionFactory();
     }
 
     @Override
@@ -101,7 +103,6 @@
     @Override
     public AgentInformation getAgentInformation(HostRef agentRef) {
         Query<AgentInformation> query = storage.createQuery(CATEGORY);
-        ExpressionFactory factory = new ExpressionFactory();
         Expression expr = factory.equalTo(Key.AGENT_ID, agentRef.getAgentId());
         query.where(expr);
         query.limit(1);
@@ -117,14 +118,16 @@
 
     @Override
     public void removeAgentInformation(AgentInformation agentInfo) {
-        Remove remove = storage.createRemove().from(CATEGORY).where(Key.AGENT_ID, agentInfo.getAgentId());
+        Expression expr = factory.equalTo(Key.AGENT_ID, agentInfo.getAgentId());
+        Remove remove = storage.createRemove().from(CATEGORY).where(expr);
         storage.removePojo(remove);
     }
 
     @Override
     public void updateAgentInformation(AgentInformation agentInfo) {
         Update update = storage.createUpdate(CATEGORY);
-        update.where(Key.AGENT_ID, agentInfo.getAgentId());
+        Expression expr = factory.equalTo(Key.AGENT_ID, agentInfo.getAgentId());
+        update.where(expr);
         update.set(START_TIME_KEY, agentInfo.getStartTime());
         update.set(STOP_TIME_KEY, agentInfo.getStopTime());
         update.set(ALIVE_KEY, agentInfo.isAlive());
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/dao/BackendInfoDAOImpl.java	Fri Jun 07 13:49:04 2013 -0400
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/dao/BackendInfoDAOImpl.java	Mon Jun 10 17:00:25 2013 -0400
@@ -56,17 +56,18 @@
 public class BackendInfoDAOImpl implements BackendInfoDAO {
 
     private final Storage storage;
+    private final ExpressionFactory factory;
 
     public BackendInfoDAOImpl(Storage storage) {
         this.storage = storage;
         storage.registerCategory(CATEGORY);
+        factory = new ExpressionFactory();
     }
 
     @Override
     public List<BackendInformation> getBackendInformation(HostRef host) {
         // Sort by order value
         Query<BackendInformation> query = storage.createQuery(CATEGORY);
-        ExpressionFactory factory = new ExpressionFactory();
         Expression expr = factory.equalTo(Key.AGENT_ID, host.getAgentId());
         query.where(expr);
 
@@ -92,7 +93,8 @@
 
     @Override
     public void removeBackendInformation(BackendInformation info) {
-        Remove remove = storage.createRemove().from(CATEGORY).where(BACKEND_NAME, info.getName());
+        Expression expr = factory.equalTo(BACKEND_NAME, info.getName());
+        Remove remove = storage.createRemove().from(CATEGORY).where(expr);
         storage.removePojo(remove);
     }
 
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/dao/VmInfoDAOImpl.java	Fri Jun 07 13:49:04 2013 -0400
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/dao/VmInfoDAOImpl.java	Mon Jun 10 17:00:25 2013 -0400
@@ -57,10 +57,12 @@
 public class VmInfoDAOImpl implements VmInfoDAO {
 
     private final Storage storage;
+    private final ExpressionFactory factory;
 
     public VmInfoDAOImpl(Storage storage) {
         this.storage = storage;
         storage.registerCategory(vmInfoCategory);
+        factory = new ExpressionFactory();
     }
 
     @Override
@@ -90,7 +92,6 @@
 
     private Query<VmInfo> buildQuery(HostRef host) {
         Query<VmInfo> query = storage.createQuery(vmInfoCategory);
-        ExpressionFactory factory = new ExpressionFactory();
         Expression expr = factory.equalTo(Key.AGENT_ID, host.getAgentId());
         query.where(expr);
         return query;
@@ -130,7 +131,8 @@
     @Override
     public void putVmStoppedTime(int vmId, long timestamp) {
         Update update = storage.createUpdate(vmInfoCategory);
-        update.where(Key.VM_ID, vmId);
+        Expression expr = factory.equalTo(Key.VM_ID, vmId);
+        update.where(expr);
         update.set(VmInfoDAO.stopTimeKey, timestamp);
         update.apply();
     }
--- a/storage/core/src/test/java/com/redhat/thermostat/storage/internal/dao/AgentInfoDAOTest.java	Fri Jun 07 13:49:04 2013 -0400
+++ b/storage/core/src/test/java/com/redhat/thermostat/storage/internal/dao/AgentInfoDAOTest.java	Mon Jun 10 17:00:25 2013 -0400
@@ -70,6 +70,7 @@
 
     private AgentInformation agentInfo1;
     private AgentInformation agent1;
+    private ExpressionFactory factory;
 
     @Before
     public void setUp() {
@@ -86,6 +87,7 @@
         agent1.setConfigListenAddress("foobar:666");
         agent1.setStartTime(100);
         agent1.setStopTime(10);
+        factory = new ExpressionFactory();
     }
 
     @Test
@@ -152,7 +154,6 @@
         List<AgentInformation> aliveAgents = dao.getAliveAgents();
 
         verify(storage).createQuery(AgentInfoDAO.CATEGORY);
-        ExpressionFactory factory = new ExpressionFactory();
         Expression expr = factory.equalTo(AgentInfoDAO.ALIVE_KEY, Boolean.TRUE);
         verify(query).where(eq(expr));
         verify(query).execute();
@@ -202,7 +203,6 @@
         AgentInformation computed = dao.getAgentInformation(agentRef);
 
         verify(storage).createQuery(AgentInfoDAO.CATEGORY);
-        ExpressionFactory factory = new ExpressionFactory();
         Expression expr = factory.equalTo(Key.AGENT_ID, agentInfo1.getAgentId());
         verify(query).where(eq(expr));
         verify(query).limit(1);
@@ -238,7 +238,8 @@
         dao.updateAgentInformation(agentInfo1);
 
         verify(storage).createUpdate(AgentInfoDAO.CATEGORY);
-        verify(mockUpdate).where(Key.AGENT_ID, "1234");
+        Expression expr = factory.equalTo(Key.AGENT_ID, "1234");
+        verify(mockUpdate).where(eq(expr));
         verify(mockUpdate).set(AgentInfoDAO.START_TIME_KEY, 100L);
         verify(mockUpdate).set(AgentInfoDAO.STOP_TIME_KEY, 10L);
         verify(mockUpdate).set(AgentInfoDAO.CONFIG_LISTEN_ADDRESS, "foobar:666");
@@ -259,7 +260,8 @@
 
         verify(storage).removePojo(mockRemove);
         verify(mockRemove).from(AgentInfoDAO.CATEGORY);
-        verify(mockRemove).where(Key.AGENT_ID, "1234");
+        Expression expr = factory.equalTo(Key.AGENT_ID, "1234");
+        verify(mockRemove).where(eq(expr));
     }
 
 }
--- a/storage/core/src/test/java/com/redhat/thermostat/storage/internal/dao/BackendInfoDAOTest.java	Fri Jun 07 13:49:04 2013 -0400
+++ b/storage/core/src/test/java/com/redhat/thermostat/storage/internal/dao/BackendInfoDAOTest.java	Mon Jun 10 17:00:25 2013 -0400
@@ -74,6 +74,7 @@
 
     private BackendInformation backendInfo1;
     private BackendInformation backend1;
+    private ExpressionFactory factory;
 
     @Before
     public void setUp() {
@@ -94,6 +95,8 @@
         backend1.setObserveNewJvm(true);
         backend1.setPids(new int[] { -1, 0, 1});
         backend1.setOrderValue(100);
+        
+        factory = new ExpressionFactory();
     }
 
     @Test
@@ -152,7 +155,6 @@
         List<BackendInformation> result = dao.getBackendInformation(agentref);
 
         verify(storage).createQuery(BackendInfoDAO.CATEGORY);
-        ExpressionFactory factory = new ExpressionFactory();
         Expression expr = factory.equalTo(Key.AGENT_ID, AGENT_ID);
         verify(query).where(eq(expr));
         verify(query).execute();
@@ -173,7 +175,8 @@
         verify(storage).removePojo(remove);
         InOrder inOrder = inOrder(remove);
         inOrder.verify(remove).from(BackendInfoDAO.CATEGORY);
-        inOrder.verify(remove).where(BackendInfoDAO.BACKEND_NAME, "backend-name");
+        Expression expr = factory.equalTo(BackendInfoDAO.BACKEND_NAME, "backend-name");
+        inOrder.verify(remove).where(eq(expr));
     }
 
 }
--- a/storage/core/src/test/java/com/redhat/thermostat/storage/internal/dao/QueryTestHelper.java	Fri Jun 07 13:49:04 2013 -0400
+++ b/storage/core/src/test/java/com/redhat/thermostat/storage/internal/dao/QueryTestHelper.java	Mon Jun 10 17:00:25 2013 -0400
@@ -42,16 +42,15 @@
 import static org.mockito.Mockito.when;
 
 import com.redhat.thermostat.storage.core.Category;
-import com.redhat.thermostat.storage.core.Key;
 import com.redhat.thermostat.storage.core.Remove;
+import com.redhat.thermostat.storage.query.Expression;
 
 public class QueryTestHelper {
 
-    @SuppressWarnings("unchecked")
     public static Remove createMockRemove() {
         Remove mockRemove = mock(Remove.class);
         when(mockRemove.from(any(Category.class))).thenReturn(mockRemove);
-        when(mockRemove.where(any(Key.class), any())).thenReturn(mockRemove);
+        when(mockRemove.where(any(Expression.class))).thenReturn(mockRemove);
         return mockRemove;
     }
 
--- a/storage/core/src/test/java/com/redhat/thermostat/storage/internal/dao/VmInfoDAOTest.java	Fri Jun 07 13:49:04 2013 -0400
+++ b/storage/core/src/test/java/com/redhat/thermostat/storage/internal/dao/VmInfoDAOTest.java	Mon Jun 10 17:00:25 2013 -0400
@@ -65,6 +65,7 @@
 import com.redhat.thermostat.storage.dao.DAOException;
 import com.redhat.thermostat.storage.dao.VmInfoDAO;
 import com.redhat.thermostat.storage.model.VmInfo;
+import com.redhat.thermostat.storage.query.ExpressionFactory;
 
 public class VmInfoDAOTest {
 
@@ -287,7 +288,8 @@
         dao.putVmStoppedTime(vmId, stopTime);
 
         verify(storage).createUpdate(VmInfoDAO.vmInfoCategory);
-        verify(mockUpdate).where(Key.VM_ID, 1);
+        ExpressionFactory factory = new ExpressionFactory();
+        verify(mockUpdate).where(factory.equalTo(Key.VM_ID, 1));
         verify(mockUpdate).set(VmInfoDAO.stopTimeKey, 3L);
         verify(mockUpdate).apply();
         verifyNoMoreInteractions(mockUpdate);
--- a/storage/mongo/src/main/java/com/redhat/thermostat/storage/mongodb/internal/MongoRemove.java	Fri Jun 07 13:49:04 2013 -0400
+++ b/storage/mongo/src/main/java/com/redhat/thermostat/storage/mongodb/internal/MongoRemove.java	Mon Jun 10 17:00:25 2013 -0400
@@ -37,16 +37,24 @@
 
 package com.redhat.thermostat.storage.mongodb.internal;
 
-import com.mongodb.BasicDBObject;
 import com.mongodb.DBObject;
 import com.redhat.thermostat.storage.core.Category;
-import com.redhat.thermostat.storage.core.Key;
 import com.redhat.thermostat.storage.core.Remove;
+import com.redhat.thermostat.storage.query.Expression;
 
 class MongoRemove implements Remove {
 
     private Category category;
     private DBObject query;
+    private MongoExpressionParser parser;
+    
+    public MongoRemove() {
+        this(new MongoExpressionParser());
+    }
+    
+    MongoRemove(MongoExpressionParser parser) {
+        this.parser = parser;
+    }
 
     @Override
     public Remove from(Category category) {
@@ -62,11 +70,8 @@
     }
 
     @Override
-    public <T> Remove where(Key<T> key, T value) {
-        if (query == null) {
-            query = new BasicDBObject();
-        }
-        query.put(key.getName(), value);
+    public Remove where(Expression expr) {
+        query = parser.parse(expr);
         return this;
     }
 
--- a/storage/mongo/src/main/java/com/redhat/thermostat/storage/mongodb/internal/MongoUpdate.java	Fri Jun 07 13:49:04 2013 -0400
+++ b/storage/mongo/src/main/java/com/redhat/thermostat/storage/mongodb/internal/MongoUpdate.java	Mon Jun 10 17:00:25 2013 -0400
@@ -42,6 +42,7 @@
 import com.redhat.thermostat.storage.core.Category;
 import com.redhat.thermostat.storage.core.Key;
 import com.redhat.thermostat.storage.core.Update;
+import com.redhat.thermostat.storage.query.Expression;
 
 class MongoUpdate implements Update {
 
@@ -51,10 +52,16 @@
     private DBObject query;
     private DBObject values;
     private Category category;
+    private MongoExpressionParser parser;
 
     public MongoUpdate(MongoStorage storage, Category category) {
+        this(storage, category, new MongoExpressionParser());
+    }
+    
+    MongoUpdate(MongoStorage storage, Category category, MongoExpressionParser parser) {
         this.storage = storage;
         this.category = category;
+        this.parser = parser;
     }
 
     Category getCategory() {
@@ -62,11 +69,8 @@
     }
 
     @Override
-    public <T> void where(Key<T> key, T value) {
-        if (query == null) {
-            query = new BasicDBObject();
-        }
-        query.put(key.getName(), value);
+    public void where(Expression expr) {
+        query = parser.parse(expr);
     }
 
     DBObject getQuery() {
--- a/storage/mongo/src/test/java/com/redhat/thermostat/storage/mongodb/internal/MongoStorageTest.java	Fri Jun 07 13:49:04 2013 -0400
+++ b/storage/mongo/src/test/java/com/redhat/thermostat/storage/mongodb/internal/MongoStorageTest.java	Mon Jun 10 17:00:25 2013 -0400
@@ -161,6 +161,7 @@
     private DB db;
     private DBCollection testCollection, emptyTestCollection, mockedCollection;
     private DBCursor cursor;
+    private ExpressionFactory factory;
 
     private MongoStorage makeStorage() {
         MongoStorage storage = new MongoStorage(conf);
@@ -200,6 +201,8 @@
         when(emptyTestCollection.getCount()).thenReturn(0L);
         when(db.collectionExists(anyString())).thenReturn(false);
         when(db.createCollection(anyString(), any(DBObject.class))).thenReturn(testCollection);
+        
+        factory = new ExpressionFactory();
     }
 
     @After
@@ -394,7 +397,8 @@
     public void verifySimpleUpdate() {
         MongoStorage storage = makeStorage();
         Update update = storage.createUpdate(testCategory);
-        update.where(Key.AGENT_ID, "test1");
+        Expression expr = factory.equalTo(Key.AGENT_ID, "test1");
+        update.where(expr);
         update.set(key2, "test2");
         update.apply();
 
@@ -419,7 +423,8 @@
     public void verifyMultiFieldUpdate() {
         MongoStorage storage = makeStorage();
         Update update = storage.createUpdate(testCategory);
-        update.where(Key.AGENT_ID, "test1");
+        Expression expr = factory.equalTo(Key.AGENT_ID, "test1");
+        update.where(expr);
         update.set(key2, "test2");
         update.set(key3, "test3");
         update.apply();
--- a/vm-heap-analysis/common/src/test/java/com/redhat/thermostat/vm/heap/analysis/common/internal/HeapDAOTest.java	Fri Jun 07 13:49:04 2013 -0400
+++ b/vm-heap-analysis/common/src/test/java/com/redhat/thermostat/vm/heap/analysis/common/internal/HeapDAOTest.java	Mon Jun 10 17:00:25 2013 -0400
@@ -67,9 +67,8 @@
 import com.redhat.thermostat.storage.core.HostRef;
 import com.redhat.thermostat.storage.core.Key;
 import com.redhat.thermostat.storage.core.Query;
+import com.redhat.thermostat.storage.core.Storage;
 import com.redhat.thermostat.storage.core.VmRef;
-import com.redhat.thermostat.storage.core.Query.Criteria;
-import com.redhat.thermostat.storage.core.Storage;
 import com.redhat.thermostat.vm.heap.analysis.common.HeapDAO;
 import com.redhat.thermostat.vm.heap.analysis.common.HistogramRecord;
 import com.redhat.thermostat.vm.heap.analysis.common.ObjectHistogram;
--- a/web/client/src/test/java/com/redhat/thermostat/web/client/internal/WebStorageTest.java	Fri Jun 07 13:49:04 2013 -0400
+++ b/web/client/src/test/java/com/redhat/thermostat/web/client/internal/WebStorageTest.java	Mon Jun 10 17:00:25 2013 -0400
@@ -99,7 +99,6 @@
 import com.redhat.thermostat.storage.core.Key;
 import com.redhat.thermostat.storage.core.Put;
 import com.redhat.thermostat.storage.core.Query;
-import com.redhat.thermostat.storage.core.Query.Criteria;
 import com.redhat.thermostat.storage.core.Remove;
 import com.redhat.thermostat.storage.core.Update;
 import com.redhat.thermostat.storage.query.Expression;
@@ -109,7 +108,6 @@
 import com.redhat.thermostat.test.FreePortFinder.TryPort;
 import com.redhat.thermostat.web.common.ExpressionSerializer;
 import com.redhat.thermostat.web.common.OperatorSerializer;
-import com.redhat.thermostat.web.common.Qualifier;
 import com.redhat.thermostat.web.common.WebInsert;
 import com.redhat.thermostat.web.common.WebQuery;
 import com.redhat.thermostat.web.common.WebRemove;
@@ -135,6 +133,8 @@
 
     private WebStorage storage;
 
+    private ExpressionFactory factory;
+
     @BeforeClass
     public static void setupCategory() {
         key1 = new Key<>("property1", true);
@@ -174,6 +174,7 @@
         method = null;
         responseStatus = HttpServletResponse.SC_OK;
         registerCategory();
+        factory = new ExpressionFactory();
     }
 
     private void startServer(int port) throws Exception {
@@ -321,22 +322,23 @@
         remove = remove.from(category);
         assertEquals(42, remove.getCategoryId());
         assertNotNull(remove);
-        remove = remove.where(key1, "test");
+        Expression expr = factory.equalTo(key1, "test");
+        remove = remove.where(expr);
         assertNotNull(remove);
-        List<Qualifier<?>> qualifiers = remove.getQualifiers();
-        assertEquals(1, qualifiers.size());
-        Qualifier<?> qualifier = qualifiers.get(0);
-        assertEquals(key1, qualifier.getKey());
-        assertEquals(Criteria.EQUALS, qualifier.getCriteria());
-        assertEquals("test", qualifier.getValue());
+        assertEquals(expr, remove.getWhereExpression());
     }
 
     @Test
     public void testRemovePojo() throws UnsupportedEncodingException, IOException {
-        Remove remove = storage.createRemove().from(category).where(key1, "test");
+        Expression expr = factory.equalTo(key1, "test");
+        Remove remove = storage.createRemove().from(category).where(expr);
         storage.removePojo(remove);
 
-        Gson gson = new Gson();
+        Gson gson = new GsonBuilder()
+                .registerTypeHierarchyAdapter(Expression.class,
+                        new ExpressionSerializer())
+                .registerTypeHierarchyAdapter(Operator.class,
+                        new OperatorSerializer()).create();
         StringReader reader = new StringReader(requestBody);
         BufferedReader bufRead = new BufferedReader(reader);
         String line = URLDecoder.decode(bufRead.readLine(), "UTF-8");
@@ -345,12 +347,7 @@
         WebRemove actualRemove = gson.fromJson(parts[1], WebRemove.class);
         
         assertEquals(42, actualRemove.getCategoryId());
-        List<Qualifier<?>> qualifiers = actualRemove.getQualifiers();
-        assertEquals(1, qualifiers.size());
-        Qualifier<?> qualifier = qualifiers.get(0);
-        assertEquals(key1, qualifier.getKey());
-        assertEquals(Criteria.EQUALS, qualifier.getCriteria());
-        assertEquals("test", qualifier.getValue());
+        assertEquals(expr, actualRemove.getWhereExpression());
     }
 
     @Test
@@ -359,13 +356,9 @@
         assertNotNull(update);
         assertEquals(42, update.getCategoryId());
 
-        update.where(key1, "test");
-        List<Qualifier<?>> qualifiers = update.getQualifiers();
-        assertEquals(1, qualifiers.size());
-        Qualifier<?> qualifier = qualifiers.get(0);
-        assertEquals(key1, qualifier.getKey());
-        assertEquals(Criteria.EQUALS, qualifier.getCriteria());
-        assertEquals("test", qualifier.getValue());
+        Expression expr = factory.equalTo(key1, "test");
+        update.where(expr);
+        assertEquals(expr, update.getWhereExpression());
 
         update.set(key1, "fluff");
         List<WebUpdate.UpdateValue> updates = update.getUpdates();
@@ -379,12 +372,17 @@
     public void testUpdate() throws UnsupportedEncodingException, IOException, JsonSyntaxException, ClassNotFoundException {
 
         Update update = storage.createUpdate(category);
-        update.where(key1, "test");
+        Expression expr = factory.equalTo(key1, "test");
+        update.where(expr);
         update.set(key1, "fluff");
         update.set(key2, 42);
         update.apply();
 
-        Gson gson = new Gson();
+        Gson gson = new GsonBuilder()
+                .registerTypeHierarchyAdapter(Expression.class,
+                        new ExpressionSerializer())
+                .registerTypeHierarchyAdapter(Operator.class,
+                        new OperatorSerializer()).create();
         StringReader reader = new StringReader(requestBody);
         BufferedReader bufRead = new BufferedReader(reader);
         String line = URLDecoder.decode(bufRead.readLine(), "UTF-8");
@@ -408,12 +406,7 @@
         assertEquals("java.lang.Integer", update2.getValueClass());
         assertNull(update2.getValue());
 
-        List<Qualifier<?>> qualifiers = receivedUpdate.getQualifiers();
-        assertEquals(1, qualifiers.size());
-        Qualifier<?> qualifier = qualifiers.get(0);
-        assertEquals(key1, qualifier.getKey());
-        assertEquals(Criteria.EQUALS, qualifier.getCriteria());
-        assertEquals("test", qualifier.getValue());
+        assertEquals(expr, receivedUpdate.getWhereExpression());
 
         parts = params[1].split("=");
         assertEquals(2, parts.length);
--- a/web/common/src/main/java/com/redhat/thermostat/web/common/Qualifier.java	Fri Jun 07 13:49:04 2013 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,78 +0,0 @@
-/*
- * Copyright 2012, 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-
-package com.redhat.thermostat.web.common;
-
-import com.redhat.thermostat.storage.core.Key;
-import com.redhat.thermostat.storage.core.Query.Criteria;
-
-public class Qualifier<T> {
-    private Key<T> key;
-    private Criteria criteria;
-    private T value;
-
-    public Qualifier() {
-        this(null, null, null);
-    }
-
-    public Qualifier(Key<T> key, Criteria criteria, T value) {
-        this.key = key;
-        this.criteria = criteria;
-        this.value = value;
-    }
-
-    public Key<T> getKey() {
-        return key;
-    }
-    public void setKey(Key<T> key) {
-        this.key = key;
-    }
-    public Criteria getCriteria() {
-        return criteria;
-    }
-    public void setCriteria(Criteria criteria) {
-        this.criteria = criteria;
-    }
-    public T getValue() {
-        return value;
-    }
-    public void setValue(T value) {
-        this.value = value;
-    }
-    
-}
-
--- a/web/common/src/main/java/com/redhat/thermostat/web/common/WebRemove.java	Fri Jun 07 13:49:04 2013 -0400
+++ b/web/common/src/main/java/com/redhat/thermostat/web/common/WebRemove.java	Mon Jun 10 17:00:25 2013 -0400
@@ -36,20 +36,17 @@
 
 package com.redhat.thermostat.web.common;
 
-import java.util.ArrayList;
-import java.util.List;
 import java.util.Map;
 
 import com.redhat.thermostat.storage.core.Category;
-import com.redhat.thermostat.storage.core.Key;
-import com.redhat.thermostat.storage.core.Query.Criteria;
 import com.redhat.thermostat.storage.core.Remove;
+import com.redhat.thermostat.storage.query.Expression;
 
 public class WebRemove implements Remove {
 
     private transient Map<Category<?>, Integer> categoryIds;
     private int categoryId;
-    private List<Qualifier<?>> qualifiers;
+    private Expression whereExpression;
 
     // NOTE: This is needed for de-serialization!
     public WebRemove() {
@@ -57,7 +54,6 @@
     }
 
     public WebRemove(Map<Category<?>, Integer> categoryIds) {
-        qualifiers = new ArrayList<>();
         this.categoryIds = categoryIds;
     }
 
@@ -68,8 +64,8 @@
     }
 
     @Override
-    public <T> WebRemove where(Key<T> key, T value) {
-        qualifiers.add(new Qualifier<T>(key, Criteria.EQUALS, value));
+    public WebRemove where(Expression expr) {
+        whereExpression = expr;
         return this;
     }
 
@@ -77,9 +73,9 @@
         return categoryId;
     }
 
-    public List<Qualifier<?>> getQualifiers() {
-        return qualifiers;
+    public Expression getWhereExpression() {
+        return whereExpression;
     }
-
+    
 }
 
--- a/web/common/src/main/java/com/redhat/thermostat/web/common/WebUpdate.java	Fri Jun 07 13:49:04 2013 -0400
+++ b/web/common/src/main/java/com/redhat/thermostat/web/common/WebUpdate.java	Mon Jun 10 17:00:25 2013 -0400
@@ -40,7 +40,7 @@
 import java.util.List;
 
 import com.redhat.thermostat.storage.core.Key;
-import com.redhat.thermostat.storage.core.Query.Criteria;
+import com.redhat.thermostat.storage.query.Expression;
 
 public class WebUpdate {
 
@@ -88,16 +88,15 @@
     }
 
     private int categoryId;
-    private List<Qualifier<?>> qualifiers;
+    private Expression whereExpression;
     private List<UpdateValue> updateValues;
 
     public WebUpdate() {
-        qualifiers = new ArrayList<>();
         updateValues = new ArrayList<>();
     }
 
-    public <T> void where(Key<T> key, T value) {
-        qualifiers.add(new Qualifier<T>(key, Criteria.EQUALS, value));
+    public void where(Expression expr) {
+        whereExpression = expr;
     }
 
     public <T> void set(Key<T> key, T value) {
@@ -112,10 +111,10 @@
         this.categoryId = categoryId;
     }
 
-    public List<Qualifier<?>> getQualifiers() {
-        return qualifiers;
+    public Expression getWhereExpression() {
+        return whereExpression;
     }
-
+    
     public List<WebUpdate.UpdateValue> getUpdates() {
         return updateValues;
     }
--- a/web/server/src/main/java/com/redhat/thermostat/web/server/WebStorageEndPoint.java	Fri Jun 07 13:49:04 2013 -0400
+++ b/web/server/src/main/java/com/redhat/thermostat/web/server/WebStorageEndPoint.java	Mon Jun 10 17:00:25 2013 -0400
@@ -75,7 +75,6 @@
 import com.redhat.thermostat.storage.core.Key;
 import com.redhat.thermostat.storage.core.Put;
 import com.redhat.thermostat.storage.core.Query;
-import com.redhat.thermostat.storage.core.Query.Criteria;
 import com.redhat.thermostat.storage.core.Remove;
 import com.redhat.thermostat.storage.core.Storage;
 import com.redhat.thermostat.storage.core.Update;
@@ -84,7 +83,6 @@
 import com.redhat.thermostat.storage.query.Operator;
 import com.redhat.thermostat.web.common.ExpressionSerializer;
 import com.redhat.thermostat.web.common.OperatorSerializer;
-import com.redhat.thermostat.web.common.Qualifier;
 import com.redhat.thermostat.web.common.StorageWrapper;
 import com.redhat.thermostat.web.common.ThermostatGSONConverter;
 import com.redhat.thermostat.web.common.WebInsert;
@@ -389,7 +387,6 @@
     }
 
     @WebStoragePathHandler( path = "remove-pojo" )
-    @SuppressWarnings({ "rawtypes", "unchecked" })
     private void removePojo(HttpServletRequest req, HttpServletResponse resp) {
         if (! isAuthorized(req, resp, Roles.DELETE)) {
             return;
@@ -399,10 +396,9 @@
         WebRemove remove = gson.fromJson(removeParam, WebRemove.class);
         Remove targetRemove = storage.createRemove();
         targetRemove = targetRemove.from(getCategoryFromId(remove.getCategoryId()));
-        List<Qualifier<?>> qualifiers = remove.getQualifiers();
-        for (Qualifier qualifier : qualifiers) {
-            assert (qualifier.getCriteria() == Criteria.EQUALS);
-            targetRemove = targetRemove.where(qualifier.getKey(), qualifier.getValue());
+        Expression expr = remove.getWhereExpression();
+        if (expr != null) {
+            targetRemove.where(expr);
         }
         storage.removePojo(targetRemove);
         resp.setStatus(HttpServletResponse.SC_OK);
@@ -419,10 +415,9 @@
             String updateParam = req.getParameter("update");
             WebUpdate update = gson.fromJson(updateParam, WebUpdate.class);
             Update targetUpdate = storage.createUpdate(getCategoryFromId(update.getCategoryId()));
-            List<Qualifier<?>> qualifiers = update.getQualifiers();
-            for (Qualifier qualifier : qualifiers) {
-                assert (qualifier.getCriteria() == Criteria.EQUALS);
-                targetUpdate.where(qualifier.getKey(), qualifier.getValue());
+            Expression expr = update.getWhereExpression();
+            if (expr != null) {
+                targetUpdate.where(expr);
             }
             List<WebUpdate.UpdateValue> updates = update.getUpdates();
             if (updates != null) {
--- a/web/server/src/test/java/com/redhat/thermostat/web/server/QueryTestHelper.java	Fri Jun 07 13:49:04 2013 -0400
+++ b/web/server/src/test/java/com/redhat/thermostat/web/server/QueryTestHelper.java	Mon Jun 10 17:00:25 2013 -0400
@@ -42,16 +42,15 @@
 import static org.mockito.Mockito.when;
 
 import com.redhat.thermostat.storage.core.Category;
-import com.redhat.thermostat.storage.core.Key;
 import com.redhat.thermostat.storage.core.Remove;
+import com.redhat.thermostat.storage.query.Expression;
 
 public class QueryTestHelper {
 
-    @SuppressWarnings("unchecked")
     public static Remove createMockRemove() {
         Remove mockRemove = mock(Remove.class);
         when(mockRemove.from(any(Category.class))).thenReturn(mockRemove);
-        when(mockRemove.where(any(Key.class), any())).thenReturn(mockRemove);
+        when(mockRemove.where(any(Expression.class))).thenReturn(mockRemove);
         return mockRemove;
     }
 
--- a/web/server/src/test/java/com/redhat/thermostat/web/server/WebStorageEndpointTest.java	Fri Jun 07 13:49:04 2013 -0400
+++ b/web/server/src/test/java/com/redhat/thermostat/web/server/WebStorageEndpointTest.java	Mon Jun 10 17:00:25 2013 -0400
@@ -153,6 +153,7 @@
     private static Key<String> key1;
     private static Key<Integer> key2;
     private static Category<TestClass> category;
+    private ExpressionFactory factory;
 
     @BeforeClass
     public static void setupCategory() {
@@ -181,7 +182,8 @@
         
         mockStorage = mock(Storage.class);
         StorageWrapper.setStorage(mockStorage);
-
+        
+        factory = new ExpressionFactory();
     }
 
     private void startServer(int port, LoginService loginService) throws Exception {
@@ -303,7 +305,6 @@
         Map<Category,Integer> categoryIdMap = new HashMap<>();
         categoryIdMap.put(category, categoryId);
         WebQuery query = new WebQuery(categoryId);
-        ExpressionFactory factory = new ExpressionFactory();
         Expression expr = factory.equalTo(key1, "fluff");
         query.where(expr);
         query.sort(key1, SortDirection.DESCENDING);
@@ -563,7 +564,6 @@
         conn.setRequestProperty("Authorization", "Basic "+ encodedAuthorization);
     }
 
-    @SuppressWarnings("unchecked")
     @Test
     public void authorizedRemovePojo() throws Exception {
         String[] roleNames = new String[] {
@@ -585,7 +585,7 @@
         
         Remove mockRemove = mock(Remove.class);
         when(mockRemove.from(any(Category.class))).thenReturn(mockRemove);
-        when(mockRemove.where(any(Key.class), any())).thenReturn(mockRemove);
+        when(mockRemove.where(any(Expression.class))).thenReturn(mockRemove);
 
         when(mockStorage.createRemove()).thenReturn(mockRemove);
 
@@ -599,8 +599,13 @@
         conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
         Map<Category<?>,Integer> categoryIds = new HashMap<>();
         categoryIds.put(category, categoryId);
-        WebRemove remove = new WebRemove(categoryIds).from(category).where(key1, "test");
-        Gson gson = new Gson();
+        Expression expr = factory.equalTo(key1, "test");
+        WebRemove remove = new WebRemove(categoryIds).from(category).where(expr);
+        Gson gson = new GsonBuilder()
+                .registerTypeHierarchyAdapter(Expression.class,
+                        new ExpressionSerializer())
+                .registerTypeHierarchyAdapter(Operator.class,
+                        new OperatorSerializer()).create();
         OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream());
         out.write("remove=");
         gson.toJson(remove, out);
@@ -610,7 +615,7 @@
         assertEquals(200, conn.getResponseCode());
         verify(mockStorage).createRemove();
         verify(mockRemove).from(category);
-        verify(mockRemove).where(key1, "test");
+        verify(mockRemove).where(eq(expr));
         verify(mockStorage).removePojo(mockRemove);
     }
     
@@ -652,11 +657,16 @@
 
         WebUpdate update = new WebUpdate();
         update.setCategoryId(categoryId);
-        update.where(key1, "test");
+        Expression expr = factory.equalTo(key1, "test");
+        update.where(expr);
         update.set(key1, "fluff");
         update.set(key2, 42);
 
-        Gson gson = new Gson();
+        Gson gson = new GsonBuilder()
+                .registerTypeHierarchyAdapter(Expression.class,
+                        new ExpressionSerializer())
+                .registerTypeHierarchyAdapter(Operator.class,
+                        new OperatorSerializer()).create();
         OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream());
         out.write("update=");
         gson.toJson(update, out);
@@ -667,7 +677,7 @@
 
         assertEquals(200, conn.getResponseCode());
         verify(mockStorage).createUpdate(category);
-        verify(mockUpdate).where(key1, "test");
+        verify(mockUpdate).where(eq(expr));
         verify(mockUpdate).set(key1, "fluff");
         verify(mockUpdate).set(key2, 42);
         verify(mockUpdate).apply();