Browse DevX
Sign up for e-mail newsletters from DevX


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

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




Building the Right Environment to Support AI, Machine Learning and Deep Learning

As you might expect given the heavy use of annotations by EJB 3.0, the transactional requirements of an EJB can be specified through annotations on the bean class. The EJB 3.0 specification defines two annotations: @TransactionManagement and @TransactionAttribute. Both of these annotations are applied at the class level.

The @TransactionManagent annotation defines whether transactions should be managed automatically by the container or manually by the bean developer. The @TransactionAttribute annotation defines the transaction propagation requirements of the bean (required, requires new, supports, etc.). The valid values for each of the annotations are enumerated by constants on the TransactionManagementType and TransactionAttributeType classes respectively.

Also true to form with the design philosophy of EJB 3.0, if either or both of these annotations are omitted the container will supply defaults. If the @TransactionManagement annotation is omitted the container will apply container-managed transactions. If the @TransactionAttribute annotation is omitted the container will assumed the required transaction propagation level.

To test this you will write a unit test for the checkout method of the MusicStore stateless EJB. The checkout method does two things: it saves the user's order and sends it to the order processor for fulfillment. This is illustrated below:

@Stateless(name = "musicStore") public class MusicStore implements IMusicStore { @EJB private IMusicStoreDAO musicStoreDAO; public void checkOut(IShoppingCart cart) { musicStoreDAO.saveMusicOrder(new MusicOrder(cart.listProducts())); OrderProcessorDAO.getInstance().submitOrder(new MusicOrder(cart.listProducts())); cart.clear(); } ...

Becaue the @TransactionManagement annotation is omitted on the MusicStore class the container will make this method transactional by default. Below is a unit test that causes the order processor to throw an exception (by using EasyMock) and verifies that the saving of the order is properly rolled-back in this situation:

public void testCheckOut() throws Exception { IMusicStore store = (IMusicStore) getInitialContext().lookup(IMusicStore.class.getName()); IShoppingCart cart = new ShoppingCart(); cart.addProductToCart(store.findProductById(1)); cart.addProductToCart(store.findProductById(2)); setupOrderProcessorException(); try { store.checkOut(cart); fail("Exception not thrown"); } catch (Exception e) { ... } assertEquals(1, store.listOrders().size()); }

Since by default the container is making the checkout method transactional this unit test passes on the first attempt as illustrated in Figure 2.

Figure 2. Creating Transaction Success: Since by default business methods on enterprise bean components have container-managed transaction applied, the transaction test passes on the first attempt.
Figure 3. Transaction Failure: This unit test passed with the default container-managed transaction setting but failed when changed to bean managed. This illustrates the validity of the unit test and the effect of the @TransactionManagement annotation.

However, by apply bean-managed transactions instead you can see this unit test failing, as shown in Figure 3.

@Stateless(name = "musicStore") @TransactionManagement(TransactionManagementType.BEAN) public class MusicStore implements IMusicStore { ...... }

One of the services that an EJB container provides for an application is lifecycle management of enterprise beans. Lifecycle management means that the container manages a pool of stateless and stateful session beans creating and destroying the beans to provide optimal performance of the application. Because a particular session bean may be removed or passivated (in the case of stateful beans) at any given time, before these events occur the container notifies the affected beans to give them an opportunity to close any open resources. It does so by invoking particular methods on the beans at pre-defined steps during the lifecycle. These methods are referred to as callback methods because the container "calls back" to the EJB.

Prior versions of the EJB specification exposed callback methods via interface methods that all beans were required to implement. These were methods like ejbActivate and ejbPassivate that allowed EJBs to take some actions prior to passivation and after activation. The fact that these methods were defined in the enterprise bean interfaces meant that all enterprise beans had to implement them regardless of whether or not they actually intended to take any particular action in response to the events they represent.

In EJB 3.0 you have much more flexibility about how you choose to tap into the lifecycle of the EJBs you develop. The first choice you have is to define callback methods in the bean class and to annotate these methods with the events you intend them to receive. One example would be to initialize the state of the ShoppingCart stateful session bean after construction. You could express this requirement as a unit test as follows:

public class ShoppingCartTest extends BaseTestCase { public void testInitialize() throws Exception { IShoppingCart cart = (IShoppingCart) getInitialContext().lookup( IShoppingCart.class.getName()); assertEquals(0, cart.listProducts().size()); } }

And an implementation via a callback method would be:

@Stateful public class ShoppingCart implements IShoppingCart { ... @PostConstruct public void initialize() { products = new ArrayList

(); } ... }

All callback methods have a signature of "public void <METHOD>()" and may throw runtime exceptions but not application exceptions. Other callback method annotations include @PreDestroy, @PostActivate, and @PrePassivate. This unit test passes when run as shown in Figure 4.

Figure 4. Callback Method Success: The initialize method has successfully populated the product collections after the bean was constructed by the EJB container.
Figure 5. Callback Listener Success: Changing from a callback method to a callback listener doesn’t break the semantics of the testInitialize unit test. .

Alternatively, instead of adding callback methods to your enterprise bean class to respond to these lifecycle events you can define a callback listener class instead. This class must be referred to from the enterprise bean class via the @CallbackListener annotation. For example, one implementation of a callback listener for the shopping cart bean would look as follows:

public class ShoppingCartListener { @PostConstruct public void initialize(ShoppingCart cart) { cart.setProducts(new ArrayList

()); } }

Notice that the method signatures for callback methods are slightly different on a listener class then on the bean class. Methods on a listener class include the target object in the method signature and follows the form of "public void<METHOD>(Object)." Dependency injection is not provided to callback listener classes although they may access the enterprise bean's environment.

And you can attach it to the shopping cart event lifecycle as shown below:

@Stateful @CallbackListener(ShoppingCartListener.class) public class ShoppingCart implements IShoppingCart { ... }

Re-running the unit test defined above illustrates that both methods produce equivalent results as shown in Figure 5.

Stateful Session Bean Removal
In addition to the callback methods discussed above you can also annotate a business method on a stateful session bean to signal to the container that the bean should be destroyed when the method is invoked. Subsequent attempts to invoke methods on the same EJB should fail as a result. This requirement can be expressed in the unit test below:

public class ShoppingCartTest extends BaseTestCase { ... public void testRemove() throws Exception { IShoppingCart cart = (IShoppingCart) getInitialContext().lookup( IShoppingCart.class.getName()); cart.remove(); try { cart.listProducts(); fail("Bean should have been destroyed"); } catch (EJBException e) { // Bean successfully removed by container } } }

Initially this test fails as illustrated in Figure 6.

Figure 6. EJB Removal Failure: Initially invoking remove on the shopping cart bean has no effect.
Figure 7. EJB Removal Success: After adding and annotating a remove method this unit test now passes showing how this method properly caused the bean to be destroyed on the server.

Adding a method annotated with @Remove will cause the above test case to pass as illustrated in Figure 7.

@Stateful @CallbackListener(ShoppingCartListener.class) public class ShoppingCart implements IShoppingCart { ... @Remove public void remove() { } }

Thanks for your registration, follow us on our social networks to keep up-to-date