TestResource.java

/*
 * Copyright (c) 2013, 2019 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package org.glassfish.jersey.tests.integration.servlet_3_chunked_io;

import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.ws.rs.GET;
import javax.ws.rs.InternalServerErrorException;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.container.AsyncResponse;
import javax.ws.rs.container.Suspended;
import javax.ws.rs.core.MediaType;

import org.glassfish.jersey.server.ChunkedOutput;

/**
 * Test resource.
 *
 * @author Marek Potociar
 */
@Path("/test")
public class TestResource {

    private static final Logger LOGGER = Logger.getLogger(TestResource.class.getName());

    /**
     * Get chunk stream of JSON data - from JSON POJOs.
     *
     * @return chunk stream.
     */
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @Path("from-pojo")
    public ChunkedOutput<Message> getFromPojo() {
        final ChunkedOutput<Message> output = new ChunkedOutput<>(Message.class, "\r\n");

        new Thread() {
            @Override
            public void run() {
                try {
                    for (int i = 0; i < 3; i++) {
                        output.write(new Message(i, "test"));
                        Thread.sleep(200);
                    }
                } catch (final IOException e) {
                    LOGGER.log(Level.SEVERE, "Error writing chunk.", e);
                } catch (final InterruptedException e) {
                    LOGGER.log(Level.SEVERE, "Sleep interrupted.", e);
                    Thread.currentThread().interrupt();
                } finally {
                    try {
                        output.close();
                    } catch (final IOException e) {
                        LOGGER.log(Level.INFO, "Error closing chunked output.", e);
                    }
                }
            }
        }.start();

        return output;
    }

    /**
     * Get chunk stream of JSON data - from string.
     *
     * @return chunk stream.
     */
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @Path("from-string")
    public ChunkedOutput<String> getFromText() {
        final ChunkedOutput<String> output = new ChunkedOutput<>(String.class);

        new Thread() {
            @Override
            public void run() {
                try {
                    for (int i = 0; i < 3; i++) {
                        output.write(new Message(i, "test").toString() + "\r\n");
                        Thread.sleep(200);
                    }
                } catch (final IOException e) {
                    LOGGER.log(Level.SEVERE, "Error writing chunk.", e);
                } catch (final InterruptedException e) {
                    LOGGER.log(Level.SEVERE, "Sleep interrupted.", e);
                    Thread.currentThread().interrupt();
                } finally {
                    try {
                        output.close();
                    } catch (final IOException e) {
                        LOGGER.log(Level.INFO, "Error closing chunked output.", e);
                    }
                }
            }
        }.start();

        return output;
    }

    /**
     * {@link org.glassfish.jersey.server.ChunkedOutput#close()} is called before method returns it's entity. Resource reproduces
     * JERSEY-2558 issue.
     *
     * @return (closed) chunk stream.
     */
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @Path("close-before-return")
    public ChunkedOutput<Message> closeBeforeReturn() {
        final ChunkedOutput<Message> output = new ChunkedOutput<>(Message.class, "\r\n");
        final CountDownLatch latch = new CountDownLatch(1);

        new Thread() {
            @Override
            public void run() {
                try {
                    for (int i = 0; i < 3; i++) {
                        output.write(new Message(i, "test"));
                        Thread.sleep(200);
                    }
                } catch (final IOException e) {
                    LOGGER.log(Level.SEVERE, "Error writing chunk.", e);
                } catch (final InterruptedException e) {
                    LOGGER.log(Level.SEVERE, "Sleep interrupted.", e);
                    Thread.currentThread().interrupt();
                } finally {
                    try {
                        output.close();
                        // Worker thread can continue.
                        latch.countDown();
                    } catch (final IOException e) {
                        LOGGER.log(Level.INFO, "Error closing chunked output.", e);
                    }
                }
            }
        }.start();

        try {
            // Wait till new thread closes the chunked output.
            latch.await();
            return output;
        } catch (final InterruptedException e) {
            throw new InternalServerErrorException(e);
        }
    }

    /**
     * Test combination of AsyncResponse and ChunkedOutput.
     *
     * @param response async response.
     */
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @Path("chunked-async")
    public void closeBeforeReturnAsync(@Suspended final AsyncResponse response) {
        // Set timeout to able to resume but not send the chunks (setting the ChunkedOutput should remove the timeout).
        response.setTimeout(1500, TimeUnit.SECONDS);

        new Thread() {

            @Override
            public void run() {
                final ChunkedOutput<Message> output = new ChunkedOutput<>(Message.class, "\r\n");

                try {
                    // Let the method return.
                    Thread.sleep(1000);
                    // Resume.
                    response.resume(output);
                    // Wait for resume to complete.
                    Thread.sleep(1000);

                    new Thread() {

                        @Override
                        public void run() {
                            try {
                                for (int i = 0; i < 3; i++) {
                                    output.write(new Message(i, "test"));
                                    Thread.sleep(200);
                                }
                            } catch (final IOException e) {
                                LOGGER.log(Level.SEVERE, "Error writing chunk.", e);
                            } catch (final InterruptedException e) {
                                LOGGER.log(Level.SEVERE, "Sleep interrupted.", e);
                                Thread.currentThread().interrupt();
                            } finally {
                                try {
                                    output.close();
                                } catch (final IOException e) {
                                    LOGGER.log(Level.INFO, "Error closing chunked output.", e);
                                }
                            }
                        }
                    }.start();
                } catch (final InterruptedException e) {
                    LOGGER.log(Level.SEVERE, "Sleep interrupted.", e);
                    Thread.currentThread().interrupt();
                }
            }
        }.start();
    }
}