Login | Register   
LinkedIn
Google+
Twitter
RSS Feed
Download our iPhone app
TODAY'S HEADLINES  |   ARTICLE ARCHIVE  |   FORUMS  |   TIP BANK
Browse DevX
Sign up for e-mail newsletters from DevX


advertisement
 

A Test-Driven Exploration of the Advanced Features of EJB 3.0 : Page 3

Learn, in a test-driven approach, how to use the more advanced features of the EJB 3.0 specification.


advertisement
Interceptors
Most of the features available to enterprise bean components in the 3.0 version of the specification existed in previous versions of the specification but were significantly redesigned. Interceptors, however, are new to EJB 3.0, although not a new concept in the arena of lightweight application development. According to the specification, "an interceptor is a method that intercepts a business method invocation." In other words, an interceptor allows bean developers to inject functionality before and after the invocation of a business method. An interceptor method can either be in the bean class itself or in a separate class known as an interceptor class. In either case, you can only have one interceptor method in a bean or interceptor class although it is possible for a bean class to have multiple interceptor classes.

The invocation of interceptor methods occurs within the same transaction and security context as the business method being invoked. Additionally, they may throw runtime exceptions or application exceptions defined in the signature of the business method.

Interceptors can be used to handle simple, localized, cross-cutting concerns such as resource initialization or logging. Below is a test case that verifies methods invoked on the music store EJB are properly logged:



public class MusicStoreTest extends BaseTestCase { ... public void testLogging() throws Exception { // Setup mock logger MockControl control = MockControl.createControl(ILogger.class); ILogger mockLogger = (ILogger) control.getMock(); LoggerFactory.setLogger(mockLogger); // Define expected behavior mockLogger.log("Before"); mockLogger.log("After"); control.replay(); // Invoke music store method IMusicStore store = (IMusicStore) getInitialContext().lookup(IMusicStore.class.getName()); store.listGenres(); // Verify results control.verify(); // Clear mock logger LoggerFactory.setLogger(null); } ... }

Running the above unit test fails initially, as shown in Figure 8, because the original implementation of the music store EJB did not correctly log its invocation.


Figure 8. Logging Interceptor Failure: Because the original implementation of the MusicStore class did not log method invocations as defined in the testLogging unit test, this test fails when executed.
 
Figure 9. Logging Interceptor Method Success: Adding the log intercept causes the testLog unit test to pass.

By adding an interceptor method, as the example code below illustrates, causes this unit test to pass as shown in Figure 9.

@Stateless(name = "musicStore") public class MusicStore implements IMusicStore { @EJB private IMusicStoreDAO musicStoreDAO; @AroundInvoke public Object log(InvocationContext context) throws Exception { Object result = null; LoggerFactory.getLogger().log("Before"); try { result = context.proceed(); LoggerFactory.getLogger().log("After"); } catch (Exception e) { LoggerFactory.getLogger().log("After with exception"); throw e; } return result; } ... }

Alternatively you could place this functionality in a class of its own, for example a LoggingInterceptor class, and annotate the MusicStore class to use this interceptor class as shown below.

public class LoggingInterceptor { @AroundInvoke public Object log(InvocationContext context) throws Exception { Object result = null; System.out.println("Before"); try { result = context.proceed(); System.out.println("After"); } catch (Exception e) { System.out.println("After with exception"); throw e; } return result; } } @Stateless(name = "musicStore") @Interceptor(LoggingInterceptor.class) public class MusicStore implements IMusicStore { ... }

Re-running the above unit test demonstrates that this implementation also satisfies the logging requirement as shown in Figure 10.

Figure 10. Logging Interceptor Class Success: Changing the logging implementation from an interceptor method to an interceptor method continues to allow the testLog unit test to pass.
Interceptors provide a rudimentary tool to address cross-cutting concerns locally within a given group of enterprise bean components. For more then the most trivial case you may want to consider instead the more robust capabilities of a true AOP tool such as AspectJ or JBoss AOP.

Exception Handling
Enterprise beans may throw any checked exceptions declared in their business interface. The EJB 3.0 specification does away with the requirement that methods on a remote interface must throw checked RemoteExceptions. Instead, if any errors are encountered in making the EJB invocation then the container will wrap the underlying exception in an unchecked EJBException object. This includes runtime exceptions thrown by the enterprise bean itself.

For example, assume that the checkout operation on the music store can throw two exceptions. The first indicates that the customer has insufficient funds and this exception, named InsufficientFundsException, is a checked exception and declared in the business interface as shown below.

public interface IMusicStore { ... public void checkOut(IShoppingCart cart) throws InsufficientFundsException; ... }

The second exception is a SystemUnavailableException and is a runtime exception that is thrown, for example, if the music store can't communicate with the order processor. Because this is an unchecked exception it does not need to be declared in the business interface of the checkout operation.

Below is a unit test that asserts that checked exceptions thrown from enterprise bean components function typically for checked exceptions thrown from POJOs:

public class MusicStoreTest extends BaseTestCase { ... public void testCheckoutInsufficientFunds() throws Exception { IMusicStore store = (IMusicStore) getInitialContext().lookup(IMusicStore.class.getName()); // Setup exception indicator MusicStore.insufficientFunds = true; try { store.checkOut(null); fail("An exception should have been thrown here"); } catch (InsufficientFundsException e) { // Exception was correctly thrown } } ... }

It is simple to control whether the music store throws these simulated exceptions by applying an interceptor to the checkout method as illustrated below:

public class MusicStore implements IMusicStore { ... @AroundInvoke public Object simulateExceptions(InvocationContext context) throws Exception { if (insufficientFunds) { throw new InsufficientFundsException(); } if (systemUnavailable) { throw new SystemUnavailableException(); } return context.proceed(); } ... }

This test passes, as shown in Figure 11, which illustrates that application exceptions declared in the business interface work as expected.

Figure 11. Exception Success: Application exceptions are thrown from enterprise bean components as expected.
The next test case defines the behavior that one would expect from unchecked exceptions thrown from a Java class:

public class MusicStore implements IMusicStore { ... public void testCheckoutSystemUnavailable() throws Exception { IMusicStore store = (IMusicStore) getInitialContext().lookup(IMusicStore.class.getName()); // Setup exception indicator MusicStore.systemUnavailable = true; try { store.checkOut(null); fail("An exception should have been thrown here"); } catch (SystemUnavailableException e) { // Exception was correctly thrown } } ... }

This test initially fails with the exception shown in Figure 12.


Figure 12. Exception Failure: Runtime exceptions are automatically wrapped with an EJBException by the EJB container.
 
Figure 13. Unchecked Exception Success: By annotating the unchecked SystemUnavailableException with the @ApplicationException annotation it is now treated as an application exception and not wrapped with an EJBException.

This exception is caused because non-application exceptions, i.e. exceptions not declared in the business interface, are wrapped by the EJB container in an EJBException. In order to declare that an arbitrary exception, such as the SystemUnavailableException in our example, is to be treated as an application exception and not wrapped, the exception should be annotated with the @ApplicationException annotation. Adding this annotation, as shown below, allows the unit test to pass as expected (see Figure 13).

@ApplicationException public class SystemUnavailableException extends RuntimeException { }

The proposed EJB 3.0 specification offers many productivity enhancements for enterprise Java development. Its emphasis on providing enterprise services to POJOs facilitates, among other things, the unit testing of enterprise java beans. The JBoss embedded EJB 3.0 implementation is particularly useful in this regard by providing a lightweight EJB 3.0 container for hosting EJB 3.0 outside of a full-fledged J2EE application server. In this article you learned how to apply a test-driven approach to unit testing many facets of EJB development including transactions, callbacks, interceptors, and exception handling.



Rod Coffin is an agile technologist at Semantra, helping to develop an innovative natural language ad hoc reporting platform. He has many years of experience mentoring teams on enterprise Java development and agile practices and has written several articles on a range of topics from Aspect-Oriented Programming to EJB 3.0. Rod is a frequent speaker at user groups and technology conferences and can be contacted via his home page.
Comment and Contribute

 

 

 

 

 


(Maximum characters: 1200). You have 1200 characters left.

 

 

Sitemap