All Downloads are FREE. Search and download functionalities are using the official Maven repository.

javax.ws.rs.sse.SseEventSource Maven / Gradle / Ivy

There is a newer version: 2.1.1
Show newest version
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2012-2015 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * http://glassfish.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package javax.ws.rs.sse;

import java.net.URL;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import javax.ws.rs.client.WebTarget;

/**
 * Client for reading and processing {@link InboundSseEvent incoming Server-Sent Events}.
 * 

* SSE event source instances of this class are thread safe. To build a new instance, you can use the * {@link #target(javax.ws.rs.client.WebTarget) SseEventSource.target(endpoint)} factory method to get * a new event source builder that can be further customised and eventually used to create a new SSE * event source. *

*

* Once a {@link SseEventSource} is created, it can be used to {@link #open open a connection} * to the associated {@link WebTarget web target}. After establishing the connection, the event source starts * processing any incoming inbound events. Whenever a new event is received, an * {@link javax.ws.rs.sse.SseEventSource.Listener#onEvent(InboundSseEvent)} method is invoked on any * registered {@link SseEventSource.Listener event listeners}. *

*

Reconnect support

*

* The {@code EventSource} supports automated recuperation from a connection loss, including * negotiation of delivery of any missed events based on the last received SSE event {@code id} field value, provided * this field is set by the server and the negotiation facility is supported by the server. In case of a connection loss, * the last received SSE event {@code id} field value is send in the * {@value javax.ws.rs.core.HttpHeaders#LAST_EVENT_ID_HEADER} HTTP * request header as part of a new connection request sent to the SSE endpoint. Upon a receipt of such reconnect request, the SSE * endpoint that supports this negotiation facility is expected to replay all missed events. Note however, that this is a * best-effort mechanism which does not provide any guaranty that all events would be delivered without a loss. You should * therefore not rely on receiving every single event and design your client application code accordingly. *

*

* By default, when a connection the the SSE endpoint is lost, the event source will wait 500 ms * before attempting to reconnect to the SSE endpoint. The SSE endpoint can however control the client-side retry delay * by including a special {@code retry} field value in the any send event. JAX-RS {@code SseEventSource} tracks any * received SSE event {@code retry} field values set by the endpoint and adjusts the reconnect delay accordingly, * using the last received {@code retry} field value as the reconnect delay. *

*

* In addition to handling the standard connection loss failures, JAX-RS {@code SseEventSource} automatically deals with any * {@code HTTP 503 Service Unavailable} responses from an SSE endpoint, that contain a * {@value javax.ws.rs.core.HttpHeaders#RETRY_AFTER} HTTP header with a valid value. The * HTTP 503 + {@value javax.ws.rs.core.HttpHeaders#RETRY_AFTER} technique is often used by HTTP endpoints * as a means of connection and traffic throttling. * In case a HTTP 503 + {@value javax.ws.rs.core.HttpHeaders#RETRY_AFTER} response is received in return to a connection * request, JAX-RS SSE event source will automatically schedule a new reconnect attempt and use the received * {@value javax.ws.rs.core.HttpHeaders#RETRY_AFTER} HTTP header value as a one-time override of the reconnect delay. *

* * @author Marek Potociar * @since 2.1 */ public interface SseEventSource extends AutoCloseable { /** * {@code SseEventSource} listener that can be registered to listen for newly received * {@link InboundSseEvent} notifications. */ interface Listener { /** * Called when a new {@link InboundSseEvent} is received by an event source. * * @param event received inbound SSE event. */ void onEvent(InboundSseEvent event); } /** * JAX-RS {@link SseEventSource} builder class. *

* Event source builder provides methods that let you conveniently configure and subsequently build * a new {@code EventSource} instance. You can obtain a new event source builder instance using * a static {@link SseEventSource#target(javax.ws.rs.client.WebTarget) EventSource.target(endpoint)} factory method. *

* For example: *

     * EventSource es = EventSource.target(endpoint).named("my source")
     *                             .reconnectingEvery(5, SECONDS)
     *                             .open();
     * 
*

*/ abstract class Builder { /** * Name of the property identifying the {@link SseEventSource.Builder} implementation * to be returned from {@link SseEventSource.Builder#newBuilder()}. */ public static final String JAXRS_DEFAULT_SSE_BUILDER_PROPERTY = "javax.ws.rs.sse.SseEventSource.Builder"; /** * Default SSE event source builder implementation class name. */ private static final String JAXRS_DEFAULT_SSE_BUILDER = "org.glassfish.jersey.media.sse.EventSource$Builder"; /** * Allows custom implementations to extend the SSE event source builder class. */ protected Builder() { } /** * Create a new SSE event source instance using the default implementation class provided by the JAX-RS * implementation provider. * * @return new SSE event source builder instance. */ static Builder newBuilder() { try { Object delegate = FactoryFinder.find(JAXRS_DEFAULT_SSE_BUILDER_PROPERTY, JAXRS_DEFAULT_SSE_BUILDER); if (!(delegate instanceof Builder)) { Class pClass = Builder.class; String classnameAsResource = pClass.getName().replace('.', '/') + ".class"; ClassLoader loader = pClass.getClassLoader(); if (loader == null) { loader = ClassLoader.getSystemClassLoader(); } URL targetTypeURL = loader.getResource(classnameAsResource); throw new LinkageError("ClassCastException: attempting to cast" + delegate.getClass().getClassLoader().getResource(classnameAsResource) + " to " + targetTypeURL); } return (Builder) delegate; } catch (Exception ex) { throw new RuntimeException(ex); } } protected abstract Builder target(WebTarget endpoint); /** * Set a custom name for the event source. *

* At present, custom event source name is mainly useful to be able to distinguish different event source * event processing threads from one another. If not set, a default name will be generated using the * SSE endpoint URI. *

* * @param name custom event source name. * @return updated event source builder instance. */ public abstract Builder named(String name); /** * Set the initial reconnect delay to be used by the event source. *

* Note that this value may be later overridden by the SSE endpoint using either a {@code retry} SSE event field * or HTTP 503 + {@value javax.ws.rs.core.HttpHeaders#RETRY_AFTER} mechanism as described * in the {@link SseEventSource} javadoc. *

* * @param delay the default time to wait before attempting to recover from a connection loss. * @param unit time unit of the reconnect delay parameter. * @return updated event source builder instance. */ public abstract Builder reconnectingEvery(long delay, TimeUnit unit); /** * Register new {@link SseEventSource.Listener event listener} to receive all streamed {@link InboundSseEvent SSE events}. * * @param listener event listener to be registered with the event source. * @see #register(SseEventSource.Listener, String, String...) */ public abstract Builder register(SseEventSource.Listener listener); /** * Add name-bound {@link SseEventSource.Listener event listener} which will be called only for incoming SSE * {@link InboundSseEvent events} whose {@link InboundSseEvent#getName() name} is equal to the specified * name(s). * * @param listener event listener to register with this event source. * @param eventName inbound event name. * @param eventNames additional event names. * @see #register(SseEventSource.Listener) */ public abstract Builder register(SseEventSource.Listener listener, String eventName, String... eventNames); /** * Build new SSE event source pointing at a SSE streaming {@link WebTarget web target}. *

* The returned event source is ready, but not {@link SseEventSource#open() connected} to the SSE endpoint. * It is expected that you will manually invoke its {@link #open()} method once you are ready to start * receiving SSE events. In case you want to build an event source instance that is already connected * to the SSE endpoint, use the event source builder {@link #open()} method instead. *

*

* Once the event source is open, the incoming events are processed by the event source in an * asynchronous task that runs in an internal single-threaded {@link ScheduledExecutorService * scheduled executor service}. *

* * @return new event source instance, ready to be connected to the SSE endpoint. * @see #open() */ public abstract SseEventSource build(); /** * Build new SSE event source pointing at a SSE streaming {@link WebTarget web target}. *

* The returned event source is already {@link SseEventSource#open() connected} to the SSE endpoint * and is processing any new incoming events. In case you want to build an event source instance * that is already ready, but not automatically connected to the SSE endpoint, use the event source * builder {@link #build()} method instead. *

*

* The incoming events are processed by the event source in an asynchronous task that runs in an * internal single-threaded {@link ScheduledExecutorService scheduled executor service}. *

* * @return new event source instance, already connected to the SSE endpoint. * @see #build() */ public abstract SseEventSource open(); } /** * Create a new {@link SseEventSource.Builder event source builder} that provides convenient way how to * configure and fine-tune various aspects of a newly prepared event source instance. * * @param endpoint SSE streaming endpoint. Must not be {@code null}. * @return a builder of a new event source instance pointing at the specified SSE streaming endpoint. * @throws NullPointerException in case the supplied web target is {@code null}. */ static Builder target(WebTarget endpoint) { return Builder.newBuilder().target(endpoint); } /** * Open the connection to the supplied SSE underlying {@link WebTarget web target} and start processing incoming * {@link InboundSseEvent events}. * * @throws IllegalStateException in case the event source has already been opened earlier. */ void open(); /** * Check if this event source instance has already been {@link #open() opened}. * * @return {@code true} if this event source is open, {@code false} otherwise. */ boolean isOpen(); /** * Close this event source. *

* The method will wait up to 5 seconds for the internal event processing tasks to complete. */ default void close() { close(5, TimeUnit.SECONDS); } /** * Close this event source and wait for the internal event processing task to complete * for up to the specified amount of wait time. *

* The method blocks until the event processing task has completed execution after a shutdown * request, or until the timeout occurs, or the current thread is interrupted, whichever happens * first. *

*

* In case the waiting for the event processing task has been interrupted, this method restores * the {@link Thread#interrupted() interrupt} flag on the thread before returning {@code false}. *

* * @param timeout the maximum time to wait. * @param unit the time unit of the timeout argument. * @return {@code true} if this executor terminated and {@code false} if the timeout elapsed * before termination or the termination was interrupted. */ boolean close(final long timeout, final TimeUnit unit); }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy