Mercurial > hg > release > thermostat-0.7
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 {