Input and Output binary streams using JERSEY?

classic Classic list List threaded Threaded
8 messages Options
Reply | Threaded
Open this post in threaded view
|

Input and Output binary streams using JERSEY?

Tauren Mills
I just posted a Jersey related question on StackOverflow and then
realized I'd be better off sending it here instead. But if you wish to
answer it, the question is here:
http://stackoverflow.com/questions/3496209/input-and-output-binary-streams-using-jersey

I'm using Jersey to implement a RESTful API that is primarily retrieve
and serve JSON encoded data. But I have some situations where I need
to accomplish the following:

* Export downloadable documents, such as PDF, XLS, ZIP, or other binary files.

* Retrieve multipart data, such some JSON plus an uploaded XLS file

I have a single-page JQuery-based web client that creates AJAX calls
to this web service. At the moment, it doesn't do form submits, and
uses GET and POST (with a JSON request object, not a form post).
Should I utilize a form post to send data and an attached binary file,
or can I create a multipart request with JSON plus binary file? How?

I've been looking through the samples included with Jersey, but
haven't found anything yet that illustrates how to do either of these
things. If it matters, I'm using Jersey with Jackson to do
Object->JSON without the XML step and am not really utilizing JAX-RS.

My application's service layer currently creates a
ByteArrayOutputStream when it generates a PDF file. What is the best
way to output this stream to the client via Jersey? I've created a
MessageBodyWriter, but I don't know how to use it from a Jersey
resource. Is that the right approach?

Here's my writer:

        @Provider
        @Produces("application/pdf")
        public class ExportPDFWriter implements
MessageBodyWriter<ByteArrayOutputStream> {
                @Override
                public void writeTo(ByteArrayOutputStream t, Class<?> type, Type genericType,
                                Annotation[] annotations, MediaType mediaType,
                                MultivaluedMap<String, Object> httpHeaders,
                                OutputStream entityStream) throws IOException,
                                WebApplicationException {
                       
                        entityStream.write(t.toByteArray());
                       
                }

                @Override
                public long getSize(ByteArrayOutputStream t, Class<?> type, Type genericType,
                                Annotation[] annotations, MediaType mediaType) {
                        // Is it possible to output a more accurate size?
                        return -1;
                }

                @Override
                public boolean isWriteable(Class<?> type, Type genericType,
                                Annotation[] annotations, MediaType mediaType) {
                        return true;
                }
        }

But how do I use it in a Resource? What would go into my getPDF method?

        @Inject
        private PDFService pdfService;

        @GET
        @Path("pdf/{id}")
        @Produces("application/pdf")
        public Response getPDF(@PathParam("id") String id) {
                ExportPDFWriter writer;
                ByteArrayOutputStream pdf = pdfService.exportPdf(id);
                // how do I use writer to output my pdf?
                return Response.ok(writer).build();
        }

Thanks for the help!
Tauren

---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]

Reply | Threaded
Open this post in threaded view
|

Re: Input and Output binary streams using JERSEY?

Cowtowncoder
On Mon, Aug 16, 2010 at 12:07 PM, Tauren Mills <[hidden email]> wrote:

> I just posted a Jersey related question on StackOverflow and then
> realized I'd be better off sending it here instead. But if you wish to
> answer it, the question is here:
> http://stackoverflow.com/questions/3496209/input-and-output-binary-streams-using-jersey
>
> I'm using Jersey to implement a RESTful API that is primarily retrieve
> and serve JSON encoded data. But I have some situations where I need
> to accomplish the following:
>
> * Export downloadable documents, such as PDF, XLS, ZIP, or other binary files.

I think easiest way would be to just return byte[], or if you don't
want to read and buffer, create StreamingOutput instance that reads
file (etc) and writes content directly to output.

For input, one alternative to multi-part could be using Jackson's
base64 support, assuming client could do base64 encoding. It is
reasonably efficient for processing so should work ok for moderate
file sizes (in megabytes at least).

-+ Tatu +-

---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]

Reply | Threaded
Open this post in threaded view
|

Re: Input and Output binary streams using JERSEY?

Tauren Mills
Tatu,

Thanks. It seemed like MessageBodyWriter complicated things. But I'm
still interested in how it would be done that way, since I haven't
been able to find any examples or information on how to use it.

I'll try just returning byte[], but think I should probably look into
StreamingOutput. The files I'm generating can get large and I'd rather
stream them.

Also, is it possible to have a single URL that can return either JSON
or a binary file? For instance, my webapp requests JSON data that it
uses to build a table structure in the DOM by passing a JSON object
that defines all of the filtering and sorting of the table. But I'd
like to also retrieve that same data in XLS format, filtered and
sorted the same.

Does it make more sense to have two different REST urls for this, or
can one url be used? If so, how would I do this?

Thanks,
Tauren


On Mon, Aug 16, 2010 at 12:11 PM, Tatu Saloranta <[hidden email]> wrote:

> On Mon, Aug 16, 2010 at 12:07 PM, Tauren Mills <[hidden email]> wrote:
>> I just posted a Jersey related question on StackOverflow and then
>> realized I'd be better off sending it here instead. But if you wish to
>> answer it, the question is here:
>> http://stackoverflow.com/questions/3496209/input-and-output-binary-streams-using-jersey
>>
>> I'm using Jersey to implement a RESTful API that is primarily retrieve
>> and serve JSON encoded data. But I have some situations where I need
>> to accomplish the following:
>>
>> * Export downloadable documents, such as PDF, XLS, ZIP, or other binary files.
>
> I think easiest way would be to just return byte[], or if you don't
> want to read and buffer, create StreamingOutput instance that reads
> file (etc) and writes content directly to output.
>
> For input, one alternative to multi-part could be using Jackson's
> base64 support, assuming client could do base64 encoding. It is
> reasonably efficient for processing so should work ok for moderate
> file sizes (in megabytes at least).
>
> -+ Tatu +-
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: [hidden email]
> For additional commands, e-mail: [hidden email]
>
>

---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]

Reply | Threaded
Open this post in threaded view
|

Re: Input and Output binary streams using JERSEY?

Cowtowncoder
On Mon, Aug 16, 2010 at 6:11 PM, Tauren Mills <[hidden email]> wrote:
> Tatu,
>
> Thanks. It seemed like MessageBodyWriter complicated things. But I'm
> still interested in how it would be done that way, since I haven't
> been able to find any examples or information on how to use it.

Yes I understand that it is good to know how things should work, since
there are cases where this would be a good approach (esp. for
framework/libraries that add type support, as opposed to application
code).

> I'll try just returning byte[], but think I should probably look into
> StreamingOutput. The files I'm generating can get large and I'd rather
> stream them.
>
> Also, is it possible to have a single URL that can return either JSON
> or a binary file? For instance, my webapp requests JSON data that it
> uses to build a table structure in the DOM by passing a JSON object
> that defines all of the filtering and sorting of the table. But I'd
> like to also retrieve that same data in XLS format, filtered and
> sorted the same.

Yes, and that's where registering typed handled (MessageBodyWriter)
would make things easier.
Otherwise you will have to manually handle mapping of requested type
into appropriate converter, which can add lots of code for larger
systems.

> Does it make more sense to have two different REST urls for this, or
> can one url be used? If so, how would I do this?

If content is logically the same, just with different representation
(data format), single URL is considered cleaner and preferable to
having multiple end points.
You will just declare multiple output types (list of choices for
@Produces) and Jersey uses appropriate converter from POJO to desired
format. Or if you are doing things more manually, you will need to
access incoming accepts types (get them injected, I forget which
object you needed but it should be easy to find) and match it to
converter to use.

-+ Tatu +-

---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]

Reply | Threaded
Open this post in threaded view
|

Re: Input and Output binary streams using JERSEY?

Tauren Mills
Tatu,

> Yes, and that's where registering typed handled (MessageBodyWriter)
> would make things easier.
> Otherwise you will have to manually handle mapping of requested type
> into appropriate converter, which can add lots of code for larger
> systems.

If I create a MessageBodyWriter<ByteArrayOutputStream> with
@Produces("application/pdf"), then any Resource method that returns
ByteArrayOutputStream with the PDF mimetype will use the writer,
correct? Is this what you mean by "registering a converter"?  It looks
like the entity-provider sample does this.

But what do I do if I don't want to return a ByteArrayOutputStream and
instead want to return a Response? For instance, getJSON() below
returns plain JSON with a Response object. I assume the method
getPDF() should use my custom MessageBodyWriter since it returns a
ByteArrayOutputStream. If I do something like the third method
getTest(), will that still use my custom MBW? And note that I want to
add some custom headers as well.

        @Inject
        private DataService dataService;

        @GET
        @Path("json/{id}")
        @Produces("application/json")
        public Response getJSON(@PathParam("id") String id) {
                // Get JSON representation of data
                ObjectNode data = dataService.getData(id);
                // Return JSON data
                return Response.ok().entity(data).build();
        }

        @GET
        @Path("pdf/{id}")
        @Produces("application/pdf")
        public ByteArrayOutputStream getPDF(@PathParam("id") String id) {
                // Return PDF representation of data
                return dataService.exportPdf(id):
       }

        @GET
        @Path("test/{id}")
        @Produces("application/pdf")
        public Response getTest(@PathParam("id") String id) {
                Date now = new Date();
                String name = "export-"+now.getTime()+".pdf";
                ByteArrayOutputStream pdf = dataService.exportPdf(id);
                return Response.ok().entity(pdf).header("Content-Disposition",
"attachment; filename="+name).build();
       }

So, still a bit confused as you can see... How do I combine getData
and getPDF into a single method? I don't want to return JSON with a
ByteArrayOutputStream. Note that I make different calls to my service
layer to get different types of data, so how do I know which one to
call in a single method? Is there a way to know in advance which
@Produces is going to be used?

Am I on the right track with something like this?

        @GET
        @Path("combined/{id}")
        @Produces("application/json","application/pdf")
        public Response getCombined(@PathParam("id") String id) {

                // how do I test for output type???
                if (testForOutputType.equals("application/pdf") {
                    Date now = new Date();
                    String name = "export-"+now.getTime()+".pdf";
                    ByteArrayOutputStream pdf = dataService.exportPdf(id);
                    return Response.ok().entity(pdf).header("Content-Disposition",
"attachment; filename="+name).build();
                }
                ObjectNode data = dataService.getData(id);
                return Response.ok().entity(data).build();
       }

Thanks again,
Tauren

---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]

Reply | Threaded
Open this post in threaded view
|

Re: Input and Output binary streams using JERSEY?

Paul Sandoz-2
Hi Tauren,

There is currently a lack of feature/bug in JAX-RS/Jersey that it is hard for the application to determine the acceptable media type that has been selected, as the content-type, when one or more media types is declared in @Produces.

So i recommend using two separate methods for the two separate media types then you do not have to perform the equality checking with an if statement.

Jersey will automatically set the content-type so you do not need to set it in the Response, if you are returning an instance. The processing of the entity set in the response will behave in the same manner as if the entity is returned directly. All Response does is enable the returning of additional meta-data.

Jersey has support for multipart/form-data. See the recent link that Mark sent that Arul blogged on:


And the JavaDoc:


Note that Jersey has support for the Content-Disposition header:


   ContentDisposition.type("attachment").fileName("name").build();

Paul.


On Aug 17, 2010, at 8:24 AM, Tauren Mills wrote:

Tatu,

Yes, and that's where registering typed handled (MessageBodyWriter)
would make things easier.
Otherwise you will have to manually handle mapping of requested type
into appropriate converter, which can add lots of code for larger
systems.

If I create a MessageBodyWriter<ByteArrayOutputStream> with
@Produces("application/pdf"), then any Resource method that returns
ByteArrayOutputStream with the PDF mimetype will use the writer,
correct? Is this what you mean by "registering a converter"?  It looks
like the entity-provider sample does this.

But what do I do if I don't want to return a ByteArrayOutputStream and
instead want to return a Response? For instance, getJSON() below
returns plain JSON with a Response object. I assume the method
getPDF() should use my custom MessageBodyWriter since it returns a
ByteArrayOutputStream. If I do something like the third method
getTest(), will that still use my custom MBW? And note that I want to
add some custom headers as well.

       @Inject
       private DataService dataService;

       @GET
       @Path("json/{id}")
@Produces("application/json")
       public Response getJSON(@PathParam("id") String id) {
// Get JSON representation of data
ObjectNode data = dataService.getData(id);
// Return JSON data
return Response.ok().entity(data).build();
}

       @GET
       @Path("pdf/{id}")
       @Produces("application/pdf")
       public ByteArrayOutputStream getPDF(@PathParam("id") String id) {
// Return PDF representation of data
               return dataService.exportPdf(id):
      }

       @GET
       @Path("test/{id}")
       @Produces("application/pdf")
       public Response getTest(@PathParam("id") String id) {
Date now = new Date();
String name = "export-"+now.getTime()+".pdf";
               ByteArrayOutputStream pdf = dataService.exportPdf(id);
return Response.ok().entity(pdf).header("Content-Disposition",
"attachment; filename="+name).build();
      }

So, still a bit confused as you can see... How do I combine getData
and getPDF into a single method? I don't want to return JSON with a
ByteArrayOutputStream. Note that I make different calls to my service
layer to get different types of data, so how do I know which one to
call in a single method? Is there a way to know in advance which
@Produces is going to be used?

Am I on the right track with something like this?

       @GET
       @Path("combined/{id}")
       @Produces("application/json","application/pdf")
       public Response getCombined(@PathParam("id") String id) {

               // how do I test for output type???
               if (testForOutputType.equals("application/pdf") {
   Date now = new Date();
   String name = "export-"+now.getTime()+".pdf";
                   ByteArrayOutputStream pdf = dataService.exportPdf(id);
   return Response.ok().entity(pdf).header("Content-Disposition",
"attachment; filename="+name).build();
               }
ObjectNode data = dataService.getData(id);
return Response.ok().entity(data).build();
      }

Thanks again,
Tauren

---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]


Reply | Threaded
Open this post in threaded view
|

Re: Input and Output binary streams using JERSEY?

Cowtowncoder
In reply to this post by Tauren Mills
Paul gave a good answer already, but just some additional notes:

On Mon, Aug 16, 2010 at 11:24 PM, Tauren Mills <[hidden email]> wrote:

> Tatu,
>
>> Yes, and that's where registering typed handled (MessageBodyWriter)
>> would make things easier.
>> Otherwise you will have to manually handle mapping of requested type
>> into appropriate converter, which can add lots of code for larger
>> systems.
>
> If I create a MessageBodyWriter<ByteArrayOutputStream> with
> @Produces("application/pdf"), then any Resource method that returns
> ByteArrayOutputStream with the PDF mimetype will use the writer,
> correct? Is this what you mean by "registering a converter"?  It looks
> like the entity-provider sample does this.

Yes I think so.

> But what do I do if I don't want to return a ByteArrayOutputStream and
> instead want to return a Response? For instance, getJSON() below

Actually, to make registered handlers work well you should neither
return "raw" types (byte array) nor data format specific ones
(ObjectNode or JsonObject), but rather POJOs that framework can
convert.
Those will then be passed to writers. So you should not define
getJSON() (and similar) at all, but rather constructing object and let
format-specific MessageBodyWriter handle conversion.

The alternative here is to do it all yourself and not defining any
MessageBodyWriters; and that is when you would do conversion first and
wrap it within StreamingOutput or return as byte[] (which usually will
not be further processed by default MessageBodyWriters).

I think that what Paul said (apologies if I am wrong) is that it is ok
to have multiple methods mapped to single URL, as long as they produce
different content types; and that this is the way to go so that Jersey
can match content type with method, instead of application code having
to (try to) do that.
But that there is still just one external endpoint to access, just
gets dispatches to one of alternative Java methods.

-+ Tatu +-

---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]

Reply | Threaded
Open this post in threaded view
|

Re: Input and Output binary streams using JERSEY?

Paul Sandoz-2

On Aug 17, 2010, at 10:30 PM, Tatu Saloranta wrote:

> Paul gave a good answer already, but just some additional notes:
>
> On Mon, Aug 16, 2010 at 11:24 PM, Tauren Mills <[hidden email]>  
> wrote:
>> Tatu,
>>
>>> Yes, and that's where registering typed handled (MessageBodyWriter)
>>> would make things easier.
>>> Otherwise you will have to manually handle mapping of requested type
>>> into appropriate converter, which can add lots of code for larger
>>> systems.
>>
>> If I create a MessageBodyWriter<ByteArrayOutputStream> with
>> @Produces("application/pdf"), then any Resource method that returns
>> ByteArrayOutputStream with the PDF mimetype will use the writer,
>> correct? Is this what you mean by "registering a converter"?  It  
>> looks
>> like the entity-provider sample does this.
>
> Yes I think so.
>
>> But what do I do if I don't want to return a ByteArrayOutputStream  
>> and
>> instead want to return a Response? For instance, getJSON() below
>
> Actually, to make registered handlers work well you should neither
> return "raw" types (byte array) nor data format specific ones
> (ObjectNode or JsonObject), but rather POJOs that framework can
> convert.
> Those will then be passed to writers. So you should not define
> getJSON() (and similar) at all, but rather constructing object and let
> format-specific MessageBodyWriter handle conversion.
>
> The alternative here is to do it all yourself and not defining any
> MessageBodyWriters; and that is when you would do conversion first and
> wrap it within StreamingOutput or return as byte[] (which usually will
> not be further processed by default MessageBodyWriters).
>
> I think that what Paul said (apologies if I am wrong) is that it is ok
> to have multiple methods mapped to single URL, as long as they produce
> different content types; and that this is the way to go so that Jersey
> can match content type with method, instead of application code having
> to (try to) do that.
> But that there is still just one external endpoint to access, just
> gets dispatches to one of alternative Java methods.
>

Correctamundo, a resource identified by a URI can serve multiple  
representations. The easiest way to do that with JAX-RS is to use the  
annotations with separate resource methods. There are cases when you  
may want to defer to another framework for such representations or the  
file system (e.g. apache server style file serving) and one could use  
the Variant support to help with that type of selection or role your  
own.

The general idea is JAX-RS can help for common cases, and for some  
edge cases, and will try not to get in the way when you need to do  
something a little more specialized.

Paul.

---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]