Chapter 9. Enterprise JavaBeans

Enterprise JavaBeans are server-side components that encapsulate application business logic. Enterprise JavaBeans simplify application development by automatically taking care of transaction management and security. There are two types of Enterprise JavaBeans: Session Beans, which perform business logic; and Message-Driven Beans, which act as a message listener.

Readers familiar with previous versions of J2EE will notice that Entity Beans were not mentioned in the above paragraph. In Java EE 5, Entity Beans have been deprecated in favor of the Java Persistence API (JPA). Entity Beans are still supported for backwards compatibility; however, the preferred way of doing Object Relational Mapping with Java EE 5 is through JPA. Refer to Chapter 4 for a detailed discussion on JPA.

The following topics will be covered in this chapter:

  • Session Beans

    • A simple session bean

    • A more realistic example

    • Using a session bean to implement the DAO design pattern

  • Message-driven beans

  • Transactions in Enterprise Java beans

    • Container-managed transactions

    • Bean-managed transactions

  • Enterprise JavaBeans life cycles

    • Stateful session bean life cycle

    • Stateless session bean life cycle

    • Message-driven bean life cycle

  • EJB timer service

  • EJB security

Session Beans

As we previously mentioned, session beans typically encapsulate business logic. In Java EE 5, only two artifacts need to be created in order to create a session bean: the bean itself, and a business interface. These artifacts need to be decorated with the proper annotations to let the EJB container know they are session beans.

Previous versions of J2EE required application developers to create several artifacts in order to create a session bean. These artifacts included the bean itself, a local or remote interface (or both), a local home or a remote home interface (or both) and a deployment descriptor. As we shall see in this chapter, EJB development has been greatly simplified in Java EE 5.

Simple Session Bean

The following example illustrates a very simple session bean:

package net.ensode.glassfishbook;
import javax.ejb.Stateless;
@Stateless

public class SimpleSessionBean implements SimpleSession
{
private String message =
"If you don't see this, it didn't work!";
public String getMessage()
{
return message;
}
}

The @Stateless annotation lets the EJB container know that this class is a stateless session bean. There are two types of session beans, stateless and stateful. Before we explain the difference between these two types of session beans, we need to clarify how an instance of an EJB is provided to an EJB client application.

When EJBs (both session beans and message-driven beans) are deployed, the EJB container creates a series of instances of each EJB. This is what is typically referred to as the EJB pool. When an EJB client application obtains an instance of an EJB, one of the instances in the pool is provided to this client application.

The difference between stateful and stateless session beans is that stateful session beans maintain conversational state with the client, where stateless session beans do not. In simple terms, what this means is that when an EJB client application obtains an instance of a stateful session bean, the same instance of the EJB is provided for each method invocation, therefore, it is safe to modify any instance variables on a stateful session bean, as they will retain their value for the next method call.

The EJB container may provide any instance of an EJB in the pool when an EJB client application requests an instance of a stateless session bean. As we are not guaranteed the same instance for every method call, values set to any instance variables in a stateless session bean may be "lost" (they are not really lost; the modification is in another instance of the EJB in the pool).

Other than being decorated with the @Stateless annotation, there is nothing special about this class. Notice that it implements an interface called SimpleSession. This interface is the bean's business interface. The SimpleSession interface is shown next:

package net.ensode.glassfishbook;
import javax.ejb.Remote;
@Remote

public interface SimpleSession
{
public String getMessage();
}

The only peculiar thing about this interface is that it is decorated with the @Remote annotation. This annotation indicates that this is a remote business interface. What this means is that the interface may be in a different JVM than the client application invoking it. Remote business interfaces may even be invoked across the network.

Business interfaces may also be decorated with the @Local interface. This annotation indicates that the business interface is a local business interface. Local business interface implementations must be in the same JVM as the client application invoking their methods.

As remote business interfaces can be invoked either from the same JVM or from a different JVM than the client application, at first glance, we might be tempted to make all of our business interfaces remote. Before doing so, we must be aware of the fact that the flexibility provided by remote business interfaces comes with a performance penalty, because method invocations are made under the assumption that they will be made across the network. As a matter of fact, most typical Java EE application consist of web applications acting as client applications for EJBs; in this case, the client application and the EJB are running on the same JVM, therefore, local interfaces are used a lot more frequently than remote business interfaces.

Once we have compiled the session bean and its corresponding business interface, we need to place them in a JAR file and deploy them. Just as with WAR files, the easiest way to deploy an EJB JAR file is to copy it to [glassfish installation directory]/glassfish/domains/domain1/autodeploy.

Now that we have seen the session bean and its corresponding business interface, let's take a look at a client sample application:

package net.ensode.glassfishbook;
import javax.ejb.EJB;
public class SessionBeanClient
{
@EJB

private static SimpleSession simpleSession;
private void invokeSessionBeanMethods()
{
System.out.println(simpleSession.getMessage());
System.out.println("
SimpleSession is of type: "
+ simpleSession.getClass().getName());
}
public static void main(String[] args)
{
new SessionBeanClient().invokeSessionBeanMethods();
}
}

The above code simply declares an instance variable of type net.ensode.SimpleSession, which is the business interface for our session bean. The instance variable is decorated with the @EJB annotation; this annotation lets the EJB container know that this variable is a business interface for a session bean. The EJB container then injects an implementation of the business interface for the client code to use.

As our client is a stand-alone application (as opposed to a Java EE artifact such as a WAR file) in order for it to be able to access code deployed in the server, it must be placed in a JAR file and executed through the appclient utility. This utility can be found at [glassfish installation directory]/glassfish/bin/. Assuming this path is in the PATH environment variable, and assuming we placed our client code in a JAR file called simplesessionbeanclient.jar, we would execute the above client code by typing the following command in the command line:

appclient -client simplesessionbeanclient.jar

Executing the above command results in the following console output:

If you don't see this, it didn't work!
SimpleSession is of type: net.ensode.glassfishbook._SimpleSession_Wrapper

which is the output of the SessionBeanClient class.

The first line of output is simply the return value of the getMessage() method we implemented in the session bean. The second line of output displays the fully qualified class name of the class implementing the business interface. Notice that the class name is not the fully qualified name of the session bean we wrote; instead, what is actually provided is an implementation of the business interface created behind the scenes by the EJB container.

A More Realistic Example

In the previous section, we saw a very simple, "Hello world" type of example. In this section, we will show a more realistic example. Session beans are frequently used as Data Access Objects (DAOs). Sometimes, they are used as a wrapper for JDBC calls, other times they are used to wrap calls to obtain or modify JPA entities. In this section, we will take the latter approach.

The following example illustrates how to implement the DAO design pattern in a session bean. Before looking at the bean implementation, let's look at the business interface corresponding to it:

package net.ensode.glassfishbook;
import javax.ejb.Remote;
@Remote
public interface CustomerDao
{
public void saveCustomer(Customer customer);
public Customer getCustomer(Long customerId);
public void deleteCustomer(Customer customer);
}

As we can see, the above is a remote interface implementing three methods; the saveCustomer() method saves customer data to the database, the getCustomer() method obtains data for a customer from the database, and the deleteCustomer() method deletes customer data from the database. All of these methods take or return an instance of the Customer entity we developed in Chapter 4 as a parameter.

Let's now take a look at the session bean implementing the above business interface. As we are about to see, there are some differences between the way JPA code is implemented in a session bean versus in a plain old Java object.

package net.ensode.glassfishbook;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import javax.annotation.Resource;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.sql.DataSource;
@Stateless
public class CustomerDaoBean implements CustomerDao
{
@PersistenceContext
private EntityManager entityManager;

@Resource(name = "jdbc/__CustomerDBPool")
private DataSource dataSource;
public void saveCustomer(Customer customer)
{
if (customer.getCustomerId() == null)
{
saveNewCustomer(customer);
}
else
{
updateCustomer(customer);
}
}
private void saveNewCustomer(Customer customer)
{
customer.setCustomerId(getNewCustomerId());
entityManager.persist(customer);
}
private void updateCustomer(Customer customer)
{
entityManager.merge(customer);
}
public Customer getCustomer(Long customerId)
{
Customer customer;
customer = entityManager.find(Customer.class, customerId);
return customer;
}
public void deleteCustomer(Customer customer)
{
entityManager.remove(customer);
}
private Long getNewCustomerId()
{
Connection connection;
Long newCustomerId = null;
try
{
connection = dataSource.getConnection();
PreparedStatement preparedStatement = connection
.prepareStatement(
"select max(customer_id)+1 as new_customer_id "
+ "from customers");
ResultSet resultSet = preparedStatement.executeQuery();
if (resultSet != null && resultSet.next())
{
newCustomerId = resultSet.getLong("new_customer_id");
}
connection.close();
}
catch (SQLException e)
{
e.printStackTrace();
}
return newCustomerId;
}
}

The first difference we should notice is that an instance of javax.persistence.EntityManager is directly injected into the session bean. In previous JPA examples, we had to inject an instance of javax.persistence.EntityManagerFactory, then use the injected EntityManagerFactory instance to obtain an instance of EntityManager.

The reason we had to do this was that our previous examples were not thread safe. What this means is that potentially the same code could be executed concurrently by more than one user. As EntityManager is not designed to be used concurrently by more than one thread, we used an EntityManagerFactory instance to provide each thread with its own instance of EntityManager. Since the EJB container assigns a session bean to a single client at time, session beans are inherently thread safe, therefore, we can inject an instance of EntityManager directly into a session bean.

The next difference between this session bean and previous JPA examples is that in previous examples, JPA calls were wrapped between calls to UserTransaction.begin() and UserTransaction.commit(). The reason we had to do this is because JPA calls are required to be in wrapped in a transaction, if they are not in a transaction, most JPA calls will throw a TransactionRequiredException. The reason we don't have to explicitly wrap JPA calls in a transaction as in previous examples is because session bean methods are implicitly transactional; there is nothing we need to do to make them that way. This default behavior is what is known as Container-Managed Transactions. Container-Managed Transactions are discussed in detail later in this chapter.

Note

As mentioned in Chapter 4, when a JPA entity is retrieved in one transaction and updated in a different transaction, the EntityManager.merge() method needs to be invoked to update the data in the database. Invoking EntityManager.persist() in this case will result in a "Cannot persist detached object" exception.

Invoking Session Beans from Web Applications

Frequently, Java EE applications consist of web applications acting as clients for EJBs. The most common way of deploying a Java EE application that consists of both a web application and one or more session beans is to package both the WAR file for the web application and the EJB JAR files into an EAR (Enterprise ARchive) file.

In this section, we will modify the example we saw in the section titled Integrating JSF and JPA from Chapter 6 so that the web application acts as a client to the DAO session bean we saw in the previous section. In order to make this application act as an EJB client, we will modify the CustomerController managed bean so that it delegates the logic to save a new customer to the database to the CustomerDaoBean session bean we developed in the previous section.

package net.ensode.glassfishbook.jsfjpa;
import javax.ejb.EJB;
import net.ensode.glassfishbook.Customer;
import net.ensode.glassfishbook.CustomerDao;
public class CustomerController
{
@EJB
CustomerDao customerDao;

private Customer customer;
public String saveCustomer()
{
String returnValue = "success";
try
{
customerDao.saveCustomer(customer);

}
catch (Exception e)
{
e.printStackTrace();
returnValue = "failure";
}
return returnValue;
}
public Customer getCustomer()
{
return customer;
}
public void setCustomer(Customer customer)
{
this.customer = customer;
}
}

As you can see, all we had to do was to declare an instance of the CustomerDao business interface, and decorate it with the @EJB annotation so that an instance of the corresponding EJB is injected, and replace the code to save data to the database with an invocation to the saveCustomer() method, which is defined in the CustomerDao business interface.

Now that we have modified our web application to be a client for our session bean, we need to package it in a WAR file. Then we need to package the WAR file, along with the EJB JAR file containing the session bean, in an EAR file.

An EAR file is a compressed ZIP file containing WAR files, EJB JAR files, and any additional libraries that either the web application or the EJB might depend on. An EAR file also contains an application.xml deployment descriptor. This deployment descriptor must be placed in a META-INF directory inside the EAR file.

The structure of our EAR file is shown in the following screenshot.

Invoking Session Beans from Web Applications

The application.xml deployment descriptor declares all the Java EE modules that are included in the EAR file.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE application PUBLIC
"-//Sun Microsystems, Inc.//DTD J2EE Application 1.3//EN"
"http://java.sun.com/dtd/application_1_3.dtd">
<application>
<display-name>savecustomer_ear</display-name>
<module>
<web>
<web-uri>daosessionbeanwebclient.war</web-uri>
<context-root>/daosessionbeanwebclient</context-root>
</web>
</module>
<module>
<ejb>daosessionbean.jar</ejb>
</module>
</application>

Each module must be nested inside a<module> element, followed by either an<ejb> element for EJB modules, a<web> element for web modules, or a<java> element for EJB clients that are not web applications.

<ejb> and<java> elements specify the name of the JAR file to be deployed.<web> elements contain a required<web-uri> element indicating the name of the WAR file to be deployed, and an optional<context-root> element used to specify the context root of the web application. If no<context-root> element is present, then the base name of the WAR file is used as its context root.

An EAR file can be created by using a ZIP tool (WinZip, 7-Zip, etc.) to create a ZIP file with its contents, or, more likely, an IDE or build tool such as Eclipse, NetBeans, ANT or Maven can be used to automate its creation. An EAR file must end with a .ear extension. Once the EAR file is created, the easiest way to deploy it is to copy it into the autodeploy directory under [glassfish installation directory]/glassfish/domains/domain1.

Message-Driven Beans

The purpose of a message-driven bean is to consume messages from a JMS Queue or a JMS topic, depending on the messaging domain used (refer to Chapter 7). A message-driven bean must be decorated with the @MessageDriven annotation. The mappedName attribute of this annotation must contain the JNDI name of the JMS message queue or JMS message topic from which the bean will be consuming messages. The following example illustrates a simple message-driven bean:

package net.ensode.glassfishbook;
import javax.ejb.MessageDriven;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;
@MessageDriven(mappedName = "jms/GlassFishBookQueue")

public class ExampleMessageDrivenBean implements MessageListener
{
public void onMessage(Message message)
{
TextMessage textMessage = (TextMessage) message;
try
{
System.out.print("Received the following message: ");
System.out.println(textMessage.getText());
System.out.println();
}
catch (JMSException e)
{
e.printStackTrace();
}
}
}

As we can see, this class is nearly identical to the ExampleMessageListener class we saw in the previous chapter; the only differences are the class name and the fact that this example is decorated with the @MessageDriven interface. It is recommended, but not required for message-driven beans to implement the javax.jms.MessageListener interface, however; message-driven beans must have a method called onMessage() whose signature is identical to this example.

Client applications never invoke a message-driven bean's methods directly, instead they put messages in the message queue or topic, then the bean consumes those messages and acts as appropriate. The preceding example simply prints the message to standard output; as message-driven beans execute inside an EJB container, standard output gets redirected to a log. To see the messages in GlassFish's server log, open the [GlassFish installation directory]/glassfish/domains/domain1/logs/server.log file.

Transactions in Enterprise Java Beans

As we mentioned earlier in this chapter, by default, any EJB methods are automatically wrapped in a transaction. This default behavior is known as Container-Managed Transactions, because transactions are managed by the EJB container. Application developers may also choose to manage transactions themselves; this can be accomplished by using Bean-Managed Transactions. Both of these approaches are discussed in the following sections.

Container-Managed Transactions

Because EJB methods are transactional by default, we run into an interesting dilemma when a session bean is invoked from client code that is already a transaction. How should the EJB container behave? Should it suspend the client transaction, execute its method in a new transaction, then resume the client transaction? Should it not create a new transaction and execute its method as part of the client transaction? Should it throw an exception?

By default, if an EJB method is invoked by client code that is already in a transaction, the EJB container will simply execute the session bean method as part of the client transaction. If this is not the behavior we need, we can change it by decorating the method with the @TransactionAttribute annotation. This annotation has a value attribute that determines how the EJB container will behave when the session bean method is invoked within an existing transaction and when it is invoked outside any transactions. The value of the value attribute is typically a constant defined in the javax.ejb.TransactionAttributeType enum. The following table lists the possible values for the @TransactionAttribute annotation:

@TransactionAttribute value

Description

TransactionAttributeType.MANDATORY

Forces the method to be invoked as part of a client transaction. If the method is called outside any transactions, it will throw a TransactionRequiredException.

TransactionAttributeType.NEVER

The method is never executed in a transaction. If the method is invoked as part of a client transaction, it will throw a RemoteException. No transaction is created if the method is not invoked inside a client transaction.

TransactionAttributeType.NOT_SUPPORTED

If the method is invoked as part of a client transaction, the client transaction is suspended; the method is executed outside any transaction. After the method completes, the client transaction is resumed. No transaction is created if the method is not invoked inside a client transaction.

TransactionAttributeType.REQUIRED

If the method is invoked as part of a client transaction, the method is executed as part of that transaction. If the method is invoked outside any transaction, a new transaction is created for the method. This is the default behavior.

TransactionAttributeType.REQUIRES_NEW

If the method is invoked as part of a client transaction, that transaction is suspended, and a new transaction is created for the method. Once the method completes, the client transaction is resumed. If the method is called outside any transactions, a new transaction is created for the method.

TransactionAttributeType.SUPPORTS

If the method is invoked as part of a client transaction, it is executed as part of that transaction. If the method is invoked outside a transaction, no new transaction is created for the method.

Although the default transaction attribute is reasonable in most cases, it is good to be able to override this default, if necessary. For example, transactions have a performance impact, therefore being able to turn off transactions for a method that does not need them is beneficial. For a case like this, we would decorate our method as illustrated in the following code snippet:

@TransactionAttribute(value=TransactionAttributeType.NEVER)
public void doitAsFastAsPossible()
{
//performance critical code goes here.
}

Other transaction attribute types can be declared by annotating the methods with the corresponding constant in the TransactionAttributeType enum.

If we wish to override the default transaction attribute consistently across all methods in a session bean, we can decorate the session bean class with the @TransactionAttribute annotation. The value of its value attribute will be applied to every method in the session bean.

Container-managed transactions are automatically rolled back whenever an exception is thrown inside an EJB method. Additionally, we can programmatically roll back a container-managed transaction by invoking the setRollbackOnly() method on an instance of javax.ejb.EJBContext corresponding to the session bean in question. The following example is a new version of the session bean we saw earlier in this chapter, modified to roll back transactions if necessary.

package net.ensode.glassfishbook;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import javax.annotation.Resource;
import javax.ejb.EJBContext;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.sql.DataSource;
@Stateless
public class CustomerDaoRollbackBean implements CustomerDaoRollback
{
@Resource
private EJBContext ejbContext;

@PersistenceContext
private EntityManager entityManager;
@Resource(name = "jdbc/__CustomerDBPool")
private DataSource dataSource;
public void saveNewCustomer(Customer customer)
{
if (customer == null || customer.getCustomerId() != null)
{
ejbContext.setRollbackOnly();
}
else
{
customer.setCustomerId(getNewCustomerId());
entityManager.persist(customer);
}
}
public void updateCustomer(Customer customer)
{
if (customer == null || customer.getCustomerId() == null)
{
ejbContext.setRollbackOnly();
}
else
{
entityManager.merge(customer);
}
}
//Additional method omitted for brevity.
}

In this version of the DAO session bean, we deleted the saveCustomer() method and made the saveNewCustomer() and updateCustomer() methods public. Each of these methods now checks to see if the customerId field is set correctly for the operation we are trying to perform (null for inserts and not null for updates). It also checks to make sure the object to be persisted is not null. If any of the checks results in invalid data, the method simply rolls back the transaction by invoking the setRollBackOnly() method on the injected instance of EJBContext and does not update the database.

Bean-Managed Transactions

As we have seen, container-managed transactions make it ridiculously easy to write code that is wrapped in a transaction; after all, there is nothing special that we need to do to make them that way. As a matter of fact, some developers are sometimes not even aware that they are writing code that will be transactional in nature when they develop session beans. Container-managed transactions cover most of the typical cases that we will encounter; however, they do have a limitation: each method can be wrapped in a single transaction or with no transaction. With container-managed transactions, it is not possible to implement a method that generates more than one transaction, but this can be accomplished by using Bean-Managed Transactions.

package net.ensode.glassfishbook;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import javax.annotation.Resource;
import javax.ejb.Stateless;
import javax.ejb.TransactionManagement;
import javax.ejb.TransactionManagementType;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.sql.DataSource;
import javax.transaction.UserTransaction;
@Stateless
@TransactionManagement(value = TransactionManagementType.BEAN)

public class CustomerDaoBmtBean implements CustomerDaoBmt
{
@Resource
private UserTransaction userTransaction;

@PersistenceContext
private EntityManager entityManager;
@Resource(name = "jdbc/__CustomerDBPool")
private DataSource dataSource;
public void saveMultipleNewCustomers(
List<Customer> customerList)
{
for (Customer customer : customerList)
{
try
{
userTransaction.begin();

customer.setCustomerId(getNewCustomerId());
entityManager.persist(customer);
userTransaction.commit();

}
catch (Exception e)
{
e.printStackTrace();
}
}
}
private Long getNewCustomerId()
{
Connection connection;
Long newCustomerId = null;
try
{
connection = dataSource.getConnection();
PreparedStatement preparedStatement =
connection.prepareStatement("select " +
"max(customer_id)+1 as new_customer_id " +
"from customers");
ResultSet resultSet = preparedStatement.executeQuery();
if (resultSet != null && resultSet.next())
{
newCustomerId = resultSet.getLong("new_customer_id");
}
connection.close();
}
catch (SQLException e)
{
e.printStackTrace();
}
return newCustomerId;
}
}

In this example, we implemented a method named saveMultipleNewCustomers(). This method takes an ArrayList of customers as its sole parameter. The intention of this method is to save as many elements in the ArrayList as possible. An exception saving one of the entities should not stop the method from attempting to save the remaining elements. This behavior is not possible using container-managed transactions, because an exception thrown when saving one of the entities would roll back the whole transaction. The only way to achieve this behavior is through bean-managed transactions.

As can be seen in the example, we declare that the session bean uses bean-managed transactions by decorating the class with the @TransactionManagement annotation, and using TransactionManagementType.BEAN as the value for its value attribute (The only other valid value for this attribute is TransactionManagementType.CONTAINER, but because this is the default value, it is not necessary to specify it.)

To be able to programmatically control transactions, we inject an instance of javax.transaction.UserTransaction, which is then used in the for loop inside the getNewCustomerId() method to begin and commit a transaction in each iteration of the loop.

If we need to roll back a bean-managed transaction, we can do it by simply calling the rollback() method on the appropriate instance of javax.transaction.UserTransaction.

Before moving on, it is worth noting that even though all the examples in this section were session beans, the concepts explained apply to message-driven beans as well.

Enterprise JavaBean Life Cycles

Enterprise JavaBeans go through different states in their life cycle. Each type of EJB has different states. States specific to each type of EJB are discussed in the next sections.

Stateful Session Bean Life Cycle

Readers experienced with previous versions of J2EE may remember that in previous versions of the specification, session beans were required to implement the javax.ejb.SessionBean interface. This interface provides methods to be executed at certain points in the session bean's life cycle. Methods provided by the SessionBean interface include:

  • ejbActivate()

  • ejbPassivate()

  • ejbRemove()

  • setSessionContext(SessionContext ctx)

The first three methods are meant to be executed at certain points in the bean's life cycle. In most cases, there is nothing to do in the implementation of these methods. This fact resulted in the vast majority of session beans implementing empty versions of these methods. Thankfully, in Java EE 5, it is no longer necessary to implement the SessionBean interface; however, if necessary, we can still write methods that

will get executed at certain points in the bean's life cycle. We can achieve this by decorating methods with specific annotations.

Before explaining the annotations available to implement life-cycle methods, a brief explanation of the session bean life cycle is in order. The life cycle of a stateful session bean is different from the life cycle of a stateless session bean.

A stateful session bean life cycle contains three states: Does Not Exist, Ready, and Passive, as shown in the following screenshot.

Stateful Session Bean Life Cycle

Before a stateful session bean is deployed, it is in the Does Not Exist state. Upon a successful deployment, the EJB container does any required dependency injection on the bean and it goes into the Ready state. At this point, the bean is ready to have its methods called by a client application.

When a stateful session bean is in the Ready state, the EJB container may decide to passivate it, that is, to move it from main memory to secondary storage; when this happens the bean goes into Passive state.

If an instance of a stateful session bean hasn't been accessed for a period of time, the EJB container will set the bean to the Does Not Exist state. By default, a stateful session bean will be sent to the Does Not Exist state after 90 minutes of inactivity. This default can be changed by going to the GlassFish administration console, expanding the Configuration node in the tree at the left-hand side, clicking on the EJB Container node, then scrolling down towards the bottom of the page and modifying the value of the Removal Timeout text field, then clicking on the Save button at the bottom-right of the main page.

Stateful Session Bean Life Cycle

However, this technique sets the timeout value for all stateful session beans. If we need to modify the timeout value for a specific session bean, we need to include a sun-ejb-jar.xml deployment descriptor in the JAR file containing the session bean. In this deployment descriptor, we can set the timeout value as the value of the<removal-timeout-in-seconds> element.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE sun-ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Application Server 9.0 EJB 3.0//EN" "http://www.sun.com/software/appserver/dtds/sun-ejb-jar_3_0-0.dtd">
<sun-ejb-jar>
<enterprise-beans>
<ejb>
<ejb-name>MyStatefulSessionBean</ejb-name>

<bean-cache>
<removal-timeout-in-seconds>
600
</removal-timeout-in-seconds>

</bean-cache>
</ejb>
</enterprise-beans>
</sun-ejb-jar>

Even though we are not required to create an ejb-jar.xml file for our session beans anymore (this used to be the case in previous versions of the J2EE specification), we can still write one if we wish to do so. The<ejb-name> element in the sun-ejb-jar.xml deployment descriptor must match the value of the element of the same name in ejb-jar.xml. If we choose not to create an ejb-jar.xml file, then this value must match the name of the EJB class. The timeout value for the stateful session bean must be the value of the<removal-timeout-in-seconds> element; as the name of the element suggests, the unit of time to use is seconds. In the above example, we set the timeout value to 600 seconds, or 10 minutes.

Any of the methods in a stateful session bean decorated with the @PostActivate annotation will be invoked just after the stateful session bean has been activated. This is equivalent to implementing the ejbActivate() method in previous versions of J2EE. Similarly, any method decorated with the @PrePassivate annotation will be invoked just before the stateful session bean is passivated. This is equivalent to implementing the ejbPassivate() method in previous versions of J2EE.

When a stateful session bean that is in the Ready state times out and is sent to the Does not Exist state, any method decorated with the @PreDestroy annotation is executed. If the session bean is in the Passive state and it times out, methods decorated with the @PreDestroy annotation are not executed. Additionally, if a client of the stateful session bean executes any method decorated with the @Remove annotation, any methods decorated with the @PreDestroy annotation are executed and the bean is marked for garbage collection. Decorating a method with the @Remove annotation is equivalent to implementing the ejbRemove() method in previous versions of the J2EE specification.

The @PostActivate, @PrePassivate, and @Remove annotations are valid only for stateful session beans. The @PreDestroy and @PostConstruct annotations are valid for stateful session beans, stateless session beans, and message-driven beans.

Stateless Session Bean Life Cycle

A stateless session bean life cycle, as shown in the following screenshot, contains only the Does Not Exist and Ready states.

Stateless Session Bean Life Cycle

Stateless session beans are never passivated. A stateless session bean's methods can be decorated with the @PostConstruct and the @PreDestroy annotations. Just as in stateful session beans, any methods decorated with the @PostConstruct annotation will be executed when the stateless session bean goes from the Does Not Exist to the Ready state, and any methods decorated with the @PreDestroy annotation will be executed when a stateless session bean goes from the Ready state to the Does Not Exist state. Since stateless session beans are never passivated, therefore any @PrePassivate and @PostActivate annotations in a stateless session bean are simply ignored by the EJB container.

Message-Driven Bean Life Cycle

Just like stateless session beans, message-driven beans, as shown in the following screenshot, contain only the Does Not Exist and Ready states.

Message-Driven Bean Life Cycle

The above image is exactly the same as the previous one. Message-driven beans have the same life cycle as stateless session beans.

A message-driven bean can have methods decorated with the @PostConstruct and @PreDestroy methods. Methods decorated with the @PostConstruct are executed just before the bean goes to the Ready state. Methods decorated with the @PreDestroy annotation are executed just before the bean goes to the Does Not Exist state.

EJB Timer Service

Stateless session beans and message-driven beans can have a method that is executed periodically at regular intervals of time. This can be accomplished by using the EJB Timer Service. The following example illustrates how to take advantage of this service.

package net.ensode.glassfishbook;
import java.io.Serializable;
import java.util.Collection;
import java.util.Date;
import java.util.logging.Logger;
import javax.annotation.Resource;
import javax.ejb.EJBContext;
import javax.ejb.Stateless;
import javax.ejb.Timeout;
import javax.ejb.Timer;
import javax.ejb.TimerService;
@Stateless
public class EjbTimerExampleBean implements EjbTimerExample
{
private static Logger logger = Logger.getLogger(EjbTimerExampleBean. class.getName());
@Resource
TimerService timerService;
public void startTimer(Serializable info)
{
Timer timer = timerService.createTimer
(new Date(), 5000, info);
}
public void stopTimer(Serializable info)
{
Timer timer;
Collection timers = timerService.getTimers();
for (Object object : timers)
{
timer = ((Timer) object);
if (timer.getInfo().equals(info))
{
timer.cancel();
break;
}
}
}
@Timeout
public void logMessage(Timer timer)
{
logger.info("This message was triggered by :" +
timer.getInfo() + " at "

+ System.currentTimeMillis());
}
}

In the above example, we inject an implementation of the javax.ejb.TimerService interface by decorating an instance variable of this type with the @Resource annotation. We can then create a timer by invoking the createTimer() method of this TimerService instance.

There are several overloaded versions of the createTimer() method; the one we chose to use takes an instance of java.util.Date as its first parameter. This parameter is used to indicate the first time the timer should expire ("go off"). In the

example, we chose to use a brand-new instance of the Date class, which in effect makes the timer expire immediately. The second parameter of the createTimer() method is the amount of time to wait, in milliseconds, before the timer expires again. In this example, the timer will expire every five seconds. The third parameter of the createTimer() method can be an instance of any class implementing the java.io.Serializable interface. As a single EJB can have several timers executing concurrently, this third parameter is used to uniquely identify each of the timers. If we don't need to identify the timers, null can be passed as a value for this parameter.

Note

The EJB method invoking TimerService.createTimer() must be called from an EJB client. Placing this call in an EJB method decorated with the @PostConstruct annotation to start the timer automatically when the bean is placed in Ready state will result in an IllegalStateException being thrown.

We can stop a timer by invoking its cancel() method. There is no way to directly obtain a single timer associated with an EJB; what we need to do is invoke the getTimers() method on the instance of TimerService that is linked to the EJB. This method will return a Collection containing all the timers associated with the EJB. We can then iterate through the collection and cancel the correct one by invoking its getInfo() method. This method will return the Serializable object we passed as a parameter to the createTimer() method.

Finally, any EJB method decorated with the @Timeout annotation will be executed when a timer expires. Methods decorated with this annotation must return void and take a single parameter of type javax.ejb.Timer. In our example, the method simply writes a message to the server log.

The following class is a stand-alone client for this EJB.

package net.ensode.glassfishbook;
import javax.ejb.EJB;
public class Client
{
@EJB
private static EjbTimerExample ejbTimerExample;
public static void main(String[] args)
{
try
{
System.out.println("Starting timer 1...");
ejbTimerExample.startTimer("Timer 1");

System.out.println("Sleeping for 2 seconds...");
Thread.sleep(2000);
System.out.println("Starting timer 2...");
ejbTimerExample.startTimer("Timer 2");

System.out.println("Sleeping for 30 seconds...");
Thread.sleep(30000);
System.out.println("Stopping timer 1...");
ejbTimerExample.stopTimer("Timer 1");

System.out.println("Stopping timer 2...");
ejbTimerExample.stopTimer("Timer 2");

System.out.println("Done.");
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}

The example simply starts a timer, waits for a couple of seconds, then starts a second timer. It then sleeps for 30 seconds and then stops both timers. After deploying the EJB and executing the client, we should see some entries like this in the server log:

[#|2007-05-05T20:41:39.518-0400|INFO|sun-appserver9.1|net.ensode.glassfishbook.EjbTimerExampleBean|_ThreadID=22;_ThreadName=p: thread-pool-1; w: 16;|This message was triggered by :Timer 1 at 1178412099518|#]
EJB timer service[#|2007-05-05T20:41:41.536-0400|INFO|sun-appserver9.1|net.ensode.glassfishbook.EjbTimerExampleBean|_ThreadID=22;_ThreadName=p: thread-pool-1; w: 16;|This message was triggered by :Timer 2 at 1178412101536|#]
[#|2007-05-05T20:41:46.537-0400|INFO|sun-appserver9.1|net.ensode.glassfishbook.EjbTimerExampleBean|_ThreadID=22;_ThreadName=p: thread-pool-1; w: 16;|This message was triggered by :Timer 1 at 1178412106537|#]
[#|2007-05-05T20:41:48.556-0400|INFO|sun-appserver9.1|net.ensode.glassfishbook.EjbTimerExampleBean|_ThreadID=22;_ThreadName=p: thread-pool-1; w: 16;|This message was triggered by :Timer 2 at 1178412108556|#]

These entries are created each time one of the timer expires.

EJB Security

Enterprise JavaBeans allow us to declaratively decide which users can access their methods. For example, some methods might only be available to users in certain roles. A typical scenario is that only users with a role of administrator can add, delete, or modify other users in the system.

The following example is a slightly modified version of the DAO session bean we saw earlier in this chapter. In this version, some methods that were previously private were made public. Additionally, the session bean was modified to allow only users in certain roles to access its methods.

package net.ensode.glassfishbook;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import javax.annotation.Resource;
import javax.annotation.security.RolesAllowed;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.sql.DataSource;
@Stateless
@RolesAllowed("appadmin")

public class CustomerDaoBean implements CustomerDao
{
@PersistenceContext
private EntityManager entityManager;
@Resource(name = "jdbc/__CustomerDBPool")
private DataSource dataSource;
public void saveCustomer(Customer customer)
{
if (customer.getCustomerId() == null)
{
saveNewCustomer(customer);
}
else
{
updateCustomer(customer);
}
}
public Long saveNewCustomer(Customer customer)
{
customer.setCustomerId(getNewCustomerId());
entityManager.persist(customer);
return customer.getCustomerId();
}
public void updateCustomer(Customer customer)
{
entityManager.merge(customer);
}
@RolesAllowed(
{ "appuser", "appadmin" })
public Customer getCustomer(Long customerId)
{
Customer customer;
customer = entityManager.find(Customer.class, customerId);
return customer;
}
public void deleteCustomer(Customer customer)
{
entityManager.remove(customer);
}
private Long getNewCustomerId()
{
Connection connection;
Long newCustomerId = null;
try
{
connection = dataSource.getConnection();
PreparedStatement preparedStatement =
connection
.prepareStatement("select max(customer_id)+1 "
"as new_customer_id from customers");
ResultSet resultSet = preparedStatement.executeQuery();
if (resultSet != null && resultSet.next())
{
newCustomerId = resultSet.getLong("new_customer_id");
}
connection.close();
}
catch (SQLException e)
{
e.printStackTrace();
}
return newCustomerId;
}
}

As you can see, we declare what roles have access to the methods by using the @RollesAllowed annotation. This annotation can take either a single String or an array of Strings as a parameter. When a single String is used as a parameter for this annotation, only users with the role specified by the parameter can access the method. If an array of Strings is used as a parameter, users with any of the roles specified by the array's elements can access the method.

The @RolesAllowed annotation can be used to decorate an EJB class, in which case its values apply to all the methods in the EJB, or to decorate one or more methods. In this second case, its values apply only to the method(s) the annotation is decorating. If, as in our example, both the EJB class and one or more of its methods are decorated with the @RolesAllowed annotation, the method-level annotation takes precedence.

Application roles need to be mapped to a security realm's group name. This mapping, along with what realm to use, is set in the sun-ejb-jar.xml deployment descriptor.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sun-ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Application Server 9.0 EJB 3.0//EN" "http://www.sun.com/software/appserver/dtds/sun-ejb-jar_3_0-0.dtd">
<sun-ejb-jar>
<security-role-mapping>
<role-name>appuser</role-name>
<group-name>appuser</group-name>
</security-role-mapping>
<security-role-mapping>
<role-name>appadmin</role-name>
<group-name>appadmin</group-name>
</security-role-mapping>

<enterprise-beans>
<ejb>
<ejb-name>CustomerDaoBean</ejb-name>
<ior-security-config>
<as-context>
<auth-method>username_password</auth-method>
<realm>file</realm>
<required>true</required>
</as-context>

</ior-security-config>
</ejb>
</enterprise-beans>
</sun-ejb-jar>

The<security-role-mapping> element of the sun-ejb-jar.xml file does the mapping between application roles and the security realm's group. The value of the<role-name> sub-element must contain the application role; this value must match the value used in the @RolesAllowed annotation. The value of the<group-name> sub-element must contain the name of the security group in the security realm used by the EJB. In this example, we map two application roles to the corresponding groups in the security realm. Although in this particular example the name of the application role and the security group match, this does not need to be the case.

Note

Automatically Matching Roles to Security Groups

It is possible to automatically match any application roles to identically named security groups in the security realm. This can be accomplished by logging in to the GlassFish web console, clicking on the Configuration node, clicking on Security, then clicking on the checkbox labeled Default Principal To Role Mapping, and saving this configuration change.

As can be seen in the example, the security realm to use for authentication is defined in the<realm> sub-element of the<as-context> element. The value of this sub-element must match the name of a valid security realm in the application server. Other sub elements of the<as-context> element include<auth-method>, the only valid value for which is username_password, and<required>, whose only valid values are true and false.

Client Authentication

If the client code accessing a secured EJB is part of a web application whose user has already authenticated, then the user's credentials will be used to determine if the user should be allowed to access the method he/she is trying to execute.

Stand-alone clients must be executed through the appclient utility. The following code illustrates a typical client for our, secured session bean.

package net.ensode.glassfishbook;
import javax.ejb.EJB;
public class Client
{
@EJB
private static CustomerDao customerDao;
public static void main(String[] args)
{
Long newCustomerId;
Customer customer = new Customer();
customer.setFirstName("Mark");
customer.setLastName("Butcher");
customer.setEmail("[email protected]");
System.out.println("Saving New Customer...");
newCustomerId = customerDao.saveNewCustomer(customer);
System.out.println("Retrieving customer...");
customer = customerDao.getCustomer(newCustomerId);
System.out.println(customer);
}
}

As you can see, there is nothing the code is doing in order to authenticate the user. The session bean is simply injected into the code via the @EJB annotation and it is used as usual. The reason this works is because the appclient utility takes care of authenticating the user. Passing the -user and -password arguments with the appropriate values will authenticate the user:

appclient -client ejbsecurityclient.jar -user peter -password secret

The above command will authenticate a user with a user name of "peter" and a password of "secret". Assuming the credentials are correct and that the user has the appropriate permissions, the EJB code will execute and we should see the expected output from the Client class above:

Saving New Customer...
Retrieving customer...
customerId = 29
firstName = Mark
lastName = Butcher
email = [email protected]

If we don't enter the user name and password from the command line, appclient will prompt us for a user name and password through a graphical window. In our example, entering the following command:

appclient -client ejbsecurityclient.jar

will result in a pop-up window like the following to show up.

Client Authentication

We can simply enter our user name and password in the appropriate fields, and after validating the credentials, the application will execute as expected.

Summary

In this chapter, we covered how to implement business logic via stateless and stateful session beans. We also explained how to take advantage of the transactional nature of EJBs to simplify implementing the Data Access Object (DAO) pattern.

Additionally, we explained the concept of Container-Managed Transactions, and how to control them by using the appropriate annotations. We also explained how to implement Bean-Managed Transaction, for cases in which Container-Managed Transactions are not enough to satisfy our requirements.

Life cycles for the different types of Enterprise Java beans were covered, including an explanation on how to have EJB methods automatically invoked by the EJB container at certain points in the life cycle.

We also covered how to have EJB methods invoked periodically by the EJB container by taking advantage of the EJB timer service.

Finally, we explained how to make sure EJB methods are only invoked by authorized users by annotating the EJB classes and/or methods and by adding the appropriate entries to the sun-ejb-jar.xml deployment descriptor.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.16.54.63