0

I have an API which returns APPLICATION_OCTET_STREAM as Media Type in response. I need to enhance it to also send a JSON body with some details regarding the file, say counts of right and wrong records in the file. So basically I need two kinds of response in same API. Is this doable ?

HariJustForFun
  • 521
  • 2
  • 8
  • 17
  • Do you mean you want your API method to return different content in different circumstances? If so your clients could set a different `Accept` header depending on whether they want the file or the JSON metadata. If you want the same request to return both JSON _and_ an Excel file in the same response, I think you'll struggle. – bunnmatt May 05 '18 at 12:34
  • If the JSON is not very large, You can use a custom header to return the JSON data, and return the file in the body of the request. – zakaria amine May 05 '18 at 13:26

1 Answers1

4

It's possible, but you will need to use a Multipart response. Keep in mind though that some clients will not be able to handle this type of response. You'll normally see this data type using in uploading files, but is not very often used as a response data type.

That being said, below is a complete example using the Jersey Test Framework. In the resource, a file and some extra data are being sent in the response, with the use of Jersey's FormDataMultiPart

@Path("test")
public static class TestResource {
    @GET
    @Produces(MediaType.MULTIPART_FORM_DATA)
    public Response get() throws Exception {
        final MultiPart multiPart = new FormDataMultiPart()
                .field("json-data", new Model("Test Value"), MediaType.APPLICATION_JSON_TYPE)
                .bodyPart(new FileDataBodyPart("file-data", new File("test.txt")));
        return Response.ok(multiPart).build();
    }
}

To make the test succeed, you should have a file called test.txt with the content "Some Test Data in File" (without quotes) on the first line of that file. This multipart response has two parts, the json-data part, which uses a Model class to model the JSON, and the file-data part which has the contents of the file.

To make the Multipart work, we need to have the MultiPartFeature register on the server and the client (for client side deserialization) and we need to have the multipart dependency in our project.

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

On the client, to get the multipart out of the response, we should read the entity as FormDataMultiPart, then we can get individual parts by name and extract them by their data type.

Response res = target("test").request().get();
FormDataMultiPart multiPart = res.readEntity(FormDataMultiPart.class);
FormDataBodyPart jsonPart = multiPart.getField("json-data");
FormDataBodyPart filePart = multiPart.getField("file-data");

Model jsonData = jsonPart.getValueAs(Model.class);
InputStream file = filePart.getValueAs(InputStream.class);

Below is the complete test.

import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.media.multipart.FormDataBodyPart;
import org.glassfish.jersey.media.multipart.FormDataMultiPart;
import org.glassfish.jersey.media.multipart.MultiPart;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
import org.glassfish.jersey.media.multipart.file.FileDataBodyPart;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;

import static org.assertj.core.api.Assertions.assertThat;

public class MultipartResponseTest extends JerseyTest {

    public static class Model {
        private String value;
        public Model() {}
        public Model(String value) {
            this.value = value;
        }
        public String getValue() {
            return this.value;
        }
        public void setValue(String value) {
            this.value = value;
        }
    }

    @Path("test")
    public static class TestResource {
        @GET
        @Produces(MediaType.MULTIPART_FORM_DATA)
        public Response get() throws Exception {
            final MultiPart multiPart = new FormDataMultiPart()
                    .field("json-data", new Model("Test Value"), MediaType.APPLICATION_JSON_TYPE)
                    .bodyPart(new FileDataBodyPart("file-data", new File("test.txt")));
            return Response.ok(multiPart).build();
        }
    }

    @Override
    public ResourceConfig configure() {
        return new ResourceConfig()
                .register(TestResource.class)
                .register(MultiPartFeature.class);
    }

    @Override
    public void configureClient(ClientConfig config) {
        config.register(MultiPartFeature.class);
    }

    @Test
    public void testIt() throws Exception {
        final Response res = target("test")
                .request().get();
        FormDataMultiPart multiPart = res.readEntity(FormDataMultiPart.class);
        FormDataBodyPart jsonPart = multiPart.getField("json-data");
        FormDataBodyPart filePart = multiPart.getField("file-data");

        Model jsonData = jsonPart.getValueAs(Model.class);
        InputStream file = filePart.getValueAs(InputStream.class);

        BufferedReader fileReader = new BufferedReader(new InputStreamReader(file));
        String fileData = fileReader.readLine();

        file.close();
        fileReader.close();

        System.out.println(jsonData.getValue());
        System.out.println(fileData);

        assertThat(jsonData.getValue()).isEqualTo("Test Value");
        assertThat(fileData).isEqualTo("Some Test Data in File");
    }
}

To use the test framework, you should add the following dependency

<dependency>
    <groupId>org.glassfish.jersey.test-framework.providers</groupId>
    <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
    <version>${jersey2.version}</version>
</dependency>
Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
  • Thanks for the detailed response. Appreciate it. I was also thinking along the lines of converting to file to base 64 encoded data and return it to the UI in an object. along with the JSON. – HariJustForFun May 10 '18 at 08:46