changeset 747:098a78b25a40

Implement large file support in web service. Reviewed-by: jerboaa Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2012-October/003835.html
author Roman Kennke <rkennke@redhat.com>
date Fri, 26 Oct 2012 13:12:03 +0200
parents 3a47a18e5b4a
children 9a66968f2bcb
files distribution/config/commands/gui.properties distribution/config/commands/service.properties distribution/config/osgi-export.properties pom.xml web/client/pom.xml web/client/src/main/java/com/redhat/thermostat/web/client/RESTStorage.java web/client/src/test/java/com/redhat/thermostat/web/client/RESTStorageTest.java web/server/pom.xml web/server/src/main/java/com/redhat/thermostat/web/server/RESTStorageEndPoint.java web/server/src/test/java/com/redhat/thermostat/web/server/RESTStorageEndpointTest.java
diffstat 10 files changed, 250 insertions(+), 10 deletions(-) [+]
line wrap: on
line diff
--- a/distribution/config/commands/gui.properties	Mon Oct 22 18:24:19 2012 -0400
+++ b/distribution/config/commands/gui.properties	Fri Oct 26 13:12:03 2012 +0200
@@ -16,7 +16,10 @@
           thermostat-thread-client-swing-@project.version@.jar, \
           thermostat-thread-client-controllers-@project.version@.jar, \
           thermostat-thread-client-common-@project.version@.jar, \
-          thermostat-osgi-process-handler-@project.version@.jar
+          thermostat-osgi-process-handler-@project.version@.jar, \
+          httpcore-osgi-@httpcomponents.version@.jar, \
+          httpclient-osgi-@httpcomponents.version@.jar
+          
 
 description = launches the GUI client
 
--- a/distribution/config/commands/service.properties	Mon Oct 22 18:24:19 2012 -0400
+++ b/distribution/config/commands/service.properties	Fri Oct 26 13:12:03 2012 +0200
@@ -17,7 +17,10 @@
           jetty-servlet-8.1.5.v20120716.jar, \
           jetty-util-8.1.5.v20120716.jar, \
           jetty-webapp-8.1.5.v20120716.jar, \
-          jetty-xml-8.1.5.v20120716.jar
+          jetty-xml-8.1.5.v20120716.jar, \
+          httpcore-osgi-@httpcomponents.version@.jar, \
+          httpclient-osgi-@httpcomponents.version@.jar, \
+          commons-fileupload-@fileupload.version@.jar
 
 description = starts and stops the thermostat storage and agent
 
--- a/distribution/config/osgi-export.properties	Mon Oct 22 18:24:19 2012 -0400
+++ b/distribution/config/osgi-export.properties	Fri Oct 26 13:12:03 2012 +0200
@@ -37,6 +37,9 @@
 com.mongodb.gridfs
 org.apache.commons.cli=1.2.0
 org.apache.commons.beanutils=1.8.3
+org.apache.commons.io
+org.apache.commons.io.output
+org.apache.commons.logging=1.1.1
 org.bson
 org.bson.types
 org.jfree.chart
--- a/pom.xml	Mon Oct 22 18:24:19 2012 -0400
+++ b/pom.xml	Fri Oct 26 13:12:03 2012 +0200
@@ -72,6 +72,7 @@
     <mongo-driver.version>2.7.3</mongo-driver.version>
     <commons-beanutils.version>1.8.3</commons-beanutils.version>
     <commons-cli.version>1.2</commons-cli.version>
+    <commons-io.version>2.4</commons-io.version>
     <jline.version>2.5</jline.version>
     <lucene.version>3.6.0</lucene.version>
     <!--
@@ -83,7 +84,8 @@
     <felix.framework.version>4.0.2</felix.framework.version>
 
     <netty.version>3.2.4.Final</netty.version>
-
+    <httpcomponents.version>4.2.1</httpcomponents.version>
+    <fileupload.version>1.2.2</fileupload.version>
   </properties>
 
   <repositories>
--- a/web/client/pom.xml	Mon Oct 22 18:24:19 2012 -0400
+++ b/web/client/pom.xml	Fri Oct 26 13:12:03 2012 +0200
@@ -94,6 +94,17 @@
     </dependency>
 
     <dependency>
+      <groupId>org.apache.httpcomponents</groupId>
+      <artifactId>httpclient-osgi</artifactId>
+      <version>${httpcomponents.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.httpcomponents</groupId>
+      <artifactId>httpcore-osgi</artifactId>
+      <version>${httpcomponents.version}</version>
+    </dependency>
+
+    <dependency>
       <groupId>com.redhat.thermostat</groupId>
       <artifactId>thermostat-web-common</artifactId>
       <version>${project.version}</version>
--- a/web/client/src/main/java/com/redhat/thermostat/web/client/RESTStorage.java	Mon Oct 22 18:24:19 2012 -0400
+++ b/web/client/src/main/java/com/redhat/thermostat/web/client/RESTStorage.java	Fri Oct 26 13:12:03 2012 +0200
@@ -53,6 +53,17 @@
 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.redhat.thermostat.common.model.Pojo;
 import com.redhat.thermostat.common.storage.Category;
@@ -225,9 +236,19 @@
     }
 
     @Override
-    public InputStream loadFile(String arg0) {
-        // TODO Auto-generated method stub
-        return null;
+    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
@@ -291,9 +312,22 @@
     }
 
     @Override
-    public void saveFile(String arg0, InputStream arg1) {
-        // TODO Auto-generated method stub
-
+    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
--- a/web/client/src/test/java/com/redhat/thermostat/web/client/RESTStorageTest.java	Mon Oct 22 18:24:19 2012 -0400
+++ b/web/client/src/test/java/com/redhat/thermostat/web/client/RESTStorageTest.java	Fri Oct 26 13:12:03 2012 +0200
@@ -42,15 +42,21 @@
 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;
@@ -94,6 +100,7 @@
     private String requestBody;
 
     private String responseBody;
+    private Map<String,String> headers;
 
     private static Category category;
     private static Key<String> key1;
@@ -117,6 +124,7 @@
 
     @Before
     public void setUp() throws Exception {
+        
         port = FreePortFinder.findFreePort(new TryPort() {
             @Override
             public void tryPort(int port) throws Exception {
@@ -127,6 +135,7 @@
         storage = new RESTStorage();
         storage.setEndpoint("http://localhost:" + port + "/");
         storage.setAgentId(new UUID(123, 456));
+        headers = new HashMap<>();
         registerCategory();
     }
 
@@ -138,6 +147,12 @@
             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));
+                }
+
                 // Read request body.
                 StringBuilder body = new StringBuilder();
                 Reader reader = request.getReader();
@@ -163,6 +178,7 @@
     @After
     public void tearDown() throws Exception {
 
+        headers = null;
         storage = null;
 
         server.stop();
@@ -389,4 +405,42 @@
         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));
+
+    }
 }
--- a/web/server/pom.xml	Mon Oct 22 18:24:19 2012 -0400
+++ b/web/server/pom.xml	Fri Oct 26 13:12:03 2012 +0200
@@ -103,6 +103,17 @@
     </dependency>
 
     <dependency>
+      <groupId>commons-fileupload</groupId>
+      <artifactId>commons-fileupload</artifactId>
+      <version>${fileupload.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>commons-io</groupId>
+      <artifactId>commons-io</artifactId>
+      <version>${commons-io.version}</version>
+    </dependency>
+
+    <dependency>
       <groupId>org.osgi</groupId>
       <artifactId>org.osgi.core</artifactId>
       <scope>provided</scope>
--- a/web/server/src/main/java/com/redhat/thermostat/web/server/RESTStorageEndPoint.java	Mon Oct 22 18:24:19 2012 -0400
+++ b/web/server/src/main/java/com/redhat/thermostat/web/server/RESTStorageEndPoint.java	Fri Oct 26 13:12:03 2012 +0200
@@ -1,6 +1,8 @@
 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;
@@ -8,10 +10,17 @@
 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.JsonArray;
 import com.google.gson.JsonParser;
@@ -49,7 +58,7 @@
     }
 
     @Override
-    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
         if (storage == null) {
             storage = StorageWrapper.getStorage();
         }
@@ -70,9 +79,51 @@
             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);
         }
     }
 
+    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");
--- a/web/server/src/test/java/com/redhat/thermostat/web/server/RESTStorageEndpointTest.java	Mon Oct 22 18:24:19 2012 -0400
+++ b/web/server/src/test/java/com/redhat/thermostat/web/server/RESTStorageEndpointTest.java	Fri Oct 26 13:12:03 2012 +0200
@@ -37,12 +37,15 @@
 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;
@@ -62,6 +65,7 @@
 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;
@@ -347,6 +351,70 @@
         
     }
 
+    @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");
+    }
 
     private void registerCategory() {
         try {