Chapter 8. Events

The Web Beans event notification facility allows Web Beans to interact in a totally decoupled manner. Event producers raise events that are then delivered to event observers by the Web Bean manager. This basic schema might sound like the familiar observer/observable pattern, but there are a couple of twists:

8.1. Event observers

An observer method is a method of a Web Bean with a parameter annotated @Observes.

public void onAnyDocumentEvent(@Observes Document document) { ... }

The annotated parameter is called the event parameter. The type of the event parameter is the observed event type. Observer methods may also specify "selectors", which are just instances of Web Beans binding types. When a binding type is used as an event selector, it is called an event binding type.

@BindingType
@Target({PARAMETER, FIELD})
@Retention(RUNTIME)
public @interface Updated { ... }

We specify the event bindings of the observer method by annotating the event parameter:

public void afterDocumentUpdate(@Observes @Updated Document document) { ... }

An observer method need not specify any event bindings—in this case it is interested in all events of a particular type. If it does specify event bindings, it is only interested in events which also have those event bindings.

The observer method may have additional parameters, which are injected according to the usual Web Beans method parameter injection semantics:

public void afterDocumentUpdate(@Observes @Updated Document document, User user) { ... }

8.2. Event producers

The event producer may obtain an event notifier object by injection:

@Observable Event<Document> documentEvent

The @Observable annotation implicitly defines a Web Bean with scope @Dependent and deployment type @Standard, with an implementation provided by the Web Bean manager.

A producer raises events by calling the fire() method of the Event interface, passing an event object:

documentEvent.fire(document);

An event object may be an instance of any Java class that has no type variables or wildcard type parameters. The event will be delivered to every observer method that:

  • has an event parameter to which the event object is assignable, and

  • specifies no event bindings.

The Web Bean manager simply calls all the observer methods, passing the event object as the value of the event parameter. If any observer method throws an exception, the Web Bean manager stops calling observer methods, and the exception is rethrown by the fire() method.

To specify a "selector", the event producer may pass an instance of the event binding type to the fire() method:

documentEvent.fire( document, new AnnotationLiteral<Updated>(){} );

The helper class AnnotationLiteral makes it possible to instantiate binding types inline, since this is otherwise difficult to do in Java.

The event will be delivered to every observer method that:

  • has an event parameter to which the event object is assignable, and

  • does not specify any event binding except for the event bindings passed to fire().

Alternatively, event bindings may be specified by annotating the event notifier injection point:

@Observable @Updated Event<Document> documentUpdatedEvent

Then every event fired via this instance of Event has the annotated event binding. The event will be delivered to every observer method that:

  • has an event parameter to which the event object is assignable, and

  • does not specify any event binding except for the event bindings passed to fire() or the annotated event bindings of the event notifier injection point.

8.3. Registering observers dynamically

It's often useful to register an event observer dynamically. The application may implement the Observer interface and register an instance with an event notifier by calling the observe() method.

documentEvent.observe( new Observer<Document>() { public void notify(Document doc) { ... } } );

Event binding types may be specified by the event notifier injection point or by passing event binding type instances to the observe() method:

documentEvent.observe( new Observer<Document>() { public void notify(Document doc) { ... } }, 
                                                new AnnotationLiteral<Updated>(){} );

8.4. Event bindings with members

An event binding type may have annotation members:

@BindingType
@Target({PARAMETER, FIELD})
@Retention(RUNTIME)
public @interface Role {
    RoleType value();
}

The member value is used to narrow the messages delivered to the observer:

public void adminLoggedIn(@Observes @Role(ADMIN) LoggedIn event) { ... }

Event binding type members may be specified statically by the event producer, via annotations at the event notifier injection point:

@Observable @Role(ADMIN) Event<LoggedIn> LoggedInEvent;}}

Alternatively, the value of the event binding type member may be determined dynamically by the event producer. We start by writing an abstract subclass of AnnotationLiteral:

abstract class RoleBinding 
    extends AnnotationLiteral<Role> 
    implements Role {}

The event producer passes an instance of this class to fire():

documentEvent.fire( document, new RoleBinding() { public void value() { return user.getRole(); } } );

8.5. Multiple event bindings

Event binding types may be combined, for example:

@Observable @Blog Event<Document> blogEvent;
...
if (document.isBlog()) blogEvent.fire(document, new AnnotationLiteral<Updated>(){});

When this event occurs, all of the following observer methods will be notified:

public void afterBlogUpdate(@Observes @Updated @Blog Document document) { ... }
public void afterDocumentUpdate(@Observes @Updated Document document) { ... }
public void onAnyBlogEvent(@Observes @Blog Document document) { ... }
public void onAnyDocumentEvent(@Observes Document document) { ... }}}

8.6. Transactional observers

Transactional observers receive their event notifications during the before or after completion phase of the transaction in which the event was raised. For example, the following observer method needs to refresh a query result set that is cached in the application context, but only when transactions that update the Category tree succeed:

public void refreshCategoryTree(@AfterTransactionSuccess @Observes CategoryUpdateEvent event) { ... }

There are three kinds of transactional observers:

  • @AfterTransactionSuccess observers are called during the after completion phase of the transaction, but only if the transaction completes successfully

  • @AfterTransactionFailure observers are called during the after completion phase of the transaction, but only if the transaction fails to complete successfully

  • @AfterTransactionCompletion observers are called during the after completion phase of the transaction

  • @BeforeTransactionCompletion observers are called during the before completion phase of the transaction

Transactional observers are very important in a stateful object model like Web Beans, because state is often held for longer than a single atomic transaction.

Imagine that we have cached a JPA query result set in the application scope:

@ApplicationScoped @Singleton
public class Catalog {

    @PersistenceContext EntityManager em;
    
    List<Product> products;

    @Produces @Catalog 
    List<Product> getCatalog() {
        if (products==null) {
            products = em.createQuery("select p from Product p where p.deleted = false")
                .getResultList();
        }
        return products;
    }
    
}

From time to time, a Product is created or deleted. When this occurs, we need to refresh the Product catalog. But we should wait until after the transaction completes successfully before performing this refresh!

The Web Bean that creates and deletes Products could raise events, for example:

@Stateless
public class ProductManager {

    @PersistenceContext EntityManager em;
    @Observable Event<Product> productEvent;

    public void delete(Product product) {
        em.delete(product);
        productEvent.fire(product, new AnnotationLiteral<Deleted>(){});
    }
    
    public void persist(Product product) {
        em.persist(product);
        productEvent.fire(product, new AnnotationLiteral<Created>(){});
    }
    
    ...
    
}

And now Catalog can observe the events after successful completion of the transaction:

@ApplicationScoped @Singleton
public class Catalog {

    ...
    
    void addProduct(@AfterTransactionSuccess @Observes @Created Product product) {
        products.add(product);
    }
    
    void addProduct(@AfterTransactionSuccess @Observes @Deleted Product product) {
        products.remove(product);
    }
    
}