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.
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.
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).
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?
The FormDataBodyPart class provides a method that allows its user to read the value as InputStream (or theoretically, any other class, for which a message body reader is present).
Multipart form data: The ENCTYPE attribute of <form> tag specifies the method of encoding for the form data. It is one of the two ways of encoding the HTML form. It is specifically used when file uploading is required in HTML form. It sends the form data to server in multiple parts because of large size of file.
A mutable model representing a MIME MultiPart entity. This class extends BodyPart because MultiPart entities can be nested inside other MultiPart entities to an arbitrary depth.
I have found the solution by following the example with FormDataMultipart
. It turns out I was very close to the answer.
The FormDataBodyPart
class provides a method that allows its user to read the value as InputStream
(or theoretically, any other class, for which a message body reader is present).
Here's the final solution:
The form remains unchanged. I have a couple of fields with the same name, in which I can place files. It's possible to use both multiple
form inputs (you want these when uploading many files from a directory) and numerous inputs that share a name (Flexible way to upload an unspecified number of files from different location). It's also possible to append the form with more inputs using JavaScript.
<form action="/files" method="post" enctype="multipart/form-data"> <fieldset> <legend>Multiple inputs with the same name</legend> <input type="file" name="test" multiple="multiple"/> <input type="file" name="test" /> <input type="file" name="test" /> </fieldset> <input type="submit" value="Upload It" /> </form>
FormDataMultipart
Here's a simplified method that reads a collection of files from a multipart form. All inputs with the same are assigned to a List
and their values are converted to InputStream
using the getValueAs
method of FormDataBodyPart
. Once you have these files as InputStream
instances, it's easy to do almost anything with them.
@POST @Path("files") @Consumes(MediaType.MULTIPART_FORM_DATA) public Response uploadMultipart(FormDataMultiPart multiPart) throws IOException{ List<FormDataBodyPart> fields = multiPart.getFields("test"); for(FormDataBodyPart field : fields){ handleInputStream(field.getValueAs(InputStream.class)); } //prepare the response } private void handleInputStream(InputStream is){ //read the stream any way you want }
@Path("/upload/multiples")
@POST
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces(MediaType.APPLICATION_JSON)
public Response uploadImage(@FormDataParam("image") List<FormDataBodyPart> imageDatas){
for( FormDataBodyPart imageData : imageDatas ){
// Your actual code.
imageData.getValueAs(InputStream.class);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With