Reputation: 13892
I am able to upload a file in jersey in the following way:
@POST
@Consumes( MediaType.MULTIPART_FORM_DATA)
public void indexFile(
@FormDataParam("file") InputStream uploadedFile,
@FormDataParam("file") FormDataContentDisposition fileInfo){
System.out.println(fileInfo.getFileName());
DAO.indexFileToSolr(uploadedFile);
}
this gives me the file as a input stream. This file contains an array of json objects in the following format:
[
{
id: 1,
title: "someTitle"
}
]
I also have a POJO/ Java class representing this object like this:
@XmlRootElement
public class MyObj{
private int id;
private String title;
//getters and setters
}
I am using jersey-media-moxy
to automatically convert between POJO and JSON/XML at the time of incoming request or outgoing response.
(I don't need to write any code for this, i just need to specify that the request will be in JSON format, or the response should be JSON, and the serialization/deserialization is handled by jersey-media-moxy
.)
The issue is , i am not able to specify this behavior in the file upload method, i see no way of telling jersey or jersey-media-moxy
to serialize the file to java objects.
I could not find any methods in jersey-media-moxy
to actually read a InputStream and serialize it to a POJO.
NOTE: i can probably use jackson
to read the json file and create a list of Java objects, but i am not very inclined to do so, firstly because that requires another library (it would be great if i can do this with jersey-media-mosy) and secondly, i am not sure whether there could be a easy way to just tell jersey that this file contains Json objects so, de-serialize it.
Upvotes: 1
Views: 1980
Reputation: 209004
If you can get the client to set the Content-Type
header for the individual part that is JSON, then handling this is trivial. With multipart, each part can have its own Content-Type
. For example part of a raw multipart request might look something like
--Boundary_1_1938025186_1463410894758
Content-Type: application/json
Content-Disposition: form-data; name="beans"
[ {"name": "peeskillet"} ]
--Boundary_1_1938025186_1463410894758--
You can see for the this particular part, the Content-Type
is set to application/json
. If you can get this from the client, then all you need to do is have a POJO parameter like you would a normal JSON request
@POST
@Path("with-content-type")
public List<Bean> post1(@FormDataParam("beans") List<Bean> beans) {
return beans;
}
If you were to try and use the above, and the client didn't set the Content-Type
, then it would default to text/plain
and the results are just weird. They are not as expected. From what I tested, it seems the bean property is set the entire raw JSON.
What we can do though is get a pre-deserialized body part, set the Content-Type
explicitly, then deserialize it.
@POST
@Path("no-content-type")
public Bean[] post2(@FormDataParam("beans") FormDataBodyPart bodyPart) throws IOException {
bodyPart.setMediaType(MediaType.APPLICATION_JSON_TYPE);
Bean[] beans = bodyPart.getEntityAs(Bean[].class);
return beans;
}
Notice instead of a List<Bean>
I use a Bean[]
. The problem with trying to get a List<Bean>
is that we can't do bodyPart.getEntityAs(List.class)
, as MOXy needs to know the generic type. So we just get it as an array. A Bean[]
would also work as a method parameter, instead of a List<Bean>
, as in the first example, if the client were to set the Content-Type
.
Below is a complete test case, using Jersey Test Framework.
import java.io.IOException;
import java.util.List;
import java.util.logging.Logger;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.xml.bind.annotation.XmlRootElement;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.filter.LoggingFilter;
import org.glassfish.jersey.media.multipart.FormDataBodyPart;
import org.glassfish.jersey.media.multipart.FormDataMultiPart;
import org.glassfish.jersey.media.multipart.FormDataParam;
import org.glassfish.jersey.media.multipart.MultiPart;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
import org.glassfish.jersey.moxy.json.MoxyJsonFeature;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.core.IsNull.notNullValue;
import static org.junit.Assert.assertThat;
/**
* Run this like any other JUnit test. Only two required dependencies.
*
* <dependency>
* <groupId>org.glassfish.jersey.test-framework.providers</groupId>
* <artifactId>jersey-test-framework-provider-inmemory</artifactId>
* <version>2.22.1</version>
* <scope>test</scope>
* </dependency>
* <dependency>
* <groupId>org.glassfish.jersey.media</groupId>
* <artifactId>jersey-media-moxy</artifactId>
* <version>2.22.1</version>
* <scope>test</scope>
* </dependency>
*
* @author Paul Samsotha
*/
public class MoxyMultipartTest extends JerseyTest {
@XmlRootElement
public static class Bean {
private String name;
public Bean() {
}
public Bean(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return this.name;
}
}
@Path("test")
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces(MediaType.APPLICATION_JSON)
public static class TestResource {
@POST
@Path("with-content-type")
public Bean[] post1(@FormDataParam("beans") Bean[] beans) {
return beans;
}
@POST
@Path("no-content-type")
public Bean[] post2(@FormDataParam("beans") FormDataBodyPart bodyPart) throws IOException {
bodyPart.setMediaType(MediaType.APPLICATION_JSON_TYPE);
Bean[] beans = bodyPart.getEntityAs(Bean[].class);
return beans;
}
}
@Override
public void configureClient(ClientConfig config) {
config.register(MultiPartFeature.class);
config.register(new LoggingFilter(Logger.getAnonymousLogger(), true));
}
@Override
public ResourceConfig configure() {
return new ResourceConfig(TestResource.class)
.register(MultiPartFeature.class)
.register(MoxyJsonFeature.class);
}
final String json = "[ {\"name\": \"peeskillet\"} ]";
@Test
public void testSettingContentType() {
final MultiPart multiPart = new FormDataMultiPart()
.field("beans", json, MediaType.APPLICATION_JSON_TYPE);
final Response response = target("test/with-content-type")
.request().post(Entity.entity(multiPart, MediaType.MULTIPART_FORM_DATA));
assertResponseHasBeans(response);
}
@Test
public void testWithoutSettingContentType() {
final MultiPart multiPart = new FormDataMultiPart()
.field("beans", json); // No Content-Type
final Response response = target("test/no-content-type")
.request().post(Entity.entity(multiPart, MediaType.MULTIPART_FORM_DATA));
assertResponseHasBeans(response);
}
private void assertResponseHasBeans(Response response) {
final List<Bean> beans = response.readEntity(new GenericType<List<Bean>>() {
});
assertThat(beans, is(notNullValue()));
assertThat(beans.isEmpty(), is(false));
assertThat(beans.get(0).getName(), is("peeskillet"));
}
}
Upvotes: 5