6

How can I serialize a Java 8 java.util.Stream<T> with Jersey. I tried to write a MessageBodyWriter, but I need to know how to compose (decorate) existing MessageBodyWriters with a new MessageBodyWriter for my Stream.

Stream<String> get(){
  return some stream of strings  
}

public <T> class StreamMessageBodyWriter<Stream<T>> 
           implements MessageBodyWriter<Stream<T>> {

  public void writeTo(.......){
    //How can I get the handle to MessageBodyWriter that will write for type T, 
    //so that I can 'collect' the 'java.util.Stream<T>' and write it to 
    //OutputStream
  }
}
Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
Dhruv Chandna
  • 571
  • 5
  • 17

2 Answers2

4

but I need to know how to compose (decorate) existing MessageBodyWriters with a new MessageBodyWriter for my Stream

You can just inject Providers and use getMessagBodyWriter(...), passing in the required details to lookup the specific writer for that type. For example

@Provider
public class StreamBodyWriter implements MessageBodyWriter<Stream> {
    
    @Context
    private Providers providers;

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

    @Override
    public long getSize(Stream stream, Class<?> type, Type genericType, 
            Annotation[] annotations, MediaType mediaType) { return -1; }

    @Override
    public void writeTo(Stream stream, Class<?> type, Type genericType, 
            Annotation[] annotations, MediaType mediaType, 
            MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) 
            throws IOException, WebApplicationException {
     
        Object obj = stream.collect(Collectors.toList());
        Class<?> objType = obj.getClass();
        
        MessageBodyWriter writer = providers.getMessageBodyWriter(objType, 
                null, annotations, mediaType);
        
        writer.writeTo(obj, objType, null, annotations, 
                mediaType, httpHeaders, entityStream);
        
    }
}

If you look at the writeTo, first I call collect then get the returned type. Then lookup the writer for that type, then simply delegate to the writer.

Here is a test

@Path("stream")
public class StreamResource {
    
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Response getStream() {
        List<Person> myList = Arrays.asList(
                              new Person("Stack"), 
                              new Person("Overflow"),
                              new Person("Sam"));
        Stream<Person> stream = myList.stream()
                                      .filter(p -> p.name.startsWith("S"));
        return Response.ok(stream).build();
    }
    
    public static class Person {
        public String name;
        public Person(String name) { this.name = name; }
        public Person() {}
    }
}

C:\>curl -v http://localhost:8080/api/stream
Result:
[{"name":"Stack"},{"name":"Sam"}]

As an aside, if you plan on manipulating the Stream in the writer, maybe look into using an Interceptor. Won't make a difference really, but if you want to stick to the Single Responsibility Principle, this is what the Interceptor is for, manipulating the request body.


Note: the above is standard JAX-RS

Alternatively...

Specifically with Jersey, you can also inject MessageBodyWorkers, for more specific lookup, and even calling its writeTo, which will delegate to the required writer, if one exsists.

Community
  • 1
  • 1
Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
  • Thanks a lot. The Providers injection is what I was looking for. I still need to find a way of not collecting the list as I want to avoid realizing a list into memory, but that is something I will work on. This really helps. – Dhruv Chandna Mar 24 '15 at 14:19
  • 3
    The `stream.collect(Collectors.toList())` defeats the purpose of using streams. If I have a stream with a huge number of elements I can't assemble them all into an in memory collection; that's one of the reasons to use Java 8 streams in the first place. –  Sep 10 '16 at 01:51
0

In line with the purpose of use streams (without using stream.collect(Collectors.toList())), there's this interesting article showing how to serialize large data from a database.

It's something like this...

@GET
@Produces( "application/json" )
public Response streamGeneratedUuids() {

    return getNoCacheResponseBuilder( Response.Status.OK ).entity( new StreamingOutput() {

        @Override
        public void write( OutputStream os ) throws IOException, WebApplicationException {
            try (PrintWriter writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(os))) ) {
                //iterate the java.util.stream and write to the OutputStream
                writer.print("....");       
            }
        }
    }).build();
}

It's not implemented with a MessageBodyWriter, but could adapted in my opinion.

Alex Pakka
  • 9,466
  • 3
  • 45
  • 69
Fabio Bonfante
  • 5,128
  • 1
  • 32
  • 37