The Observer Pattern is a very popular software design pattern in every object oriented programming language. The concept is that an object, the subject, will be monitored by one or more objects, the observer(s), which will be notified when specific state changes happen on the subject. The state change is called an event and this pattern is at the core of most event-handling systems.
Events are part of Java SE since its very beginning and have always been standard in common UI frameworks such as AWT, Swing, and JavaFX. By contrast, Java EE never had a specific JSR to attend to such requirements until the JSR 299 (Context and Dependency Injection for Java EE) release that defines an event-handling mechanism which is completely integrated with Java EE and easy to use.
In order to show an example of this mechanism, we're going to create an auditing module for the Store application, which is very similar to what has been accomplished by the logging interceptor in the previous section, illustrating key concepts of event handling in Java EE 6.
Defining auditing can be very tricky, but in the context of our example, it means displaying additional information for a specific function or method call. In a sense, it will be very similar to a log entry, but for the sake of the example, the audit entry will have more information, such as method parameters and possibly the response of the method call. The solution is illustrated by the following diagram:
The details of the solution and how to create its components are given as follows:
com.packt.store.audit
in the Store project.AuditEvent
. This class defines the event data's structure:package com.packt.store.audit; public class AuditEvent { private final long timestamp = System.currentTimeMillis(); private String message; private Object[] params = null; public AuditEvent(String message, Object[] params) { this.message = message; this.params = params; } public String toString() { StringBuilder sb = new StringBuilder(); sb.append("[").append(new Date(timestamp)).append("] - ") .append(message); if (getParams() != null) { sb.append("- Param value(s): "); for (Object o : params) sb.append(o).append(","); sb.deleteCharAt(sb.length() - 1); } return sb.toString(); } // getters and setters }
Audit
. It will be the binder between the interceptor and our code, marking the classes or methods that must be audited. This is the same concept we saw when implementing the log interceptor:package com.packt.store.audit; //imports omitted for brevity @Inherited @InterceptorBinding @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.TYPE}) public @interface Audit { }
@Enter
and @Exit
, which will act as event qualifiers. A CDI qualifier is a special annotation that can be applied to a class or field to indicate the kind of bean we're working with. In our example, CDI qualifiers will differentiate the events and qualify them into two categories, representing the entry and exit points of a method:package com.packt.store.audit; //imports omitted for brevity @Qualifier @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE}) public @interface Enter { } package com.packt.store.audit; //imports omitted for brevity @Qualifier @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE}) public @interface Exit { }
AuditHandler
. This class will simply print the audit message to the standard output, but a creative reader can actually implement anything here, such as publishing the message to a JMS queue or making a web service call. Note that we're using qualifiers to filter which event the methods should listen to:package com.packt.store.audit; // imports omitted for brevity @Stateless @Named public class AuditHandler { private static final String PREFIX = " [ AUDIT ] "; private static final String ENTER = "[ Entering ]"; private static final String EXIT = "[ Exiting ]"; public void logEnter(@Observes @Enter AuditEvent event) { System.out.println(PREFIX + ENTER + event); } public void logExit(@Observes @Exit AuditEvent event) { System.out.println(PREFIX + EXIT + event); } }
AuditInterceptor
, which will be the actual interceptor that traps the messages from the annotated classes or methods and forwards them as CDI events. The events are observed by AuditHandler
, but there are no dependencies in compile or design time between the two classes:package com.packt.store.audit; //imports omitted for brevity @Audit @Interceptor public class AuditInterceptor implements Serializable { private static final long serialVersionUID = 1L; @Inject @Enter // The Event referenced here is javax.enterprise.event.Event Event<AuditEvent> enterEvent; @Inject @Exit Event<AuditEvent> exitEvent; @AroundInvoke public Object auditMethod(InvocationContext ic) throws Exception { enterEvent.fire(new AuditEvent(ic.getMethod().toString(), (ic.getParameters().length > 0 ? ic.getParameters() : null))); Object obj = ic.proceed(); exitEvent.fire(new AuditEvent(ic.getMethod().toString(), (ic.getParameters().length > 0 ? ic.getParameters() : null))); return obj; } }
Note the usage of the @Enter
and @Exit
qualifiers in the event objects.
If you compare this interceptor implementation to the one created for the logging mechanism, you will notice that it isn't calling the handler directly as we did before, it just publishes events that will be consumed by components that the interceptor doesn't have to know about. This is one benefit of this approach, decoupling the producers and consumers and creating a more flexible structure.
beans.xml
file (under /WEB-INF/
) to tell the container that the AuditInterceptor
class must be loaded as an interceptor:<beans xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd"> <interceptors> <class>com.packt.store.log.LogInterceptor</class><class>com.packt.store.audit.AuditInterceptor</class> </interceptors> </beans>
SearchManager
class and add the @Audit
decorator to this class. This will perform the audit functionality on every method of the class:@Named("search")
@SessionScoped
@Audit
public class SearchManager implements Serializable {
…
http://localhost:7001/store/index.jsf
and check the output of the server to see the audit entries. If the server was started from Eclipse, you can see them on the Console tab:<Nov 17, 2012 9:25:03 PM BRST> <Notice> <Stdout> <BEA-000000> <[ AUDIT ][ Exiting ][Sat Nov 17 21:25:03 BRST 2012] - public java.util.List com.packt.store.search.SearchManager.getTheaters()> <Nov 17, 2012 9:25:03 PM BRST> <Notice> <Stdout> <BEA-000000> <[ AUDIT ][ Exiting ][Sat Nov 17 21:25:03 BRST 2012] - public int com.packt.store. search.SearchManager.getTheater()> <Nov 17, 2012 9:25:03 PM BRST> <Notice> <Stdout> <BEA-000000> <[ AUDIT ][ Entering ][Sat Nov 17 21:25:03 BRST 2012] - public java.util.List com.packt.store. search.SearchManager.getMovies()> <Nov 17, 2012 9:25:03 PM BRST> <Notice> <Stdout> <BEA-000000> <[ AUDIT ][ Exiting ][Sat Nov 17 21:25:03 BRST 2012] - public java.util.List com.packt.store. search.SearchManager.getMovies()> <Nov 17, 2012 9:25:03 PM BRST> <Notice> <Stdout> <BEA-000000> <[ AUDIT ][ Entering ][Sat Nov 17 21:25:03 BRST 2012] - public int com.packt.store. search.SearchManager.getMovie()> <Nov 17, 2012 9:25:03 PM BRST> <Notice> <Stdout> <BEA-000000> <[ AUDIT ][ Exiting ][Sat Nov 17 21:25:03 BRST 2012] - public int com.packt.store. search.SearchManager.getMovie()>
Let's review what we have done in this section. We created another interceptor in the Store application to handle audit entries based on a new annotation, @Audit
, which can be applied to classes and methods. The interceptor uses CDI events to communicate with a simple handler, which, in this example, only writes a message to the standard output of WebLogic Server. These events can be listened to by multiple classes if needed, so based on what you've learned, you can create a JMS or a web service handler that can send specific audit messages to these components.
Note that the AuditHandler
class in this example is an EJB, and that the processing of the @Observer
decoration occurs by default in the same thread as the event publisher (our business class). In order to decouple the caller thread from the called object, we just need to add the @Asynchronous
decoration to AuditHandler
.