Chapter 7. Events

Web Beans may produce and consume events. This facility allows Web Beans to interact in a completely decoupled fashion, with no compile-time dependency between the two Web Beans.

An event comprises:

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

An event consumer observes events of a specific type, the observed event type, with a specific set of instances of event binding types, the observed event bindings.

7.1. Event types and binding types

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

An event binding type is just an ordinary binding type as specified in Section 2.3.2, “Defining binding types” with the exception that it may be declared @Target({FIELD, PARAMETER}).

More formally, an event binding type is a Java annotation defined as @Target({FIELD, PARAMETER}) or @Target({METHOD, FIELD, PARAMETER, TYPE}) and @Retention(RUNTIME). All event binding types must specify the @BindingType meta-annotation.

An event consumer will be notified of an event if the observed event type it specifies is one of the event types of the event, and if all the observed event bindings it specifies are event bindings of the event.

7.2. Firing an event via the Manager interface

The Manager interface provides a method for firing events:

public interface Manager {
      
    public void fireEvent(Object event, Annotation... bindings);
    
    ...
      
}

The first argument is the event object:

public void login() {
    ...
    manager.fireEvent( new LoggedInEvent(user) );
    
}

If the type of the event object passed to fireEvent() contains type variables or wildcards, an IllegalArgumentException is thrown.

The remaining arguments are the event bindings, optional instances of event binding types:

public void login() {
    User user = ...;
    manager.fireEvent( user, new LoggedInBinding() {} );
    
}

where LoggedInBinding is an implementation of the event binding type LoggedIn:

public class LoggedInBinding 
        extends AnnotationLiteral<LoggedIn> 
        implements LoggedIn {}

7.3. Observing events via the Observer interface

An observer consumes events and allows the application to react to events that occur.

Observers of Web Beans events implement the Observer interface.

public interface Observer<T> {
      
    public void notify(T event);
    
}

An observer instance may be registered with the Web Bean manager by calling Manager.addObserver():

public interface Manager {
      
    public <T> Manager addObserver(Observer<T> observer, Class<T> eventType,
                                      Annotation... bindings);
    public <T> Manager addObserver(Observer<T> observer, TypeLiteral<T> eventType, 
                                      Annotation... bindings);
      
    ...
      
}

The first parameter is the observer object. The second parameter is the observed event type. The remaining parameters are optional observed event binding types. The observer is notified when an event object that is assignable to the observed event type is raised with the observed event binding types.

An observer instance may be deregistered by calling Manager.removeObserver():

public interface Manager {
    
    public <T> Manager removeObserver(Observer<T> observer, TypeLiteral<T> eventType, 
                                         Annotation... bindings);
    public <T> Manager removeObserver(Observer<T> observer, Class<T> eventType, 
                                         Annotation... bindings);
      
    ...
      
}

If the observed event type passed to addObserver() or removeObserver() contains type variables or wildcards, an IllegalArgumentException is thrown.

If two instances of the same binding type are passed to addObserver() or removeObserver(), a DuplicateBindingTypeException is thrown.

If an instance of an annotation that is not a binding type is passed to addObserver() or removeObserver(), an IllegalArgumentException is thrown.

7.4. Observer invocation

When an event is fired by the application, the Web Bean manager must:

  • determine the observers for that event by calling Manager.resolveObservers(), passing the event object and all event binding type instances, then,

  • for each observer, call the notify() method of the Observer interface, passing the event object.

Observers may throw exceptions. If an observer throws an exception, the exception aborts processing of the event. No other observers of that event will be called. The fireEvent() method rethrows the exception.

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

7.5. Observer methods

An observer method is an observer defined via annotations, instead of by explicitly implementing the Observer interface.

An observer method must be a non-static method of a simple Web Bean implementation class or enterprise Web Bean implementation class. If the Web Bean is an enterprise Web Bean, the observer method must be a business method of the EJB.

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

A Web Bean may declare multiple observer methods.

7.5.1. 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 Web Bean manager considers the type and binding types of the event parameter.

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

If the type of the event parameter contains type variables or wildcards, a DefinitionException is thrown by the Web Bean manager at initialization time.

7.5.2. Declaring an observer method using annotations

A observer method may be declared using annotations by annotating a parameter @Observes. That parameter is the event parameter.

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

If a method has more than one parameter annotated @Observes, a DefinitionException is thrown by the Web Bean manager at initialization time.

If an observer method is annotated @Produces, @Initializer or @Destructor, or has a parameter annotated @Disposes, a DefinitionException is thrown by the Web Bean manager at initialization time.

The event parameter may declare binding types:

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

7.5.3. Declaring an observer method using XML

For a Web Beans defined in XML, an observer method may be declared using the method name, the <Observes> element, and the parameter types of the method:

<myapp:afterLogin>
    <Observes>
        <myapp:LoggedInEvent/>
    </Observes>
</myapp:afterLogin>
<myapp:afterLogin>
    <Observes>
        <myapp:LoggedInEvent>
            <myapp:Admin/>
        </myapp:LoggedInEvent>
    </Observes>
</myapp:afterLogin>

When an observer method is declared in XML, the Web Bean manager ignores binding annotations applied to the Java method parameters.

If the implementation class of a Web Bean declared in XML does not have a method with parameters that match those declared in XML, a NonexistentMethodException is thrown by the Web Bean manager at initialization time.

7.5.4. Observer method parameters

In addition to the event parameter, observer methods may declare additional parameters, which may declare binding types. The Web Bean manager calls Manager.getInstanceByType() to determine a value for each parameter of an observer method and calls the observer method with those parameter values.

public void afterLogin(@Observes LoggedInEvent event, @Manager User user, @Logger Log log) { ... }
public void afterAdminLogin(@Observes @Admin LoggedInEvent event, @Logger Log log) { ... }
<myapp:afterLogin>
    
    <Observes>
        <myapp:LoggedInEvent/>
    </Observes>
    
    <myapp:User>
        <myapp:Manager/>
    </myapp:User>
    
    <myfwk:Log>
        <myfwk:Logger/>
    </myfwk:Log>
    
</myapp:afterLogin>
<myapp:afterAdminLogin>
    
    <Observes>
        <myapp:LoggedInEvent>
            <myapp:Admin/>
        </myapp:LoggedInEvent>
    </Observes>
    
    <myfwk:Log>
        <myfwk:Logger/>
    </myfwk:Log>
    
</myapp:afterAdminLogin>

7.5.5. Conditional observers

Conditional observers are observer methods which are notified of an event only if an instance of the Web Bean that defines the observer method already exists in the current context.

A conditional observers may be declared by annotating the event parameter with the @IfExists annotation.

public void refreshOnDocumentUpdate(@IfExists @Observes @Updated Document doc) { ... }

Conditional observers may be declared in XML by adding a child <IfExists> element to the <Observes> element.

<myapp:refreshOnDocumentUpdate>
    <Observes>
        <IfExists/>
        <myapp:Document>
            <myapp:Updated/>
        </myapp:Document>
    </Observes>
</myapp:refreshOnDocumentUpdate>

7.5.6. Transactional observers

Transactional observers 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.

Transactional observers may be declared by annotating the event parameter of the observer method.

  • The @AfterTransactionCompletion annotation specifies that an observer method should be called during the after completion phase.

  • The @AfterTransactionSuccess annotation specifies that an observer method should be called during the after completion phase, only when the transaction completes successfully.

  • The @AfterTransactionFailure annotation specifies that an observer method should be called during the after completion phase, only when the transaction fails.

  • The @BeforeTransactionCompletion annotation specifies that an observer method should be called during the before completion phase.

void onDocumentUpdate(@Observes @AfterTransactionSuccess @Updated Document doc) { ... }

Transactional observers may be declared in XML by a child element of the <Observes> element.

  • The <AfterTransactionCompletion> element specifies that the observer method should be called during the after completion phase.

  • The <AfterTransactionSuccess> element specifies that the observer method should be called during the after completion phase, only when the transaction completes successfully.

  • The <AfterTransactionFailure> element specifies that the observer method should be called during the after completion phase, only when the transaction fails.

  • The <BeforeTransactionCompletion> element specifies that the observer method should be called during the before completion phase.

<myapp:onDocumentUpdate>
    <Observes>
        <AfterTransactionSuccess/>
        <myapp:Document>
            <myapp:Updated/>
        </myapp:Document>
    </Observes>
</myapp:onDocumentUpdate>

7.5.7. Observer object for an observer method

For every observer method of an enabled Web Bean, the Web Bean manager is responsible for providing and registering an appropriate implementation of the Observer interface, that delegates event notifications to the observer method.

The notify() method of the Observer implementation for an observer method either invokes the observer method immediately, or registers the observer method for later invocation during the transaction completion phase, via a JTA Synchronization object.

  • If the observer is a transactional observer and there is currently a JTA transaction in progress, the observer object calls the observer method during the appropriate transaction completion phase. At the appropriate point during the completion phase of the transaction, the Web Bean manager invokes the observer method. If the observer is a method of an EJB bean, the method is called with the same client invocation context as the call to the JTA Synchronization interface.

  • Otherwise, the Web Bean manager calls the observer immediately. If the observer is a method of an EJB bean, the method is called in the client invocation context of the code that called Event.fire().

To invoke an observer method, the Web Bean manager must:

  • obtain the Bean object for the most specialized Web Bean that specializes the Web Bean which declares the observer method, and then

  • obtain the context object by calling Manager.getContext(), passing the Web Bean scope, then

  • obtain an instance of the Web Bean by calling Context.get(), passing the Bean instance representing the Web Bean and false if this observer method is a conditional observer or true otherwise as the value of the create parameter, and then

  • if the get() method returned a non-null value, invoke the observer method on the returned instance, passing the event object to the event parameter and passing the object returned by Manager.getInstanceByType() to each of the other parameters.

Observer methods may throw exceptions:

  • If the observer is a transactional observer, any exception is caught and logged by the Web Bean manager.

  • Otherwise, the exception is rethrown by the notify() method of the observer object. If the exception is a checked exception, it is wrapped and rethrown as an (unchecked) ObserverException.

The observer object is registered by calling Manager.addObserver(), passing the event parameter type as the observed event type, and the binding types of the event parameter as the observed event binding types.

7.6. The Event interface

Alternatively, an instance of the Event interface may be injected via use of the @Observable binding annotation:

@Observable Event<LoggedInEvent> loggedInEvent;

Additional binding annotations may be specified at the injection point:

@Observable @Admin Event<LoggedInEvent> loggedInEvent;

The Event interface provides a method for firing events of a specific type, and a method for registering observers for events of the same type:

public interface Event<T> {
      
    public void fire(T event, Annotation... bindings);
    
    public void observe(Observer<T> observer, Annotation... bindings);
      
}

The first parameter of fire() is the event object. The remaining parameters are event binding types.

The first parameter of observe() is the observer object. The remaining parameters are the observed event binding types.

If two instances of the same binding type are passed to fire() or observes(), a DuplicateBindingTypeException is thrown.

If an instance of an annotation that is not a binding type is passed to fire() or observes(), an IllegalArgumentException is thrown.

The @Observable annotation or <Observable> element may be applied to any field of a Web Bean implementation class or to any parameter of a producer method, initializer method, disposal method, Web Bean remove method or Web Bean constructor where the type of the field or parameter is Event, and an actual type parameter is specified.

If the type of the injection point is not of type Event, if no actual type parameter is specified, or if the type parameter contains a type variable or wildcard, a DefinitionException is thrown by the Web Bean manager at initialization time.

Whenever the @Observable annotation appears at an injection point, an implicit Web Beans exists with:

  • exactly the API type and binding annotations that appear at the injection point,

  • deployment type @Standard,

  • @Dependent scope,

  • no Web Bean name, and

  • an implementation provided automatically by the Web Bean manager.

The fire() method of the provided implementation of Event must call Manager.fireEvent(), passing the following parameters:

  • the event object passed to Event.fire()

  • all binding annotations declared at the injection point, except @Observable

  • all binding annotation instances passed to Event.fire()

The application may fire events by calling the fire() method:

@Observable @LoggedIn Event<User> loggedInEvent;
...
if ( user.isAdmin() ) {
    loggedInEvent.fire( user, new AdminBinding() {} );
}
else {
    loggedInEvent.fire(user);
}

In this example, an event of type User, with binding types @LoggedIn and, sometimes, @Admin occurs.

The observe() method of the provided implementation of Event must call Manager.addObserver(), passing the following parameters:

  • the observer object passed to Event.observe()

  • all binding annotations declared at the injection point, except @Observable

  • all binding annotation instances passed to Event.observe()

The application may register observers by calling the observe() method:

@Observable @LoggedIn Event<User> loggedInEvent;
...
loggedInEvent.observe( new Observer<User>() { public void notify(User user) { ... } } );

7.7. Observer resolution

The method Manager.resolveObservers() resolves observers for an event:

public interface Manager {
      
      public <T> Set<Observer<T>> resolveObservers(T event, Annotation... bindings);
      
      ...
      
}

The first parameter of resolveObservers() is the event object. The remaining parameters are event binding types.

If the type of the event object passed to resolveObservers() contains type variables or wildcards, an IllegalArgumentException is thrown.

If two instances of the same binding type are passed to resolveObservers(), a DuplicateBindingTypeException is thrown.

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

When searching for observers for an event, the Web Bean manager searches for observers which satisfy the following rules:

  • the event object must be assignable to the observed event type, taking type parameters into consideration, and

  • for each observed event binding type, (a) an instance of the binding type must have been passed to fireEvent() and (b) any member values of the binding type must match the member values of the instance passed to fireEvent().

7.7.1. Event binding annotations with members

As usual, the binding type may have annotation members:

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

Consider the following event:

public void login() {
    final User user = ...;
    manager.fireEvent( new LoggedInEvent(user), 
            new RoleBinding() { public String value() { return user.getRole(); } );
}

Where RoleBinding is an implementation of the binding type Role:

public abstract class RoleBinding 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 Web Bean manager uses equals() to compare event binding type member values.

7.7.2. Multiple event binding annotations

An event parameter may have multiple binding annotations:

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

Then this observer method will only be notified if all the event binding types are specified when the event is fired:

manager.fireEvent( document, new UpdatedBinding() {}, new ByAdminBinding() {} );

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

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