пятница, 27 июля 2012 г.

How to work with huge output in Jersey RESTful Web services.

How to work with huge output in Jersey RESTful Web services.
The typical usecase for Jersey + JAXB - work with whole XML document and single JAXB objects tree. But sometimes the set of objects very huge and output can not be created, because of OutOfMemoryError or other reasons. In this case need to send output as stream. Code if following:
package az.edu.hugedoc;

/**
 * User: Igor Azarny iazarny@yahoo.com
 * Date: 7/27/12
 * Time: 1:56 PM
 */

import az.edu.data.model.ObjectFactory;
import az.edu.data.model.Person;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.StreamingOutput;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.util.UUID;


@Path("xmlrepo")
public class HugeDocAsXmlStream {

    private ObjectFactory of;
    private Marshaller marshaller;

    public HugeDocAsXmlStream() {
        try {
            JAXBContext jc = JAXBContext.newInstance(Person.class);
            marshaller = jc.createMarshaller();
            marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);
            of = new ObjectFactory();
        } catch (JAXBException e) {
            e.printStackTrace(); // impossible
        }

    }

    @GET
    @Produces({"text/xml"})
    public StreamingOutput getHugeDocAsXmlStreamOutput() {
        return new StreamingOutput() {
            public void write(OutputStream output) throws IOException, WebApplicationException {
                XMLStreamWriter xsw = null;
                try {
                    xsw = XMLOutputFactory.newInstance().createXMLStreamWriter(output);
                    xsw.writeStartElement("personrow");

                    /**
                     *
                     * Original idea - to use hibernate scrollable result of objects.
                     *
                     * ScrollableResults results = hibernateSession.createCriteria(persistentClass.class)
                     * .setFetchSize(BATCH_SIZE)
                     * .scroll(ScrollMode.FORWARD_ONLY);
                     *
                     * while (results.next()) {
                     *     index++;
                     *     T entity = (T) results.get(0);
                     *
                     */

                    for (int i = 0; i < 1000; i++) {
                        JAXBElement<Person> personJAXBElement = of.createPerson(
                                new Person(i, "Ivan " + UUID.randomUUID().toString(), "KillTesterOff " + UUID.randomUUID().toString())
                        );
                        marshaller.marshal(personJAXBElement, xsw);
                        xsw.flush();    // Force flush the stream

                    }
                    xsw.writeEndElement();
                    xsw.writeEndDocument();
                } catch (Exception e) {
                    throw new WebApplicationException(e);
                } finally {
                    if (xsw != null) {
                        try {
                            xsw.close();
                        } catch (XMLStreamException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        };
    }

}