I have successfully developed a service, in which I read files uploaded in a multipart form in Jersey. Here's an extremely simplified version of what I've been doing:
@POST
@Path("FileCollection")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadFile(@FormDataParam("file") InputStream uploadedInputStream,
@FormDataParam("file") FormDataContentDisposition fileDetail) throws IOException {
//handle the file
}
This works just fine but I've been given a new requirement. In addition to the file I'm uploading, I have to handle an arbitrary number of resources. Let's assume these are image files.
I figured I'd just provide the client with a form with one input for the file, one input for the first image and a button to allow adding more inputs to the form (using AJAX or simply plain JavaScript).
<form action="blahblahblah" method="post" enctype="multipart/form-data">
<input type="file" name="file" />
<input type="file" name="image" />
<input type="button" value="add another image" />
<input type="submit" />
</form>
So the user can append the form with more inputs for images, like this:
<form action="blahblahblah" method="post" enctype="multipart/form-data">
<input type="file" name="file" />
<input type="file" name="image" />
<input type="file" name="image" />
<input type="file" name="image" />
<input type="button" value="add another image" />
<input type="submit" />
</form>
I hoped it would be simple enough to read the fields with the same name as a collection. I've done it successfully with text inputs in MVC .NET and I thought it wouldn't be harder in Jersey. It turns out I was wrong.
Having found no tutorials on the subject, I started experimenting.
In order to see how to do it, I dumbed the problem down to simple text inputs.
<form action="blahblabhblah" method="post" enctype="multipart/form-data">
<fieldset>
<legend>Multiple inputs with the same name</legend>
<input type="text" name="test" />
<input type="text" name="test" />
<input type="text" name="test" />
<input type="text" name="test" />
<input type="submit" value="Upload It" />
</fieldset>
</form>
Obviously, I needed to have some sort of collection as a parameter to my method. Here's what I tried, grouped by collection type.
Array
At first, I checked whether Jersey was smart enough to handle a simple array:
@POST
@Path("FileCollection")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadFile(@FormDataParam("test") String[] inputs) {
//handle the request
}
but the array wasn't injected as expected.
MultiValuedMap
Having failed miserably, I remembered that MultiValuedMap
objects could be handled out of the box.
@POST
@Path("FileCollection")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadFile(MultiValuedMap<String, String> formData) {
//handle the request
}
but it doesn't work either. This time, I got an exception
SEVERE: A message body reader for Java class javax.ws.rs.core.MultivaluedMap,
and Java type javax.ws.rs.core.MultivaluedMap<java.lang.String, java.lang.String>,
and MIME media type multipart/form-data;
boundary=----WebKitFormBoundaryxgxeXiWk62fcLALU was not found.
I was told that this exception could be gotten rid of by including the mimepull
library so I added the following dependency to my pom:
<dependency>
<groupId>org.jvnet</groupId>
<artifactId>mimepull</artifactId>
<version>1.3</version>
</dependency>
Unfortunately the problem persists. It's probably a matter of choosing the right body reader and using different parameters for the generic. I'm not sure how to do this. I want to consume both file and text inputs, as well as some others (mostly Long
values and custom parameter classes).
FormDataMultipart
After some more research, I found the FormDataMultiPart class. I've successfully used it to extract the string values from my form
@POST
@Path("upload2")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadMultipart(FormDataMultiPart multiPart){
List<FormDataBodyPart> fields = multiPart.getFields("test");
System.out.println("Name\tValue");
for(FormDataBodyPart field : fields){
System.out.println(field.getName() + "\t" + field.getValue());
//handle the values
}
//prepare the response
}
The problem is, this is a solution to the simplified version of my problem. While I know that every single parameter injected by Jersey is created by parsing a string at some point (no wonder, it's HTTP after all) and I have some experience writing my own parameter classes, I don't really how to convert these fields to InputStream
or File
instances for further processing.
Therefore, before diving into Jersey source code to see how these objects are created, I decided to ask here whether there is an easier way to read a set (of unknown size) of files. Do you know how to solve this conundrum?