Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

File upload along with other object in Jersey restful web service

I want to create an employee information in the system by uploading an image along with employee data. I am able to do it with different rest calls using jersey. But I want to achieve in one rest call. I provide below the structure. Please help me how to do in this regard.

@POST @Path("/upload2") @Consumes({MediaType.MULTIPART_FORM_DATA,MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) public Response uploadFileWithData(         @FormDataParam("file") InputStream fileInputStream,         @FormDataParam("file") FormDataContentDisposition contentDispositionHeader,         Employee emp) {  //..... business login  } 

Whenever I am trying to do, I get error in Chrome postman. The simple structure of my Employee json is given below.

{     "Name": "John",     "Age": 23,     "Email": "[email protected]",     "Adrs": {         "DoorNo": "12-A",         "Street": "Street-11",         "City": "Bangalore",         "Country": "Karnataka"     } } 

However I can do it by making two different call, but I want to achieve in one rest call so that I can receive the file as well as the actual data of the employee.

Request you to help in this regard.

like image 594
Sambit Avatar asked Dec 22 '14 19:12

Sambit


People also ask

Can we upload file using REST API?

You can use this parameter to set metadata values to a collection already assigned to any parent folder. The rules are the same as those applied to the set metadata values REST API. Use Content-Type: application/json to describe this information as a JSON object. File to upload.

What are the features of Jersey RESTful web services?

Jersey RESTful Web Services framework is open source, production quality, framework for developing RESTful Web Services in Java that provides support for JAX-RS APIs and serves as a JAX-RS (JSR 311 & JSR 339) Reference Implementation. Jersey framework is more than the JAX-RS Reference Implementation.

What is Jersey MultiPart?

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.


2 Answers

You can't have two Content-Types (well technically that's what we're doing below, but they are separated with each part of the multipart, but the main type is multipart). That's basically what you are expecting with your method. You are expecting mutlipart and json together as the main media type. The Employee data needs to be part of the multipart. So you can add a @FormDataParam("emp") for the Employee.

@FormDataParam("emp") Employee emp) { ... 

Here's the class I used for testing

@Path("/multipart") public class MultipartResource {          @POST     @Path("/upload2")     @Consumes({MediaType.MULTIPART_FORM_DATA})     public Response uploadFileWithData(             @FormDataParam("file") InputStream fileInputStream,             @FormDataParam("file") FormDataContentDisposition cdh,             @FormDataParam("emp") Employee emp) throws Exception{                  Image img = ImageIO.read(fileInputStream);         JOptionPane.showMessageDialog(null, new JLabel(new ImageIcon(img)));         System.out.println(cdh.getName());         System.out.println(emp);                  return Response.ok("Cool Tools!").build();     }  } 

First I just tested with the client API to make sure it works

@Test public void testGetIt() throws Exception {          final Client client = ClientBuilder.newBuilder()         .register(MultiPartFeature.class)         .build();     WebTarget t = client.target(Main.BASE_URI).path("multipart").path("upload2");      FileDataBodyPart filePart = new FileDataBodyPart("file",                                               new File("stackoverflow.png"));     // UPDATE: just tested again, and the below code is not needed.     // It's redundant. Using the FileDataBodyPart already sets the     // Content-Disposition information     filePart.setContentDisposition(             FormDataContentDisposition.name("file")                                     .fileName("stackoverflow.png").build());      String empPartJson             = "{"             + "  \"id\": 1234,"             + "  \"name\": \"Peeskillet\""             + "}";      MultiPart multipartEntity = new FormDataMultiPart()             .field("emp", empPartJson, MediaType.APPLICATION_JSON_TYPE)             .bodyPart(filePart);                Response response = t.request().post(             Entity.entity(multipartEntity, multipartEntity.getMediaType()));     System.out.println(response.getStatus());     System.out.println(response.readEntity(String.class));      response.close(); } 

I just created a simple Employee class with an id and name field for testing. This works perfectly fine. It shows the image, prints the content disposition, and prints the Employee object.

I'm not too familiar with Postman, so I saved that testing for last :-)

enter image description here

It appears to work fine also, as you can see the response "Cool Tools". But if we look at the printed Employee data, we'll see that it's null. Which is weird because with the client API it worked fine.

If we look at the Preview window, we'll see the problem

enter image description here

There's no Content-Type header for the emp body part. You can see in the client API I explicitly set it

MultiPart multipartEntity = new FormDataMultiPart()         .field("emp", empPartJson, MediaType.APPLICATION_JSON_TYPE)         .bodyPart(filePart); 

So I guess this is really only part of a full answer. Like I said, I am not familiar with Postman So I don't know how to set Content-Types for individual body parts. The image/png for the image was automatically set for me for the image part (I guess it was just determined by the file extension). If you can figure this out, then the problem should be solved. Please, if you find out how to do this, post it as an answer.

See UPDATE below for solution


And just for completeness...

  • See here for more about MultiPart with Jersey.

Basic configurations:

Dependency:

<dependency>     <groupId>org.glassfish.jersey.media</groupId>     <artifactId>jersey-media-multipart</artifactId>     <version>${jersey2.version}</version> </dependency> 

Client config:

final Client client = ClientBuilder.newBuilder()     .register(MultiPartFeature.class)     .build(); 

Server config:

// Create JAX-RS application. final Application application = new ResourceConfig()     .packages("org.glassfish.jersey.examples.multipart")     .register(MultiPartFeature.class); 

If you're having problems with the server configuration, one of the following posts might help

  • What exactly is the ResourceConfig class in Jersey 2?
  • 152 MULTIPART_FORM_DATA: No injection source found for a parameter of type public javax.ws.rs.core.Response

UPDATE

So as you can see from the Postman client, some clients are unable to set individual parts' Content-Type, this includes the browser, in regards to it's default capabilities when using FormData (js).

We can't expect the client to find away around this, so what we can do, is when receiving the data, explicitly set the Content-Type before deserializing. For example

@POST @Path("upload2") @Consumes(MediaType.MULTIPART_FORM_DATA) public Response uploadFileAndJSON(@FormDataParam("emp") FormDataBodyPart jsonPart,                                   @FormDataParam("file") FormDataBodyPart bodyPart) {       jsonPart.setMediaType(MediaType.APPLICATION_JSON_TYPE);      Employee emp = jsonPart.getValueAs(Employee.class); } 

It's a little extra work to get the POJO, but it is a better solution than forcing the client to try and find it's own solution.

Another option is to use a String parameter and use whatever JSON library you use to deserialze the String to the POJO (like Jackson ObjectMapper). With the previous option, we just let Jersey handle the deserialization, and it will use the same JSON library it uses for all the other JSON endpoints (which might be preferred).


Asides

  • There is a conversation in these comments that you may be interested in if you are using a different Connector than the default HttpUrlConnection.
like image 98
Paul Samsotha Avatar answered Sep 28 '22 03:09

Paul Samsotha


You can access the Image File and data from a form using MULTIPART FORM DATA By using the below code.

@POST @Path("/UpdateProfile") @Consumes(value={MediaType.APPLICATION_JSON,MediaType.MULTIPART_FORM_DATA}) @Produces(value={MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML}) public Response updateProfile(     @FormDataParam("file") InputStream fileInputStream,     @FormDataParam("file") FormDataContentDisposition contentDispositionHeader,     @FormDataParam("ProfileInfo") String ProfileInfo,     @FormDataParam("registrationId") String registrationId) {      String filePath= "/filepath/"+contentDispositionHeader.getFileName();      OutputStream outputStream = null;     try {         int read = 0;         byte[] bytes = new byte[1024];         outputStream = new FileOutputStream(new File(filePath));          while ((read = fileInputStream.read(bytes)) != -1) {             outputStream.write(bytes, 0, read);         }          outputStream.flush();         outputStream.close();     } catch (FileNotFoundException e) {         e.printStackTrace();     } catch (IOException e) {         e.printStackTrace();     } finally {         if (outputStream != null) {              try {                 outputStream.close();             } catch(Exception ex) {}         }     } } 
like image 37
zameer Avatar answered Sep 28 '22 01:09

zameer