/** * Get the original event data string {@link String}. * * @return event data de-serialized into a string. * @throws javax.ws.rs.ProcessingException when provided type can't be read. The thrown exception wraps the original cause. * @since 2.3 */ public String readData() { return readData(STRING_AS_GENERIC_TYPE, null); }
/** * Create a new {@link EventSource.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}. * @since 2.3 */ public static Builder target(WebTarget endpoint) { return new Builder(endpoint); }
/** * Register new {@link EventListener event listener} to receive all streamed {@link InboundEvent SSE events}. * * @param listener event listener to be registered with the event source. * @see #register(EventListener, String, String...) */ public void register(final EventListener listener) { register(listener, null); }
@POST public void addMessage(final String message) throws IOException { final EventOutput localOutput = eventOutput; if (localOutput != null) { eventOutput.write(new OutboundEvent.Builder().name("custom-message").data(String.class, message).build()); } }
/** * Set event data and java type of event data. * * This is a convenience method that derives the event data type information from the runtime type of * the event data. The supplied event data may be represented as {@link javax.ws.rs.core.GenericEntity}. * <p> * Note that multiple invocations of this method result in previous even data being replaced with new one. * </p> * * @param data event data. Must not be {@code null}. * @return updated builder instance. * @throws NullPointerException in case the {@code data} parameter is {@code null}. * @since 2.3 */ public Builder data(Object data) { if (data == null) { throw new NullPointerException(LocalizationMessages.OUT_EVENT_DATA_NULL()); } return data(ReflectionHelper.genericTypeFor(data), data); }
/** * Get the new SSE message stream channel. * * @return new SSE message stream channel. */ @GET @Produces(SseFeature.SERVER_SENT_EVENTS) public EventOutput getMessageStream() { LOGGER.info("--> SSE connection received."); final EventOutput eventOutput = new EventOutput(); broadcaster.add(eventOutput); return eventOutput; }
private EventSource(final WebTarget target, final String name, final long reconnectDelay, final boolean disableKeepAlive, final boolean open) { if (target == null) { throw new NullPointerException("Web target is 'null'."); } this.target = SseFeature.register(target); this.reconnectDelay = reconnectDelay; this.disableKeepAlive = disableKeepAlive; final String esName = (name == null) ? createDefaultName(target) : name; this.executor = new CloseableClientExecutor(Executors.newSingleThreadScheduledExecutor( new ThreadFactoryBuilder().setNameFormat(esName + "-%d") .setDaemon(true) .build())); if (open) { open(); } }
@Override public OutboundSseEvent.Builder newEventBuilder() { return new OutboundEvent.Builder(); }
/** * Build new SSE event source pointing at a SSE streaming {@link WebTarget web target}. * <p> * The returned event source is already {@link EventSource#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. * </p> * <p> * 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}. * </p> * * @return new event source instance, already connected to the SSE endpoint. * @see #build() */ public EventSource open() { // opening directly in the constructor is just plain ugly... final EventSource source = new EventSource(endpoint, name, reconnect, disableKeepAlive, false); source.open(); return source; } }
/** * Close this event source. * * The method will wait up to 5 seconds for the internal event processing task to complete. */ public void close() { close(5, TimeUnit.SECONDS); }
/** * Build {@link OutboundEvent}. * <p> * There are two valid configurations: * <ul> * <li>if a {@link Builder#comment(String) comment} is set, all other parameters are optional. * If event {@link Builder#data(Class, Object) data} and {@link Builder#mediaType(MediaType) media type} is set, * event data will be serialized after the comment.</li> * <li>if a {@link Builder#comment(String) comment} is not set, at least the event * {@link Builder#data(Class, Object) data} must be set. All other parameters are optional.</li> * </ul> * </p> * * @return new {@link OutboundEvent} instance. * @throws IllegalStateException when called with invalid configuration (neither a comment nor event data are set). */ public OutboundEvent build() { if (comment == null && data == null && type == null) { throw new IllegalStateException(LocalizationMessages.OUT_EVENT_NOT_BUILDABLE()); } return new OutboundEvent(name, id, reconnectDelay, type, mediaType, data, comment); } }
private void checkClosed() { if (isClosed()) { throw new IllegalStateException(LocalizationMessages.EVENT_SOURCE_ALREADY_CLOSED()); } } }
private InboundEvent(final String name, final String id, final String comment, final long reconnectDelay, final byte[] data, final MessageBodyWorkers messageBodyWorkers, final Annotation[] annotations, final MediaType mediaType, final MultivaluedMap<String, String> headers) { this.name = name; this.id = id; this.comment = comment; this.reconnectDelay = reconnectDelay; this.data = stripLastLineBreak(data); this.messageBodyWorkers = messageBodyWorkers; this.annotations = annotations; this.mediaType = mediaType; this.headers = headers; }
/** * Build new SSE event source pointing at a SSE streaming {@link WebTarget web target}. * <p> * The returned event source is ready, but not {@link EventSource#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. * </p> * <p> * 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}. * </p> * * @return new event source instance, ready to be connected to the SSE endpoint. * @see #open() */ public EventSource build() { return new EventSource(endpoint, name, reconnect, disableKeepAlive, false); }
/** * Get the raw event data bytes. * * @return raw event data bytes. The returned byte array may be empty if the event does not * contain any data. */ @SuppressWarnings("unused") public byte[] getRawData() { if (isEmpty()) { return data; } return Arrays.copyOf(data, data.length); }
@Override public CompletionStage<?> broadcast(final OutboundSseEvent event) { if (event == null) { throw new IllegalArgumentException(LocalizationMessages.PARAM_NULL("event")); } publish(event); // TODO JAX-RS 2.1 return null; }
/** * Read event data as a given generic type. * * @param type generic type to be used for event data de-serialization. * @return event data de-serialized as an instance of a given type. * @throws javax.ws.rs.ProcessingException when provided type can't be read. The thrown exception wraps the original cause. * @since 2.3 */ @SuppressWarnings("unused") public <T> T readData(GenericType<T> type) { return readData(type, null); }
@Override public String toString() { String s; try { s = readData(); } catch (ProcessingException e) { s = "<Error reading data into a string>"; } return "InboundEvent{" + "name='" + name + '\'' + ", id='" + id + '\'' + ", comment=" + (comment == null ? "[no comments]" : '\'' + comment + '\'') + ", data=" + s + '}'; }
/** * Read event data as a given Java type. * * @param type Java type to be used for event data de-serialization. * @return event data de-serialized as an instance of a given type. * @throws javax.ws.rs.ProcessingException when provided type can't be read. The thrown exception wraps the original cause. * @since 2.3 */ public <T> T readData(Class<T> type) { return readData(new GenericType<T>(type), null); }
/** * Read event data as a given Java type. * * @param messageType Java type to be used for event data de-serialization. * @param mediaType {@link MediaType media type} to be used for event data de-serialization. * @return event data de-serialized as an instance of a given type. * @throws javax.ws.rs.ProcessingException when provided type can't be read. The thrown exception wraps the original cause. * @since 2.3 */ @SuppressWarnings("unused") public <T> T readData(Class<T> messageType, MediaType mediaType) { return readData(new GenericType<T>(messageType), mediaType); }