SeamFramework.orgCommunity Documentation
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:
not only are event producers decoupled from observers; observers are completely decoupled from producers,
observers can specify a combination of "selectors" to narrow the set of event notifications they will receive, and
observers can be notified immediately, or can specify that delivery of the event should be delayed until the end of the current transaction
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 bindingsin 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) { ... }
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.
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>(){} );
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(); } } );
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) { ... }}}
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 Product
s 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);
}
}