changeset 766:13eda5b74828

Rename REST* to Web*. Reviewed-by: jerboaa Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2012-November/004078.html
author Roman Kennke <rkennke@redhat.com>
date Mon, 12 Nov 2012 15:48:34 +0100
parents 3881f5978b05
children f16b52396b14
files web/client/src/main/java/com/redhat/thermostat/web/client/Activator.java web/client/src/main/java/com/redhat/thermostat/web/client/RESTStorage.java web/client/src/main/java/com/redhat/thermostat/web/client/RESTStorageProvider.java web/client/src/main/java/com/redhat/thermostat/web/client/WebStorage.java web/client/src/main/java/com/redhat/thermostat/web/client/WebStorageProvider.java web/client/src/test/java/com/redhat/thermostat/web/client/RESTStorageTest.java web/client/src/test/java/com/redhat/thermostat/web/client/WebStorageTest.java web/common/src/main/java/com/redhat/thermostat/web/common/RESTQuery.java web/common/src/main/java/com/redhat/thermostat/web/common/WebQuery.java web/common/src/test/java/RESTQueryTest.java web/common/src/test/java/com/redhat/thermostat/web/common/WebQueryTest.java web/server/src/main/java/com/redhat/thermostat/web/server/RESTStorageEndPoint.java web/server/src/main/java/com/redhat/thermostat/web/server/WebServiceCommand.java web/server/src/main/java/com/redhat/thermostat/web/server/WebServiceLauncher.java web/server/src/main/java/com/redhat/thermostat/web/server/WebStorageEndPoint.java web/server/src/main/webapp/WEB-INF/web.xml web/server/src/test/java/com/redhat/thermostat/web/server/RESTStorageEndpointTest.java web/server/src/test/java/com/redhat/thermostat/web/server/WebStorageEndpointTest.java
diffstat 18 files changed, 1855 insertions(+), 1858 deletions(-) [+]
line wrap: on
line diff
--- a/web/client/src/main/java/com/redhat/thermostat/web/client/Activator.java	Tue Nov 06 11:00:12 2012 -0500
+++ b/web/client/src/main/java/com/redhat/thermostat/web/client/Activator.java	Mon Nov 12 15:48:34 2012 +0100
@@ -50,7 +50,7 @@
     
     @Override
     public void start(BundleContext context) throws Exception {
-        RESTStorageProvider storage = new RESTStorageProvider();
+        WebStorageProvider storage = new WebStorageProvider();
         this.reg = context.registerService(StorageProvider.class.getName(), storage, null);
     }
 
--- a/web/client/src/main/java/com/redhat/thermostat/web/client/RESTStorage.java	Tue Nov 06 11:00:12 2012 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,377 +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.client;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.Reader;
-import java.lang.reflect.Array;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.net.URLEncoder;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-
-import org.apache.http.HttpResponse;
-import org.apache.http.NameValuePair;
-import org.apache.http.StatusLine;
-import org.apache.http.client.HttpClient;
-import org.apache.http.client.entity.UrlEncodedFormEntity;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.entity.mime.MultipartEntity;
-import org.apache.http.entity.mime.content.InputStreamBody;
-import org.apache.http.impl.client.DefaultHttpClient;
-import org.apache.http.message.BasicNameValuePair;
-
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import com.redhat.thermostat.common.model.AgentIdPojo;
-import com.redhat.thermostat.common.model.Pojo;
-import com.redhat.thermostat.common.storage.Category;
-import com.redhat.thermostat.common.storage.Connection;
-import com.redhat.thermostat.common.storage.Cursor;
-import com.redhat.thermostat.common.storage.Query;
-import com.redhat.thermostat.common.storage.Remove;
-import com.redhat.thermostat.common.storage.Storage;
-import com.redhat.thermostat.common.storage.Update;
-import com.redhat.thermostat.web.common.RESTQuery;
-import com.redhat.thermostat.web.common.ThermostatGSONConverter;
-import com.redhat.thermostat.web.common.WebInsert;
-import com.redhat.thermostat.web.common.WebRemove;
-import com.redhat.thermostat.web.common.WebUpdate;
-
-public class RESTStorage extends Storage {
-
-    private final class WebConnection extends Connection {
-        WebConnection() {
-            connected = true;
-        }
-        @Override
-        public void disconnect() {
-            connected = false;
-            fireChanged(ConnectionStatus.DISCONNECTED);
-        }
-
-        @Override
-        public void connect() {
-            connected = true;
-            fireChanged(ConnectionStatus.CONNECTED);
-        }
-        @Override
-        public String getUrl() {
-            return endpoint;
-        }
-    }
-
-    private String endpoint;
-    private UUID agentId;
-
-    private Map<Category, Integer> categoryIds;
-    private Gson gson;
-
-    public RESTStorage() {
-        endpoint = "http://localhost:8082";
-        categoryIds = new HashMap<>();
-        gson = new GsonBuilder().registerTypeHierarchyAdapter(Pojo.class, new ThermostatGSONConverter()).create();
-    }
-
-    @Override
-    public void registerCategory(Category category) {
-        try {
-            URL url = new URL(endpoint + "/register-category");
-            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
-            String enc = "UTF-8";
-            conn.setRequestProperty("Content-Encoding", enc);
-            conn.setDoOutput(true);
-            conn.setDoInput(true);
-            conn.setRequestMethod("POST");
-            OutputStream out = conn.getOutputStream();
-            OutputStreamWriter writer = new OutputStreamWriter(out);
-            writer.write("name=");
-            writer.write(URLEncoder.encode(category.getName(), enc));
-            writer.write("&category=");
-            writer.write(URLEncoder.encode(gson.toJson(category), enc));
-            writer.flush();
-
-            InputStream in = conn.getInputStream();
-            Reader reader = new InputStreamReader(in);
-            Integer id = gson.fromJson(reader, Integer.class);
-            categoryIds.put(category, id);
-        } catch (IOException ex) {
-            throw new RuntimeException(ex);
-        }
-
-    }
-
-    @Override
-    public Query createQuery() {
-        return new RESTQuery(categoryIds);
-    }
-
-    @Override
-    public Remove createRemove() {
-        return new WebRemove(categoryIds);
-    }
-
-    @Override
-    public WebUpdate createUpdate() {
-        return new WebUpdate(categoryIds);
-    }
-
-    @Override
-    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();
-            OutputStreamWriter writer = new OutputStreamWriter(out);
-            ((RESTQuery) query).setResultClassName(resultClass.getName());
-            gson.toJson(query, writer);
-            writer.flush();
-
-            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
-    public <T extends Pojo> T findPojo(Query query, Class<T> resultClass) {
-        try {
-            ((RESTQuery) query).setResultClassName(resultClass.getName());
-            URL url = new URL(endpoint + "/find-pojo");
-            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
-            conn.setDoInput(true);
-            conn.setDoOutput(true);
-            conn.setRequestMethod("POST");
-            OutputStream out = conn.getOutputStream();
-            OutputStreamWriter writer = new OutputStreamWriter(out);
-            gson.toJson(query, writer);
-            writer.flush();
-
-            InputStream in = conn.getInputStream();
-            T result = gson.fromJson(new InputStreamReader(in), resultClass);
-            return result;
-        } catch (IOException ex) {
-            throw new RuntimeException(ex);
-        }
-    }
-
-    @Override
-    public String getAgentId() {
-        return agentId.toString();
-    }
-
-    @Override
-    public Connection getConnection() {
-        return new WebConnection();
-    }
-
-    @Override
-    public long getCount(Category category) {
-        try {
-            URL url = new URL(endpoint + "/get-count");
-            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
-            conn.setDoInput(true);
-            conn.setDoOutput(true);
-            conn.setRequestMethod("POST");
-            OutputStream out = conn.getOutputStream();
-            OutputStreamWriter writer = new OutputStreamWriter(out);
-            writer.write("category=");
-            gson.toJson(categoryIds.get(category), writer);
-            writer.write("\n");
-            writer.flush();
-
-            InputStream in = conn.getInputStream();
-            long result = gson.fromJson(new InputStreamReader(in), Long.class);
-            return result;
-        } catch (IOException ex) {
-            throw new RuntimeException(ex);
-        }
-    }
-
-    @Override
-    public InputStream loadFile(String name) {
-        try {
-            HttpClient httpClient = new DefaultHttpClient();
-            HttpPost httpPost = new HttpPost(endpoint + "/load-file");
-            List<NameValuePair> formparams = new ArrayList<NameValuePair>();
-            formparams.add(new BasicNameValuePair("file", name));
-            UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formparams, "UTF-8");
-            httpPost.setEntity(entity);
-            HttpResponse response = httpClient.execute(httpPost);
-            return response.getEntity().getContent();
-        } catch (IOException ex) {
-            throw new RuntimeException(ex);
-        }
-    }
-
-    @Override
-    public void purge() {
-        try {
-            HttpClient httpClient = new DefaultHttpClient();
-            HttpPost httpPost = new HttpPost(endpoint + "/purge");
-            HttpResponse response = httpClient.execute(httpPost);
-            int status = response.getStatusLine().getStatusCode();
-            if (status != 200) {
-                throw new IOException("Server returned status: " + status);
-            }
-        } catch (IOException ex) {
-            throw new RuntimeException(ex);
-        }
-    }
-
-    @Override
-    public void putPojo(Category category, boolean replace, AgentIdPojo pojo) {
-        // TODO: This logic should probably be moved elsewhere. I.e. out of the Storage API.
-        if (pojo.getAgentId() == null) {
-            pojo.setAgentId(getAgentId());
-        }
-        try {
-            int categoryId = categoryIds.get(category);
-            WebInsert insert = new WebInsert(categoryId, replace, pojo.getClass().getName());
-            URL url = new URL(endpoint + "/put-pojo");
-            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
-            conn.setDoOutput(true);
-            conn.setDoInput(true);
-            conn.setRequestMethod("POST");
-            OutputStream out = conn.getOutputStream();
-            OutputStreamWriter writer = new OutputStreamWriter(out);
-            writer.write("insert=");
-            writer.write(URLEncoder.encode(gson.toJson(insert), "UTF-8"));
-            writer.write("&pojo=");
-            writer.write(URLEncoder.encode(gson.toJson(pojo), "UTF-8"));
-            writer.flush();
-            checkResponseCode(conn);
-        } catch (IOException ex) {
-            throw new RuntimeException(ex);
-        }
-    }
-
-    private void checkResponseCode(HttpURLConnection conn) throws IOException {
-        int responseCode = conn.getResponseCode();
-        if (responseCode != HttpURLConnection.HTTP_OK) {
-            throw new RuntimeException("Web server returned HTTP code: " + responseCode);
-        }
-    }
-
-    @Override
-    public void removePojo(Remove remove) {
-        try {
-            URL url = new URL(endpoint + "/remove-pojo");
-            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
-            conn.setDoOutput(true);
-            conn.setRequestMethod("POST");
-            OutputStream out = conn.getOutputStream();
-            OutputStreamWriter writer = new OutputStreamWriter(out);
-            writer.write("remove=");
-            writer.write(URLEncoder.encode(gson.toJson(remove), "UTF-8"));
-            writer.flush();
-            checkResponseCode(conn);
-        } catch (IOException ex) {
-            throw new RuntimeException(ex);
-        }
-    }
-
-    @Override
-    public void saveFile(String name, InputStream in) {
-        try {
-            HttpClient httpClient = new DefaultHttpClient();
-            HttpPost httpPost = new HttpPost(endpoint + "/save-file");
-            InputStreamBody body = new InputStreamBody(in, name);
-            MultipartEntity entity = new MultipartEntity();
-            entity.addPart("file", body);
-            httpPost.setEntity(entity);
-            HttpResponse response = httpClient.execute(httpPost);
-            StatusLine status = response.getStatusLine();
-            if (status.getStatusCode() != 200) {
-                throw new IOException("Server returned status: " + status);
-            }
-        } catch (IOException ex) {
-            throw new RuntimeException(ex);
-        }
-    }
-
-    @Override
-    public void setAgentId(UUID agentId) {
-        this.agentId = agentId;
-    }
-
-    @Override
-    public void updatePojo(Update update) {
-        WebUpdate webUp = (WebUpdate) update;
-        try {
-            URL url = new URL(endpoint + "/update-pojo");
-            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
-            conn.setDoOutput(true);
-            conn.setDoInput(true);
-            conn.setRequestMethod("POST");
-            OutputStream out = conn.getOutputStream();
-            OutputStreamWriter writer = new OutputStreamWriter(out);
-            writer.write("update=");
-            writer.write(URLEncoder.encode(gson.toJson(webUp), "UTF-8"));
-            List<WebUpdate.UpdateValue> updateValues = webUp.getUpdates();
-            List<Object> values = new ArrayList<>(updateValues.size());
-            for (WebUpdate.UpdateValue updateValue : updateValues) {
-                values.add(updateValue.getValue());
-            }
-            writer.write("&values=");
-            writer.write(URLEncoder.encode(gson.toJson(values), "UTF-8"));
-            writer.flush();
-            checkResponseCode(conn);
-        } catch (IOException ex) {
-            throw new RuntimeException(ex);
-        }
-    }
-
-    public void setEndpoint(String endpoint) {
-        this.endpoint = endpoint;
-    }
-
-}
--- a/web/client/src/main/java/com/redhat/thermostat/web/client/RESTStorageProvider.java	Tue Nov 06 11:00:12 2012 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,27 +0,0 @@
-package com.redhat.thermostat.web.client;
-
-import com.redhat.thermostat.common.config.StartupConfiguration;
-import com.redhat.thermostat.common.storage.Storage;
-import com.redhat.thermostat.common.storage.StorageProvider;
-
-public class RESTStorageProvider implements StorageProvider {
-
-    private StartupConfiguration config;
-    
-    @Override
-    public Storage createStorage() {
-        return new RESTStorage();
-    }
-
-    @Override
-    public void setConfig(StartupConfiguration config) {
-        this.config = config;
-    }
-
-    @Override
-    public boolean canHandleProtocol() {
-        // use http since this might be https at some point
-        return config.getDBConnectionString().startsWith("http");
-    }
-
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/client/src/main/java/com/redhat/thermostat/web/client/WebStorage.java	Mon Nov 12 15:48:34 2012 +0100
@@ -0,0 +1,377 @@
+/*
+ * 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 java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.lang.reflect.Array;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.NameValuePair;
+import org.apache.http.StatusLine;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.mime.MultipartEntity;
+import org.apache.http.entity.mime.content.InputStreamBody;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.message.BasicNameValuePair;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.redhat.thermostat.common.model.AgentIdPojo;
+import com.redhat.thermostat.common.model.Pojo;
+import com.redhat.thermostat.common.storage.Category;
+import com.redhat.thermostat.common.storage.Connection;
+import com.redhat.thermostat.common.storage.Cursor;
+import com.redhat.thermostat.common.storage.Query;
+import com.redhat.thermostat.common.storage.Remove;
+import com.redhat.thermostat.common.storage.Storage;
+import com.redhat.thermostat.common.storage.Update;
+import com.redhat.thermostat.web.common.WebQuery;
+import com.redhat.thermostat.web.common.ThermostatGSONConverter;
+import com.redhat.thermostat.web.common.WebInsert;
+import com.redhat.thermostat.web.common.WebRemove;
+import com.redhat.thermostat.web.common.WebUpdate;
+
+public class WebStorage extends Storage {
+
+    private final class WebConnection extends Connection {
+        WebConnection() {
+            connected = true;
+        }
+        @Override
+        public void disconnect() {
+            connected = false;
+            fireChanged(ConnectionStatus.DISCONNECTED);
+        }
+
+        @Override
+        public void connect() {
+            connected = true;
+            fireChanged(ConnectionStatus.CONNECTED);
+        }
+        @Override
+        public String getUrl() {
+            return endpoint;
+        }
+    }
+
+    private String endpoint;
+    private UUID agentId;
+
+    private Map<Category, Integer> categoryIds;
+    private Gson gson;
+
+    public WebStorage() {
+        endpoint = "http://localhost:8082";
+        categoryIds = new HashMap<>();
+        gson = new GsonBuilder().registerTypeHierarchyAdapter(Pojo.class, new ThermostatGSONConverter()).create();
+    }
+
+    @Override
+    public void registerCategory(Category category) {
+        try {
+            URL url = new URL(endpoint + "/register-category");
+            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+            String enc = "UTF-8";
+            conn.setRequestProperty("Content-Encoding", enc);
+            conn.setDoOutput(true);
+            conn.setDoInput(true);
+            conn.setRequestMethod("POST");
+            OutputStream out = conn.getOutputStream();
+            OutputStreamWriter writer = new OutputStreamWriter(out);
+            writer.write("name=");
+            writer.write(URLEncoder.encode(category.getName(), enc));
+            writer.write("&category=");
+            writer.write(URLEncoder.encode(gson.toJson(category), enc));
+            writer.flush();
+
+            InputStream in = conn.getInputStream();
+            Reader reader = new InputStreamReader(in);
+            Integer id = gson.fromJson(reader, Integer.class);
+            categoryIds.put(category, id);
+        } catch (IOException ex) {
+            throw new RuntimeException(ex);
+        }
+
+    }
+
+    @Override
+    public Query createQuery() {
+        return new WebQuery(categoryIds);
+    }
+
+    @Override
+    public Remove createRemove() {
+        return new WebRemove(categoryIds);
+    }
+
+    @Override
+    public WebUpdate createUpdate() {
+        return new WebUpdate(categoryIds);
+    }
+
+    @Override
+    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();
+            OutputStreamWriter writer = new OutputStreamWriter(out);
+            ((WebQuery) query).setResultClassName(resultClass.getName());
+            gson.toJson(query, writer);
+            writer.flush();
+
+            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
+    public <T extends Pojo> T findPojo(Query query, Class<T> resultClass) {
+        try {
+            ((WebQuery) query).setResultClassName(resultClass.getName());
+            URL url = new URL(endpoint + "/find-pojo");
+            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+            conn.setDoInput(true);
+            conn.setDoOutput(true);
+            conn.setRequestMethod("POST");
+            OutputStream out = conn.getOutputStream();
+            OutputStreamWriter writer = new OutputStreamWriter(out);
+            gson.toJson(query, writer);
+            writer.flush();
+
+            InputStream in = conn.getInputStream();
+            T result = gson.fromJson(new InputStreamReader(in), resultClass);
+            return result;
+        } catch (IOException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    @Override
+    public String getAgentId() {
+        return agentId.toString();
+    }
+
+    @Override
+    public Connection getConnection() {
+        return new WebConnection();
+    }
+
+    @Override
+    public long getCount(Category category) {
+        try {
+            URL url = new URL(endpoint + "/get-count");
+            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+            conn.setDoInput(true);
+            conn.setDoOutput(true);
+            conn.setRequestMethod("POST");
+            OutputStream out = conn.getOutputStream();
+            OutputStreamWriter writer = new OutputStreamWriter(out);
+            writer.write("category=");
+            gson.toJson(categoryIds.get(category), writer);
+            writer.write("\n");
+            writer.flush();
+
+            InputStream in = conn.getInputStream();
+            long result = gson.fromJson(new InputStreamReader(in), Long.class);
+            return result;
+        } catch (IOException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    @Override
+    public InputStream loadFile(String name) {
+        try {
+            HttpClient httpClient = new DefaultHttpClient();
+            HttpPost httpPost = new HttpPost(endpoint + "/load-file");
+            List<NameValuePair> formparams = new ArrayList<NameValuePair>();
+            formparams.add(new BasicNameValuePair("file", name));
+            UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formparams, "UTF-8");
+            httpPost.setEntity(entity);
+            HttpResponse response = httpClient.execute(httpPost);
+            return response.getEntity().getContent();
+        } catch (IOException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    @Override
+    public void purge() {
+        try {
+            HttpClient httpClient = new DefaultHttpClient();
+            HttpPost httpPost = new HttpPost(endpoint + "/purge");
+            HttpResponse response = httpClient.execute(httpPost);
+            int status = response.getStatusLine().getStatusCode();
+            if (status != 200) {
+                throw new IOException("Server returned status: " + status);
+            }
+        } catch (IOException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    @Override
+    public void putPojo(Category category, boolean replace, AgentIdPojo pojo) {
+        // TODO: This logic should probably be moved elsewhere. I.e. out of the Storage API.
+        if (pojo.getAgentId() == null) {
+            pojo.setAgentId(getAgentId());
+        }
+        try {
+            int categoryId = categoryIds.get(category);
+            WebInsert insert = new WebInsert(categoryId, replace, pojo.getClass().getName());
+            URL url = new URL(endpoint + "/put-pojo");
+            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+            conn.setDoOutput(true);
+            conn.setDoInput(true);
+            conn.setRequestMethod("POST");
+            OutputStream out = conn.getOutputStream();
+            OutputStreamWriter writer = new OutputStreamWriter(out);
+            writer.write("insert=");
+            writer.write(URLEncoder.encode(gson.toJson(insert), "UTF-8"));
+            writer.write("&pojo=");
+            writer.write(URLEncoder.encode(gson.toJson(pojo), "UTF-8"));
+            writer.flush();
+            checkResponseCode(conn);
+        } catch (IOException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    private void checkResponseCode(HttpURLConnection conn) throws IOException {
+        int responseCode = conn.getResponseCode();
+        if (responseCode != HttpURLConnection.HTTP_OK) {
+            throw new RuntimeException("Web server returned HTTP code: " + responseCode);
+        }
+    }
+
+    @Override
+    public void removePojo(Remove remove) {
+        try {
+            URL url = new URL(endpoint + "/remove-pojo");
+            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+            conn.setDoOutput(true);
+            conn.setRequestMethod("POST");
+            OutputStream out = conn.getOutputStream();
+            OutputStreamWriter writer = new OutputStreamWriter(out);
+            writer.write("remove=");
+            writer.write(URLEncoder.encode(gson.toJson(remove), "UTF-8"));
+            writer.flush();
+            checkResponseCode(conn);
+        } catch (IOException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    @Override
+    public void saveFile(String name, InputStream in) {
+        try {
+            HttpClient httpClient = new DefaultHttpClient();
+            HttpPost httpPost = new HttpPost(endpoint + "/save-file");
+            InputStreamBody body = new InputStreamBody(in, name);
+            MultipartEntity entity = new MultipartEntity();
+            entity.addPart("file", body);
+            httpPost.setEntity(entity);
+            HttpResponse response = httpClient.execute(httpPost);
+            StatusLine status = response.getStatusLine();
+            if (status.getStatusCode() != 200) {
+                throw new IOException("Server returned status: " + status);
+            }
+        } catch (IOException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    @Override
+    public void setAgentId(UUID agentId) {
+        this.agentId = agentId;
+    }
+
+    @Override
+    public void updatePojo(Update update) {
+        WebUpdate webUp = (WebUpdate) update;
+        try {
+            URL url = new URL(endpoint + "/update-pojo");
+            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+            conn.setDoOutput(true);
+            conn.setDoInput(true);
+            conn.setRequestMethod("POST");
+            OutputStream out = conn.getOutputStream();
+            OutputStreamWriter writer = new OutputStreamWriter(out);
+            writer.write("update=");
+            writer.write(URLEncoder.encode(gson.toJson(webUp), "UTF-8"));
+            List<WebUpdate.UpdateValue> updateValues = webUp.getUpdates();
+            List<Object> values = new ArrayList<>(updateValues.size());
+            for (WebUpdate.UpdateValue updateValue : updateValues) {
+                values.add(updateValue.getValue());
+            }
+            writer.write("&values=");
+            writer.write(URLEncoder.encode(gson.toJson(values), "UTF-8"));
+            writer.flush();
+            checkResponseCode(conn);
+        } catch (IOException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    public void setEndpoint(String endpoint) {
+        this.endpoint = endpoint;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/client/src/main/java/com/redhat/thermostat/web/client/WebStorageProvider.java	Mon Nov 12 15:48:34 2012 +0100
@@ -0,0 +1,27 @@
+package com.redhat.thermostat.web.client;
+
+import com.redhat.thermostat.common.config.StartupConfiguration;
+import com.redhat.thermostat.common.storage.Storage;
+import com.redhat.thermostat.common.storage.StorageProvider;
+
+public class WebStorageProvider implements StorageProvider {
+
+    private StartupConfiguration config;
+    
+    @Override
+    public Storage createStorage() {
+        return new WebStorage();
+    }
+
+    @Override
+    public void setConfig(StartupConfiguration config) {
+        this.config = config;
+    }
+
+    @Override
+    public boolean canHandleProtocol() {
+        // use http since this might be https at some point
+        return config.getDBConnectionString().startsWith("http");
+    }
+
+}
--- a/web/client/src/test/java/com/redhat/thermostat/web/client/RESTStorageTest.java	Tue Nov 06 11:00:12 2012 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,462 +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.client;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import java.io.BufferedReader;
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.Reader;
-import java.io.StringReader;
-import java.io.UnsupportedEncodingException;
-import java.net.URLDecoder;
-import java.util.Arrays;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.server.Request;
-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.google.gson.JsonArray;
-import com.google.gson.JsonParser;
-import com.google.gson.JsonSyntaxException;
-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.Remove;
-import com.redhat.thermostat.test.FreePortFinder;
-import com.redhat.thermostat.test.FreePortFinder.TryPort;
-import com.redhat.thermostat.web.common.Qualifier;
-import com.redhat.thermostat.web.common.RESTQuery;
-import com.redhat.thermostat.web.common.WebInsert;
-import com.redhat.thermostat.web.common.WebRemove;
-import com.redhat.thermostat.web.common.WebUpdate;
-
-public class RESTStorageTest {
-
-    private Server server;
-
-    private int port;
-
-    private String requestBody;
-
-    private String responseBody;
-    private Map<String,String> headers;
-    private String method;
-    private String requestURI;
-
-    private static Category category;
-    private static Key<String> key1;
-    private static Key<Integer> key2;
-
-    private RESTStorage storage;
-
-    @BeforeClass
-    public static void setupCategory() {
-        key1 = new Key<>("property1", true);
-        key2 = new Key<>("property2", 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() {
-            @Override
-            public void tryPort(int port) throws Exception {
-                startServer(port);
-            }
-        });
-
-        storage = new RESTStorage();
-        storage.setEndpoint("http://localhost:" + port + "/");
-        storage.setAgentId(new UUID(123, 456));
-        headers = new HashMap<>();
-        requestURI = null;
-        method = null;
-        registerCategory();
-    }
-
-    private void startServer(int port) throws Exception {
-        server = new Server(port);
-        server.setHandler(new AbstractHandler() {
-            
-            @Override
-            public void handle(String target, Request baseRequest,
-                    HttpServletRequest request, HttpServletResponse response)
-                    throws IOException, ServletException {
-                Enumeration<String> headerNames = request.getHeaderNames();
-                while (headerNames.hasMoreElements()) {
-                    String headerName = headerNames.nextElement();
-                    headers.put(headerName, request.getHeader(headerName));
-                }
-
-                method = request.getMethod();
-                requestURI = request.getRequestURI();
-
-                // Read request body.
-                StringBuilder body = new StringBuilder();
-                Reader reader = request.getReader();
-                while (true) {
-                    int read = reader.read();
-                    if (read == -1) {
-                        break;
-                    }
-                    body.append((char) read);
-                }
-                requestBody = body.toString();
-                // Send response body.
-                response.setStatus(HttpServletResponse.SC_OK);
-                if (responseBody != null) {
-                    response.getWriter().write(responseBody);
-                }
-                baseRequest.setHandled(true);
-            }
-        });
-        server.start();
-    }
-
-    @After
-    public void tearDown() throws Exception {
-
-        headers = null;
-        requestURI = null;
-        method = null;
-        storage = null;
-
-        server.stop();
-        server.join();
-    }
-
-    private void registerCategory() {
-
-        // Return 42 for categoryId.
-        Gson gson = new Gson();
-        responseBody = gson.toJson(42);
-
-        storage.registerCategory(category);
-    }
-
-    @Test
-    public void testFindPojo() {
-
-        TestObj obj = new TestObj();
-        obj.setProperty1("fluffor");
-        Gson gson = new Gson();
-        responseBody = gson.toJson(obj);
-
-        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);
-
-        assertEquals(42, restQuery.getCategoryId());
-        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());
-
-        assertEquals("fluffor", result.getProperty1());
-    }
-
-    @Test
-    public void testFindAllPojos() {
-
-        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);
-
-        assertEquals(42, restQuery.getCategoryId());
-        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());
-    }
-
-    @Test
-    public void testPut() throws IOException, JsonSyntaxException, ClassNotFoundException {
-
-        TestObj obj = new TestObj();
-        obj.setProperty1("fluff");
-
-        storage.putPojo(category, true, obj);
-
-        Gson gson = new Gson();
-        StringReader reader = new StringReader(requestBody);
-        BufferedReader bufRead = new BufferedReader(reader);
-        String line = URLDecoder.decode(bufRead.readLine(), "UTF-8");
-        String [] params = line.split("&");
-        assertEquals(2, params.length);
-        String[] parts = params[0].split("=");
-        assertEquals("insert", parts[0]);
-        WebInsert insert = gson.fromJson(parts[1], WebInsert.class);
-        assertEquals(42, insert.getCategoryId());
-        assertEquals(true, insert.isReplace());
-        assertEquals(TestObj.class.getName(), insert.getPojoClass());
-
-        parts = params[1].split("=");
-        assertEquals(2, parts.length);
-        assertEquals("pojo", parts[0]);
-        Object resultObj = gson.fromJson(parts[1], Class.forName(insert.getPojoClass()));
-        assertEquals(obj, resultObj);
-    }
-
-    @Test
-    public void testCreateRemove() {
-        WebRemove remove = (WebRemove) storage.createRemove();
-        assertNotNull(remove);
-        remove = remove.from(category);
-        assertEquals(42, remove.getCategoryId());
-        assertNotNull(remove);
-        remove = remove.where(key1, "test");
-        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());
-    }
-
-    @Test
-    public void testRemovePojo() throws UnsupportedEncodingException, IOException {
-        Remove remove = storage.createRemove().from(category).where(key1, "test");
-        storage.removePojo(remove);
-
-        Gson gson = new Gson();
-        StringReader reader = new StringReader(requestBody);
-        BufferedReader bufRead = new BufferedReader(reader);
-        String line = URLDecoder.decode(bufRead.readLine(), "UTF-8");
-        System.err.println("line: " + line);
-        String[] parts = line.split("=");
-        assertEquals("remove", parts[0]);
-        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());
-    }
-
-    @Test
-    public void testCreateUpdate() {
-        WebUpdate update = (WebUpdate) storage.createUpdate();
-        assertNotNull(update);
-        update = update.from(category);
-        assertEquals(42, update.getCategoryId());
-        assertNotNull(update);
-        update = update.where(key1, "test");
-        assertNotNull(update);
-        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());
-        update = update.set(key1, "fluff");
-        assertNotNull(update);
-        List<WebUpdate.UpdateValue> updates = update.getUpdates();
-        assertEquals(1, updates.size());
-        assertEquals("fluff", updates.get(0).getValue());
-        assertEquals(key1, updates.get(0).getKey());
-        assertEquals("java.lang.String", updates.get(0).getValueClass());
-    }
-
-    @Test
-    public void testUpdate() throws UnsupportedEncodingException, IOException, JsonSyntaxException, ClassNotFoundException {
-
-        WebUpdate update = storage.createUpdate().from(category).where(key1, "test").set(key1, "fluff").set(key2, 42);
-        storage.updatePojo(update);
-
-        Gson gson = new Gson();
-        StringReader reader = new StringReader(requestBody);
-        BufferedReader bufRead = new BufferedReader(reader);
-        String line = URLDecoder.decode(bufRead.readLine(), "UTF-8");
-        String [] params = line.split("&");
-        assertEquals(2, params.length);
-        String[] parts = params[0].split("=");
-        assertEquals("update", parts[0]);
-        WebUpdate receivedUpdate = gson.fromJson(parts[1], WebUpdate.class);
-        assertEquals(42, receivedUpdate.getCategoryId());
-
-        List<WebUpdate.UpdateValue> updates = receivedUpdate.getUpdates();
-        assertEquals(2, updates.size());
-
-        WebUpdate.UpdateValue update1 = updates.get(0);
-        assertEquals(key1, update1.getKey());
-        assertEquals("java.lang.String", update1.getValueClass());
-        assertNull(update1.getValue());
-
-        WebUpdate.UpdateValue update2 = updates.get(1);
-        assertEquals(key2, update2.getKey());
-        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());
-
-        parts = params[1].split("=");
-        assertEquals(2, parts.length);
-        assertEquals("values", parts[0]);
-        JsonParser jsonParser = new JsonParser();
-        JsonArray jsonArray = jsonParser.parse(parts[1]).getAsJsonArray();
-        String value1 = gson.fromJson(jsonArray.get(0), String.class);
-        assertEquals("fluff", value1);
-        int value2 = gson.fromJson(jsonArray.get(1), Integer.class);
-        assertEquals(42, value2);
-    }
-
-    @Test
-    public void testGetCount() throws UnsupportedEncodingException, IOException {
-
-        Gson gson = new Gson();
-        responseBody = gson.toJson(12345);
-
-        long result = storage.getCount(category);
-
-        StringReader reader = new StringReader(requestBody);
-        BufferedReader bufRead = new BufferedReader(reader);
-        String line = URLDecoder.decode(bufRead.readLine(), "UTF-8");
-        String[] parts = line.split("=");
-        assertEquals("category", parts[0]);
-        assertEquals("42", parts[1]);
-        assertEquals(12345, result);
-    }
-
-    @Test
-    public void testSaveFile() {
-        String data = "Hello World";
-        ByteArrayInputStream in = new ByteArrayInputStream(data.getBytes());
-        storage.saveFile("fluff", in);
-        assertEquals("chunked", headers.get("Transfer-Encoding"));
-        String contentType = headers.get("Content-Type");
-        assertTrue(contentType.startsWith("multipart/form-data; boundary="));
-        String boundary = contentType.split("boundary=")[1];
-        String[] lines = requestBody.split("\n");
-        assertEquals("--" + boundary, lines[0].trim());
-        assertEquals("Content-Disposition: form-data; name=\"file\"; filename=\"fluff\"", lines[1].trim());
-        assertEquals("Content-Type: application/octet-stream", lines[2].trim());
-        assertEquals("Content-Transfer-Encoding: binary", lines[3].trim());
-        assertEquals("", lines[4].trim());
-        assertEquals("Hello World", lines[5].trim());
-        assertEquals("--" + boundary + "--", lines[6].trim());
-        
-    }
-
-    @Test
-    public void testLoadFile() throws IOException {
-        responseBody = "Hello World";
-        InputStream in = storage.loadFile("fluff");
-        assertEquals("file=fluff", requestBody.trim());
-        byte[] data = new byte[11];
-        int totalRead = 0;
-        while (totalRead < 11) {
-            int read = in.read(data, totalRead, 11 - totalRead);
-            if (read < 0) {
-                fail();
-            }
-            totalRead += read;
-        }
-        assertEquals("Hello World", new String(data));
-
-    }
-
-    @Test
-    public void testPurge() {
-        storage.purge();
-        assertEquals("POST", method);
-        assertTrue(requestURI.endsWith("/purge"));
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/client/src/test/java/com/redhat/thermostat/web/client/WebStorageTest.java	Mon Nov 12 15:48:34 2012 +0100
@@ -0,0 +1,462 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Request;
+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.google.gson.JsonArray;
+import com.google.gson.JsonParser;
+import com.google.gson.JsonSyntaxException;
+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.Remove;
+import com.redhat.thermostat.test.FreePortFinder;
+import com.redhat.thermostat.test.FreePortFinder.TryPort;
+import com.redhat.thermostat.web.common.Qualifier;
+import com.redhat.thermostat.web.common.WebQuery;
+import com.redhat.thermostat.web.common.WebInsert;
+import com.redhat.thermostat.web.common.WebRemove;
+import com.redhat.thermostat.web.common.WebUpdate;
+
+public class WebStorageTest {
+
+    private Server server;
+
+    private int port;
+
+    private String requestBody;
+
+    private String responseBody;
+    private Map<String,String> headers;
+    private String method;
+    private String requestURI;
+
+    private static Category category;
+    private static Key<String> key1;
+    private static Key<Integer> key2;
+
+    private WebStorage storage;
+
+    @BeforeClass
+    public static void setupCategory() {
+        key1 = new Key<>("property1", true);
+        key2 = new Key<>("property2", 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() {
+            @Override
+            public void tryPort(int port) throws Exception {
+                startServer(port);
+            }
+        });
+
+        storage = new WebStorage();
+        storage.setEndpoint("http://localhost:" + port + "/");
+        storage.setAgentId(new UUID(123, 456));
+        headers = new HashMap<>();
+        requestURI = null;
+        method = null;
+        registerCategory();
+    }
+
+    private void startServer(int port) throws Exception {
+        server = new Server(port);
+        server.setHandler(new AbstractHandler() {
+            
+            @Override
+            public void handle(String target, Request baseRequest,
+                    HttpServletRequest request, HttpServletResponse response)
+                    throws IOException, ServletException {
+                Enumeration<String> headerNames = request.getHeaderNames();
+                while (headerNames.hasMoreElements()) {
+                    String headerName = headerNames.nextElement();
+                    headers.put(headerName, request.getHeader(headerName));
+                }
+
+                method = request.getMethod();
+                requestURI = request.getRequestURI();
+
+                // Read request body.
+                StringBuilder body = new StringBuilder();
+                Reader reader = request.getReader();
+                while (true) {
+                    int read = reader.read();
+                    if (read == -1) {
+                        break;
+                    }
+                    body.append((char) read);
+                }
+                requestBody = body.toString();
+                // Send response body.
+                response.setStatus(HttpServletResponse.SC_OK);
+                if (responseBody != null) {
+                    response.getWriter().write(responseBody);
+                }
+                baseRequest.setHandled(true);
+            }
+        });
+        server.start();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+
+        headers = null;
+        requestURI = null;
+        method = null;
+        storage = null;
+
+        server.stop();
+        server.join();
+    }
+
+    private void registerCategory() {
+
+        // Return 42 for categoryId.
+        Gson gson = new Gson();
+        responseBody = gson.toJson(42);
+
+        storage.registerCategory(category);
+    }
+
+    @Test
+    public void testFindPojo() {
+
+        TestObj obj = new TestObj();
+        obj.setProperty1("fluffor");
+        Gson gson = new Gson();
+        responseBody = gson.toJson(obj);
+
+        Query query = storage.createQuery().from(category).where(key1, Criteria.EQUALS, "fluff");
+
+        TestObj result = storage.findPojo(query, TestObj.class);
+        WebQuery restQuery = gson.fromJson(requestBody, WebQuery.class);
+
+        assertEquals(42, restQuery.getCategoryId());
+        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());
+
+        assertEquals("fluffor", result.getProperty1());
+    }
+
+    @Test
+    public void testFindAllPojos() {
+
+        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);
+        WebQuery restQuery = gson.fromJson(requestBody, WebQuery.class);
+
+        assertEquals(42, restQuery.getCategoryId());
+        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());
+    }
+
+    @Test
+    public void testPut() throws IOException, JsonSyntaxException, ClassNotFoundException {
+
+        TestObj obj = new TestObj();
+        obj.setProperty1("fluff");
+
+        storage.putPojo(category, true, obj);
+
+        Gson gson = new Gson();
+        StringReader reader = new StringReader(requestBody);
+        BufferedReader bufRead = new BufferedReader(reader);
+        String line = URLDecoder.decode(bufRead.readLine(), "UTF-8");
+        String [] params = line.split("&");
+        assertEquals(2, params.length);
+        String[] parts = params[0].split("=");
+        assertEquals("insert", parts[0]);
+        WebInsert insert = gson.fromJson(parts[1], WebInsert.class);
+        assertEquals(42, insert.getCategoryId());
+        assertEquals(true, insert.isReplace());
+        assertEquals(TestObj.class.getName(), insert.getPojoClass());
+
+        parts = params[1].split("=");
+        assertEquals(2, parts.length);
+        assertEquals("pojo", parts[0]);
+        Object resultObj = gson.fromJson(parts[1], Class.forName(insert.getPojoClass()));
+        assertEquals(obj, resultObj);
+    }
+
+    @Test
+    public void testCreateRemove() {
+        WebRemove remove = (WebRemove) storage.createRemove();
+        assertNotNull(remove);
+        remove = remove.from(category);
+        assertEquals(42, remove.getCategoryId());
+        assertNotNull(remove);
+        remove = remove.where(key1, "test");
+        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());
+    }
+
+    @Test
+    public void testRemovePojo() throws UnsupportedEncodingException, IOException {
+        Remove remove = storage.createRemove().from(category).where(key1, "test");
+        storage.removePojo(remove);
+
+        Gson gson = new Gson();
+        StringReader reader = new StringReader(requestBody);
+        BufferedReader bufRead = new BufferedReader(reader);
+        String line = URLDecoder.decode(bufRead.readLine(), "UTF-8");
+        System.err.println("line: " + line);
+        String[] parts = line.split("=");
+        assertEquals("remove", parts[0]);
+        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());
+    }
+
+    @Test
+    public void testCreateUpdate() {
+        WebUpdate update = (WebUpdate) storage.createUpdate();
+        assertNotNull(update);
+        update = update.from(category);
+        assertEquals(42, update.getCategoryId());
+        assertNotNull(update);
+        update = update.where(key1, "test");
+        assertNotNull(update);
+        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());
+        update = update.set(key1, "fluff");
+        assertNotNull(update);
+        List<WebUpdate.UpdateValue> updates = update.getUpdates();
+        assertEquals(1, updates.size());
+        assertEquals("fluff", updates.get(0).getValue());
+        assertEquals(key1, updates.get(0).getKey());
+        assertEquals("java.lang.String", updates.get(0).getValueClass());
+    }
+
+    @Test
+    public void testUpdate() throws UnsupportedEncodingException, IOException, JsonSyntaxException, ClassNotFoundException {
+
+        WebUpdate update = storage.createUpdate().from(category).where(key1, "test").set(key1, "fluff").set(key2, 42);
+        storage.updatePojo(update);
+
+        Gson gson = new Gson();
+        StringReader reader = new StringReader(requestBody);
+        BufferedReader bufRead = new BufferedReader(reader);
+        String line = URLDecoder.decode(bufRead.readLine(), "UTF-8");
+        String [] params = line.split("&");
+        assertEquals(2, params.length);
+        String[] parts = params[0].split("=");
+        assertEquals("update", parts[0]);
+        WebUpdate receivedUpdate = gson.fromJson(parts[1], WebUpdate.class);
+        assertEquals(42, receivedUpdate.getCategoryId());
+
+        List<WebUpdate.UpdateValue> updates = receivedUpdate.getUpdates();
+        assertEquals(2, updates.size());
+
+        WebUpdate.UpdateValue update1 = updates.get(0);
+        assertEquals(key1, update1.getKey());
+        assertEquals("java.lang.String", update1.getValueClass());
+        assertNull(update1.getValue());
+
+        WebUpdate.UpdateValue update2 = updates.get(1);
+        assertEquals(key2, update2.getKey());
+        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());
+
+        parts = params[1].split("=");
+        assertEquals(2, parts.length);
+        assertEquals("values", parts[0]);
+        JsonParser jsonParser = new JsonParser();
+        JsonArray jsonArray = jsonParser.parse(parts[1]).getAsJsonArray();
+        String value1 = gson.fromJson(jsonArray.get(0), String.class);
+        assertEquals("fluff", value1);
+        int value2 = gson.fromJson(jsonArray.get(1), Integer.class);
+        assertEquals(42, value2);
+    }
+
+    @Test
+    public void testGetCount() throws UnsupportedEncodingException, IOException {
+
+        Gson gson = new Gson();
+        responseBody = gson.toJson(12345);
+
+        long result = storage.getCount(category);
+
+        StringReader reader = new StringReader(requestBody);
+        BufferedReader bufRead = new BufferedReader(reader);
+        String line = URLDecoder.decode(bufRead.readLine(), "UTF-8");
+        String[] parts = line.split("=");
+        assertEquals("category", parts[0]);
+        assertEquals("42", parts[1]);
+        assertEquals(12345, result);
+    }
+
+    @Test
+    public void testSaveFile() {
+        String data = "Hello World";
+        ByteArrayInputStream in = new ByteArrayInputStream(data.getBytes());
+        storage.saveFile("fluff", in);
+        assertEquals("chunked", headers.get("Transfer-Encoding"));
+        String contentType = headers.get("Content-Type");
+        assertTrue(contentType.startsWith("multipart/form-data; boundary="));
+        String boundary = contentType.split("boundary=")[1];
+        String[] lines = requestBody.split("\n");
+        assertEquals("--" + boundary, lines[0].trim());
+        assertEquals("Content-Disposition: form-data; name=\"file\"; filename=\"fluff\"", lines[1].trim());
+        assertEquals("Content-Type: application/octet-stream", lines[2].trim());
+        assertEquals("Content-Transfer-Encoding: binary", lines[3].trim());
+        assertEquals("", lines[4].trim());
+        assertEquals("Hello World", lines[5].trim());
+        assertEquals("--" + boundary + "--", lines[6].trim());
+        
+    }
+
+    @Test
+    public void testLoadFile() throws IOException {
+        responseBody = "Hello World";
+        InputStream in = storage.loadFile("fluff");
+        assertEquals("file=fluff", requestBody.trim());
+        byte[] data = new byte[11];
+        int totalRead = 0;
+        while (totalRead < 11) {
+            int read = in.read(data, totalRead, 11 - totalRead);
+            if (read < 0) {
+                fail();
+            }
+            totalRead += read;
+        }
+        assertEquals("Hello World", new String(data));
+
+    }
+
+    @Test
+    public void testPurge() {
+        storage.purge();
+        assertEquals("POST", method);
+        assertTrue(requestURI.endsWith("/purge"));
+    }
+}
--- a/web/common/src/main/java/com/redhat/thermostat/web/common/RESTQuery.java	Tue Nov 06 11:00:12 2012 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,103 +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.common;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-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 RESTQuery extends AbstractQuery {
-
-    private List<Qualifier<?>> qualifiers;
-    private String resultClassName;
-
-    private transient Map<Category, Integer> categoryIdMap;
-
-    private int categoryId;
-
-    public RESTQuery() {
-        this(null);
-    }
-
-    public RESTQuery(Map<Category, Integer> categoryIdMap) {
-        qualifiers = new ArrayList<>();
-        this.categoryIdMap = categoryIdMap;
-    }
-
-    @Override
-    public Query from(Category category) {
-        categoryId = categoryIdMap.get(category);
-        return this;
-    }
-
-    public int getCategoryId() {
-        return categoryId;
-    }
-
-    public void setCategoryId(int categoryId) {
-        this.categoryId = categoryId;
-    }
-
-    @Override
-    public <T> Query where(Key<T> key, Criteria criteria, T value) {
-        qualifiers.add(new Qualifier<>(key, criteria, value));
-        return this;
-    }
-
-    public List<Qualifier<?>> getQualifiers() {
-        return qualifiers;
-    }
-
-    public void setQualifiers(List<Qualifier<?>> qualifiers) {
-        this.qualifiers = qualifiers;
-    }
-
-    public String getResultClassName() {
-        return resultClassName;
-    }
-
-    public void setResultClassName(String resultClassName) {
-        this.resultClassName = resultClassName;
-    }
-
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/common/src/main/java/com/redhat/thermostat/web/common/WebQuery.java	Mon Nov 12 15:48:34 2012 +0100
@@ -0,0 +1,103 @@
+/*
+ * 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.common;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+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 WebQuery extends AbstractQuery {
+
+    private List<Qualifier<?>> qualifiers;
+    private String resultClassName;
+
+    private transient Map<Category, Integer> categoryIdMap;
+
+    private int categoryId;
+
+    public WebQuery() {
+        this(null);
+    }
+
+    public WebQuery(Map<Category, Integer> categoryIdMap) {
+        qualifiers = new ArrayList<>();
+        this.categoryIdMap = categoryIdMap;
+    }
+
+    @Override
+    public Query from(Category category) {
+        categoryId = categoryIdMap.get(category);
+        return this;
+    }
+
+    public int getCategoryId() {
+        return categoryId;
+    }
+
+    public void setCategoryId(int categoryId) {
+        this.categoryId = categoryId;
+    }
+
+    @Override
+    public <T> Query where(Key<T> key, Criteria criteria, T value) {
+        qualifiers.add(new Qualifier<>(key, criteria, value));
+        return this;
+    }
+
+    public List<Qualifier<?>> getQualifiers() {
+        return qualifiers;
+    }
+
+    public void setQualifiers(List<Qualifier<?>> qualifiers) {
+        this.qualifiers = qualifiers;
+    }
+
+    public String getResultClassName() {
+        return resultClassName;
+    }
+
+    public void setResultClassName(String resultClassName) {
+        this.resultClassName = resultClassName;
+    }
+
+}
--- a/web/common/src/test/java/RESTQueryTest.java	Tue Nov 06 11:00:12 2012 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,72 +0,0 @@
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertSame;
-
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import org.junit.Test;
-
-import com.redhat.thermostat.common.storage.Category;
-import com.redhat.thermostat.common.storage.Key;
-import com.redhat.thermostat.common.storage.Query.Criteria;
-import com.redhat.thermostat.web.common.Qualifier;
-import com.redhat.thermostat.web.common.RESTQuery;
-
-/*
- * 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.
- */
-
-public class RESTQueryTest {
-
-    @Test
-    public void test() {
-        Key<String> key1 = new Key<>("testkey", true);
-        Category category = new Category("test", key1);
-        Map<Category,Integer> categoryIdMap = new HashMap<>();
-        categoryIdMap.put(category, 42);
-        RESTQuery query = new RESTQuery(categoryIdMap);
-        query.from(category).where(key1, Criteria.EQUALS, "fluff");
-
-        List<Qualifier<?>> qualifiers = query.getQualifiers();
-        assertEquals(1, qualifiers.size());
-        Qualifier<?> qualifier = qualifiers.get(0);
-        assertEquals(key1, qualifier.getKey());
-        assertEquals(Criteria.EQUALS, qualifier.getCriteria());
-        assertEquals("fluff", qualifier.getValue());
-
-        assertEquals(42, query.getCategoryId());
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/common/src/test/java/com/redhat/thermostat/web/common/WebQueryTest.java	Mon Nov 12 15:48:34 2012 +0100
@@ -0,0 +1,73 @@
+package com.redhat.thermostat.web.common;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Test;
+
+import com.redhat.thermostat.common.storage.Category;
+import com.redhat.thermostat.common.storage.Key;
+import com.redhat.thermostat.common.storage.Query.Criteria;
+import com.redhat.thermostat.web.common.Qualifier;
+import com.redhat.thermostat.web.common.WebQuery;
+
+/*
+ * 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.
+ */
+
+public class WebQueryTest {
+
+    @Test
+    public void test() {
+        Key<String> key1 = new Key<>("testkey", true);
+        Category category = new Category("test", key1);
+        Map<Category,Integer> categoryIdMap = new HashMap<>();
+        categoryIdMap.put(category, 42);
+        WebQuery query = new WebQuery(categoryIdMap);
+        query.from(category).where(key1, Criteria.EQUALS, "fluff");
+
+        List<Qualifier<?>> qualifiers = query.getQualifiers();
+        assertEquals(1, qualifiers.size());
+        Qualifier<?> qualifier = qualifiers.get(0);
+        assertEquals(key1, qualifier.getKey());
+        assertEquals(Criteria.EQUALS, qualifier.getCriteria());
+        assertEquals("fluff", qualifier.getValue());
+
+        assertEquals(42, query.getCategoryId());
+    }
+}
--- a/web/server/src/main/java/com/redhat/thermostat/web/server/RESTStorageEndPoint.java	Tue Nov 06 11:00:12 2012 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,340 +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 java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.Reader;
-import java.io.Writer;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.apache.commons.fileupload.FileItem;
-import org.apache.commons.fileupload.FileItemFactory;
-import org.apache.commons.fileupload.FileUploadException;
-import org.apache.commons.fileupload.disk.DiskFileItemFactory;
-import org.apache.commons.fileupload.servlet.ServletFileUpload;
-
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonParser;
-import com.redhat.thermostat.common.model.AgentIdPojo;
-import com.redhat.thermostat.common.model.Pojo;
-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.Remove;
-import com.redhat.thermostat.common.storage.Storage;
-import com.redhat.thermostat.common.storage.Update;
-import com.redhat.thermostat.web.common.Qualifier;
-import com.redhat.thermostat.web.common.RESTQuery;
-import com.redhat.thermostat.web.common.StorageWrapper;
-import com.redhat.thermostat.web.common.ThermostatGSONConverter;
-import com.redhat.thermostat.web.common.WebInsert;
-import com.redhat.thermostat.web.common.WebRemove;
-import com.redhat.thermostat.web.common.WebUpdate;
-
-@SuppressWarnings("serial")
-public class RESTStorageEndPoint extends HttpServlet {
-
-    private Storage storage;
-    private Gson gson;
-
-    static final String STORAGE_ENDPOINT = "storageEndpoint";
-    
-    private int currentCategoryId;
-
-    private Map<String, Integer> categoryIds;
-    private Map<Integer, Category> categories;
-
-    public void init() {
-        gson = new GsonBuilder().registerTypeHierarchyAdapter(Pojo.class, new ThermostatGSONConverter()).create();
-        categoryIds = new HashMap<>();
-        categories = new HashMap<>();
-    }
-
-    @Override
-    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
-        if (storage == null) {
-            storage = StorageWrapper.getStorage();
-        }
-        String uri = req.getRequestURI();
-        int lastPartIdx = uri.lastIndexOf("/");
-        String cmd = uri.substring(lastPartIdx + 1);
-        if (cmd.equals("find-pojo")) {
-            findPojo(req, resp);
-        } else if (cmd.equals("find-all")) {
-            findAll(req, resp);
-        } else if (cmd.equals("put-pojo")) {
-            putPojo(req, resp);
-        } else if (cmd.equals("register-category")) {
-            registerCategory(req, resp);
-        } else if (cmd.equals("remove-pojo")) {
-            removePojo(req, resp);
-        } else if (cmd.equals("update-pojo")) {
-            updatePojo(req, resp);
-        } else if (cmd.equals("get-count")) {
-            getCount(req, resp);
-        } else if (cmd.equals("save-file")) {
-            saveFile(req, resp);
-        } else if (cmd.equals("load-file")) {
-            loadFile(req, resp);
-        } else if (cmd.equals("purge")) {
-            purge(req, resp);
-        }
-    }
-
-    private void purge(HttpServletRequest req, HttpServletResponse resp) {
-        storage.purge();
-        resp.setStatus(HttpServletResponse.SC_OK);
-    }
-
-    private void loadFile(HttpServletRequest req, HttpServletResponse resp) throws IOException {
-        String name = req.getParameter("file");
-        InputStream data = storage.loadFile(name);
-        OutputStream out = resp.getOutputStream();
-        byte[] buffer = new byte[512];
-        int read = 0;
-        while (read >= 0) {
-            read = data.read(buffer);
-            if (read > 0) {
-                out.write(buffer, 0, read);
-            }
-        }
-    }
-
-    private void saveFile(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-        boolean isMultipart = ServletFileUpload.isMultipartContent(req);
-        if (! isMultipart) {
-            throw new ServletException("expected multipart message");
-        }
-        FileItemFactory factory = new DiskFileItemFactory();
-        ServletFileUpload upload = new ServletFileUpload(factory);
-        try {
-            @SuppressWarnings("unchecked")
-            List<FileItem> items = upload.parseRequest(req);
-            for (FileItem item : items) {
-                String fieldName = item.getFieldName();
-                if (fieldName.equals("file")) {
-                    String name = item.getName();
-                    InputStream in = item.getInputStream();
-                    storage.saveFile(name, in);
-                }
-            }
-        } catch (FileUploadException ex) {
-            throw new ServletException(ex);
-        }
-        
-    }
-
-    private void getCount(HttpServletRequest req, HttpServletResponse resp) {
-        try {
-            String categoryParam = req.getParameter("category");
-            int categoryId = gson.fromJson(categoryParam, Integer.class);
-            Category category = categories.get(categoryId);
-            long result = storage.getCount(category);
-            resp.setStatus(HttpServletResponse.SC_OK);
-            resp.setContentType("application/json");
-            gson.toJson(result, resp.getWriter());
-            resp.flushBuffer();
-        } catch (IOException ex) {
-            ex.printStackTrace();
-            resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
-        }
-    }
-
-    private synchronized void registerCategory(HttpServletRequest req, HttpServletResponse resp) throws IOException {
-        String categoryName = req.getParameter("name");
-        String categoryParam = req.getParameter("category");
-        int id;
-        if (categoryIds.containsKey(categoryName)) {
-            id = categoryIds.get(categoryName);
-        } else {
-            // The following has the side effect of registering the newly deserialized Category in the Categories clas.
-            Category category = gson.fromJson(categoryParam, Category.class);
-            storage.registerCategory(category);
-
-            id = currentCategoryId;
-            categoryIds.put(categoryName, id);
-            categories.put(id, category);
-            currentCategoryId++;
-        }
-        resp.setStatus(HttpServletResponse.SC_OK);
-        resp.setContentType("application/json");
-        Writer writer = resp.getWriter();
-        gson.toJson(id, writer);
-        writer.flush();
-    }
-
-    private void putPojo(HttpServletRequest req, HttpServletResponse resp) {
-        try {
-            String insertParam = req.getParameter("insert");
-            WebInsert insert = gson.fromJson(insertParam, WebInsert.class);
-            Class<? extends AgentIdPojo> pojoCls = (Class<? extends AgentIdPojo>) Class.forName(insert.getPojoClass());
-            String pojoParam = req.getParameter("pojo");
-            AgentIdPojo pojo = gson.fromJson(pojoParam, pojoCls);
-            int categoryId = insert.getCategoryId();
-            Category category = getCategoryFromId(categoryId);
-            storage.putPojo(category, insert.isReplace(), pojo);
-            resp.setStatus(HttpServletResponse.SC_OK);
-        } catch (ClassNotFoundException ex) {
-            resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
-        }
-    }
-
-    @SuppressWarnings({ "rawtypes", "unchecked" })
-    private void removePojo(HttpServletRequest req, HttpServletResponse resp) {
-        String removeParam = req.getParameter("remove");
-        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());
-        }
-        storage.removePojo(targetRemove);
-        resp.setStatus(HttpServletResponse.SC_OK);
-    }
-
-    @SuppressWarnings({ "rawtypes", "unchecked" })
-    private void updatePojo(HttpServletRequest req, HttpServletResponse resp) {
-        try {
-            String updateParam = req.getParameter("update");
-            WebUpdate update = gson.fromJson(updateParam, WebUpdate.class);
-            Update targetUpdate = storage.createUpdate();
-            targetUpdate = targetUpdate.from(getCategoryFromId(update.getCategoryId()));
-            List<Qualifier<?>> qualifiers = update.getQualifiers();
-            for (Qualifier qualifier : qualifiers) {
-                assert (qualifier.getCriteria() == Criteria.EQUALS);
-                targetUpdate = targetUpdate.where(qualifier.getKey(), qualifier.getValue());
-            }
-            List<WebUpdate.UpdateValue> updates = update.getUpdates();
-            if (updates != null) {
-                String valuesParam = req.getParameter("values");
-                JsonParser parser = new JsonParser();
-                JsonArray jsonArray = parser.parse(valuesParam)
-                        .getAsJsonArray();
-                int index = 0;
-                for (WebUpdate.UpdateValue updateValue : updates) {
-                    Class valueClass = Class.forName(updateValue
-                            .getValueClass());
-                    Object value = gson.fromJson(jsonArray.get(index),
-                            valueClass);
-                    index++;
-                    Key key = updateValue.getKey();
-                    targetUpdate.set(key, value);
-                }
-            }
-            storage.updatePojo(targetUpdate);
-            resp.setStatus(HttpServletResponse.SC_OK);
-        } catch (ClassNotFoundException ex) {
-            ex.printStackTrace();
-            resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
-        }
-    }
-
-    @SuppressWarnings({ "rawtypes", "unchecked" })
-    private void findPojo(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);
-            Object result = storage.findPojo(targetQuery, resultClass);
-            writeResponse(resp, result);
-        } catch (ClassNotFoundException e) {
-            e.printStackTrace();
-            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) {
-            e.printStackTrace();
-            resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "result class not found");
-        }
-    }
-
-    private Query constructTargetQuery(RESTQuery query) {
-        Query targetQuery = storage.createQuery();
-        int categoryId = query.getCategoryId();
-        Category category = getCategoryFromId(categoryId);
-        targetQuery = targetQuery.from(category);
-        List<Qualifier<?>> qualifiers = query.getQualifiers();
-        for (Qualifier q : qualifiers) {
-            targetQuery = targetQuery.where(q.getKey(), q.getCriteria(), q.getValue());
-        }
-        return targetQuery;
-    }
-
-    private Category getCategoryFromId(int categoryId) {
-        Category category = categories.get(categoryId);
-        return category;
-    }
-
-    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();
-    }
-
-}
--- a/web/server/src/main/java/com/redhat/thermostat/web/server/WebServiceCommand.java	Tue Nov 06 11:00:12 2012 -0500
+++ b/web/server/src/main/java/com/redhat/thermostat/web/server/WebServiceCommand.java	Mon Nov 12 15:48:34 2012 +0100
@@ -43,10 +43,6 @@
 
 public class WebServiceCommand extends SimpleCommand {
 
-    public WebServiceCommand() {
-        // TODO Auto-generated constructor stub
-    }
-
     @Override
     public void run(CommandContext ctx) throws CommandException {
         String storageURL = ctx.getArguments().getArgument("storageURL");
--- a/web/server/src/main/java/com/redhat/thermostat/web/server/WebServiceLauncher.java	Tue Nov 06 11:00:12 2012 -0500
+++ b/web/server/src/main/java/com/redhat/thermostat/web/server/WebServiceLauncher.java	Mon Nov 12 15:48:34 2012 +0100
@@ -51,8 +51,8 @@
     void start() throws Exception {
         server = new Server(port);
         ServletHandler handler = new ServletHandler();
-        ServletHolder servletHolder = new ServletHolder("rest-storage-end-point", new RESTStorageEndPoint());
-        servletHolder.setInitParameter(RESTStorageEndPoint.STORAGE_ENDPOINT, storageURL);
+        ServletHolder servletHolder = new ServletHolder("rest-storage-end-point", new WebStorageEndPoint());
+        servletHolder.setInitParameter(WebStorageEndPoint.STORAGE_ENDPOINT, storageURL);
         handler.setServlets(new ServletHolder[] { servletHolder });
         ServletMapping mapping = new ServletMapping();
         mapping.setPathSpec("/");
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/server/src/main/java/com/redhat/thermostat/web/server/WebStorageEndPoint.java	Mon Nov 12 15:48:34 2012 +0100
@@ -0,0 +1,340 @@
+/*
+ * 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 java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.fileupload.FileItem;
+import org.apache.commons.fileupload.FileItemFactory;
+import org.apache.commons.fileupload.FileUploadException;
+import org.apache.commons.fileupload.disk.DiskFileItemFactory;
+import org.apache.commons.fileupload.servlet.ServletFileUpload;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonParser;
+import com.redhat.thermostat.common.model.AgentIdPojo;
+import com.redhat.thermostat.common.model.Pojo;
+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.Remove;
+import com.redhat.thermostat.common.storage.Storage;
+import com.redhat.thermostat.common.storage.Update;
+import com.redhat.thermostat.web.common.Qualifier;
+import com.redhat.thermostat.web.common.WebQuery;
+import com.redhat.thermostat.web.common.StorageWrapper;
+import com.redhat.thermostat.web.common.ThermostatGSONConverter;
+import com.redhat.thermostat.web.common.WebInsert;
+import com.redhat.thermostat.web.common.WebRemove;
+import com.redhat.thermostat.web.common.WebUpdate;
+
+@SuppressWarnings("serial")
+public class WebStorageEndPoint extends HttpServlet {
+
+    private Storage storage;
+    private Gson gson;
+
+    static final String STORAGE_ENDPOINT = "storageEndpoint";
+    
+    private int currentCategoryId;
+
+    private Map<String, Integer> categoryIds;
+    private Map<Integer, Category> categories;
+
+    public void init() {
+        gson = new GsonBuilder().registerTypeHierarchyAdapter(Pojo.class, new ThermostatGSONConverter()).create();
+        categoryIds = new HashMap<>();
+        categories = new HashMap<>();
+    }
+
+    @Override
+    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
+        if (storage == null) {
+            storage = StorageWrapper.getStorage();
+        }
+        String uri = req.getRequestURI();
+        int lastPartIdx = uri.lastIndexOf("/");
+        String cmd = uri.substring(lastPartIdx + 1);
+        if (cmd.equals("find-pojo")) {
+            findPojo(req, resp);
+        } else if (cmd.equals("find-all")) {
+            findAll(req, resp);
+        } else if (cmd.equals("put-pojo")) {
+            putPojo(req, resp);
+        } else if (cmd.equals("register-category")) {
+            registerCategory(req, resp);
+        } else if (cmd.equals("remove-pojo")) {
+            removePojo(req, resp);
+        } else if (cmd.equals("update-pojo")) {
+            updatePojo(req, resp);
+        } else if (cmd.equals("get-count")) {
+            getCount(req, resp);
+        } else if (cmd.equals("save-file")) {
+            saveFile(req, resp);
+        } else if (cmd.equals("load-file")) {
+            loadFile(req, resp);
+        } else if (cmd.equals("purge")) {
+            purge(req, resp);
+        }
+    }
+
+    private void purge(HttpServletRequest req, HttpServletResponse resp) {
+        storage.purge();
+        resp.setStatus(HttpServletResponse.SC_OK);
+    }
+
+    private void loadFile(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        String name = req.getParameter("file");
+        InputStream data = storage.loadFile(name);
+        OutputStream out = resp.getOutputStream();
+        byte[] buffer = new byte[512];
+        int read = 0;
+        while (read >= 0) {
+            read = data.read(buffer);
+            if (read > 0) {
+                out.write(buffer, 0, read);
+            }
+        }
+    }
+
+    private void saveFile(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+        boolean isMultipart = ServletFileUpload.isMultipartContent(req);
+        if (! isMultipart) {
+            throw new ServletException("expected multipart message");
+        }
+        FileItemFactory factory = new DiskFileItemFactory();
+        ServletFileUpload upload = new ServletFileUpload(factory);
+        try {
+            @SuppressWarnings("unchecked")
+            List<FileItem> items = upload.parseRequest(req);
+            for (FileItem item : items) {
+                String fieldName = item.getFieldName();
+                if (fieldName.equals("file")) {
+                    String name = item.getName();
+                    InputStream in = item.getInputStream();
+                    storage.saveFile(name, in);
+                }
+            }
+        } catch (FileUploadException ex) {
+            throw new ServletException(ex);
+        }
+        
+    }
+
+    private void getCount(HttpServletRequest req, HttpServletResponse resp) {
+        try {
+            String categoryParam = req.getParameter("category");
+            int categoryId = gson.fromJson(categoryParam, Integer.class);
+            Category category = categories.get(categoryId);
+            long result = storage.getCount(category);
+            resp.setStatus(HttpServletResponse.SC_OK);
+            resp.setContentType("application/json");
+            gson.toJson(result, resp.getWriter());
+            resp.flushBuffer();
+        } catch (IOException ex) {
+            ex.printStackTrace();
+            resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+        }
+    }
+
+    private synchronized void registerCategory(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        String categoryName = req.getParameter("name");
+        String categoryParam = req.getParameter("category");
+        int id;
+        if (categoryIds.containsKey(categoryName)) {
+            id = categoryIds.get(categoryName);
+        } else {
+            // The following has the side effect of registering the newly deserialized Category in the Categories clas.
+            Category category = gson.fromJson(categoryParam, Category.class);
+            storage.registerCategory(category);
+
+            id = currentCategoryId;
+            categoryIds.put(categoryName, id);
+            categories.put(id, category);
+            currentCategoryId++;
+        }
+        resp.setStatus(HttpServletResponse.SC_OK);
+        resp.setContentType("application/json");
+        Writer writer = resp.getWriter();
+        gson.toJson(id, writer);
+        writer.flush();
+    }
+
+    private void putPojo(HttpServletRequest req, HttpServletResponse resp) {
+        try {
+            String insertParam = req.getParameter("insert");
+            WebInsert insert = gson.fromJson(insertParam, WebInsert.class);
+            Class<? extends AgentIdPojo> pojoCls = (Class<? extends AgentIdPojo>) Class.forName(insert.getPojoClass());
+            String pojoParam = req.getParameter("pojo");
+            AgentIdPojo pojo = gson.fromJson(pojoParam, pojoCls);
+            int categoryId = insert.getCategoryId();
+            Category category = getCategoryFromId(categoryId);
+            storage.putPojo(category, insert.isReplace(), pojo);
+            resp.setStatus(HttpServletResponse.SC_OK);
+        } catch (ClassNotFoundException ex) {
+            resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+        }
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    private void removePojo(HttpServletRequest req, HttpServletResponse resp) {
+        String removeParam = req.getParameter("remove");
+        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());
+        }
+        storage.removePojo(targetRemove);
+        resp.setStatus(HttpServletResponse.SC_OK);
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    private void updatePojo(HttpServletRequest req, HttpServletResponse resp) {
+        try {
+            String updateParam = req.getParameter("update");
+            WebUpdate update = gson.fromJson(updateParam, WebUpdate.class);
+            Update targetUpdate = storage.createUpdate();
+            targetUpdate = targetUpdate.from(getCategoryFromId(update.getCategoryId()));
+            List<Qualifier<?>> qualifiers = update.getQualifiers();
+            for (Qualifier qualifier : qualifiers) {
+                assert (qualifier.getCriteria() == Criteria.EQUALS);
+                targetUpdate = targetUpdate.where(qualifier.getKey(), qualifier.getValue());
+            }
+            List<WebUpdate.UpdateValue> updates = update.getUpdates();
+            if (updates != null) {
+                String valuesParam = req.getParameter("values");
+                JsonParser parser = new JsonParser();
+                JsonArray jsonArray = parser.parse(valuesParam)
+                        .getAsJsonArray();
+                int index = 0;
+                for (WebUpdate.UpdateValue updateValue : updates) {
+                    Class valueClass = Class.forName(updateValue
+                            .getValueClass());
+                    Object value = gson.fromJson(jsonArray.get(index),
+                            valueClass);
+                    index++;
+                    Key key = updateValue.getKey();
+                    targetUpdate.set(key, value);
+                }
+            }
+            storage.updatePojo(targetUpdate);
+            resp.setStatus(HttpServletResponse.SC_OK);
+        } catch (ClassNotFoundException ex) {
+            ex.printStackTrace();
+            resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+        }
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    private void findPojo(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        try {
+            Reader in = req.getReader();
+            WebQuery query = gson.fromJson(in, WebQuery.class);
+            Class resultClass = Class.forName(query.getResultClassName());
+            Query targetQuery = constructTargetQuery(query);
+            Object result = storage.findPojo(targetQuery, resultClass);
+            writeResponse(resp, result);
+        } catch (ClassNotFoundException e) {
+            e.printStackTrace();
+            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();
+            WebQuery query = gson.fromJson(in, WebQuery.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) {
+            e.printStackTrace();
+            resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "result class not found");
+        }
+    }
+
+    private Query constructTargetQuery(WebQuery query) {
+        Query targetQuery = storage.createQuery();
+        int categoryId = query.getCategoryId();
+        Category category = getCategoryFromId(categoryId);
+        targetQuery = targetQuery.from(category);
+        List<Qualifier<?>> qualifiers = query.getQualifiers();
+        for (Qualifier q : qualifiers) {
+            targetQuery = targetQuery.where(q.getKey(), q.getCriteria(), q.getValue());
+        }
+        return targetQuery;
+    }
+
+    private Category getCategoryFromId(int categoryId) {
+        Category category = categories.get(categoryId);
+        return category;
+    }
+
+    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();
+    }
+
+}
--- a/web/server/src/main/webapp/WEB-INF/web.xml	Tue Nov 06 11:00:12 2012 -0500
+++ b/web/server/src/main/webapp/WEB-INF/web.xml	Mon Nov 12 15:48:34 2012 +0100
@@ -7,7 +7,7 @@
 
   <servlet>
     <servlet-name>reststorage-servlet</servlet-name>
-    <servlet-class>com.redhat.thermostat.web.server.RESTStorageEndPoint</servlet-class>
+    <servlet-class>com.redhat.thermostat.web.server.WebStorageEndPoint</servlet-class>
   </servlet>
 
   <servlet-mapping>
--- a/web/server/src/test/java/com/redhat/thermostat/web/server/RESTStorageEndpointTest.java	Tue Nov 06 11:00:12 2012 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,469 +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.junit.Assert.fail;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
-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.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.Reader;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.net.URLEncoder;
-import java.util.HashMap;
-import java.util.Map;
-
-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 org.mockito.ArgumentCaptor;
-
-import com.google.gson.Gson;
-import com.redhat.thermostat.common.model.BasePojo;
-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.Entity;
-import com.redhat.thermostat.common.storage.Key;
-import com.redhat.thermostat.common.storage.Persist;
-import com.redhat.thermostat.common.storage.Query;
-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;
-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;
-import com.redhat.thermostat.web.common.WebInsert;
-import com.redhat.thermostat.web.common.WebRemove;
-import com.redhat.thermostat.web.common.WebUpdate;
-
-public class RESTStorageEndpointTest {
-
-    @Entity
-    public static class TestClass extends BasePojo {
-        private String key1;
-        private int key2;
-        @Persist
-        public String getKey1() {
-            return key1;
-        }
-        @Persist
-        public void setKey1(String key1) {
-            this.key1 = key1;
-        }
-        @Persist
-        public int getKey2() {
-            return key2;
-        }
-        @Persist
-        public void setKey2(int key2) {
-            this.key2 = key2;
-        }
-        public boolean equals(Object o) {
-            if (! (o instanceof TestClass)) {
-                return false;
-            }
-            TestClass other = (TestClass) o;
-            return key1.equals(other.key1) && key2 == other.key2;
-        }
-    }
-
-    private Server server;
-    private int port;
-    private Storage mockStorage;
-    private Integer categoryId;
-
-    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);
-            }
-        });
-        registerCategory();
-    }
-
-    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());
-        restStorage.registerCategory(category);
-        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);
-        Map<Category,Integer> categoryIdMap = new HashMap<>();
-        categoryIdMap.put(category, categoryId);
-        RESTQuery query = (RESTQuery) new RESTQuery(categoryIdMap).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());
-    }
-
-    @Test
-    public void testPutPojo() throws IOException {
-
-        TestClass expected1 = new TestClass();
-        expected1.setKey1("fluff1");
-        expected1.setKey2(42);
-
-        String endpoint = getEndpoint();
-
-        URL url = new URL(endpoint + "/put-pojo");
-        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
-        conn.setDoOutput(true);
-        conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
-        WebInsert insert = new WebInsert(categoryId, true, TestClass.class.getName());
-        Gson gson = new Gson();
-        OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream());
-        out.write("insert=");
-        gson.toJson(insert, out);
-        out.flush();
-        out.write("&pojo=");
-        gson.toJson(expected1, out);
-        out.write("\n");
-        out.flush();
-        assertEquals(200, conn.getResponseCode());
-        verify(mockStorage).putPojo(category, true, expected1);
-    }
-
-    @Test
-    public void testRemovePojo() throws IOException {
-
-        Remove mockRemove = mock(Remove.class);
-        when(mockRemove.from(any(Category.class))).thenReturn(mockRemove);
-        when(mockRemove.where(any(Key.class), any())).thenReturn(mockRemove);
-
-        when(mockStorage.createRemove()).thenReturn(mockRemove);
-
-        String endpoint = getEndpoint();
-
-        URL url = new URL(endpoint + "/remove-pojo");
-        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
-        conn.setDoOutput(true);
-        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();
-        OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream());
-        out.write("remove=");
-        gson.toJson(remove, out);
-        out.write("\n");
-        out.flush();
-
-        assertEquals(200, conn.getResponseCode());
-        verify(mockStorage).createRemove();
-        verify(mockRemove).from(category);
-        verify(mockRemove).where(key1, "test");
-        verify(mockStorage).removePojo(mockRemove);
-    }
-
-    @Test
-    public void testUpdatePojo() throws IOException {
-
-        Update mockUpdate = mock(Update.class);
-        when(mockUpdate.from(any(Category.class))).thenReturn(mockUpdate);
-        when(mockUpdate.where(any(Key.class), any())).thenReturn(mockUpdate);
-        when(mockUpdate.set(any(Key.class), any())).thenReturn(mockUpdate);
-        when(mockStorage.createUpdate()).thenReturn(mockUpdate);
-
-        String endpoint = getEndpoint();
-
-        URL url = new URL(endpoint + "/update-pojo");
-        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
-        conn.setDoOutput(true);
-        conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
-        Map<Category,Integer> categoryIds = new HashMap<>();
-        categoryIds.put(category, categoryId);
-        WebUpdate update = new WebUpdate(categoryIds).from(category).where(key1, "test").set(key1, "fluff").set(key2, 42);
-        Gson gson = new Gson();
-        OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream());
-        out.write("update=");
-        gson.toJson(update, out);
-        out.write("&values=");
-        gson.toJson(new Object[] {"fluff", 42 }, out);
-        out.write("\n");
-        out.flush();
-
-        assertEquals(200, conn.getResponseCode());
-        verify(mockStorage).createUpdate();
-        verify(mockUpdate).from(category);
-        verify(mockUpdate).where(key1, "test");
-        verify(mockUpdate).set(key1, "fluff");
-        verify(mockUpdate).set(key2, 42);
-        verify(mockStorage).updatePojo(mockUpdate);
-    }
-
-
-    @Test
-    public void testGetCount() throws IOException {
-
-        when(mockStorage.getCount(category)).thenReturn(12345L);
-        String endpoint = getEndpoint();
-
-        URL url = new URL(endpoint + "/get-count");
-        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
-        conn.setDoOutput(true);
-        conn.setDoInput(true);
-        conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
-        Gson gson = new Gson();
-        OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream());
-        out.write("category=" + categoryId);
-        out.flush();
-
-        InputStream in = conn.getInputStream();
-        Reader reader = new InputStreamReader(in);
-        long result = gson.fromJson(reader, Long.class);
-        assertEquals(200, conn.getResponseCode());
-        assertEquals(12345, result);
-        verify(mockStorage).getCount(category);
-        
-    }
-
-    @Test
-    public void testSaveFile() throws IOException {
-        String endpoint = getEndpoint();
-
-        URL url = new URL(endpoint + "/save-file");
-        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
-        conn.setDoOutput(true);
-        conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=fluff");
-        conn.setRequestProperty("Transfer-Encoding", "chunked");
-        OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream());
-        out.write("--fluff\r\n");
-        out.write("Content-Disposition: form-data; name=\"file\"; filename=\"fluff\"\r\n");
-        out.write("Content-Type: application/octet-stream\r\n");
-        out.write("Content-Transfer-Encoding: binary\r\n");
-        out.write("\r\n");
-        out.write("Hello World\r\n");
-        out.write("--fluff--\r\n");
-        out.flush();
-        int status = conn.getResponseCode();
-        System.err.println("status: " + status);
-        ArgumentCaptor<InputStream> inCaptor = ArgumentCaptor.forClass(InputStream.class);
-        verify(mockStorage).saveFile(eq("fluff"), inCaptor.capture());
-        InputStream in = inCaptor.getValue();
-        byte[] data = new byte[11];
-        int totalRead = 0;
-        while (totalRead < 11) {
-            int read = in.read(data, totalRead, 11 - totalRead);
-            if (read < 0) {
-                fail();
-            }
-            totalRead += read;
-        }
-        assertEquals("Hello World", new String(data));
-    }
-
-    @Test
-    public void testLoadFile() throws IOException {
-
-        byte[] data = "Hello World".getBytes();
-        InputStream in = new ByteArrayInputStream(data);
-        when(mockStorage.loadFile("fluff")).thenReturn(in);
-
-        String endpoint = getEndpoint();
-        URL url = new URL(endpoint + "/load-file");
-
-        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
-        conn.setDoOutput(true);
-        conn.setDoInput(true);
-        OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream());
-        out.write("file=fluff");
-        out.flush();
-        in = conn.getInputStream();
-        data = new byte[11];
-        int totalRead = 0;
-        while (totalRead < 11) {
-            int read = in.read(data, totalRead, 11 - totalRead);
-            if (read < 0) {
-                fail();
-            }
-            totalRead += read;
-        }
-        assertEquals("Hello World", new String(data));
-        verify(mockStorage).loadFile("fluff");
-    }
-
-    @Test
-    public void testPurge() throws IOException {
-        String endpoint = getEndpoint();
-        URL url = new URL(endpoint + "/purge");
-        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
-        conn.setDoOutput(true);
-        conn.setRequestMethod("POST");
-        int status = conn.getResponseCode();
-        assertEquals(200, status);
-        verify(mockStorage).purge();
-    }
-
-    private void registerCategory() {
-        try {
-            String endpoint = getEndpoint();
-            URL url = new URL(endpoint + "/register-category");
-            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
-            String enc = "UTF-8";
-            conn.setRequestProperty("Content-Encoding", enc);
-            conn.setDoOutput(true);
-            conn.setDoInput(true);
-            conn.setRequestMethod("POST");
-            OutputStream out = conn.getOutputStream();
-            Gson gson = new Gson();
-            OutputStreamWriter writer = new OutputStreamWriter(out);
-            writer.write("name=");
-            writer.write(URLEncoder.encode(category.getName(), enc));
-            writer.write("&category=");
-            writer.write(URLEncoder.encode(gson.toJson(category), enc));
-            writer.flush();
-
-            InputStream in = conn.getInputStream();
-            Reader reader = new InputStreamReader(in);
-            Integer id = gson.fromJson(reader, Integer.class);
-            categoryId = id;
-        } catch (IOException ex) {
-            throw new RuntimeException(ex);
-        }
-    }
-
-    private String getEndpoint() {
-        return "http://localhost:" + port + "/storage";
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/server/src/test/java/com/redhat/thermostat/web/server/WebStorageEndpointTest.java	Mon Nov 12 15:48:34 2012 +0100
@@ -0,0 +1,469 @@
+/*
+ * 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.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+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.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.util.HashMap;
+import java.util.Map;
+
+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 org.mockito.ArgumentCaptor;
+
+import com.google.gson.Gson;
+import com.redhat.thermostat.common.model.BasePojo;
+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.Entity;
+import com.redhat.thermostat.common.storage.Key;
+import com.redhat.thermostat.common.storage.Persist;
+import com.redhat.thermostat.common.storage.Query;
+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;
+import com.redhat.thermostat.test.FreePortFinder;
+import com.redhat.thermostat.test.FreePortFinder.TryPort;
+import com.redhat.thermostat.web.client.WebStorage;
+import com.redhat.thermostat.web.common.WebQuery;
+import com.redhat.thermostat.web.common.StorageWrapper;
+import com.redhat.thermostat.web.common.WebInsert;
+import com.redhat.thermostat.web.common.WebRemove;
+import com.redhat.thermostat.web.common.WebUpdate;
+
+public class WebStorageEndpointTest {
+
+    @Entity
+    public static class TestClass extends BasePojo {
+        private String key1;
+        private int key2;
+        @Persist
+        public String getKey1() {
+            return key1;
+        }
+        @Persist
+        public void setKey1(String key1) {
+            this.key1 = key1;
+        }
+        @Persist
+        public int getKey2() {
+            return key2;
+        }
+        @Persist
+        public void setKey2(int key2) {
+            this.key2 = key2;
+        }
+        public boolean equals(Object o) {
+            if (! (o instanceof TestClass)) {
+                return false;
+            }
+            TestClass other = (TestClass) o;
+            return key1.equals(other.key1) && key2 == other.key2;
+        }
+    }
+
+    private Server server;
+    private int port;
+    private Storage mockStorage;
+    private Integer categoryId;
+
+    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);
+            }
+        });
+        registerCategory();
+    }
+
+    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);
+
+        WebStorage restStorage = new WebStorage();
+        restStorage.setEndpoint(getEndpoint());
+        restStorage.registerCategory(category);
+        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);
+        Map<Category,Integer> categoryIdMap = new HashMap<>();
+        categoryIdMap.put(category, categoryId);
+        WebQuery query = (WebQuery) new WebQuery(categoryIdMap).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());
+    }
+
+    @Test
+    public void testPutPojo() throws IOException {
+
+        TestClass expected1 = new TestClass();
+        expected1.setKey1("fluff1");
+        expected1.setKey2(42);
+
+        String endpoint = getEndpoint();
+
+        URL url = new URL(endpoint + "/put-pojo");
+        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+        conn.setDoOutput(true);
+        conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
+        WebInsert insert = new WebInsert(categoryId, true, TestClass.class.getName());
+        Gson gson = new Gson();
+        OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream());
+        out.write("insert=");
+        gson.toJson(insert, out);
+        out.flush();
+        out.write("&pojo=");
+        gson.toJson(expected1, out);
+        out.write("\n");
+        out.flush();
+        assertEquals(200, conn.getResponseCode());
+        verify(mockStorage).putPojo(category, true, expected1);
+    }
+
+    @Test
+    public void testRemovePojo() throws IOException {
+
+        Remove mockRemove = mock(Remove.class);
+        when(mockRemove.from(any(Category.class))).thenReturn(mockRemove);
+        when(mockRemove.where(any(Key.class), any())).thenReturn(mockRemove);
+
+        when(mockStorage.createRemove()).thenReturn(mockRemove);
+
+        String endpoint = getEndpoint();
+
+        URL url = new URL(endpoint + "/remove-pojo");
+        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+        conn.setDoOutput(true);
+        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();
+        OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream());
+        out.write("remove=");
+        gson.toJson(remove, out);
+        out.write("\n");
+        out.flush();
+
+        assertEquals(200, conn.getResponseCode());
+        verify(mockStorage).createRemove();
+        verify(mockRemove).from(category);
+        verify(mockRemove).where(key1, "test");
+        verify(mockStorage).removePojo(mockRemove);
+    }
+
+    @Test
+    public void testUpdatePojo() throws IOException {
+
+        Update mockUpdate = mock(Update.class);
+        when(mockUpdate.from(any(Category.class))).thenReturn(mockUpdate);
+        when(mockUpdate.where(any(Key.class), any())).thenReturn(mockUpdate);
+        when(mockUpdate.set(any(Key.class), any())).thenReturn(mockUpdate);
+        when(mockStorage.createUpdate()).thenReturn(mockUpdate);
+
+        String endpoint = getEndpoint();
+
+        URL url = new URL(endpoint + "/update-pojo");
+        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+        conn.setDoOutput(true);
+        conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
+        Map<Category,Integer> categoryIds = new HashMap<>();
+        categoryIds.put(category, categoryId);
+        WebUpdate update = new WebUpdate(categoryIds).from(category).where(key1, "test").set(key1, "fluff").set(key2, 42);
+        Gson gson = new Gson();
+        OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream());
+        out.write("update=");
+        gson.toJson(update, out);
+        out.write("&values=");
+        gson.toJson(new Object[] {"fluff", 42 }, out);
+        out.write("\n");
+        out.flush();
+
+        assertEquals(200, conn.getResponseCode());
+        verify(mockStorage).createUpdate();
+        verify(mockUpdate).from(category);
+        verify(mockUpdate).where(key1, "test");
+        verify(mockUpdate).set(key1, "fluff");
+        verify(mockUpdate).set(key2, 42);
+        verify(mockStorage).updatePojo(mockUpdate);
+    }
+
+
+    @Test
+    public void testGetCount() throws IOException {
+
+        when(mockStorage.getCount(category)).thenReturn(12345L);
+        String endpoint = getEndpoint();
+
+        URL url = new URL(endpoint + "/get-count");
+        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+        conn.setDoOutput(true);
+        conn.setDoInput(true);
+        conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
+        Gson gson = new Gson();
+        OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream());
+        out.write("category=" + categoryId);
+        out.flush();
+
+        InputStream in = conn.getInputStream();
+        Reader reader = new InputStreamReader(in);
+        long result = gson.fromJson(reader, Long.class);
+        assertEquals(200, conn.getResponseCode());
+        assertEquals(12345, result);
+        verify(mockStorage).getCount(category);
+        
+    }
+
+    @Test
+    public void testSaveFile() throws IOException {
+        String endpoint = getEndpoint();
+
+        URL url = new URL(endpoint + "/save-file");
+        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+        conn.setDoOutput(true);
+        conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=fluff");
+        conn.setRequestProperty("Transfer-Encoding", "chunked");
+        OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream());
+        out.write("--fluff\r\n");
+        out.write("Content-Disposition: form-data; name=\"file\"; filename=\"fluff\"\r\n");
+        out.write("Content-Type: application/octet-stream\r\n");
+        out.write("Content-Transfer-Encoding: binary\r\n");
+        out.write("\r\n");
+        out.write("Hello World\r\n");
+        out.write("--fluff--\r\n");
+        out.flush();
+        int status = conn.getResponseCode();
+        System.err.println("status: " + status);
+        ArgumentCaptor<InputStream> inCaptor = ArgumentCaptor.forClass(InputStream.class);
+        verify(mockStorage).saveFile(eq("fluff"), inCaptor.capture());
+        InputStream in = inCaptor.getValue();
+        byte[] data = new byte[11];
+        int totalRead = 0;
+        while (totalRead < 11) {
+            int read = in.read(data, totalRead, 11 - totalRead);
+            if (read < 0) {
+                fail();
+            }
+            totalRead += read;
+        }
+        assertEquals("Hello World", new String(data));
+    }
+
+    @Test
+    public void testLoadFile() throws IOException {
+
+        byte[] data = "Hello World".getBytes();
+        InputStream in = new ByteArrayInputStream(data);
+        when(mockStorage.loadFile("fluff")).thenReturn(in);
+
+        String endpoint = getEndpoint();
+        URL url = new URL(endpoint + "/load-file");
+
+        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+        conn.setDoOutput(true);
+        conn.setDoInput(true);
+        OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream());
+        out.write("file=fluff");
+        out.flush();
+        in = conn.getInputStream();
+        data = new byte[11];
+        int totalRead = 0;
+        while (totalRead < 11) {
+            int read = in.read(data, totalRead, 11 - totalRead);
+            if (read < 0) {
+                fail();
+            }
+            totalRead += read;
+        }
+        assertEquals("Hello World", new String(data));
+        verify(mockStorage).loadFile("fluff");
+    }
+
+    @Test
+    public void testPurge() throws IOException {
+        String endpoint = getEndpoint();
+        URL url = new URL(endpoint + "/purge");
+        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+        conn.setDoOutput(true);
+        conn.setRequestMethod("POST");
+        int status = conn.getResponseCode();
+        assertEquals(200, status);
+        verify(mockStorage).purge();
+    }
+
+    private void registerCategory() {
+        try {
+            String endpoint = getEndpoint();
+            URL url = new URL(endpoint + "/register-category");
+            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+            String enc = "UTF-8";
+            conn.setRequestProperty("Content-Encoding", enc);
+            conn.setDoOutput(true);
+            conn.setDoInput(true);
+            conn.setRequestMethod("POST");
+            OutputStream out = conn.getOutputStream();
+            Gson gson = new Gson();
+            OutputStreamWriter writer = new OutputStreamWriter(out);
+            writer.write("name=");
+            writer.write(URLEncoder.encode(category.getName(), enc));
+            writer.write("&category=");
+            writer.write(URLEncoder.encode(gson.toJson(category), enc));
+            writer.flush();
+
+            InputStream in = conn.getInputStream();
+            Reader reader = new InputStreamReader(in);
+            Integer id = gson.fromJson(reader, Integer.class);
+            categoryId = id;
+        } catch (IOException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    private String getEndpoint() {
+        return "http://localhost:" + port + "/storage";
+    }
+}