Linux Application Development For The Enterprise (Charles River Media Programming)

 < Day Day Up > 


The CORBA framework existed before the Java technology emerged, with C++ as the main language of implementation and has been extended to include Java into the framework, while Java also is naturally a good fit to embrace the CORBA architecture because they both implement their remote object access mechanisms through the Internet Inter-ORB Protocol (IIOP). However, the subject of this subsection, Enterprise JavaBeans (EJB), provides a robust distributed object architecture that is exclusively designed for using the Java technology and is based on the RMI framework that was discussed in the beginning of this chapter. The concept of implementing JavaBeans was examined in an earlier chapter during a discussion of the principles of Java programming. The Enterprise JavaBeans are different from JavaBeans because they are implemented as distributed objects and are hosted through an EJB container or an application server. Before attempting to understand the concepts of EJBs, it is important to learn about JavaBeans. It should be noted that the EJB framework can be used for building any type of Enterprise class applications in addition to the more predominantly familiar Web applications. It should also be noted that this framework can be integrated with applications built on other middleware technologies in the Enterprise, such as MOM and CORBA. There have also been instances where people attempted to make the Microsoft COM/DCOM-based applications communicate with Java-based EJB applications, although this is not necessarily a preferred choice for many organizations. COM/DCOM-based applications may be interfaced with Enterprise JavaBeans by way of using the middleware technologies such as CORBA and MOM. This subsection will focus on EJB technology and will demonstrate the process of building EJBs and deploying them on Enterprise-class application servers.

General Concepts

In a typical client-server application, the server performs the role of providing service to the client applications by sending appropriate responses to the requests made by them. This is essentially the concept without regard to the type of client-server application; it might be a traditional TCP/IP-based application, a middleware-based application (CORBA, MOM), or a Web-based application. In traditional client-server applications, the server-side developers have to develop two types of logic. The first part has to do with the most common server tasks such as establishing and maintaining the connections with the clients, with the database objects, and with the external systems, managing the lifetime of the server objects, and marshalling the data elements between the client and the server objects, to name a few; there are more activities performed by the server. However, these activities are common to almost all the client-server applications, although minor differences might exist among them. The second part of the server activities is to implement the business logic behind the business objects that service the clients. This part is unique to every application and hence has to be developed per the application’s needs. Although the business logic cannot be standardized, it is still possible to standardize the server management activities described previously. It is this concept that led to the design of EJB framework. Figure 8.10 shows a typical scenario where the server management framework sits between the client and the service provider EJBs and provides controlled access of the EJBs to the clients.

Figure 8.10: Simple representation of EJB framework.

Because the server management framework provides the most general services common to every application, the J2EE architecture defined these general service requirements in a specification that is implemented by many industry leading software vendors in the form of J2EE-compliant application servers. These include the IBM WebSphere application server, BEA Web Logic application server, Borland Enterprise server, Oracle application server, and so on. A typical application server contains an HTTP server at its core, whose job is to process incoming HTTP requests and provide corresponding HTTP responses. The Apache open source Web server is a very popular HTTP server, and many commercial application servers are built over the Apache Web server. A simple HTTP server is not sufficient to serve servlets and Java Server Pages (JSP is an extension of servletsand forms the Java technology and is discussed in the following section) for creating dynamic Web pages. Because the JSP pages need to be processed by a JSP engine within a Java Virtual Machine context, an additional layer of JSP engine is built over the simple HTTP server. Although the open source Apache Tomcat server is the perfect example for a JSP engine and is employed by some of the commercial application servers, some other vendors have implemented their own versions of JSP engine as per JSP specifications. Having a JSP engine itself is not sufficient to support Java-based Web application development, as it will not be able to host the Enterprise JavaBeans, thus emphasizing the need to build a second layer over the HTTP server core, known as the EJB container or application server. The three layers comprising of a J2EE application server are depicted in Figure 8.11.

Figure 8.11: The logical layers within an application server.

A few aspects of Enterprise JavaBeans are worth noting, as described below, to eliminate some misinterpretations spread across the industry and to provide answers to the questions raised in the minds of professionals and others.

Participant Roles in EJB Applications

It has been mentioned earlier that the process of building EJB-based applications brings in predefined roles to the participants, direct or indirect. These are explained here for a ready reference.

The EJB Container at a Glance

While the Enterprise JavaBeans provide the implementation of business logic in an Enterprise Java application, the EJB container performs a number of tasks silently behind the scenes. So that the readers might have a better understanding of the whole architecture, these activities are identified and listed here.

EJBs at a Glance

Now that we understand the need to develop EJBs and their role in large-scale Enterprise applications, let us examine what types of EJBs are designed in this framework. There are three basic types of EJBs—session beans, entity beans, and message-driven beans. The session beans enable the client applications to establish a conversational session with the server, the entity beans represent business entities or simple database objects, and the message-driven beans service the clients’ messages delivered asynchronously rather than through an interactive session. The session beans and message-driven beans perform similar roles, although the former opens an interactive session to do the task and the latter performs its task in the background (or asynchronous) mode. Thus, session beans and message-driven beans perform actions initiated by the clients on the data objects, which are the entity beans, and therefore they will directly interact with the entity beans. Each of these three types of beans has a specific purpose for which it is created. For example, the session beans are designed with the purpose of performing processing logic in the application such as performing computations, comparing similarities of objects, and so on. If they need to access data from a database, they interact with the entity beans, as entity beans perform database-related logic such as extracting data from the database, performing database updates, and so on. The simple relationship between the three types of beans is shown in Figure 8.12.

Figure 8.12: Relationship between different types of Enterprise JavaBeans.

Session Beans

With respect to EJBs, the conversational state of a bean may be looked at as the state of instance variables of the bean object at any point of time. Those EJBs whose state outlives the session—which means the state of the bean can be stored to a persistent storage (such as a database) and re-created later—are known as entity beans, and those EJBs that cannot outlive the session are known as session beans. Session beans are the simplest form of EJBs and are useful for creating a conversational session between the client and the server bean and performing a simple task through the session bean. A conversational session may be viewed as consisting of creating a session bean, calling one or more methods on the session bean to perform a task, and then finally closing the session or removing the bean.

In a way, this is similar to accessing a Web page through a Web browser. Once we type the URL in the Web browser and request it to get the page, the page is obtained from the respective Web server and displayed in the browser window. Soon after, we request another page through the same browser window from the same server; by default the server does not know that the request has come from the same browser client it serviced earlier. In fact, within the short time gap between two consecutive requests from the same browser client, the server might have serviced some other browser clients from a different computer located in a different country. Thus, each request is independent of the other and is known to be processed in stateless sessions, as the underlying HTTP protocol is stateless by design and by purpose. However, there are ways to create stateful sessions in Web applications, as discussed earlier in this chapter.

The similarity of Web applications and EJBs ends here, as EJBs are designed over the Java RMI protocol (which creates stateful sessions between clients and servers) and therefore are capable of maintaining state between method calls or even after session termination. Not all session beans are capable of storing their state to a persistent storage, but they are capable of maintaining session state between method calls if designed so, thus enabling themselves to perform in two different ways. The first type of session beans supports stateless sessions, and hence they are called stateless session beans. These beans do not save their state between method calls within the same conversational session. The other type supports stateful sessions, and hence they are called stateful session beans. These beans save their state between method calls within the same conversational session.

The EJB container maintains a pool of session beans, and every time a client requests a new session bean, it assigns a bean from the pool to that client if a bean is available; otherwise it creates a new session bean and assigns it to the client. Similarly, when the client requests to remove the bean, the container does not physically remove the bean instance from the memory; rather it terminates the assignment of the bean to the client and returns the bean to the bean pool. Thus, it is the container that determines and manages the lifetime of the beans, and not the clients.

Because stateful session beans maintain the client’s conversational session state between method calls from the same client, they cannot be accessed by more than one client at a time. Therefore, the container ensures that a particular stateful session bean is assigned to a single client at a time. However, once the session is terminated with that client, the container is free to assign that session bean to another client or to the same client upon its next connection request. Thus, stateful session beans can service different clients sequentially after successful closure of a session with the preceding client. On the contrary, the stateless sessions can service multiple clients concurrently, as they do not maintain session state between method calls with any single client. In this respect, they resemble the stateless HTTP protocol, as each method call from a client may be viewed as a single request from the client.

Entity Beans

While the session beans are necessary to establish and maintain sessions with the clients, the entitybeansare designed to perform the background work and represent business entities or business objects. Entity beans are capable of saving their state to persistent storage such as a database system, so that they can be rebuilt to their state before they were saved after a possible planned shutdown or an unplanned crash of the application or the application server. However, entity beans live within the context of a transaction, and therefore an application crash might result in bringing the state of entity beans to the point of last commit. Entity beans do not directly interact with the end users; however, they are accessed by other Enterprise JavaBeans (either session beans, message-driven beans, or other entity beans). Typically, entity beans represent rows in a database table or a view. Because entity beans represent business objects (or database objects), they are identified (and characterized) by a unique primary key. As database objects may be related to one another (through the typical database relationships identified via the keys), even the entity beans have relationships among themselves. When we talk of persistence of entity beans, there are two ways the bean persistence can be implemented. The first method is called Bean Managed Persistence (BMP), which means the bean implementation handles the storage of the bean’s state to the database and contains the code necessary to access the database and execute the required SQL statements. The second method is called Container Managed Persistence (CMP), which means that the container handles the storage of bean’s state to the database, and hence the bean code does not contain any database access code.

Message-Driven Beans

As mentioned earlier, the message-driven beans receive clients’ requests in the form of asynchronous messages. The convenience of asynchronous messages is that messages may be sent to target objects even when the object (or its container) is not running at the time messages were sent. In such a situation, the messages are held by the messaging server in message queues and will be delivered to the target objects when they start running and accepting client messages. In a way, the message-driven beans provide an offline model of communication between the clients and the EJBs. When the message-driven bean is ready to accept messages, it acts as a message listener and accepts messages from any client that targets the particular bean while sending the message. Message-driven beans do not save their state and hence can process messages from multiple clients simultaneously.

Mechanics Behind Implementing the EJBs

What are the technical characteristics of an EJB? Well, EJB is certainly a Java class, because it runs in a Java environment. Does it mean every Java class can work as an EJB? Certainly not. Let us examine what is contained in this Java class and how it differs from other Java classes. Based on the emphasis the Java architecture made on the use of interfaces, the readers should clearly understand that interfaces are the way Java enforces standardization of an object’s behavior with the external entities. Because Java does not support multiple inheritance models (unlike C++, which supports inheriting from multiple base classes), using interfaces is a requirement and the de-facto standard in building complex Enterprise applications in Java. For example, an EJB interacts with two external entities at a minimum—a client and the EJB container. The client may be local (another bean) or remote (external application); the container is local. The words local and remote are defined in the context of the environment or computer where the EJB runs. Hence, the interaction of EJBs with these two entities should be standardized.

Because EJB framework is built upon the RMI-IIOP architecture, every object that needs to be accessed by remote clients should implement the java.rmi.Remote interface. The J2EE framework created a new interface javax.ejb.EJBObject, which is extended from the java.rmi.Remote interface, and also added a few methods related to the EJB architecture. The javax.ejb.EJBObject declaration is shown here.

javax.ejb.EJBObject Interface

public interface javax.ejb.EJBObject extends java.rmi.Remote { public EJBHome getEJBHome() throws java.rmi.RemoteException; public Handle getHandle() throws java.rmi.RemoteException; public Object getPrimaryKey() throws java.rmi.RemoteException; public boolean isIdentical(EJBObject obj) throws java.rmi.RemoteException, javax.ejb.RemoveException; public void remove() throws java.rmi.RemoteException, javax.ejb.RemoveException; }

The methods declared in this interface are used by the container while interacting with the Enterprise JavaBean. For every Enterprise JavaBean, a remote interface must be created with a custom name by extending the javax.ejb.EJBObject interface. In doing so, we are ensuring two features: first, the java.rmi.Remote interface is automatically included because the EJBObject interface is extended from it, and secondly, the container will be able to interact with the Enterprise JavaBean through the EJBObject methods. Because the origin of our custom interface is java.rmi.Remote interface, our custom interface is identified as the remote interface for our Enterprise JavaBean. In our custom remote interface, we should declare all the methods that the client should be able to access on the bean while these methods are implemented in the bean. A sample declaration of the custom remote interface is shown here. From the example, it may also be noted that the method declarations should throw the java.rmi.RemoteException. These are the methods that will be implemented in the Enterprise JavaBean and will be accessed by the client through the remote interface.

public interface CustomRemote extends javax.ejb.EJBObject { public String getCustomerName(int custId) throws java.rmi.RemoteException; public int addNewCustomer(String customerName) throws java.rmi.RemoteException; }

As mentioned earlier, the methods declared in the EJBObject interface should not be implemented in the bean, as they are automatically implemented by the container while creating the EJBObject instances. Because the client accesses the bean’s methods through the custom remote interface, we need to find a way for the client to obtain this reference. For that purpose, the J2EE architecture provides a factory interface—known as the home interface and identified by the javax.ejb.EJBHome interface—which is also extended from java.rmi.Remote interface and hence can be accessed by the remote clients. The declaration of the EJBHome interface is given here.

javax.ejb.EJBHome Interface

public interface javax.ejb.EJBHome extends java.rmi.Remote { public EJBMetaData getEJBMetaData() throws java.rmi.RemoteException; public HomeHandle getEJBHomeHandle() throws java.rmi.RemoteException; public void remove(javax.ejb.Handle handle) throws java.rmi.RemoteException, javax.ejb.RemoveException; public void remove(Object PrimaryKey) throws java.rmi.RemoteException, javax.ejb.RemoveException; }

For every custom remote interface we design, a corresponding custom home interface should be created by extending the javax.ejb.EJBHome interface and giving it a custom name. In this custom home interface, we should define one or more create(. . .) methods that return to the client, an object of the custom remote interface. Each of these methods should throw the java.rmi.RemoteException, and may be overloaded with different signatures. For every create(. . .) method in the custom home interface, there should be a corresponding ejbCreate(. . .) method in the Enterprise Bean, with the same signature; however, all the ejbCreate(. . .) methods should return void. Here is an example of the custom home interface. This example shows two overloaded create(. . .) methods with different input arguments, but both return the remote interface.

public interface CustomHome extends javax.ejb.EJBHome { public CustomRemote create() throws java.rmi.RemoteException; public CustomRemote create(String name) throws java.rmi.RemoteException; }

Once the interfaces are declared, then we write the bean implementation. The J2EE architecture provides the javax.ejb.EnterpiseBean interface to represent an Enterprise Bean, which extends the java.io.Serializable interface, as the Enterprise Bean objects should be serializable in order to save their state to secondary disk storage when the container passivates the bean. There are three interfaces derived from this interface, the javax.ejb.SessionBean to define methods necessary for a session bean, the javax.ejb.EntityBean to define methods necessary for an entity bean, and the javax.ejb.MessageDrivenBean to define methods necessary for a message-driven bean. Therefore, depending on the type of bean we are building, we need to implement the appropriate interface. Because the bean class implements the appropriate sub-interface of javax.ejb.EnterpriseBean interface, it is the responsibility of the bean class to provide the implementation of the methods declared in the corresponding bean interface. For the convenience of the readers, the methods declared in the bean subinterfaces are shown here.

javax.ejb.SessionBean Interface Methods

public interface javax.ejb.SessionBean extends javax.ejb.EnterpriseBean { public void ejbActivate() throws javax.ejb.EJBException, java.rmi.RemoteException; public void ejbPassivate() throws javax.ejb.EJBException, java.rmi.RemoteException; public void ejbRemove() throws javax.ejb.EJBException, java.rmi.RemoteException; public void setSessionContext(javax.ejb.SessionContext ctx) throws javax.ejb.EJBException, java.rmi.RemoteException; }

javax.ejb.EntityBean Interface Methods

public interface javax.ejb.EntityBean extends javax.ejb.EnterpriseBean { public void ejbActivate() throws javax.ejb.EJBException, java.rmi.RemoteException; public void ejbLoad() throws javax.ejb.EJBException, java.rmi.RemoteException; public void ejbPassivate() throws javax.ejb.EJBException, java.rmi.RemoteException; public void ejbRemove() throws javax.ejb.RemoveException, javax.ejb.EJBException, java.rmi.RemoteException; public void ejbStore() throws javax.ejb.EJBException, java.rmi.RemoteException; public void setEntityContext(javax.ejb.EntityContext ctx) throws javax.ejb.EJBException, java.rmi.RemoteException; public void unsetEntityContext() throws javax.ejb.EJBException, java.rmi.RemoteException; }

javax.ejb.MessageDrivenBean Interface Methods

public interface javax.ejb.MessageDriveBean extends javax.ejb. EnterpriseBean { public void ejbRemove() throws javax.ejb.EJBException; public void setMessageDrivenContext (javax.ejb.MessageDrivenContext ctx) throws javax.ejb.EJBException; }

The default methods declared in these interfaces are known as the callback methods (or event handlers) that are invoked when the corresponding event takes place. For example, when the container has to activate a bean instance, the ejbActivate method is invoked after activating the bean instance and setting the conversational state to the value that the bean had before it was last passivated, or to the default state if it is the first time the bean is establishing connection with the client. Thus, the ejbActivate() method provides an opportunity to acquire any resources required by the bean, such as database connections, socket connections, and so on. On the other hand, the ejbPassivate() method is invoked before the bean is going to be passivated and should be used to gracefully release any resources previously acquired by the bean.

Activating means the bean instance is associated with its corresponding EJBObject (or remote interface) and made available to the client for use. A bean instance is activated when the corresponding EJBObject (or remote interface) receives a request from the client. Passivating is the activity of disassociating the bean instance from the corresponding EJBObject (or remote interface), after which the bean is either put into the bean pool for future activation or is removed and made a target for garbage collection. A bean instance is passivated if it is idle for a longer period of time. At the time of passivation, the bean’s conversational state is serialized to secondary disk storage if the bean is a stateful session bean. While passivating a bean, the container will not serialize the bean’s transient fields. A bean is not passivated when it is in the middle of a transaction and the transaction is not complete. When an EJBObject receives a subsequent request after the bean instance is passivated, the container automatically activates a bean instance (of same object type), associates it with the EJBObject, and sets the conversational state of the new instance to the point the previously passivated bean left before passivation. Thus, while the client is executing a series of method calls, it may so happen that the bean instance that is executing a subsequent method call may not be the same bean instance that started servicing the client; however, the container ensures that the newly assigned bean instance continues the conversation from the point where the previous bean instance left before being passivated. All this happens without the knowledge of the client. In the case of stateless session beans, it is up to the container to keep the bean instance in existence or remove it after executing the method call; however, the application should not rely on the state information of the bean in subsequent method calls. Therefore the ejbActivate() and ejbPassivate() methods do not apply to the stateless session beans. The ejbRemove() method is invoked by the container as a result of the client’s execution of the remove() method on the associated home interfaceand just before removing the bean instance from memory to put it in the bean pool and provides an opportunity to the bean to release any resources.

As mentioned earlier, the bean implementation will have one or more ejbCreate(. . .) methods to facilitate creating the bean; there is one such method for every create(. . .) method within the home interface created for the bean. When the client application executes one of the overloaded create() methods on the home interface, the container creates an instance of the bean, executes the corresponding ejbCreate() method, and passes any arguments to this method that were passed to the create() method on the home interface. This is why the create() and ejbCreate() pair of methods should have the same type and sequence of method arguments; however, the ejbCreate() method should return void, while the create() method returns the remote interface to the client. Thus, the ejbCreate() methods work similarly to the constructors in a traditional Java class, providing the bean an opportunity to initialize its state variables with the values received from the input arguments.

After the bean has been created and the ejbCreate() method is executed, the container then calls a method to set the runtime context of the bean. This method name and the input argument type for the method vary, depending on the type of bean. For example, if the bean is a session bean, the setSessionContext() method is called with the input argument of type javax.ejb.SessionContext. In the case of entity beans, the setEntityContext() method is called with the input argument of type javax.ejb.EntityContext, and in the case of message-driven beans, the setMessageDrivenContext() method is called with an argument of type javax.ejb.MessageDrivenContext. The runtime context of the bean is retained throughout the lifetime of the bean, and therefore should be captured to a state variable of appropriate type within this method. The runtime context of the bean provides some useful information, depending on the bean type, and may be accessed through the corresponding context type. The SessionContext object is available to the session bean type and provides details such as the EJBObject that is associated with the bean, the EJBHome that created the bean, and so on. Similarly, the EntityContext object is available to the entity bean type and provides similar details such as the SessionContext object, the primary key associated with the data object that the entity bean represents, and so on. The MessageDrivenContext object also provides similar details. hese context objects inherit most of the details from their superinterface javax.ejb.EJBContext and additionally provide more details specific to the Enterprise JavaBean type, except the MessageDrivenContext, which does not provide any additional details other than those inherited from its superinterface.It is important to keep in mind that the details provided by the running context of the bean may change when a new bean instance is associated with the EJBObject at the time the bean is reactivated after it was passivated once.

The entity beans have a few additional methods. The ejbStore() method is executed by the container to instruct the bean to synchronize the state of the database object with the contents of the bean or save the bean’s contents to the database. The ejbLoad() method is executed by the container to instruct the bean to synchronize its contents with those of the database, which means to load the contents of the database into the bean. These two methods are executed within the context of the transaction determined by the value specified for the transaction attribute in the bean’s deployment descriptor. The unsertEntityContext() method is executed by the container to unset the entity context previously set before removing the instance from memory and making it a target for the garbage collector. The MessageDrivenBean interface supports only two default events the ejbRemote() method and the setMessageDrivenContext() method.

The following example class implements the SessionBean: interface and shows which of the methods need to be implemented.

public class CustomBean implements javax.ejb.SessionBean { private SessionContext ctx; private String cutomerName; // The methods supporting session bean lifetime follow from here public void ejbActivate() { // coding this method is optional } public void ejbPassivate() { // coding this method is optional } public void ejbRemove() { // coding this method is optional } public void setSessionContext(SessionContext context) { ctx = context; } // The methods supporting home interface follow from here public void ejbCreate() { // Initialize any instance variables } public void ejbCreate(String name) { // Initialize any instance variables customerName = name; } // The methods supporting remote interface follow from here public String getCustomerName(int custId) { return customerName; } public int addNewCustomer(String customerName) { // Add business logic here } }

Figure 8.13 shows a typical scenario where a stateless session bean is in action. The relationships between different interfaces are shown in this figure, including the essential steps to bring a stateless session into action—obtain the home interface, execute the create method on the home interface to obtain the remote interface, and execute the remote interface methods. When the remote interface methods are executed, the corresponding methods on the bean are executed. Also, when more than one method is executed on the remote interface, the state of the session is not maintained between these method calls, as it is a stateless session bean.

Figure 8.13: Stateless session bean in action

Figure 8.14 shows a typical scenario with a stateful session bean in action. As shown in the figure, the stateful session bean differs from the stateless session bean in that it is passivated and activated by the container to the bean pool when the container determines it necessary.

Figure 8.14: Stateful session bean in action.

So far, the discussion has focused mainly on the session beans, even though some of the topics covered also apply to the entity beans. However, with respect to the entity beans, there is a lot more to learn. An entity bean is an object view of the data from a persistent storage, such as a relational database. In other words, an entity bean represents a row of data from one or more tables in a relational database, which means that, when instantiated, an object of an entity bean contains data from a database and acts as a gateway to the client applications to access the data. The bean is designed so that it loads the data from the database when directed and stores its data back to the database when instructed to do so. The client applications can work with the data only within the context of the bean instance. Every entity bean is identified (and distinguished) by a primary key (corresponding to the primary key of a table in the database), and therefore we need to create a primary key class associated with an entity bean class. As noted in the earlier discussion, this class does not exist for session beans. The primary key classshould be serializable, and its instances represent primary keys associated with corresponding entity bean instances. As one would expect, the data contained in the primary key object is necessary and sufficient to uniquely identify an entity bean object. Entity beans certainly participate in transactions and survive the application server crashes or failures because they represent data from the database. The ejbLoad() and ejbStore() methods are exclusive to the entity beans and are used to read the data from the database into the bean instance variables and save the bean instance variable data to the database, respectively, and are called by the container. For example, when a transaction is committed, the container would call the ejbStore() method on all the entity beans within the context of the transaction. Because the container is multi-threaded and because it services multiple clients each in its own thread, each of the threads might be running an instance of the same entity bean class, which means that several entity bean instances of the same bean class may be active concurrently. This is perfectly acceptable to the underlying database systems, as most of the currently existing relational databases also support concurrent multi-user access. Each of these bean instances represents a snapshot of the data at the time the data is loaded into the bean. However, whichever of these bean instances enters into a transaction first will obtain a lock on the database object so that other bean instances will be able to get their next snapshot (or their chance to enter into a transaction) only after the current transaction is committed or rolled back.

Mere instantiation of an entity bean does not synchronize its state with the database. The synchronization only happens through the primary key object, which is an instance of the primary key class. Also, the container may choose to create one or more instances of the entity bean without loading the data from the database. This way, the bean instances can be pooled, which helps in saving time spent in frequent creation and destruction of the bean instances. It should be noted that the entity bean pool is different (and unique) for every bean type because the underlying database object represented by the entity bean is different. When the container receives a request for a specific entity bean, it picks one of the pooled instances from the bean pool corresponding to the requested bean type, activates the bean instance by associating it with an EJBObject and a specific primary key, and synchronizes its data from the database. At this time, the bean is specific because it contains specific values for its instance variables. Again the bean instance may be passivated by the container if the bean is not participating in a transaction and is idle for longer time. With respect to entity beans, passivating means disassociating the bean instance from the EJBObject and the specific primary key; during passivation, the state of the entity bean is stored to the database through the ejbStore() method. The container will not passivate an entity bean instance if the instance is participating in a transaction that is due for a commit or rollback. Similar to the session beans, the ejbActivate() method is the ideal place to acquire any resources, and the ejbPassivate() method is the ideal place to release such resources. Figure 8.15 shows a simple pictorial view of a scenario with an entity bean in action.

Figure 8.15: An Entity Bean in action.

Because the entity beans store their state to a persistent storage such as a relational database, there should be database access logic to perform the SQL statements. There are two types of persistence offered by the EJB architecture—bean-managed persistence (BMP) and the container-managed persistence (CMP). While developing bean-managed persistent entity beans,the developer has to write all the database access code in the form of methods on the entity bean class. This approach involves a large amount of database access code, and hence the entity bean class may become huge. The developer has full control over database access logic and can write any fine-grained code. On the other hand, while developing the container-managed persistent entity beans,the container assumes the responsibility to perform all the necessary database access logic, thus reducing the entity bean class size and complexity. However, this process restricts the developer’s role in performing any fine-grained logic control, as any persistence-related requirements should be passed to the container through the bean design and associated changes in the deployment descriptor. Although most of the following discussion is applicable to both types of entity beans, particularly with respect to the interaction (and sequence of events) that takes place between the container and the beans, any differences will be pointed out for the benefit of the readers.

Let us examine the sequence of events that takes place when the create() method is called on the home interface. When the create() method is called on the home interface, the ejbCreate() method is called on the entity bean instance, which triggers the creation of a new database object (e.g., a new row in a table) in the underlying database. The ejbCreate() method returns the primary key object (corresponding to the newly created row), which is used by the home interface to create an instance of the remote object (also known as the EJBObject) and returned to the client application which initiated the create() method. Every create() method declared on the home interface should return the remote interface object and should have a corresponding ejbCreate() method on the entity bean class, which returns an object of the primary key and whose input parameters, their types, and sequence match with those of the create() method in the home interface. The primary key object returned by the ejbCreate() method is used by the container to identify the entity bean instance at any time. Both the create() and ejbCreate() methods should be declared public and should not be static or final. The input arguments should follow the rules specified by the RMI framework, as RMI is the underlying data marshalling technique used by EJB framework. For every ejbCreate() method, there should be a corresponding ejbPostCreate() method with the same set and sequence of input arguments, but the return value should be void. The container calls this the ejbPostCreate() method after the ejbCreate() method. This method can be used to perform any initialization tasks.

Similarly, when the client invokes the remove() method on the home interface, the ejbRemove() method is called on the entity bean, which triggers deletion of the data object (e.g., the corresponding row in a table) in the underlying database. It should be noted that the ejbRemove() method only deletes the underlying database object and does not delete the bean instance.

When a bean is activated, the container sets the bean’s context in the form of an instance of javax.ejb.EntityContext, which gives a lot of information to the bean instance. After setting the entity context, the bean is able to understand what data object it is going to represent and what are other related details, such as the associated EJBObject and transactional information. One of the most important methods of the EntityContext interface is getPrimaryKey(), which should be used by the bean to know what data object it is representing from the underlying database. This is necessary, particularly when loading the data from the database through the ejbLoad() method and removing the data object through the ejbRemove() method; otherwise the bean instance does not know which data object to load or remove, respectively.

There are other methods supported by the bean and exposed by the home interface that are used to find data from the database. Because these methods are used to find data objects, they are known as finder methods. Thus, unlike the case with the session beans, the home interface of entity beans exposes the create, remove, and finder methods.

While implementing BMP entity beans,we should implement at least one finder method, which is the ejbFindByPrimaryKey() method. For every finder method declared in the entity bean class, there should be a corresponding finder method declared in the bean’s home interface. The finder methods in the entity bean have names of the form ejbFind<. . .>(), whereas the finder methods in the home interface have their corresponding names starting with find without the prefix ejb. For example, for the ejbFindByPrimaryKey() method in the bean class, the corresponding name in the home interface should be findByPrimaryKey(). Similarly, if we write a finder method ejbFindByCustomerName() in the bean class, the corresponding finder method in the home interface should have the name findByCustomerName(). We can implement as many finder methods as we desire. The finder methods can be serviced by the bean instances in the pool; i.e., for the finder method to be called, the bean instance does not have to be active. Thus, when a bean receives a call on one of its finder methods, the bean should be able to execute it and return one or more primary key objects to the container (depending on whether it finds one or more data objects matching the selection criteria), so that the container will be able to create one or more EJBObjects to be returned to the client. The ejbFind<. . .>() methods should always return either a primary key object if a single object is to be returned or a collection of primary key objects if more than one object is to be returned. Similarly, the finder methods on the home interface should return the remote object interface or a collection (of remote object interfaces). The finder methods on both the bean class and the home interface should include the javax.ejb.FinderException exception in their throws clause.

Having focused our attention so far on the BMP entity beans, let us now continue our discussion with the CMP entity beans. This discussion presented here is specific to the EJB 2.0 specification, as it is the latest specification implemented by a majority of the vendors at the time of this writing. The CMP model makes a clear separation between the bean instance and the representation of its persistence, which permits the entity bean to be redeployed across different container implementations and different data stores. This means that entity beans developed in one environment can be easily deployed in another environment without recompiling the bean classes. Here, environment means mainly the container and database combinations. This kind of flexibility is very much needed and appreciated because of the very nature of beans (i.e., because the entity beans’ persistence is container-managed). In CMP entity beans, the logic to handle the bean’s persistence is handled by the container and therefore should not be coded by the bean developers in the bean’s class. However, the container should be informed about the persistent fields of the bean through the bean’s deployment descriptor, and the access methods (get and set methods) of these persistent fields should be declared in the bean’s class as abstract methods. The persistent field definitions in the deployment descriptor are collectively known as the abstract persistent schema and are identified by the cmp-field elements. Only those fields declared in the deployment descriptor will be created by the container as persistent fields, and only those access methods (of the fields) declared as abstract in the bean’s class will be implemented by the container. Because the bean’s class contains abstract methods, the bean’s class is abstract and will not be directly used by the container to instantiate the bean. The container builds a subclass dynamically deriving from the bean class and implements the persistent fields as well as concrete implementation of their access methods. It is this class that is used by the container to instantiate a bean to service the clients. The deployment descriptor of the bean performs a major role in the CMP entity beans, compared to any other type of EJBs. In other words, it is the deployment descriptor that bridges the gap between the bean class and the container. In addition to the persistent field definitions, the abstract persistent schema also describes logical relationship among the entity beans through the cmr-field elements, but without specifying how to relate (or map) the abstract persistent schema with respect to a physical database (or a specific persistent storage mechanism). As mentioned earlier, it is the Application Deployer who uses the information provided in the deployment descriptor to do this mapping, using the tools provided by the container vendor. This is part of designing the runtime environment, making sure that the runtime environment created for the application is compatible to run the application within the context of the container. However, the runtime mapping of the relationships between the information provided in the deployment descriptor and the physical database is performed by the container. The classes generated by the container use the JDBC or SQLJ code to access the database and are separate from the bean classes. Therefore, while implementing CMP-based entity beans, the bean classes can be deployed to any container that is built to the EJB 2.0 specification (or any particular version of the specification that is appropriate at the time of application implementation). The finder methods declared in the home interface of the entity bean are not coded in the bean class while implementing CMP entity beans; rather, the necessary logic is provided in the deployment descriptor using the EJB-QL (EJB Query Language), which is an object query language. EJB-QL statements are converted by the container to appropriate JDBC statements during runtime. In addition to the finder methods, the CMP entity beans also support select methods to execute queries that are not exposed to the clients directly; instead, the select methods are executed from within the business methods of the bean. The select methods are of the form ejbSelect<. . .>(. . .), where the first set of ellipses may be replaced by a custom method name and the second set of ellipses may be replaced with zero or more input arguments to the method. For example, the ejbSelectAllCustomers(String zipCode) method might represent a method used to retrieve all the customers located within a specific zip code. The select methods are executed in the context of the transaction specified through the transaction attribute of the entity bean. The bean provider defines the select methods as abstract methods within the bean class, while the actual implementation of the methods is generated at the time of deployment. Although the preceding discussion has introduced a number of new terms to the readers, the examples provided in the chapter will answer the majority of questions raised.

The last topic of this subsection before we turn our attention toward examples is message-driven beans. As the name suggests, the message-driven beans are used to receive messages from a message queue, send messages back to the queue, and work with the Java Messaging API, which was discussed in the previous chapter. Because the beans are deployed within the context of a container, in this case the container acts as a listener to the message queue. The container registers with the queue so that the messages arriving at the queue are received by the container. The message-driven bean implements the javax.jms.MessageListener interface in addition to the methods declared in the javax.ejb.MessageDrivenBean interface. The javax.jms.MessageListener interface declares only one method. It is the onMessage() method, which is invoked by the container when a message is received from the queue. The method receives a Message object type as a single input argument. As discussed in the previous chapter, this Message object should be cast to specific message type, such as a text message type, object message type, and so on.

A message-driven bean plays dual roles. On one side, it plays a role similar to the session bean because it interacts with the external world, and on the other side, it plays a backend role, as the container projects (or registers) itself to the message queue in receiving and sending messages. The addition of message-driven beans to the EJB 2.0 specification adds support to service asynchronous client messages, which form a major part in many Enterprise systems. In addition, the message-driven beans can service multiple message queues (or in other words, multiple external asynchronous clients) concurrently. Another feature of the message-driven beans is that they are also capable of working in offline mode, which means that the sender can send as many messages to the queue as is governed by the capacity of the queue even when the application server is down. When the server next is up, the container receives all the messages held in the queue in the same order as they were sent by the sender and ensures that each message is completely processed by the onMessage event handler of the bean before firing the next event. It should be noted that because message-driven beans work with message queues, they do not use the RMI-IIOP protocol, unlike the other two types of Enterprise beans. Therefore, they do not implement the home interface and remote interface. In fact, with respect to complexity of implementation, these are the simplest kind of beans that can be implemented; the entity beans have the most complex architecture, and the session beans fall in-between.

EJB Examples Using JBuilder8 IDE

In the following subsections, we will walk through the process of creating a couple of examples using Borland JBuilder8 (Enterprise Edition) IDE and Borland Enterprise Server Edition 5.2. The first example is building a stateless session bean that will implement a method to return current timestamp on the server, and the second example is building an entity bean that is queried through the session bean built in the first example.

Stateless Session Bean

JBuilder8 is one of the very powerful IDEs for developing Enterprise-class Java applications in addition to building desktop applications and other server type applications. The following steps will illustrate how easy it is to develop EJBs in our applications.

Create a project in JBuilder8 IDE and save the project files in a directory of your choice and with a name of your choice. The example project is named ‘StateLessSessionBeans’. Then the project properties should be updated to ensure that the project is properly configured to run with the selected application server. Invoke the Project PropertiesWizard by selecting the Project ® Project Properties option from the main menu. In the wizard, open the last page, Server. The displayed window looks similar to Figure 8.16.

Figure 8.16: Project Properties window in JBuilder8 IDE.

In this window, the radio button named ‘Single server for all services in project’ should be selected, with the Borland Enterprise Server AppServer Edition5.0.2-5.1.xitem selected from the drop-down list. If this server is not displayed in the list, it could be for one of two reasons; either the server is not installed on the system, or it is not configured with the IDE. It is required that Borland Enterprise Server AppServer Edition 5.1 or 5.2 be installed on the system and configured with the IDE. If the server is already installed on the system, to configure the server with the IDE select the Tools ® Configure Server item from the main menu. The displayed wizard will have a number of servers listed, most of them grayed out. If a server is not configured (or is disabled), the corresponding item is grayed out in the list. The Tomcat JSP engines (different versions) are enabled by default. From the list, select the Borland Enterprise Server AppServer Edition, and in the right pane, click the ‘Enabled’checkbox. This will show the appropriate (default) configuration information. Make necessary changes in these entries and click OK to save the configuration. Configuring a server with the IDE is to be done only once after the server is installed.

From the pop-up menu, select the Create EJB ® Session Bean item to create a session bean. The bean is created and displayed in object notation with the (default) name and methods. If you click the left mouse button on the bean name, then a small window is displayed where the bean’s properties can be updated. In this window, the name of the window is changed to ‘CurrentTimeSession’, as shown in Figure 8.20.

Figure 8.20: Bean Properties window.

Then place the mouse pointer on the bean name and click the right button to display another popup menu with items necessary to create fields and methods. The Add ® Method item is selected to add a new method, as shown in Figure 8.21. In the displayed window, enter the method name, return value and input arguments, as shown in Figure 8.22. Add a new method GetCurrentServerTime(),which does not take any input arguments and returns a java.lang.String object.

Figure 8.21: Adding bean methods in the Visual Designer.

Figure 8.22: Updating method details.

  • Once the changes are saved, the IDE automatically creates three program files: the remote and home interfaces and the bean implementation. The files are named based on the bean name; in this case, they are CurrentTimeSession, CurrentTimeSessionHome, and CurrentTimeSessionBean, respectively. The source code of these programs is displayed in Listings 8.9, 8.10 and 8.11, respectively. The majority of the code is written by the IDE using the details provided during the bean design. In this example, the developer has to write the implementation of the GetCurrentServerTime() method only, in the bean implementation class.

  • Next, we need to build a client application that can make use of the bean’s methods. JBuilder8 IDE provides an easy way to build a test client, which may be converted to a real client application at a later point of time. In the Enterprisepage of the Object Gallery, the EJB Test Client icon may be double-clicked to invoke the corresponding wizard, as shown in Figure 8.23. Most of the options displayed in this window are acceptable, and readers who are building example programs should make any necessary changes.

    Figure 8.23: EJB Test Client Wizard.

  • Once the options are selected and the OK button is clicked, the test client application is built. Although most of the program is written by the IDE, the last two lines in the main() function are written by the developer. As mentioned earlier, this is only a test client application, but it is very helpful in building a real-world client as most of the code from the test client can be copied to the real client application. Listing 8.12 displays the source code for the test client program.

  • Build the complete project at once by choosing the appropriate menu item from the Project menu. Because most of the code is written by the IDE, there should not be any compilation errors. At this time, the EJB module JAR file is also built, with all the necessary files included in it.

  • Create a runtime configuration for the server module, through the Runtime Configuration Wizard, by selecting the Run ® Configurations item from the main menu. This wizard is shown in Figure 8.24, where all the necessary parameters and other details should be provided before running the application. We need to create a runtime configuration for the server module and the client module.

    Figure 8.24: Runtime Configuration Wizard.

  • The next step is to run the programs and test the application. This step consists of three sequential actions. Start the Borland Enterprise Server, deploy the EJB JAR file, and finally run the client application. The Borland Enterprise Server is started within JBuilder8 IDE, by selecting the Tools ® Borland Enterprise Server Management Agent item from the menu item. A successful startup of the server is identified by a group of messages thrown to the console, as shown in Figure 8.25.

    Figure 8.25: JBuilder8 IDE with Borland Enterprise Server startup messages.

  • Then, run the server module (or deploy the EJB JAR file) using the server runtime configuration created in an earlier step. When the server module is run, the JAR file is deployed to the container, and the EJBs in the JAR file are instantiated and ready for use. The corresponding messages may be viewed in Figure 8.26.

    Figure 8.26: JBuilder8 IDE with EJB deployment messages.

  • Finally, run the client application using the client runtime configuration created during test client building. The client application is able to access the session bean deployed in the EJB server, and the retrieved server timestamp is displayed in the console window, as shown in Figure 8.27.

    Figure 8.27: JBuilder8 IDE with test client messages.

This listing is available on the accompanying CD-ROM.

Listing 8.9: CurrentTimeSession.java—Remote Interface

package statelesssessionbeans; import javax.ejb.*; import java.util.*; import java.rmi.*; public interface CurrentTimeSession extends javax.ejb.EJBObject { public String GetCurrentServerTime() throws RemoteException; }

Listing 8.10, Listing 8.11, and Listing 8.12 are available on the accompanying CD-ROM.

Listing 8.10: CurrentTimeSessionHome.java—Home Interface

package statelesssessionbeans; import javax.ejb.*; import java.util.*; import java.rmi.*; public interface CurrentTimeSessionHome extends javax.ejb.EJBHome { public CurrentTimeSession create() throws CreateException, RemoteException; }

Listing 8.11: CurrentTimeSessionBean.java—Bean Implementation

package statelesssessionbeans; import javax.ejb.*; public class CurrentTimeSessionBean implements SessionBean { SessionContext sessionContext; public void ejbCreate() throws CreateException { /**@todo Complete this method*/ } public void ejbRemove() { /**@todo Complete this method*/ } public void ejbActivate() { /**@todo Complete this method*/ } public void ejbPassivate() { /**@todo Complete this method*/ } public void setSessionContext(SessionContext sessionContext) { this.sessionContext = sessionContext; } public java.lang.String GetCurrentServerTime() { /**@todo Complete this method*/ java.util.Calendar currTime = java.util.Calendar.getInstance(); return currTime.getTime().toString(); } }

Listing 8.12: CurrentTimeSessionTestClient1.java—Client

package statelesssessionbeans; import javax.naming.*; import javax.rmi.PortableRemoteObject; public class CurrentTimeSessionTestClient1 extends Object { private static final String ERROR_NULL_REMOTE = "Remote interface reference is null. It must be created by calling one of the Home interface methods first."; private static final int MAX_OUTPUT_LINE_LENGTH = 100; private boolean logging = true; private CurrentTimeSessionHome currentTimeSessionHome = null; private CurrentTimeSession currentTimeSession = null; //Construct the EJB test client public CurrentTimeSessionTestClient1() { initialize(); } public void initialize() { long startTime = 0; if (logging) { log("Initializing bean access."); startTime = System.currentTimeMillis(); } try { //get naming context Context context = new InitialContext(); //look up jndi name Object ref = context.lookup("CurrentTimeSession"); //look up jndi name and cast to Home interface currentTimeSessionHome = (CurrentTimeSessionHome) PortableRemoteObject.narrow(ref, CurrentTimeSessionHome.class); if (logging) { long endTime = System.currentTimeMillis(); log("Succeeded initializing local bean access through Local Home interface."); log("Execution time: " + (endTime - startTime) + " ms."); } } catch(Exception e) { if (logging) { log("Failed initializing bean access."); } e.printStackTrace(); } } //---------------------------------------------------------------------------- // Methods that use Home interface methods to generate a Remote interface reference //---------------------------------------------------------------------------- public CurrentTimeSession create() { long startTime = 0; if (logging) { log("Calling create()"); startTime = System.currentTimeMillis(); } try { currentTimeSession = currentTimeSessionHome.create(); if (logging) { long endTime = System.currentTimeMillis(); log("Succeeded: create()"); log("Execution time: " + (endTime - startTime) + " ms."); } } catch(Exception e) { if (logging) { log("Failed: create()"); } e.printStackTrace(); } if (logging) { log("Return value from create(): " + currentTimeSession + "."); } return currentTimeSession; } //---------------------------------------------------------------------------- // Methods that use Remote interface methods to access data through the bean //---------------------------------------------------------------------------- public String GetCurrentServerTime() { String returnValue = ""; if (currentTimeSession == null) { System.out.println("Error in GetCurrentServerTime(): " + ERROR_NULL_REMOTE); return returnValue; } long startTime = 0; if (logging) { log("Calling GetCurrentServerTime()"); startTime = System.currentTimeMillis(); } try { returnValue = currentTimeSession.GetCurrentServerTime(); if (logging) { long endTime = System.currentTimeMillis(); log("Succeeded: GetCurrentServerTime()"); log("Execution time: " + (endTime - startTime) + " ms."); } } catch(Exception e) { if (logging) { log("Failed: GetCurrentServerTime()"); } e.printStackTrace(); } if (logging) { log("Return value from GetCurrentServerTime(): " + returnValue + "."); } return returnValue; } //---------------------------------------------------------------------------- // Utility Methods //---------------------------------------------------------------------------- private void log(String message) { if (message == null) { System.out.println("-- null"); return ; } if (message.length() > MAX_OUTPUT_LINE_LENGTH) { System.out.println("-- " + message.substring(0, MAX_OUTPUT_LINE_LENGTH) + " ..."); } else { System.out.println("-- " + message); } } //Main method public static void main(String[] args) { CurrentTimeSessionTestClient1 client = new CurrentTimeSessionTestClient1(); // Use the client object to call one of the Home interface wrappers // above, to create a Remote interface reference to the bean. // If the return value is of the Remote interface type, you can use it // to access the remote interface methods. You can also just use the // client object to call the Remote interface wrappers. // This part of the code is written by the developers // Everything else till this point is written by the JBuilder8 IDE. client.create(); System.out.println(client.GetCurrentServerTime()); } }

Developing Entity Beans

It should be noted that developing EJB-based applications is very easy using a tool such as JBuilder8, as most of the mundane work is automatically performed for the developers with the help of the details provided through the visual designer. Now we will extend the example developed in the previous subsection to create a container-managed persistent entity bean and access it through the session bean, as outlined in the following steps:

  • Because entity beans represent database objects and do not directly interface with the clients, these are accessed by the clients through session beans. Therefore, to demonstrate the entity beans, we need to create a session bean. As we already built a session bean example before, we can make use of it as well. Keep the previous project open so that we can add new components and files to it.

  • In addition, we will need a database for the purpose of demonstrating an entity bean example. The JDataStore database shipped with JBuilder8 (Enterprise Edition) is used, as it is already installed in the system. The processing steps are exactly the same for any other database, with the exception of the JDBC driver and the connection URL.

  • Instead of attempting to work directly on the provided database, a copy of the database is created in the same directory. In this case, a copy of the Employee database is created with the name ‘EmployeeSample’. Typically, it is located in the /samples/JDataStore/datastores subdirectory within the home directory of JBuilder8. JDataStore Explorer is one of the executables installed along with the database server and is useful for exploring the individual databases and their contents (including table structures and data). When the database is installed, no user id and password are associated. Later on, the database can be protected by assigning a user id and password. More details on JDataStore Explorer is beyond the current scope, and the readers are encouraged to explore this topic on their own as it is fairly simple and intuitive.

  • The Employeetable in the database is chosen for demonstrating this example. From within the tree list of project files in the top left pane of the IDE, double-click the EJB Modulethat was created in the earlier example, so that the EJB visual designer is displayed in the right editor pane. In addition, you may notice that the tree list contents displayed in the lower left pane also change to display a list of data sources created for the project. Because we have not added any data sources so far, this list must be currently empty.

  • Place the mouse pointer on the datasourcestree list item in the lower left pane and click the right mouse button. A pop-up menu is displayed; select Import Schema from Database item from this menu. Then the Database Schema Providerwindow is displayed, as shown in Figure 8.28, where we can either enter database schema details manually or import the details from a connection object, if one is already established. Once the database schema details are entered, make sure that the username and password are also entered and click the OK button.

    Figure 8.28: Database Schema Provider.

  • If the database schema details are properly entered, the lower left pane will display the tables and other objects from the specified schema. Identify the Employeetable from the schema, place the mouse pointer on the table name, and click the right button to display a pop-up window from which the ‘Create CMP 2.0 Entity Bean’item is selected, as this example is built to demonstrate the container-managed persistent entity bean.

  • The IDE creates the entity bean object and places it by the side of the previously created session bean object. When the properties of this entity bean are displayed as in Figure 8.29, it may be noticed that it selects the localinterface by default. This should be changed to remoteor local/remote,because we need a remote interface to be implemented by the object. Depending on the type of interface selected, the Java programs are created by the IDE and displayed in the top left pane tree list. These are typically the home and remote interfaces along with the bean implementation.

    Figure 8.29: Entity bean properties.

  • Now create a new method in the session bean using the visual editor, as explained earlier. The method is named getEmployeeName(), which returns the employee name as a java.lang.String object and takes one input argument of type java.lang.Short for the employee number to be retrieved.

  • The top left pane tree list also lists the Enterprise beans created within the EJB module (or the JAR file). When each of these items are double-clicked in this list, the appropriate bean properties are shown in the right editor pane in several tab pages, as shown in Figure 8.30. In the figure, the first tab page with most common features is displayed. Open this window for the session bean because we need to create a local reference of the entity bean within the session bean.

    Figure 8.30: Session bean properties—general window.

  • From this figure, it may be noticed that in this window, you may choose to select the session bean as stateful or stateless as you desire, among other settings. Go to the EJB Local Referencestab page in this window, as displayed in Figure 8.31. In this page, you need to create a local reference for the entity bean by clicking the Add button and selecting the appropriate items in the drop-down lists for the Linkand Typefields; all other values should be left to the default values displayed.

    Figure 8.31: Session bean properties—EJB Local References.

  • Next we need to write the implementation code. Listings 8.13, 8.14, and 8.15 display the home interface, remote interface, and bean implementation, respectively. We do not change any of the code provided in these programs. At this point, the bean implementation typically represents a record from the Employeetable of the database, and it should also be noted that it is an abstract class as discussed earlier. This is because, in the case of container-managed persistent beans, the container will dynamically create the implementation to retrieve data from the database.

  • Listing 8.16 shows the modified remote interface of the session bean, as it now contains the newly defined remote method, getEmployeeName().

    Listing 8.17 is the modified session bean implementation, where the new method is implemented to retrieve the employee’s first name and last name for the specified employee number and concatenated to a single string object of employee name. Typically, the session bean locates the entity bean through the naming service and then narrows it down to the remote home interface of the entity bean. Then, executing the findByPrimaryKey() method on the remote home interface retrieves the remote interface representing the entity bean object. It is this remote interface that provides additional getter and setter methods on the entity bean object. Note that it is this session bean implementation that interacts with the client and not the entity bean, because entity beans remain in the backend (without direct client interaction).

  • Listing 8.18 is the test client implementation, as explained in the previous subsection. Most of the code in this program is implemented by the IDE as mentioned earlier.

  • Now rebuild the entire application and redeploy the JAR file. Before attempting to redeploy the JAR file and starting the server module, start the Borland Enterprise Server from within the IDE as explained before. Figure 8.32 displays the two Enterprise beans (session bean and entity bean) deployed in the container when the server module is started using the runtime configuration.

    Figure 8.32: JBuilder8 IDE with EJB Deployment messages.

  • Finally, run the newly built test client. If everything has been done properly, the test client should run successfully and display the retrieved record in the console window, as shown in Figure 8.33.

    Figure 8.33: JBuilder8 IDE with test client messages.

Listing 8.13 through Listing 8.18 are available on the accompanying CD-ROM.

Listing 8.13: EmployeeRemoteHome.java—Home Interface

package statelesssessionbeans; import javax.ejb.*; import java.util.*; import java.rmi.*; public interface EmployeeRemoteHome extends javax.ejb.EJBHome { public EmployeeRemote create(Short empNo) throws CreateException, RemoteException; public EmployeeRemote findByPrimaryKey(Short empNo) throws FinderException, RemoteException; }

Listing 8.14: EmployeeRemote.java—Remote Interface

package statelesssessionbeans; import javax.ejb.*; import java.util.*; import java.rmi.*; import java.sql.*; import java.math.*; public interface EmployeeRemote extends javax.ejb.EJBObject { public Short getEmpNo() throws RemoteException; public void setFirstName(String firstName) throws RemoteException; public String getFirstName() throws RemoteException; public void setLastName(String lastName) throws RemoteException; public String getLastName() throws RemoteException; public void setPhoneExt(String phoneExt) throws RemoteException; public String getPhoneExt() throws RemoteException; public void setHireDate(Timestamp hireDate) throws RemoteException; public Timestamp getHireDate() throws RemoteException; public void setDeptNo(String deptNo) throws RemoteException; public String getDeptNo() throws RemoteException; public void setJobCode(String jobCode) throws RemoteException; public String getJobCode() throws RemoteException; public void setJobGrade(Short jobGrade) throws RemoteException; public Short getJobGrade() throws RemoteException; public void setJobCountry(String jobCountry) throws RemoteException; public String getJobCountry() throws RemoteException; public void setSalary(BigDecimal salary) throws RemoteException; public BigDecimal getSalary() throws RemoteException; public void setFullName(String fullName) throws RemoteException; public String getFullName() throws RemoteException; }

Listing 8.15: EmployeeBean.java—Bean Implementation

package statelesssessionbeans; import javax.ejb.*; abstract public class EmployeeBean implements EntityBean { EntityContext entityContext; public java.lang.Short ejbCreate(java.lang.Short empNo) throws CreateException { setEmpNo(empNo); return null; } public void ejbPostCreate(java.lang.Short empNo) throws CreateException { /**@todo Complete this method*/ } public void ejbRemove() throws RemoveException { /**@todo Complete this method*/ } public abstract void setEmpNo(java.lang.Short empNo); public abstract void setFirstName(java.lang.String firstName); public abstract void setLastName(java.lang.String lastName); public abstract void setPhoneExt(java.lang.String phoneExt); public abstract void setHireDate(java.sql.Timestamp hireDate); public abstract void setDeptNo(java.lang.String deptNo); public abstract void setJobCode(java.lang.String jobCode); public abstract void setJobGrade(java.lang.Short jobGrade); public abstract void setJobCountry(java.lang.String jobCountry); public abstract void setSalary(java.math.BigDecimal salary); public abstract void setFullName(java.lang.String fullName); public abstract java.lang.Short getEmpNo(); public abstract java.lang.String getFirstName(); public abstract java.lang.String getLastName(); public abstract java.lang.String getPhoneExt(); public abstract java.sql.Timestamp getHireDate(); public abstract java.lang.String getDeptNo(); public abstract java.lang.String getJobCode(); public abstract java.lang.Short getJobGrade(); public abstract java.lang.String getJobCountry(); public abstract java.math.BigDecimal getSalary(); public abstract java.lang.String getFullName(); public void ejbLoad() { /**@todo Complete this method*/ } public void ejbStore() { /**@todo Complete this method*/ } public void ejbActivate() { /**@todo Complete this method*/ } public void ejbPassivate() { /**@todo Complete this method*/ } public void unsetEntityContext() { this.entityContext = null; } public void setEntityContext(EntityContext entityContext) { this.entityContext = entityContext; } }

Listing 8.16: CurrentTimeSession.java—Modified

package statelesssessionbeans; import javax.ejb.*; import java.util.*; import java.rmi.*; public interface CurrentTimeSession extends javax.ejb.EJBObject { public String GetCurrentServerTime() throws RemoteException; public String getEmployeeName(Short empNo) throws RemoteException; }

Listing 8.17: CurrentTimeSessionBean.java—Modified

package statelesssessionbeans; import javax.ejb.*; import javax.naming.*; public class CurrentTimeSessionBean implements SessionBean { SessionContext sessionContext; public void ejbCreate() throws CreateException { /**@todo Complete this method*/ } public void ejbRemove() { /**@todo Complete this method*/ } public void ejbActivate() { /**@todo Complete this method*/ } public void ejbPassivate() { /**@todo Complete this method*/ } public void setSessionContext(SessionContext sessionContext) { this.sessionContext = sessionContext; } public java.lang.String GetCurrentServerTime() { java.util.Calendar currTime = java.util.Calendar.getInstance(); return currTime.getTime().toString(); } // This is the newly added method implementation // for the method getEmployeeName(). public java.lang.String getEmployeeName(Short empNo) { String empName = null; String empFName = null; String empLName = null; try { Context context = new InitialContext(); Object object = context.lookup("EmployeeRemote"); EmployeeRemoteHome eHome = (EmployeeRemoteHome)javax.rmi.PortableRemoteObject.narrow(object, EmployeeRemoteHome.class); EmployeeRemote emp = eHome.findByPrimaryKey(empNo); empFName = emp.getFirstName(); empLName = emp.getLastName(); empName = empFName + " "; empName += empLName; } catch (ClassCastException cce) { return "ClassCastException occured while executing getEmployeeName method"; } catch (NamingException nex) { return "NamingException occured while executing getEmployeeName method"; } catch (FinderException fex) { return "FinderException occured while executing getEmployeeName method"; } catch (Exception ex) { return "General Exception occured while executing getEmployeeName method"; } return empName; } }

Listing 8.18: EnterpriseBeanTestClient2.java—Test Client

package statelesssessionbeans; import javax.naming.*; import javax.rmi.PortableRemoteObject; public class EnterpriseBeanTestClient2 extends Object { private static final String ERROR_NULL_REMOTE = "Remote interface reference is null. It must be created by calling one of the Home interface methods first."; private static final int MAX_OUTPUT_LINE_LENGTH = 100; private boolean logging = true; private CurrentTimeSessionHome currentTimeSessionHome = null; private CurrentTimeSession currentTimeSession = null; //Construct the EJB test client public EnterpriseBeanTestClient2() { initialize(); } public void initialize() { long startTime = 0; if (logging) { log("Initializing bean access."); startTime = System.currentTimeMillis(); } try { //get naming context Context context = new InitialContext(); //look up jndi name Object ref = context.lookup("CurrentTimeSession"); //look up jndi name and cast to Home interface currentTimeSessionHome = (CurrentTimeSessionHome) PortableRemote Object.narrow(ref, CurrentTimeSessionHome.class); if (logging) { long endTime = System.currentTimeMillis(); log("Succeeded initializing local bean access through Local Home interface."); log("Execution time: " + (endTime - startTime) + " ms."); } } catch(Exception e) { if (logging) { log("Failed initializing bean access."); } e.printStackTrace(); } } //---------------------------------------------------------------------------- // Methods that use Home interface methods to generate a Remote interface reference //---------------------------------------------------------------------------- public CurrentTimeSession create() { long startTime = 0; if (logging) { log("Calling create()"); startTime = System.currentTimeMillis(); } try { currentTimeSession = currentTimeSessionHome.create(); if (logging) { long endTime = System.currentTimeMillis(); log("Succeeded: create()"); log("Execution time: " + (endTime - startTime) + " ms."); } } catch(Exception e) { if (logging) { log("Failed: create()"); } e.printStackTrace(); } if (logging) { log("Return value from create(): " + currentTimeSession + "."); } return currentTimeSession; } //---------------------------------------------------------------------------- // Methods that use Remote interface methods to access data through the bean //---------------------------------------------------------------------------- public String GetCurrentServerTime() { String returnValue = ""; if (currentTimeSession == null) { System.out.println("Error in GetCurrentServerTime(): " + ERROR_NULL_REMOTE); return returnValue; } long startTime = 0; if (logging) { log("Calling GetCurrentServerTime()"); startTime = System.currentTimeMillis(); } try { returnValue = currentTimeSession.GetCurrentServerTime(); if (logging) { long endTime = System.currentTimeMillis(); log("Succeeded: GetCurrentServerTime()"); log("Execution time: " + (endTime - startTime) + " ms."); } } catch(Exception e) { if (logging) { log("Failed: GetCurrentServerTime()"); } e.printStackTrace(); } if (logging) { log("Return value from GetCurrentServerTime(): " + returnValue + "."); } return returnValue; } public String getEmployeeName(Short empNo) { String returnValue = ""; if (currentTimeSession == null) { System.out.println("Error in getEmployeeName(): " + ERROR_NULL_REMOTE); return returnValue; } long startTime = 0; if (logging) { log("Calling getEmployeeName(" + empNo + ")"); startTime = System.currentTimeMillis(); } try { returnValue = currentTimeSession.getEmployeeName(empNo); if (logging) { long endTime = System.currentTimeMillis(); log("Succeeded: getEmployeeName(" + empNo + ")"); log("Execution time: " + (endTime - startTime) + " ms."); } } catch(Exception e) { if (logging) { log("Failed: getEmployeeName(" + empNo + ")"); } e.printStackTrace(); } if (log ging) { log("Return value from getEmployeeName(" + empNo + "): " + returnValue + "."); } return returnValue; } //---------------------------------------------------------------------------- // Utility Methods //---------------------------------------------------------------------------- private void log(String message) { if (message == null) { System.out.println("-- null"); return ; } if (message.length() > MAX_OUTPUT_LINE_LENGTH) { System.out.println("-- " + message.substring(0, MAX_OUTPUT_LINE_LENGTH) + " ..."); } else { System.out.println("-- " + message); } } //Main method public static void main(String[] args) { EnterpriseBeanTestClient2 client = new EnterpriseBeanTestClient2(); // Use the client object to call one of the Home interface wrappers // above, to create a Remote interface reference to the bean. // If the return value is of the Remote interface type, you can use it // to access the remote interface methods. You can also just use the // client object to call the Remote interface wrappers. // This part of the code is written by the developers // Everything else till this point is written by the JBuilder8 IDE. short empNo = 2; client.create(); System.out.println(client.getEmployeeName(new Short(empNo))); } }

Additional Considerations

Throughout this chapter we have demonstrated the use of Borland JBuilder8 IDE and Borland Enterprise Server 5.2. However, the procedure is very similar if we use other versions of these products or products from other vendors such as IBM or Oracle. Among other vendor products, there may be differences in the IDE features and visual design features due to their inherent product designs. One advantage with Borland JBuilder8 (or any later version) is that it supports deployment to multiple vendors’ application servers.

An important consideration when deploying Enterprise Java applications is to implement a way to pool multiple database connections to improve the overall system performance. This can be done either by implementing custom connection pooling or by using the connection management techniques supported by the application servers.


 < Day Day Up > 

Категории