Skip to content

Latest commit

 

History

History
592 lines (395 loc) · 23.8 KB

events.asciidoc

File metadata and controls

592 lines (395 loc) · 23.8 KB

Events

Beans may produce and consume events. This facility allows beans to interact in a completely decoupled fashion, with no compile-time dependency between the interacting beans. Most importantly, it allows stateful beans in one architectural tier of the application to synchronize their internal state with state changes that occur in a different tier.

An event comprises:

  • A Java object - the event object

  • A set of instances of qualifier types - the event qualifiers

The event object acts as a payload, to propagate state from producer to consumer. The event qualifiers act as topic selectors, allowing the consumer to narrow the set of events it observes.

An observer method acts as event consumer, observing events of a specific type - the observed event type - with a specific set of qualifiers - the observed event qualifiers. An observer method will be notified of an event if the event object is assignable to the observed event type, and if the set of observed event qualifiers is a subset of all the event qualifiers of the event.

Event types and qualifier types

An event object is an instance of a concrete Java class with no unresolvable type variables. The event types of the event include all superclasses and interfaces of the runtime class of the event object.

An event type may not contain an unresolvable type variable. A wildcard type is not considered an unresolvable type variable.

An event qualifier type is just an ordinary qualifier type as specified in [defining_qualifier_types], typically defined as @Target({METHOD, FIELD, PARAMETER, TYPE}) or @Target({FIELD, PARAMETER}).

Every event has the qualifier @javax.enterprise.inject.Any, even if it does not explicitly declare this qualifier.

Any Java type may be an observed event type.

Firing events

Beans fire events via an instance of the javax.enterprise.event.Event interface, which may be injected:

@Inject Event<LoggedInEvent> loggedInEvent;

Any combination of qualifiers may be specified at the injection point:

@Inject @Admin Event<LoggedInEvent> adminLoggedInEvent;

Or the application may specify qualifiers dynamically:

@Inject Event<LoggedInEvent> loggedInEvent;
...
LoggedInEvent event = new LoggedInEvent(user);
if ( user.isAdmin() ) {
    loggedInEvent.select( new AdminQualifier() ).fire(event);
}
else {
    loggedInEvent.fire(event);
    loggedInEvent.fireAsync(event);
}

In this example, the event sometimes has the qualifier @Admin, depending upon the value of user.isAdmin(). It is first fired synchronously with fire() then asynchronously with fireAsync().

Firing events synchronously

The method fire() accepts an event object:

public void login() {
    ...
    loggedInEvent.fire( new LoggedInEvent(user) );
}

Event fired with the fire() method is fired synchronously. All the resolved synchronous observers (as defined in Observer resolution) are called in the thread in which fire() was called. A synchronous observer notification blocks the calling thread until it completes.

Firing events asynchronously

Events may also be fired asynchronously using one of the methods fireAsync()

@Inject Event<LoggedInEvent> loggedInEvent;

public void login() {
    ...
    loggedInEvent.fireAsync( new LoggedInEvent(user) );
}

Event fired with the fireAsync() method is fired asynchronously. All the resolved asynchronous observers (as defined in Observer resolution) are called in one or more different threads.

Method fireAsync() returns immediately.

The Event interface

The Event interface provides a method for firing events with a specified combination of type and qualifiers:

public interface Event<T> {

    public void fire(T event);
    public <U extends T> CompletionStage<U> fireAsync(U event);
    public <U extends T> CompletionStage<U> fireAsync(U event, NotificationOptions options);

    public Event<T> select(Annotation... qualifiers);
    public <U extends T> Event<U> select(Class<U> subtype, Annotation... qualifiers);
    public <U extends T> Event<U> select(TypeLiteral<U> subtype, Annotation... qualifiers);

}

For an injected Event:

  • the specified type is the type parameter specified at the injection point, and

  • the specified qualifiers are the qualifiers specified at the injection point.

For example, this injected Event has specified type LoggedInEvent:

@Inject Event<LoggedInEvent> any;

The select() method returns a child Event for a given specified type and additional specified qualifiers. If no specified type is given, the specified type is the same as the parent.

For example, this child Event has required type AdminLoggedInEvent and additional specified qualifier @Admin:

Event<AdminLoggedInEvent> admin = any.select(
            AdminLoggedInEvent.class,
            new AdminQualifier() );

If the specified type contains a type variable, an IllegalArgumentException is thrown.

If two instances of the same qualifier type are passed to select(), an IllegalArgumentException is thrown.

If an instance of an annotation that is not a qualifier type is passed to select(), an IllegalArgumentException is thrown.

The methods fire() and fireAsync() fire an event with the specified qualifiers and notify observers, as defined by Observer notification. If the container is unable to resolve the parameterized type of the event object, it uses the specified type to infer the parameterized type of the event types.

The method fireAsync() may be called with a NotificationOptions object to configure the observer methods notification, e.g. to specify an Executor object to be used for asynchronous delivery. The container is permitted to define other non-portable notification options.

The following elements are container specific:

  • the default Executor used by the container when fireAsync() is called without specifying an Executor,

  • the CompletionStage returned by fireAsync methods, and

  • all dependent stages of this initial CompletionStage.

If the runtime type of the event object contains an unresolvable type variable, an IllegalArgumentException is thrown.

If the runtime type of the event object is assignable to the type of a container lifecycle event, an IllegalArgumentException is thrown.

The built-in Event

The container must provide a built-in bean with:

  • Event<X> in its set of bean types, for every Java type X that does not contain a type variable,

  • every event qualifier type in its set of qualifier types,

  • scope @Dependent,

  • no bean name, and

  • an implementation provided automatically by the container.

If an injection point of raw type Event is defined, the container automatically detects the problem and treats it as a definition error.

The built-in implementation must be a passivation capable dependency, as defined in [passivation_capable_dependency].

Observer resolution

The process of matching an event to its observer methods is called observer resolution. The container considers event type and qualifiers when resolving observers.

Observer resolution usually occurs at runtime.

An event is delivered to an observer method if:

  • The observer method belongs to an enabled bean.

  • An event type is assignable to the observed event type, taking type parameters into consideration.

  • The observer method has no event qualifiers or has a subset of the event qualifiers. An observer method has an event qualifier if it has an observed event qualifier with (a) the same type and (b) the same annotation member value for each member which is not annotated @javax.enterprise.util.Nonbinding.

  • Either the event is not a container lifecycle event, as defined in [init_events], or the observer method belongs to an extension.

  • The event is fired synchronously and the observer is a synchronous observer as defined in Declaring an observer method.

  • The event is fired asynchronously and the observer is an asynchronous observer as defined in Declaring an observer method.

If the runtime type of the event object contains an unresolvable type variable, the container must throw an IllegalArgumentException.

For a custom implementation of the ObserverMethod interface defined in [observer_method], the container must call getObservedType() and getObservedQualifiers() to determine the observed event type and qualifiers, and isAsync() to determine whether the observer is asynchronous or synchronous.

Assignability of type variables, raw and parameterized types

An event type is considered assignable to a type variable if the event type is assignable to the upper bound, if any.

A parameterized event type is considered assignable to a raw observed event type if the raw types are identical.

A parameterized event type is considered assignable to a parameterized observed event type if they have identical raw type and for each parameter:

  • the observed event type parameter is an actual type with identical raw type to the event type parameter, and, if the type is parameterized, the event type parameter is assignable to the observed event type parameter according to these rules, or

  • the observed event type parameter is a wildcard and the event type parameter is assignable to the upper bound, if any, of the wildcard and assignable from the lower bound, if any, of the wildcard, or

  • the observed event type parameter is a type variable and the event type parameter is assignable to the upper bound, if any, of the type variable.

Event qualifier types with members

As usual, the qualifier type may have annotation members:

@Qualifier
@Target(PARAMETER)
@Retention(RUNTIME)
public @interface Role {
    String value();
}

Consider the following event:

@Inject Event<LoggedInEvent> loggedInEvent;
...
public void login() {
    final User user = ...;
    loggedInEvent.select(new RoleQualifier() { public String value() { return user.getRole(); } }).fire(new LoggedInEvent(user));
}

Where RoleQualifier is an implementation of the qualifier type Role:

public abstract class RoleQualifier
        extends AnnotationLiteral<Role>
        implements Role {}

Then the following observer method will always be notified of the event:

public void afterLogin(@Observes LoggedInEvent event) { ... }

Whereas this observer method may or may not be notified, depending upon the value of user.getRole():

public void afterAdminLogin(@Observes @Role("admin") LoggedInEvent event) { ... }

As usual, the container uses equals() to compare event qualifier type member values.

Multiple event qualifiers

An event parameter may have multiple qualifiers.

public void afterDocumentUpdatedByAdmin(@Observes @Updated @ByAdmin Document doc) { ... }

Then this observer method will be notified if the set of observer qualifiers is a subset of the fired event’s qualifiers or an empty set:

@Inject Event<Document> documentEvent;
...
documentEvent.select(new UpdatedQualifier(), new ByAdminQualifier(), new ClarificationQualifier()).fire(document);

In the above example the event is fired with @ByAdmin, @Updated, and @Clarification qualifiers. The observer qualifiers are @Updated and @ByAdmin. Observer qualifiers therefore form a subset of event qualifiers and the observer will be notified.

Other, less specific, observers will also be notified of this event:

public void afterDocumentUpdated(@Observes @Updated Document doc) { ... }
public void afterDocumentEvent(@Observes Document doc) { ... }

On the other hand, following observer will not be notified as slightly different behaviour applies to observers with @Default qualifier:

public void afterDocumentDefaultEvent(@Observes @Default Document doc) { ... }

Such observer will only be notified for events having either no qualifiers or only @Default qualifier:

@Inject Event<Document> documentEvent;
@Inject @Default Event<Document> documentDefaultEvent;
...
documentEvent.fire(document);
documentDefaultEvent.fire(document);

Observer methods

An observer method allows the application to receive and respond to event notifications.

An observer method is a non-abstract method of a managed bean class or of an extension, as defined in [init_events]. An observer method may be either static or non-static.

There may be arbitrarily many observer methods with the same event parameter type and qualifiers.

A bean (or extension) may declare multiple observer methods.

Event parameter of an observer method

Each observer method must have exactly one event parameter, of the same type as the event type it observes. When searching for observer methods for an event, the container considers the type and qualifiers of the event parameter.

If the event parameter does not explicitly declare any qualifier, the observer method observes events with no qualifier.

The event parameter type may contain a type variable or wildcard.

The event parameter may be an array type whose component type contains a type variable or a wildcard.

Modifications made to the event parameter in an observer method are propagated to following observers. The container is not required to guarantee a consistent state for an event parameter modified by asynchronous observers.

Declaring an observer method

An observer method may be declared by annotating a parameter @javax.enterprise.event.Observes or @javax.enterprise.event.ObservesAsync of a default-access, public, protected or private method. That parameter is the event parameter. The declared type of the parameter is the observed event type.

If @Observes is used the observer method is a synchronous observer method.

If @ObservesAsync is used the observer method is an asynchronous observer method.

public void afterLogin(@Observes LoggedInEvent event) { ... }

public void asyncAfterLogin(@ObservesAsync LoggedInEvent event) { ... }

If a method has more than one parameter annotated @Observes or @ObservesAsync, the container automatically detects the problem and treats it as a definition error.

Observed event qualifiers may be declared by annotating the event parameter:

public void afterLogin(@Observes @Admin LoggedInEvent event) { ... }

If an observer method is annotated @Produces or @Inject or has a parameter annotated @Disposes, the container automatically detects the problem and treats it as a definition error.

Interceptors and decorators may not declare observer methods. If an interceptor or decorator has a method with a parameter annotated @Observes or @ObservesAsync, the container automatically detects the problem and treats it as a definition error.

In addition to the event parameter, observer methods may declare additional parameters, which may declare qualifiers. These additional parameters are injection points.

public void afterLogin(@Observes LoggedInEvent event, @Manager User user, Logger log) { ... }

The EventMetadata interface

The interface javax.enterprise.inject.spi.EventMetadata provides access to metadata about an observed event.

public interface EventMetadata {
    public Set<Annotation> getQualifiers();
    public InjectionPoint getInjectionPoint();
    public Type getType();
}
  • getQualifiers() returns the set of qualifiers with which the event was fired.

  • getInjectionPoint() returns the InjectionPoint from which this event payload was fired, or null if it was fired from BeanManager.fireEvent(…​).

  • getType() returns the type representing runtime class of the event object with type variables resolved.

The container must provide a bean with scope @Dependent, bean type EventMetadata and qualifier @Default, allowing observer methods to obtain information about the events they observe.

If an injection point of type EventMetadata and qualifier @Default which is not a parameter of an observer method exists, the container automatically detects the problem and treats it as a definition error.

public void afterLogin(@Observes LoggedInEvent event, EventMetadata metadata) { ... }

Conditional observer methods

A conditional observer method is an observer method which is notified of an event only if an instance of the bean that defines the observer method already exists in the current context.

A conditional observer method may be declared by specifying notifyObserver=IF_EXISTS.

public void refreshOnDocumentUpdate(@Observes(notifyObserver=IF_EXISTS) @Updated Document doc) { ... }

public void asyncRefreshOnDocumentUpdate(@ObservesAsync(notifyObserver=IF_EXISTS) @Updated Document doc) { ... }

Beans with scope @Dependent may not have conditional observer methods. If a bean with scope @Dependent has an observer method declared notifyObserver=IF_EXISTS, the container automatically detects the problem and treats it as a definition error.

The enumeration javax.enterprise.event.Reception identifies the possible values of notifyObserver:

public enum Reception { IF_EXISTS, ALWAYS }

Transactional observer methods

Transactional observer methods are observer methods which receive event notifications during the before or after completion phase of the transaction in which the event was fired. If no transaction is in progress when the event is fired, they are notified at the same time as other observers.

  • A before completion observer method is called during the before completion phase of the transaction.

  • An after completion observer method is called during the after completion phase of the transaction.

  • An after success observer method is called during the after completion phase of the transaction, only when the transaction completes successfully.

  • An after failure observer method is called during the after completion phase of the transaction, only when the transaction fails.

The enumeration javax.enterprise.event.TransactionPhase identifies the kind of transactional observer method:

public enum TransactionPhase {
    IN_PROGRESS,
    BEFORE_COMPLETION,
    AFTER_COMPLETION,
    AFTER_FAILURE,
    AFTER_SUCCESS
}

A transactional observer method may be declared by specifying any value other than IN_PROGRESS for during:

void onDocumentUpdate(@Observes(during=AFTER_SUCCESS) @Updated Document doc) { ... }

Asynchronous observer cannot be declared transactional.

Observer notification

When an event is fired by the application, the container must:

  • determine the observer methods for that event according to the rules of observer resolution defined by Observer resolution, then,

  • for each observer method, either invoke the observer method immediately, or register the observer method for later invocation during the transaction completion phase, using a JTA Synchronization.

  • honor the priority of observer methods as defined in Observer ordering.

The container calls observer methods as defined in [observers_method_invocation].

  • If the observer method is a transactional observer method and there is currently a JTA transaction in progress, the container calls the observer method during the appropriate transaction completion phase.

  • If there is no context active for the scope to which the bean declaring the observer method belongs, then the observer method should not be called.

  • Otherwise, the container calls the observer immediately.

Any observer method called before completion of a transaction may call setRollbackOnly() to force a transaction rollback. An observer method may not directly initiate, commit or rollback JTA transactions.

Observer methods may throw exceptions:

  • If the observer method is a transactional observer method, any exception is caught and logged by the container.

  • If the observer method is asynchronous, the exception aborts processing of the observer but not of the event. Exception management during an asynchronous event is defined in Handling multiple exceptions thrown during an asynchronous event.

  • Otherwise, the exception aborts processing of the event. No other observer methods of that event will be called. The BeanManager.fireEvent() or Event.fire() method rethrows the exception. If the exception is a checked exception, it is wrapped and rethrown as an (unchecked) ObserverException.

For a custom implementation of the ObserverMethod interface defined in [observer_method], the container must call getTransactionPhase() to determine if the observer method is transactional observer method, and notify() which accepts javax.enterprise.inject.spi.EventContext to invoke the method.

Handling multiple exceptions thrown during an asynchronous event

If an event is asynchronous, and an exception is thrown by any of its notified observers, the CompletionStage returned by fireAsync will complete exceptionally with java.util.concurrent.CompletionException exception. CompletionException contains all exceptions thrown by observers as suppressed exceptions. They can be accessed as an array of throwable with the getSuppressed method.

It can be handled with one of the CompletionStage methods related to exceptions:

myEvent.fireAsync(anEventObject)
       .handle((ok, ex) -> {
           if (ok != null) {
               return ok;
           } else {
             for (Throwable t : ex.getSuppressed()) {
                      ...
             }
              ...
           } });

If no exception is thrown by observers then the resulting CompletionStage is completed normally with the event object.

Observer ordering

Before the actual observer notification, the container determines an order in which the observer methods for a certain event are invoked. The priority of an observer method may be declared using the @Priority annotation. If no @Priority annotation is specified, the default priority APPLICATION + 500 is assumed. Observers with smaller priority values are called first.

void afterLogin(@Observes @Priority(APPLICATION) LoggedInEvent event) { ... }

The order of more than one observer with the same priority is undefined and the observer methods are notified therefore in a non predictable order.

Observer method invocation context

The transaction context and lifecycle contexts active when an observer method is invoked depend upon what kind of observer method it is.

  • If the observer method is asynchronous, it is called in a new lifecycle contexts and a new transaction context. As specified in [builtin_contexts], contexts associated with built-in normal scope don’t propagate across asynchronous observers.

  • If the observer method is a before completion transactional observer method, it is called within the context of the transaction that is about to complete and with the same lifecycle contexts.

  • Otherwise, if the observer method is any other kind of transactional observer method, it is called in an unspecified transaction context, but with the same lifecycle contexts as the transaction that just completed.

  • Otherwise, the observer method is called in the same transaction context and lifecycle contexts as the invocation of Event.fire() or BeanManager.fireEvent().