jakarta.enterprise.concurrent.Asynchronous Maven / Gradle / Ivy
/*
* Copyright (c) 2021 Contributors to the Eclipse Foundation
*
* 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 jakarta.enterprise.concurrent;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.CompletableFuture;
import jakarta.enterprise.util.Nonbinding;
import jakarta.interceptor.InterceptorBinding;
/**
* Annotates a CDI managed bean method to run asynchronously.
* The CDI managed bean must not be a Jakarta Enterprise Bean,
* and neither the method nor its class can be annotated with
* the MicroProfile Asynchronous annotation.
*
* The Jakarta EE Product Provider runs the method on a {@link ManagedExecutorService}
* and returns to the caller a {@link java.util.concurrent.CompletableFuture CompletableFuture}
* that is backed by the same ManagedExecutorService
* to represent the execution of the method. The ManagedExecutorService
* is the default asynchronous execution facility for the CompletableFuture
* and and all dependent stages that are created from those, and so on,
* as defined by the ManagedExecutorService
JavaDoc API.
* The Jakarta EE Product Provider makes this CompletableFuture
available
* to the asynchronous method implementation via the
* {@link Result#getFuture Asynchronous.Result.getFuture} and
* {@link Result#complete Asynchronous.Result.complete} methods.
*
* For example,
*
*
* {@literal @}Asynchronous
* public CompletableFuture{@literal } hoursWorked(LocalDate from, LocalDate to) {
* // Application component's context is made available to the async method,
* try (Connection con = ((DataSource) InitialContext.doLookup(
* "java:comp/env/jdbc/timesheetDB")).getConnection()) {
* ...
* return Asynchronous.Result.complete(total);
* } catch (NamingException | SQLException x) {
* throw new CompletionException(x);
* }
* }
*
*
* with usage,
*
*
* hoursWorked(mon, fri).thenAccept(total {@literal ->} {
* // Application component's context is made available to dependent stage actions,
* DataSource ds = InitialContext.doLookup(
* "java:comp/env/jdbc/payrollDB");
* ...
* });
*
*
* When the asynchronous method implementation returns a different
* CompletableFuture
instance, the Jakarta EE Product Provider
* uses the completion of that instance to complete the CompletableFuture
* that the Jakarta EE Product Provider returns to the caller,
* completing it with the same result or exception.
*
* For example,
*
*
* {@literal @}Asynchronous
* public CompletableFuture{@literal >} findSingleLayoverFlights(Location source, Location dest) {
* try {
* ManagedExecutorService executor = InitialContext.doLookup(
* "java:comp/DefaultManagedExecutorService");
*
* return executor.supplyAsync(source::flightsFrom)
* .thenCombine(executor.completedFuture(dest.flightsTo()),
* Itinerary::sourceMatchingDest);
* } catch (NamingException x) {
* throw new CompletionException(x);
* }
* }
*
*
* with usage,
*
*
* findSingleLayoverFlights(RST, DEN).thenApply(Itinerary::sortByPrice);
*
*
*
* Methods with the following return types can be annotated to be
* asynchronous methods:
*
* - {@link java.util.concurrent.CompletableFuture CompletableFuture}
* - {@link java.util.concurrent.CompletionStage CompletionStage}
* void
*
*
* The Jakarta EE Product Provider raises
* {@link java.lang.UnsupportedOperationException UnsupportedOperationException}
* if other return types are used or if the annotation is placed at the class
* level. The injection target of ElementType.TYPE
is to be used only
* by the CDI extension that is implemented by the Jakarta EE Product Provider to
* register the asynchronous method interceptor. Applications must only use the
* asynchronous method annotation at method level.
*
* Exceptions that are raised by asynchronous methods are not raised directly
* to the caller because the method runs asynchronously to the caller.
* Instead, the CompletableFuture
that represents the result
* is completed with the raised exception. Asynchronous methods are
* discouraged from raising checked exceptions because checked exceptions
* force the caller to write exception handling code that is unreachable.
* When a checked exception occurs, the asynchronous method implementation
* can flow the exception back to the resulting CompletableFuture
* either by raising a
* {@link java.util.concurrent.CompletionException CompletionException}
* with the original exception as the cause, or it can take the equivalent
* approach of exceptionally completing the CompletableFuture
, using
* {@link java.util.concurrent.CompletableFuture#completeExceptionally completeExceptionally}
* to supply the original exception as the cause.
*
* Except where otherwise stated, the Jakarta EE Product Provider raises
* {@link java.util.concurrent.RejectedExecutionException RejectedExecutionException}
* upon invocation of the asynchronous method if evident upfront that it cannot
* be accepted, for example if the JNDI name is not valid or points to something
* other than a managed executor resource. If determined at a later point that the
* asynchronous method cannot run (for example, if unable to establish thread context),
* then the Jakarta EE Product Provider completes the CompletableFuture
* exceptionally with {@link java.util.concurrent.CancellationException CancellationException},
* and chains a cause exception if there is any.
*
* The Jakarta EE Product Provider must assign the interceptor for asynchronous methods
* to have priority of Interceptor.Priority.PLATFORM_BEFORE + 5
.
* Interceptors with a lower priority, such as Transactional
, must run on
* the thread where the asynchronous method executes, rather than on the submitting thread.
* When an asynchronous method is annotated as Transactional
,
* the transactional types which can be used are:
* TxType.REQUIRES_NEW
, which causes the method to run in a new transaction, and
* TxType.NOT_SUPPORTED
, which causes the method to run with no transaction.
* All other transaction attributes must result in
* {@link java.lang.UnsupportedOperationException UnsupportedOperationException}
* upon invocation of the asynchronous method.
*
* @since 3.0
*/
// TODO the above restrictions on Transactional interceptors could be eliminated
// if transaction context propagation is later added to the spec.
@Documented
@Inherited
@InterceptorBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE })
public @interface Asynchronous {
/**
* JNDI name of a {@link ManagedExecutorService} or {@link ManagedScheduledExecutorService}
* upon which to run the asynchronous method.
*
* The default value is the JNDI name of the built-in ManagedExecutorService
* that is provided by the Jakarta EE platform provider:
* java:comp/DefaultManagedExecutorService
*
* @return managed executor service JNDI name.
*/
@Nonbinding
String executor() default "java:comp/DefaultManagedExecutorService";
/**
* Mechanism by which the Jakarta EE Product Provider makes available
* to the asynchronous method implementation the same
* {@link java.util.concurrent.CompletableFuture CompletableFuture}
* instance that the Jakarta EE Product Provider supplies to the caller
* of the asynchronous method.
*
* Before invoking the asynchronous method implementation on a thread,
* the Jakarta EE Product Provider invokes the {@link #setFuture} method
* which makes available to the asynchronous method implementation
* the same CompletableFuture
that the Jakarta EE Product Provider
* returns to the caller.
*
* The asynchronous method implementation invokes the {@link #getFuture} method
* to obtain the same CompletableFuture
that the
* Jakarta EE Product Provider returns to the caller.
* The asynchronous method implementation can choose to complete
* this future (normally or exceptionally) or otherwise arrange for its
* completion, for example upon completion of a pipeline of completion stages.
* Having this same CompletableFuture
also enables the asynchronous
* method implementation to determine if the caller has forcibly completed
* (such as by cancellation or any other means) the CompletableFuture
,
* in which case the asynchronous method implementation could decide to end
* immediately rather than continue processing.
*
* For example,
*
*
* {@literal @}Asynchronous
* public CompletableFuture{@literal } hoursWorked(LocalDateTime from, LocalDateTime to) {
* CompletableFuture{@literal } future = Asynchronous.Result.getFuture();
* if (future.isDone())
* return future;
*
* try (Connection con = ((DataSource) InitialContext.doLookup(
* "java:comp/env/jdbc/timesheetDB")).getConnection()) {
* ...
* for (ResultSet result = stmt.executeQuery(); result.next() {@literal &&} !future.isDone(); )
* ...
* future.complete(total);
* } catch (NamingException | SQLException x) {
* future.completeExceptionally(x);
* }
* return future;
* }
*
*
* After the asynchronous method completes, the Jakarta EE Product Provider
* invokes the {@link #setFuture} method with a null
value
* to clear it from the thread.
*
* @since 3.0
*/
public static final class Result {
private static final ThreadLocal> FUTURES = new ThreadLocal>();
// Prevent instantiation
private Result() {
}
/**
* Completes the {@link java.util.concurrent.CompletableFuture CompletableFuture}
* instance that the Jakarta EE Product Provider supplies to the caller of the
* asynchronous method.
*
* This method must only be invoked by the asynchronous method implementation.
*
* @param type of result returned by the asynchronous method's CompletableFuture
.
* @param result result with which to complete the asynchronous method's CompletableFuture
.
* @return the same CompletableFuture
that the container returns to the caller.
* @throws IllegalStateException if the CompletableFuture
for an asynchronous
* method is not present on the thread.
*/
public static CompletableFuture complete(final T result) {
@SuppressWarnings("unchecked")
CompletableFuture future = (CompletableFuture) FUTURES.get();
if (future == null) {
throw new IllegalStateException();
}
future.complete(result);
return future;
}
/**
* Obtains the same {@link java.util.concurrent.CompletableFuture CompletableFuture}
* instance that the Jakarta EE Product Provider supplies to the caller of the
* asynchronous method.
*
* This method must only be invoked by the asynchronous method implementation.
*
* @param type of result returned by the asynchronous method's CompletableFuture
.
* @return the same CompletableFuture
that the container returns to the caller.
* @throws IllegalStateException if the CompletableFuture
for an asynchronous
* method is not present on the thread.
*/
public static CompletableFuture getFuture() {
@SuppressWarnings("unchecked")
CompletableFuture future = (CompletableFuture) FUTURES.get();
if (future == null) {
throw new IllegalStateException();
}
return future;
}
/**
* Before invoking the asynchronous method implementation on a thread,
* the Jakarta EE Product Provider invokes this method to make available
* to the asynchronous method implementation the same CompletableFuture
* that the Jakarta EE Product Provider returns to the caller.
*
* After the asynchronous method completes, the Jakarta EE Product Provider
* invokes this method with a null
value
* to clear it from the thread.
*
* This method must only be invoked by the Jakarta EE Product Provider.
*
* @param type of result returned by the asynchronous method's CompletableFuture
.
* @param future CompletableFuture
that the container returns to the caller,
* or null
to clear it.
*/
public static void setFuture(final CompletableFuture future) {
if (future == null) {
FUTURES.remove();
} else {
FUTURES.set(future);
}
}
}
}