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

org.glassfish.jersey.internal.util.JerseyPublisher Maven / Gradle / Ivy

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2017 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
 * https://oss.oracle.com/licenses/CDDL+GPL-1.1
 * or 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 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 org.glassfish.jersey.internal.util;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.function.BiPredicate;
import java.util.function.Consumer;

import org.glassfish.jersey.internal.LocalizationMessages;
import org.glassfish.jersey.internal.jsr166.Flow;
import org.glassfish.jersey.internal.jsr166.SubmissionPublisher;


/**
 * Implementation of {@link Flow.Publisher} corresponding to reactive streams specification.
 * 

* Delegates to {@link SubmissionPublisher} repackaged from jsr166. * * @author Adam Lindenthal (adam.lindenthal at oracle.com) */ public class JerseyPublisher implements Flow.Publisher { private static final int DEFAULT_BUFFER_CAPACITY = 256; private SubmissionPublisher submissionPublisher = new SubmissionPublisher<>(); private final PublisherStrategy strategy; /** * Creates a new JerseyPublisher using the {@link ForkJoinPool#commonPool()} for async delivery to subscribers * (unless it does not support a parallelism level of at least two, in which case, a new Thread is created to run * each task), with maximum buffer capacity of {@value DEFAULT_BUFFER_CAPACITY} and default {@link PublisherStrategy}, * which is {@link PublisherStrategy#BEST_EFFORT}. */ public JerseyPublisher() { this(ForkJoinPool.commonPool(), DEFAULT_BUFFER_CAPACITY, PublisherStrategy.BEST_EFFORT); } /** * Creates a new JerseyPublisher using the {@link ForkJoinPool#commonPool()} for async delivery to subscribers * (unless it does not support a parallelism level of at least two, in which case, a new Thread is created to run * each task), with maximum buffer capacity of {@value DEFAULT_BUFFER_CAPACITY} and given {@link PublisherStrategy}. * * @param strategy publisher delivering strategy */ public JerseyPublisher(final PublisherStrategy strategy) { this(ForkJoinPool.commonPool(), DEFAULT_BUFFER_CAPACITY, strategy); } /** * Creates a new JerseyPublisher using the given {@link Executor} for async delivery to subscribers, with the default * maximum buffer size of {@value DEFAULT_BUFFER_CAPACITY} and default {@link PublisherStrategy}, which is * {@link PublisherStrategy#BEST_EFFORT}. * * @param executor {@code Executor} the executor to use for async delivery, * supporting creation of at least one independent thread * @throws NullPointerException if executor is null * @throws IllegalArgumentException if maxBufferCapacity not positive */ public JerseyPublisher(final Executor executor) { this(executor, PublisherStrategy.BEST_EFFORT); } /** * Creates a new JerseyPublisher using the given {@link Executor} for async delivery to subscribers, with the default * maximum buffer size of {@value DEFAULT_BUFFER_CAPACITY} and given {@link PublisherStrategy}. * * @param executor {@code Executor} the executor to use for async delivery, * supporting creation of at least one independent thread * @param strategy publisher delivering strategy * @throws NullPointerException if executor is null * @throws IllegalArgumentException if maxBufferCapacity not positive */ public JerseyPublisher(final Executor executor, final PublisherStrategy strategy) { this.strategy = strategy; submissionPublisher = new SubmissionPublisher<>(executor, DEFAULT_BUFFER_CAPACITY); } /** * Creates a new JerseyPublisher using the {@link ForkJoinPool#commonPool()} for async delivery to subscribers * (unless it does not support a parallelism level of at least two, in which case, a new Thread is created to run * each task), with specified maximum buffer capacity and default {@link PublisherStrategy}, which is * {@link PublisherStrategy#BEST_EFFORT}. * * @param maxBufferCapacity the maximum capacity for each * subscriber's buffer (the enforced capacity may be rounded up to * the nearest power of two and/or bounded by the largest value * supported by this implementation; method {@link #getMaxBufferCapacity} * returns the actual value) */ public JerseyPublisher(final int maxBufferCapacity) { this(ForkJoinPool.commonPool(), maxBufferCapacity, PublisherStrategy.BEST_EFFORT); } /** * Creates a new JerseyPublisher using the given {@link Executor} for async delivery to subscribers, with the given * maximum buffer size for each subscriber and given {@link PublisherStrategy}. * * @param executor {@code Executor} the executor to use for async delivery, * supporting creation of at least one independent thread * @param maxBufferCapacity the maximum capacity for each * subscriber's buffer (the enforced capacity may be rounded up to * the nearest power of two and/or bounded by the largest value * supported by this implementation; method {@link #getMaxBufferCapacity} * returns the actual value) * @param strategy publisher delivering strategy * @throws NullPointerException if executor is null * @throws IllegalArgumentException if maxBufferCapacity not positive */ public JerseyPublisher(final Executor executor, final int maxBufferCapacity, PublisherStrategy strategy) { this.strategy = strategy; submissionPublisher = new SubmissionPublisher<>(executor, maxBufferCapacity); } @Override public void subscribe(final Flow.Subscriber subscriber) { submissionPublisher.subscribe(new SubscriberWrapper(subscriber)); } /** * Publishes the given item to each current subscriber by asynchronously invoking its onNext method. *

* Blocks uninterruptibly while resources for any subscriber are unavailable. * * @param data published data * @return the estimated maximum lag among subscribers * @throws IllegalStateException if closed * @throws NullPointerException if data is null * @throws java.util.concurrent.RejectedExecutionException if thrown by Executor */ private int submit(final T data) { return submissionPublisher.submit(data); } /** * Processes all published items using the given Consumer function. Returns a CompletableFuture that is completed * normally when this publisher signals {@code onComplete()}, or completed exceptionally upon any error, or an * exception is thrown by the Consumer, or the returned CompletableFuture is cancelled, in which case no further * items are processed. * * @param consumer function to process all published data * @return a {@link CompletableFuture} that is completed normally when the publisher signals onComplete, * and exceptionally upon any error or cancellation * @throws NullPointerException if consumer is null */ public CompletableFuture consume(final Consumer consumer) { return submissionPublisher.consume(consumer); } /** * Publishes the given item, if possible, to each current subscriber * by asynchronously invoking its * {@link Flow.Subscriber#onNext(Object) onNext} method. * The item may be dropped by one or more subscribers if resource * limits are exceeded, in which case the given handler (if non-null) * is invoked, and if it returns true, retried once. Other calls to * methods in this class by other threads are blocked while the * handler is invoked. Unless recovery is assured, options are * usually limited to logging the error and/or issuing an {@link * Flow.Subscriber#onError(Throwable) onError} * signal to the subscriber. *

* This method returns a status indicator: If negative, it * represents the (negative) number of drops (failed attempts to * issue the item to a subscriber). Otherwise it is an estimate of * the maximum lag (number of items submitted but not yet * consumed) among all current subscribers. This value is at least * one (accounting for this submitted item) if there are any * subscribers, else zero. *

* If the Executor for this publisher throws a * RejectedExecutionException (or any other RuntimeException or * Error) when attempting to asynchronously notify subscribers, or * the drop handler throws an exception when processing a dropped * item, then this exception is rethrown. * * @param item the (non-null) item to publish * @param onDrop if non-null, the handler invoked upon a drop to a * subscriber, with arguments of the subscriber and item; if it * returns true, an offer is re-attempted (once) * @return if negative, the (negative) number of drops; otherwise * an estimate of maximum lag * @throws IllegalStateException if closed * @throws NullPointerException if item is null * @throws RejectedExecutionException if thrown by Executor */ private int offer(T item, BiPredicate, ? super T> onDrop) { return offer(item, 0, TimeUnit.MILLISECONDS, onDrop); } /** * Publishes the given item, if possible, to each current subscriber * by asynchronously invoking its {@link * Flow.Subscriber#onNext(Object) onNext} method, * blocking while resources for any subscription are unavailable, * up to the specified timeout or until the caller thread is * interrupted, at which point the given handler (if non-null) is * invoked, and if it returns true, retried once. (The drop handler * may distinguish timeouts from interrupts by checking whether * the current thread is interrupted.) * Other calls to methods in this class by other * threads are blocked while the handler is invoked. Unless * recovery is assured, options are usually limited to logging the * error and/or issuing an * {@link Flow.Subscriber#onError(Throwable) onError} * signal to the subscriber. *

* This method returns a status indicator: If negative, it * represents the (negative) number of drops (failed attempts to * issue the item to a subscriber). Otherwise it is an estimate of * the maximum lag (number of items submitted but not yet * consumed) among all current subscribers. This value is at least * one (accounting for this submitted item) if there are any * subscribers, else zero. *

* If the Executor for this publisher throws a * RejectedExecutionException (or any other RuntimeException or * Error) when attempting to asynchronously notify subscribers, or * the drop handler throws an exception when processing a dropped * item, then this exception is rethrown. * * @param item the (non-null) item to publish * @param timeout how long to wait for resources for any subscriber * before giving up, in units of {@code unit} * @param unit a {@code TimeUnit} determining how to interpret the * {@code timeout} parameter * @param onDrop if non-null, the handler invoked upon a drop to a * subscriber, with arguments of the subscriber and item; if it * returns true, an offer is re-attempted (once) * @return if negative, the (negative) number of drops; otherwise * an estimate of maximum lag * @throws IllegalStateException if closed * @throws NullPointerException if item is null * @throws RejectedExecutionException if thrown by Executor */ private int offer(T item, long timeout, TimeUnit unit, BiPredicate, ? super T> onDrop) { BiPredicate, ? super T> callback; callback = onDrop == null ? this::onDrop : (BiPredicate, T>) (subscriber, data) -> { onDrop.test(getSubscriberWrapper(subscriber).getWrappedSubscriber(), data); return false; }; return submissionPublisher.offer(item, timeout, unit, callback); } private boolean onDrop(Flow.Subscriber subscriber, T t) { subscriber.onError(new IllegalStateException(LocalizationMessages.SLOW_SUBSCRIBER(t))); getSubscriberWrapper(subscriber).getSubscription().cancel(); return false; } private SubscriberWrapper getSubscriberWrapper(Flow.Subscriber subscriber) { if (subscriber instanceof SubscriberWrapper) { return ((SubscriberWrapper) subscriber); } else { throw new IllegalArgumentException(LocalizationMessages.UNKNOWN_SUBSCRIBER()); } } /** * Publishes the given item to all current subscribers by invoking its {@code onNext() method} using {@code Executor} * provided as constructor parameter (or the default {@code Executor} if not provided). *

* Concrete behaviour is specified by {@link PublisherStrategy} selected upon {@code JerseyPublisher} creation. * * @param item the (non-null) item to publish. * @return if negative, the (negative) number of drops; otherwise an estimate of maximum lag. * @throws IllegalStateException if closed * @throws NullPointerException if item is null * @throws RejectedExecutionException if thrown by {@code Executor} */ public int publish(T item) { if (PublisherStrategy.BLOCKING == strategy) { return submit(item); } else { // PublisherStrategy.BEST_EFFORT return submissionPublisher.offer(item, this::onDrop); } } /** * Unless already closed, issues {@code onComplete()} signals to current subscribers, and disallows subsequent * attempts to publish. Upon return, this method does NOT guarantee that all subscribers have yet * completed. */ public void close() { submissionPublisher.close(); } /** * Issues onError signals to current subscribers with the given error, and disallows subsequent attempts to publish. * * @param error the {@code onError} argument sent to subscribers * @throws NullPointerException if error is null */ public void closeExceptionally(final Throwable error) { submissionPublisher.closeExceptionally(error); } /** * Returns an estimate of the maximum number of items produced but not yet consumed among all current subscribers. * * @return estimated maximum lag */ public int estimateMaximumLag() { return submissionPublisher.estimateMaximumLag(); } /** * Returns an estimate of the minimum number of items requested but not yet produced, among all current subscribers. * * @return estimated minimum demand */ public long estimateMinimumDemand() { return submissionPublisher.estimateMinimumDemand(); } /** * Returns the exception associated with {@link #closeExceptionally}, or null if not closed or if closed normally. * * @return exception thrown on closing or {@code null} */ public Throwable getClosedException() { return submissionPublisher.getClosedException(); } /** * Returns the maximum per-subscriber buffer capacity. * * @return the maximum per-subscriber buffer capacity */ public int getMaxBufferCapacity() { return submissionPublisher.getMaxBufferCapacity(); } public static class SubscriberWrapper implements Flow.Subscriber { private Flow.Subscriber subscriber; private Flow.Subscription subscription = null; public SubscriberWrapper(Flow.Subscriber subscriber) { this.subscriber = subscriber; } @Override public void onSubscribe(final Flow.Subscription subscription) { this.subscription = subscription; subscriber.onSubscribe(new Flow.Subscription() { @Override public void request(final long n) { subscription.request(n); } @Override public void cancel() { subscription.cancel(); } }); } @Override public void onNext(final T item) { subscriber.onNext(item); } @Override public void onError(final Throwable throwable) { subscriber.onError(throwable); } @Override public void onComplete() { subscriber.onComplete(); } public Flow.Subscriber getWrappedSubscriber() { return subscriber; } /** * Get reference to subscriber's {@link Flow.Subscription}. * * @return subscriber's {@code subscription} */ public Flow.Subscription getSubscription() { return this.subscription; } } public enum PublisherStrategy{ /** * Blocking publisher strategy - tries to deliver to all subscribers regardless the cost. * * The thread is blocked uninterruptibly while resources for any subscriber are unavailable. * This strategy comes with a risk of thread exhaustion, that will lead to publisher being completely blocked by slow * or incorrectly implemented subscribers. */ BLOCKING, /** * Best effort publisher strategy - tries to deliver to all subscribers if possible without blocking the processing. * * If the buffer is full, publisher invokes {@code onError()} and cancels subscription on a subscriber, that is not * capable of read the messages at a speed sufficient to unblock the processing. */ BEST_EFFORT, } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy