/******************************************************************************* * Copyright (c) 2012-2016 Codenvy, S.A. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.everrest.core.impl.integration; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterators; import org.apache.commons.fileupload.FileItem; import org.everrest.core.impl.BaseTest; import org.everrest.core.impl.ContainerResponse; import org.everrest.core.impl.EnvironmentContext; import org.everrest.core.impl.MultivaluedMapImpl; import org.everrest.core.impl.provider.multipart.InputItem; import org.everrest.core.impl.provider.multipart.OutputItem; import org.everrest.core.tools.ByteArrayContainerResponseWriter; import org.junit.Test; import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.Application; import javax.ws.rs.core.GenericEntity; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static com.google.common.collect.Lists.newArrayList; import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE; import static javax.ws.rs.core.MediaType.TEXT_XML_TYPE; import static org.everrest.core.impl.provider.multipart.OutputItem.anOutputItem; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class MultipartTest extends BaseTest { @Path("/1") public static class Resource1 { private Iterator<FileItem> expected; public Resource1(Iterator<FileItem> expected) { this.expected = expected; } @POST @Consumes("multipart/*") public void m(Iterator<FileItem> fileItemIterator) throws Exception { while (fileItemIterator.hasNext()) { if (!expected.hasNext()) { fail("Wrong number of parsed items"); } FileItem inputFileItem = fileItemIterator.next(); FileItem expectedFileItem = expected.next(); assertEquals(expectedFileItem.getContentType(), inputFileItem.getContentType()); assertEquals(expectedFileItem.isFormField(), inputFileItem.isFormField()); assertEquals(expectedFileItem.getFieldName(), inputFileItem.getFieldName()); assertEquals(expectedFileItem.getName(), inputFileItem.getName()); assertEquals(expectedFileItem.getString(), inputFileItem.getString()); } if (expected.hasNext()) { fail("Wrong number of parsed items"); } } } @Path("/2") public static class Resource2 { private List<InputItem> expected; public Resource2(List<InputItem> expected) { this.expected = expected; } @POST @Consumes("multipart/*") public void m(List<InputItem> items) throws Exception { assertEquals(expected.size(), items.size()); for (int i = 0; i < items.size(); i++) { InputItem item = items.get(i); InputItem expectedItem = expected.get(i); assertEquals(expectedItem.getName(), item.getName()); assertEquals(expectedItem.getFilename(), item.getFilename()); assertEquals(expectedItem.getMediaType(), item.getMediaType()); assertEquals(expectedItem.getBodyAsString(), item.getBodyAsString()); } } } @Path("/3") public static class Resource3 { private Map<String, InputItem> expected; public Resource3(Map<String, InputItem> expected) { this.expected = expected; } @POST @Consumes("multipart/*") public void m(Map<String, InputItem> map) throws Exception { assertEquals(expected.size(), map.size()); for (InputItem inputItem : map.values()) { InputItem expectedInputItem = expected.get(inputItem.getName()); assertNotNull(expectedInputItem); assertEquals(expectedInputItem.getName(), inputItem.getName()); assertEquals(expectedInputItem.getFilename(), inputItem.getFilename()); assertEquals(expectedInputItem.getMediaType(), inputItem.getMediaType()); assertEquals(expectedInputItem.getBodyAsString(), inputItem.getBodyAsString()); } } } @Path("/4") public static class Resource4 { @GET @Produces("multipart/form-data;boundary=" + BOUNDARY) public GenericEntity<List<OutputItem>> m1() throws Exception { List<OutputItem> list = new ArrayList<>(3); list.add(anOutputItem().withName("xml-file").withEntity(XML_DATA).withMediaType(TEXT_XML_TYPE).withFilename("foo.xml").build()); list.add(anOutputItem().withName("json-file").withEntity(new JsonData("hello world")).withMediaType(APPLICATION_JSON_TYPE).withFilename("foo.json").build()); list.add(anOutputItem().withName("field").withEntity(TEXT_DATA).build()); return new GenericEntity<List<OutputItem>>(list) { }; } } public static class JsonData { private String data; public JsonData(String data) { this.data = data; } public String getData() { return data; } public void setData(String data) { this.data = data; } } private static final String XML_DATA = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><root><data>hello world</data></root>"; private static final String JSON_DATA = "{\"data\":\"hello world\"}"; private static final String TEXT_DATA = "to be or not to be"; private static final String BOUNDARY = "abcdef"; private static final String FORM_DATA = "--" + BOUNDARY + "\r\n" + "Content-Disposition: form-data; name=\"xml-file\"; filename=\"foo.xml\"\r\n" + "Content-Type: text/xml\r\n" + "\r\n" + XML_DATA + "\r\n" + "--" + BOUNDARY + "\r\n" + "Content-Disposition: form-data; name=\"json-file\"; filename=\"foo.json\"\r\n" + "Content-Type: application/json\r\n" + "\r\n" + JSON_DATA + "\r\n" + "--" + BOUNDARY + "\r\n" + "Content-Disposition: form-data; name=\"field\"\r\n" + "\r\n" + TEXT_DATA + "\r\n" + "--" + BOUNDARY + "--\r\n"; @Test public void testInputMultipartFormApache() throws Exception { Iterator<FileItem> expected = Iterators.forArray(createFileItem("text/xml", false, "xml-file", "foo.xml", XML_DATA), createFileItem("application/json", false, "json-file", "foo.json", JSON_DATA), createFileItem(null, true, "field", null, TEXT_DATA)); processor.addApplication(new Application() { @Override public Set<Object> getSingletons() { return Collections.<Object>singleton(new Resource1(expected)); } }); doPost("/1"); } @Test public void testInputMultipartFormList() throws Exception { List<InputItem> expected = newArrayList(createInputItem("xml-file", "foo.xml", TEXT_XML_TYPE, XML_DATA), createInputItem("json-file", "foo.json", APPLICATION_JSON_TYPE, JSON_DATA), createInputItem("field", null, null, TEXT_DATA)); processor.addApplication(new Application() { @Override public Set<Object> getSingletons() { return Collections.<Object>singleton(new Resource2(expected)); } }); doPost("/2"); } @Test public void testInputMultipartFormMap() throws Exception { Map<String, InputItem> expected = ImmutableMap.of("xml-file", createInputItem("xml-file", "foo.xml", TEXT_XML_TYPE, XML_DATA), "json-file", createInputItem("json-file", "foo.json", APPLICATION_JSON_TYPE, JSON_DATA), "field", createInputItem("field", null, null, TEXT_DATA)); processor.addApplication(new Application() { @Override public Set<Object> getSingletons() { return Collections.<Object>singleton(new Resource3(expected)); } }); doPost("/3"); } @Test public void testOutputMultipartFormList() throws Exception { processor.addApplication(new Application() { @Override public Set<Object> getSingletons() { return Collections.<Object>singleton(new Resource4()); } }); ByteArrayContainerResponseWriter writer = new ByteArrayContainerResponseWriter(); ContainerResponse response = launcher.service("GET", "/4", "", null, null, writer, null); assertEquals(200, response.getStatus()); assertEquals(FORM_DATA, new String(writer.getBody())); } private static InputItem createInputItem(String name, String fileName, MediaType mediaType, String body) throws IOException { InputItem inputItem = mock(InputItem.class); when(inputItem.getName()).thenReturn(name); when(inputItem.getFilename()).thenReturn(fileName); when(inputItem.getMediaType()).thenReturn(mediaType); when(inputItem.getBodyAsString()).thenReturn(body); return inputItem; } private static FileItem createFileItem(String contentType, boolean isFormField, String fieldName, String name, String body) { FileItem fileItem = mock(FileItem.class); when(fileItem.getContentType()).thenReturn(contentType); when(fileItem.isFormField()).thenReturn(isFormField); when(fileItem.getFieldName()).thenReturn(fieldName); when(fileItem.getName()).thenReturn(name); when(fileItem.getString()).thenReturn(body); return fileItem; } private void doPost(String path) throws Exception { ByteArrayOutputStream out = new ByteArrayOutputStream(); PrintWriter w = new PrintWriter(out); w.write(FORM_DATA); w.flush(); String contentType = "multipart/form-data;boundary=" + BOUNDARY; byte[] content = out.toByteArray(); EnvironmentContext env = new EnvironmentContext(); env.put(HttpServletRequest.class, mockHttpServletRequest(content, contentType)); MultivaluedMap<String, String> headers = new MultivaluedMapImpl(); headers.putSingle("content-type", contentType); ContainerResponse response = launcher.service("POST", path, "", headers, content, env); assertEquals(204, response.getStatus()); } private HttpServletRequest mockHttpServletRequest(byte[] content, String contentType) throws Exception { ByteArrayInputStream contentAsStream = new ByteArrayInputStream(content); ServletInputStream servletInputStream = createServletInputStream(contentAsStream); HttpServletRequest httpServletRequest = mock(HttpServletRequest.class); when(httpServletRequest.getInputStream()).thenReturn(servletInputStream); when(httpServletRequest.getContentType()).thenReturn(contentType); return httpServletRequest; } private ServletInputStream createServletInputStream(ByteArrayInputStream in) { return new ServletInputStream() { @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener readListener) { } @Override public int read() throws IOException { return in.read(); } }; } }