Section 6.7. Entity Beans


6.7. Entity Beans

An entity bean represents data that is stored in a database or some other persistent storage. Entity beans are persistent across client sessions and the lifetime of the server. No matter when or where you get a reference to an entity bean with a given identity, the bean should reflect the current state of the persistent data it represents. Multiple clients can access entity beans with the same identity at the same time. The EJB container manages these concurrent transactions for the underlying entity, ensuring that client transactions are properly isolated from each other, consistent, and persistent.

Returning to our running example of a profile service, we started with a session EJB, ProfileManager, that simply created profiles (represented as Java beans) for the client. Then we changed the design into a single Profile EJB, implemented as a stateful session bean. The major drawback in our stateful session Profile is that its profile data isn't persistent. A profile is created by a client and updated through client method calls, but once the Profile reference is given up by the client, or if the server crashes or shuts down for some reason, the accumulated profile data is lost. What we really want is a bean whose state is stored in a relational database or some other persistent storage that can be reloaded at a later time when the user reenters a profiled application. An entity EJB provides this functionality, and we can make our profiles persistent by implementing the Profile bean as an entity EJB. We'll use an entity bean implementation of the Profile EJB to demonstrate the features of entity EJBs.

At a basic level, entity beans are implemented similarly to session beans. You need to provide a local and/or remote home interface, a local and/or remote client interface, and a bean implementation. An entity bean, however, requires some additional methods in its home interface and bean implementation, to support the management of its persistent state and to allow clients to look up an entity in persistent storage. Entity beans must also provide a class that serves as its primary key, a unique identifier for entities.

The persistent data associated with an entity bean can be managed in two ways by the EJB containerusing bean-managed persistence (BMP) or container-managed persistence (CMP). With CMP, you leave the database calls to the container, and instead you tell the container (in the EJB's deployment descriptor) about bean properties and references that need to be persisted. The deployment tools provided with the EJB server are responsible for generating the corresponding database calls in the classes it uses to implement and deploy your bean. With BMP, you provide the actual database calls for managing your bean's persistent storage as part of your bean implementation.

If you can rely on the EJB container to handle your entity bean's persistence, this can be a huge benefit since it saves you from having to add (and debug) JDBC code to your beans and potentially makes your bean more portable across different persistent storage schemes (database vendors, schema variations, and even other persistence mechanisms like object databases). But even with the expanded CMP support provided in EJB 2.0 and 2.1, the automated persistence support in EJB is limited, and at times you'll need to manage persistence directly in your bean implementation. We discuss the pros and cons of each of these scenarios a bit later in this section.

6.7.1. Finder Methods

Since entity beans are persistent and can be accessed by multiple clients, clients have to be able to find them as well as create them. To this end, an entity bean's home interface(s) can provide finder methods , named with a findXXX( ) scheme, that a client can invoke to look up preexisting entities in persistent storage and have them returned in the form of an EJB reference. For each finder method, the bean implementation has to have a corresponding ejbFindXXX( ) method that takes the same arguments. The findXXX( ) methods on the home interface can have any name, as long as the method name begins with find. A person bean, for example, might define a findBySSN( ) method that lets you look up citizens of the U.S. using their Social Security number. The EJB implementation class must then have an ejbFindBySSN( ) method that implements this lookup function.

The entity version of our Profile bean has two finder methods defined in its remote home interface:

 public Profile findByPrimaryKey(String name)     throws RemoteException, FinderException;   public Collection findByEntryValue(String key, String value)     throws RemoteException, FinderException;

The first one finds a Profile based on the name of the profile owner, which in our case is the unique identifier for a Profile. The second finder finds any Profiles that have a particular value for a named entry.

A client can use the findXXX( ) methods on the home interface to determine if an entity (or entities) with a given identity already exists in persistent storage. From the client's point of view, if a findXXX( ) method finds an appropriate entity or entities, entity bean instances are initialized and associated with these persistent entities, and references to the matching beans are returned to the client. If the identified entity is not found in persistent storage, a javax.ejb.FinderException is thrown. All findXXX( ) methods on the bean's home interface must declare that they can throw FinderException and, if it's a remote home interface, RemoteException (or one of the parents of Remote Exception).

Each findXXX( ) method on the home interface must return either an instance of the bean's remote interface or a collection of these objects. If a finder method can potentially return multiple beans, it has to have a return type that is either an Enumeration or a Collection. In our profile bean example, the findByPrimaryKey( ) method can return only a single profile as a result, so it should be declared as returning an instance of the profile bean type. The findByEntryValue() method, on the other hand, can match multiple profiles, so it returns a Collection.

Every entity bean is required to have a findByPrimaryKey( ) method defined on each of its home interfaces. This finder method takes a single argument (an instance of the primary key for the entity bean) and returns the entity corresponding to that primary key. By definition, a primary key uniquely identifies a single entity, so this finder method should return a single bean instance. We discuss primary keys shortly in "Entity Bean Implementations."

6.7.2. Select Methods

In addition to finder methods, entity beans that make use of container-managed persistence (discussed later) can have select methods as well. Select methods are utility methods that are accessible only from the bean implementation class itself; they aren't visible to the client. One of the principles behind CMP is to encapsulate the persistence details and not expose them in the EJB implementation. However, it is sometimes necessary to perform lookups against the underlying persistent store from business methods in your EJB implementation. Select methods provide this lookup functionality without forcing you to put database details into your bean code. You can define any number of ejbSelectXXX( ) methods as abstract methods on your bean implementation class. The EJB container is responsible for providing implementations for these methods based on the persistence logic you provide for these methods in your deployment descriptors.

6.7.3. Entity Bean Implementations

Entity bean implementations must include all the basic container callbacks mentioned earlier: ejbActivate( ), ejbPassivate( ), ejbRemove( ), and an ejbCreate( ) method for each create( ) method provided in the EJB's home interface(s). Entity beans must also provide several persistence-related callbacks as well as some additional callbacks related to the lifecycle model applied to entity beans. Before we discuss these, we should understand how primary keys play a role with entity beans.

6.7.3.1. Primary keys

Every type of entity bean must have an associated primary key class. The primary key serves as a unique identifier for the persistent entities represented by the entity bean. The primary key for a person's records in a database, for example, might be a first and last name, a Social Security number (for U.S. citizens), or some other identification number. The primary key used for entity beans is public, in that clients can see the primary key for an entity bean, and the primary key is used directly by the bean implementation to load or update its state from persistent storage.

A primary key can be either a custom class that we write as part of the EJB implementation, or it can be a basic Java object type, like a String, Float, or Integer. The name of the user uniquely identifies our Profile bean, for example. A custom primary key class for an entity bean implementation of our Profile might look something like the following:

 public class ProfilePK implements java.io.Serializable {   public String mName;   public ProfilePK(  ) {     mName = null;   }   public ProfilePK(String name) {     mName = name;   }   public boolean equals(Object other) {     if (other instanceof ProfilePK &&         this.mName.equals(((ProfilePK)other).mName)) {       return true;     }     else {       return false;     }   }   public int hashCode(  ) {     return mName.hashCode(  );   } }

However, since the unique identifier for a Profile is simply a single String, we can just use java.lang.String as the primary key for our entity Profile bean and avoid having to write the primary key class in the first place.

A primary key class needs to satisfy the requirements of being a Value type in RMI-IIOP, because it may need to be transmitted to remote clients. In practical terms, this means that the primary key class has to be Serializable and must not implement the java.rmi.Remote interface. A primary key class also has to have a valid implementation of the equals( ) and hashCode( ) methods so that the container can manage primary keys internally using collections, for example. If your entity bean uses container-managed persistence, the primary key class must obey the following additional rules:

  • It must be a public class.

  • It must have a default constructor (one with no arguments) that is public.

  • All its data members must be public.

  • All of the names of the data members on the class must match the names of container-managed data members on the entity bean.

We've satisfied all of these requirements with our ProfilePK primary key class (note that the only data member, mName, has to have a matching mName data member on the entity implementation of the Profile bean).

6.7.3.2. Entity bean container callbacks

Instead of the single setSessionContext( ) callback method required on session EJBs, an entity EJB implementation is required to have the following two context-related callbacks:


public void setEntityContext(EntityContext ctx)

The container calls this method after a new instance of the bean has been constructed, but before any of its ejbCreate( ) methods are called. The bean is responsible for storing the context object. This method takes the place of the corresponding setSessionContext( ) method on session beans.


public void unsetEntityContext(EntityContext ctx)

The container calls this method before the entity bean is destroyed to disassociate the bean from its identity, represented by its primary key.

When an EJB container is managing an entity EJB, it requires several additional container callbacks related to persistence operations:


public primaryKeyType ejbFindByPrimaryKey( primaryKeyType) throws FinderException

This is the only required finder method on an entity bean. Both the argument and the return type must be the bean's primary key type. The container is responsible for converting the returned primary key into a valid EJB reference for the client.


public void ejbPostCreate( )

If needed, an entity bean can optionally supply an ejbPostCreate( ) method for each ejbCreate( ) method it provides, taking the same arguments. The container calls the ejbPostCreate( ) method after the bean's ejbCreate( ) method has been called and after the container has initialized the transaction context for the bean.


public void ejbLoad( )

This method is called by the container to cause the bean instance to load its state from persistent storage. The container can call this bean method anytime after the bean has been created to do an initial load from persistent storage or to refresh the bean's state from the database (e.g., after a business method on the bean has completed or after the container has detected an update to the same persistent entity by another bean instance).


public void ejbStore( )

This method is called by the container to cause the bean to write its current runtime state to persistent storage. This method can be called anytime after a bean is created.

In addition to these entity-specific methods on bean implementations, the semantics of some of the other standard methods are slightly different for entity beans . Each ejbCreate( ) method, for example, is actually a request to create a new entity in persistent storage. The implementations of the create methods should not only assign any state data passed in as arguments but also create a record in persistent storage for the new entity bean described by the method arguments. In addition to this semantic difference, the signatures of ejbCreate( ) methods on entity beans can be different from session beans. For an entity bean that manages its own persistence (a bean-managed entity bean), the ejbCreate( ) methods return the primary key type for the bean. For a container-managed entity bean, the ejbCreate( ) methods return void, the same as for session beans.

An entity bean can be passivated by its container, but the meaning of being passivated is slightly different as well. A container passivates an entity bean (calling its ejbPassivate( ) method in the process) when it wants to disassociate the bean from the persistent data entity it has been representing. After being passivated, the bean may be put into the container's "wait" pool, to be associated with another client-requested entity at a later time, or it may be removed from the server altogether.

A client calling the remove( ) method on an entity bean reference or on the home interface for a bean is also interpreted differently: it's a request to remove the entity from persistent storage entirely. The entity bean implementation should therefore remove its state from the persistent storage in its ejbRemove( ) implementation.

6.7.4. Deployment Options for Entity Beans

When deploying entity EJBs, include an <entity> element in the <enterprise-beans> element within the deployment descriptor. A sample <entity> element for our Profile EJB is shown here:

 ... <enterprise-beans>     ...     <entity>         <display-name>BMP Profile Bean</display-name>         <ejb-name>BMPProfileBean</ejb-name>         <home>com.oreilly.jent.ejb.beanManaged.ProfileHome</home>         <remote>com.oreilly.jent.ejb.Profile</remote>         <ejb-class>com.oreilly.jent.ejb.beanManaged.ProfileBean</ejb-class>         <persistence-type>Bean</persistence-type>         <prim-key-class>java.lang.String</prim-key-class>         <reentrant>false</reentrant>         <resource-ref>             <res-ref-name>jdbc/ProfileDB</res-ref-name>             <res-type>javax.sql.DataSource</res-type>             <res-auth>Container</res-auth>         </resource-ref>     </entity>     ... </enterprise-beans> ...

The <entity> element contains much of the same basic required information as a session element (<ejb-name>, <home>, <remote>, <local-home>, <local>, <ejb-class>), but it also contains several elements specific to entity beans. Three of these are required: <persistence-type>, <prim-key-class>, and <reentrant>.

The <persistence-type> element specifies how the persistence for your entity bean is to be managed. The allowed values for this element are Bean or Container, indicating the two fundamental ways that entity persistence can be handled. Our ProfileBean is written to use bean-managed persistence, so we set the persistence type to Bean.

The <prim-key-class> element specifies which class is being used as the primary key for this bean. We chose to use Strings rather than our custom ProfilePK class, so we specify java.lang.String as our primary key type.

The <reentrant> element indicates whether an EJB can be accessed by multiple clients concurrently. If an EJB is marked reentrant, it's eligible for concurrent access; otherwise, it's run in a single-threaded manneronly one client request is processed at a time by any given EJB instance within the container. In our example, we haven't written our ProfileBean to be threadsafe (a quick look at the setEntry( ) implementation in the source code will confirm that), so we've set the <reentrant> element to false. The <reentrant> property has other implications related to loopback calls (method implementations on the bean that result in calls back to the same bean instance). Refer to O'Reilly's Enterprise JavaBeans or the EJB specification for full details on loopback calls.

6.7.5. The Entity Context

The EJB container provides context information to an entity bean in the form of an EntityContext object. The container sets this object using the bean's setEntityContext( ) method and removes it when the bean is being removed by calling the bean's unsetEntityContext( ) method. Like SessionContext, EntityContext provides the bean with access to its corresponding remotely exported object through the getEJBObject( ) method. The EntityContext also gives an entity bean access to its primary key through getPrimaryKey( ). The declared return type of this method is Object, but the object returned is of the bean's primary key type. Note that the data accessed through the EntityContext might be changed by the EJB container during the bean's lifetime, as explained in the next section. For this reason, you shouldn't store the EJB remote object reference or primary key in data variables in the bean object since they might not be valid for the entire lifetime of the bean. Our entity ProfileBean, for example, stores the EntityContext reference in an instance variable, where it can access the context data as needed during its lifetime.

6.7.6. Lifecycle of an Entity Bean

Before the first client asks for an entity bean by calling a create( ) or findXXX( ) method on its home interface, an EJB container might decide to create a pool of entity beans to handle client requests for beans. This potentially reduces the amount of time it takes for a client to receive an entity bean remote reference after it makes a request for an entity bean. To add a bean to its pool, the container creates an instance of your bean implementation class and sets its context using the setEntityContext( ) method. At this point, the entity bean hasn't been associated with a particular data entity, so it doesn't have an identity and therefore isn't eligible for handling client business method calls on bean references.

When a client calls a create( ) method on the bean's home interface, the container picks a bean out of this wait pool and calls the corresponding ejbCreate( ) method on the bean. If the ejbCreate( ) method is successful, it returns one or more primary key objects to the container. For each primary key, the container picks an entity bean out of its pool to be assigned to the entity represented by the key. Next, the container assigns the bean's identity by setting the properties in its EntityContext object (e.g., its primary key and remote object values). If the bean has an ejbPostCreate( ) method, that gets called after the bean's entity identity has been set. The ejbCreate( ) method should create the entity in persistent storage, if the bean is managing its own persistence.

Alternatively, the client might call a findXXX( ) method on the home interface. The container picks one of the pooled entity beans and calls the corresponding ejbFindXXX( ) method on it. If the finder method finds one or more matching entities in persistent storage, the container uses entity beans in the wait pool to represent these entities. It picks entity beans out of the pool and calls their ejbActivate( ) methods. Before calling ejbActivate( ), the container sets the bean's context by assigning the corresponding primary key and remote object reference in its context.

After an entity bean has been activated (either by being created through one of its ejbCreate( ) methods or by being found and having its ejbActivate( ) method called), it is associated with a specific entity in persistent storage and with a specific client stub. At any point after this, the container can call the bean's ejbLoad( ) or ejbStore( ) method to force the bean to read or write its state from or to persistent storage. The bean's business methods can also be invoked by clients when it is in this state.

At some point, the container may decide to put the bean back into its internal pool. This might happen after all remote references to the bean have been released or after a certain period of inactivity with the bean. The container might also do this as a reaction to client-loading issues (e.g., time-sharing pooled beans between client requests). When the container wants to remove the association between the entity bean instance and the client stub but doesn't want the object's state removed from persistent store, it calls the bean's ejbPassivate( ) method. The bean can release any resources it allocated while in the active state, but it doesn't have to update persistent storage for the entity it represents, as this was done the last time the container invoked the bean's ejbStore( ) method.

The bean can also lose its association with an entity when the client decides to remove the entity. The client does this either by calling a remove( ) method on the bean's home interface or calling the remove( ) method directly on an EJB object. When one of these things happens, the container calls the bean's ejbRemove( ) method, and the bean deletes the data in persistent storage pertaining to the entity it represents. After the ejbRemove( ) method completes, the container puts the bean back into its internal pool.

6.7.7. Bean-Managed Persistence

Bean-managed persistence (BMP ) is one option for dealing with the persistence of the state of entity EJBs. In BMP, all of the persistence management is done directly in the EJB implementation class, using custom JDBC calls or whatever API is appropriate for the persistent storage being used. The persistence management of an entity bean is handled in the ejbCreate( ), ejbRemove( ), ejbLoad( ), and ejbStore( ) methods on the bean implementation. The persistent store is also accessed directly in the ejbFindXXX( ) methods on the implementation of BMP beans.

Let's look at a BMP entity implementation of our Profile EJB. We've already seen some details on the home and client interfaces for this entity bean in earlier sections. The purpose of the bean is the same as our stateful session version: it represents a profile for a named application user, maintaining a list of name/value pairs for various attributes and options. The difference is that this Profile EJB represents a profile entity that exists as a set of persistent data (stored in a database, in this case). The full source code for the implementation class can be found in the file com/oreilly/jent/ejb/beanManaged/ProfileBean.java in the code bundle for the book; we'll cover some highlights in this tutorial.

The structure of the entity Profile implementation is similar to the stateful session bean version. All of the EJB-required methods, including ejbActivate( ), ejbPassivate( ), ejbCreate( ), and ejbRemove( ), are present. The ejbActivate() and ejbPassivate( ) methods are called as the container moves the bean in and out of the entity bean "wait" pool, so it's a chance to free up any resources, such as file handles, before the bean is passivated and reclaim them if needed when it is activated again.

The ejbCreate( ) methods on the ProfileBean create a new profile entity in the database, so our ejbCreate( ) method (associated with the create( ) method in our home interface) is a bit more complicated than in our session beans because now it needs to make all the JDBC calls to create a profile entity in the underlying database:

 public String ejbCreate(String name)       throws DuplicateProfileException, CreateException {       Connection conn = null;       Statement s = null;       try {           conn = getConnection();           s = conn.createStatement();           s.executeUpdate("insert into profile (name) values ('"                           + name + "')");       }       catch (SQLException se) {           System.out.println("Error creating profile, assuming duplicate.");           try {               StringWriter strw = new StringWriter();               PrintWriter prntw = new PrintWriter(strw);               se.printStackTrace(prntw);               throw new DuplicateProfileException(                       "SQL error creating profile for " +                       name + ": " + se.toString() +                       "\n" + strw.toString());           }           catch (Exception e) {}       }       catch (NamingException ne) {           System.out.println("Error accessing DataSource");           throw new CreateException("Error accessing DataSource.");       }       finally {           try { s.close(); } catch (Exception e) {}           try { conn.close(); } catch (Exception e) {}       }       System.out.println("ProfileBean created for " + name + ".");       return name;   }

Notice that the ejbCreate( ) method is using an internal utility method on our implementation, getConnection( ), to get a JDBC connection to the database. This method looks for a DataSource stored in the JNDI context of the J2EE server and uses it to create a connection to the database. For details on configuring and using DataSources, see Chapter 8.

Another difference in this ejbCreate( ) method is that it returns the EJB's primary key type (a String, in this case). The returned primary key value must represent the identity of the actual entity that was created by the ejbCreate( ) method. In our case, we just return the name of the profile owner, since that's our unique identifier for profiles. The EJB container is responsible for intercepting the primary key object returned by the ejbCreate( ) method, converting it to a reference to a live Profile EJB, and returning a Profile stub to the client that originally called the create( ) method on the ProfileHome interface.

An entity bean can also optionally provide an ejbPostCreate( ) method for each create( ) method on its home interface(s). The container calls this method after the ejbCreate( ) method returns and after the container initializes the bean's transaction context. The ejbPostCreate( ) method has to match the arguments to its corresponding create( ) and ejbCreate( ) methods. In our case, nothing needs to be done in the ejbPostCreate( ) method, so we simply log that it's been called and return.

The ejbRemove( ) method on our ProfileBean deletes all the records for this profile entity from the database:

   public void ejbRemove() {       // Get this profile's name       String key = (String)mContext.getPrimaryKey();       Connection conn = null;       Statement s = null;       try {           conn = getConnection();           s = conn.createStatement();           // Clear out any profile entries           s.executeUpdate("delete from profile_entry where name = '"                           + key + "'");           // Clear out the profile itself           s.executeUpdate("delete from profile where name = '" + key + "'");             System.out.println("ProfileBean removed.");       }       catch (SQLException se) {           System.out.println("Error removing profile for " + key);           se.printStackTrace();       }       catch (NamingException ne) {           System.out.println("Error accessing DataSource");           ne.printStackTrace();       }       finally {           try { s.close(); } catch (Exception e) {}           try { conn.close(); } catch (Exception e) {}       }   }

In terms of the actual business methods of the Profile EJB, a Properties object holds the profile entries for the user, and the getEntry( ) and setEntry( ) remote method implementations access this Properties object for the client. The name is found in the primary key object, and the primary key is stored for us in the EntityContext that the container gives us through the setEntityContext( ) method. The getName( ) method on the ProfileBean implementation shows how we retrieve the username for the profile using the getPrimaryKey( ) method on the EntityContext:

 public String getName(  ) {     return (String)mContext.getPrimaryKey(  ); }

We've removed the setName( ) method from the entity version of our Profile EJB since we don't want to allow the client to change the name of an existing, active entity bean. The Profile client interface for this bean, not shown here, is similar to the Profile interface in Example 6-6, with the setName( ) method removed. Since the Profile is now a persistent entity bean and the name is the primary key, or identifying attribute, of the bean, the name of the bean should be set only when the bean is created. While the entity bean is active, it is associated with a profile entity for a specific user, and the client should read only the name associated with the profile.

Two additional persistence-related container callbacks are needed on entity bean implementations, ejbLoad( ) and ejbStore( ). Since this bean is using BMP, our ejbLoad( ) and ejbStore( ) methods do all the JDBC calls directly, just like the other persistence callbacks in this version of the Profile EJB:

   public void ejbLoad() {       try {           String key = (String)mContext.getPrimaryKey();           loadFromDB(key);       }       catch (Exception e) {           System.out.println("Failed to load ProfileBean: ");           e.printStackTrace();           throw new EJBException("ejbLoad failed: ", e);       }       System.out.println("ProfileBean load finished.");   }     public void ejbStore() {       String key = (String)mContext.getPrimaryKey();       Connection conn = null;       Statement s = null;       try {           conn = getConnection();           s = conn.createStatement();           // Clear out old profile entries; replace with current ones           s.executeUpdate(              "delete from profile_entry where name = '" + key + "'");           Enumeration pKeys = mEntries.propertyNames();           while (pKeys.hasMoreElements()) {               String pKey = (String)pKeys.nextElement();               String pValue = mEntries.getProperty(pKey);               s.executeUpdate(                 "insert into profile_entry (name, key, value) values "                 + "('" + key + "', '" + pKey + "', '" + pValue + "')");           }       }       catch (Exception e) {           System.out.println("Failed to store ProfileBean: ");           e.printStackTrace();           throw new EJBException("ejbStore failed: ", e);       }       finally {           try { s.close(); } catch (Exception e) {}           try { conn.close(); } catch (Exception e) {}       }       System.out.println("ProfileBean store finished.");   }

An ejbFindXXX( ) method in our entity ProfileBean corresponds to each findXXX( ) method in ProfileHome. The ejbFindByPrimaryKey( ) method simply takes the primary key passed in as an argument and attempts to load the data for the entity from the database.

 public String ejbFindByPrimaryKey(String key) throws FinderException {     loadFromDB(key);     return key; }

If successful, it returns the primary key to the container, where it is converted to a remote Profile object to be returned to the client. It's not necessary to actually load all the profile data here in the finder method; we need only to verify that the specified entity exists in the database and either return the primary key to signal success or throw an exception. Since we already have the internal loadFromDB( ) method used in ejbLoad( ), it is a simple matter to reuse it in this finder method. If the performance hit for loading the profile data twice is too great, we would have to rewrite the finder method to simply check the PROFILE table for a record matching the name in the primary key.

The ejbFindByEntryValue( ) method takes key and value String arguments and attempts to find any and all profile entities with a matching key/value pair in the PROFILE_ENTRY table. Each name that has such a record is converted to a primary key object and returned to the container in a Collection.

   public Collection ejbFindByEntryValue(String key, String value)       throws FinderException {       LinkedList userList = new LinkedList();       // Get a new connection from the EJB server       Connection conn = null;       Statement s = null;       try {           conn = getConnection();           s = conn.createStatement();           // Issue a query for matching profile entries, grabbing           // just the name           s.executeQuery("select distinct(name) from profile_entry where " +                          " key = '" + key + "' and value = '"                          + value + "'");           // Convert the results in primary keys and return an enumeration           ResultSet results = s.getResultSet();           while (results.next()) {               String name = results.getString(1);               userList.add(name);           }       }       catch (SQLException se) {           // Failed to do database lookup           throw new FinderException();       }       catch (NamingException ne) {           // Failed to access DataSource           throw new FinderException();       }       finally {           try { s.close(); } catch (Exception e) {}           try { conn.close(); } catch (Exception e) {}       }       return userList;   }

The container converts each primary key object in the returned Collection into a remote Profile object and returns the set to the client. If we encounter a database problem along the way, we throw a FinderException.

6.7.8. Container-Managed Persistence

In our first entity-based Profile, the persistent state of the profile entity is managed by the bean itself. Custom JDBC code in the ProfileBean implementation loads, stores, and removes the entity's database entries. The EJB container calls the appropriate callback methods on your entity bean, but your bean implementation is responsible for connecting to the database and making all of the necessary queries and updates to reflect the lifecycle of the data entity.

Container-managed persistence(CMP ) allows you to define properties on your entity bean implementation that represent the state of the entity and tell the EJB container how to map these properties to persistent storage. With CMP, the container is responsible for loading, updating, and removing the entity data from persistent storage, based on the mapping you provide. The container invokes the callbacks on your bean implementation to notify it when it has performed these tasks for you, allowing your bean to do any follow-up work that may be required. The container also implements all the finder methods required by the bean's home interface.

If you want to take advantage of CMP, you have to indicate this to the EJB container when you deploy the bean, you need to provide a bean implementation that assumes that the container is performing all persistence management, and you need to supply the container with a data mapping at deployment time.

Implementing CMP-based entity beans involves describing the persistence properties of your bean rather than implementing them directly:

  1. In your bean implementation class, declare abstract accessor methods for persistent properties that represent the state of the bean.

  2. Declare finder and select methods in the home interface(s) for the EJB.

  3. Map the persistent state properties to persistent storage in the EJB deployment descriptor.

  4. Provide the container with the logic needed to implement the finder and select methods.

The EJB container is responsible for generating the relevant code to load, store, and update the state of the EJB to and from persistent storage and to implement the finder and select methods defined on your home interface(s). The various persistence-related container callback methods on your implementation class (ejbLoad( ), ejbStore( ), ejbCreate( ), and ejbRemove( )) are still used as callbacks by the EJB container, but in the case of CMP, they're invoked by the container just before and after it performs the corresponding persistence operation.

The complete source code for a CMP implementation of our Profile bean can be found in the source bundle for the book, in the file com/oreilly/jent/ejb/containerManaged/ProfileBean.java. We discuss the key features here.

In comparison to our earlier BMP-based ProfileBean, this bean is somewhat simpler since the ejbRemove( ), ejbLoad( ), and ejbStore( ) methods don't need to perform database calls.

The container handles the loading and storing of the bean's data and the removal of any entities from the database, so we don't need to do anything about these operations in our bean implementation. In our Profile bean case, all we do in our ejbLoad( ) and ejbStore() methods is map the persistence data managed by the container into our own internal data structures. This is not typical for CMP beans; we'll discuss the reasons that we need to do this in the next section when we discuss abstract persistence mappings. Here is the code for ejbLoad( ) and ejbStore( ):

 public void ejbLoad(  ) {     try {       transferToProps(  );     }     catch (IOException e) {       System.out.println("Failed to load ProfileBean: ");       e.printStackTrace(  );       throw new EJBException("ejbLoad failed: ", e);     } }   public void ejbStore(  ) {     try {       transferToBytes(  );     }     catch (IOException e) {       System.out.println("Failed to store ProfileBean: ");       e.printStackTrace(  );       throw new EJBException("ejbStore failed: ", e);     } }

The important thing to note here is that these callbacks aren't doing any database operationsthe container is doing the database calls, and we're just getting ready for this (ejbStore( )) or cleaning up afterward (ejbLoad( )).

The same is true of the ejbRemove( ) methodit's called just before the container is about to remove this entity's data from the database. In our case, there's nothing to do, so we provide an empty method:

 public void ejbRemove(  ) {     System.out.println("ProfileBean removed."); }

6.7.8.1. Mapping container-managed fields: Abstract persistence schema

In the CMP model, the persistent state of your entity EJBs is specified in terms of an abstract persistence schema . The abstract schema consists of a set of named EJBs (the "tables" of the schema) and the persistent properties declared on your entity EJBs (the "columns" of the schema). You describe the components of this abstract schema in your EJB deployment descriptor and provide the container with a mapping from this abstract schema to a real persistence store (such as a relational database schema or an object database). The container takes all of this information and uses it to generate persistence management code for your EJB.

You indicate abstract persistence fields on your bean by providing abstract accessors (JavaBeans-style set and get methods) on the EJB implementation class. The EJB container provides concrete implementations for these abstract accessor methods in its generated classes. In our ProfileBean implementation class, we have two sets of accessors for the two abstract persistent fields needed by our bean: get/setName( ) for the name property and get/setEntriesBytes( ) for the entriesBytes property:

 abstract public String getName(  ); abstract public void setName(String name);   abstract public byte[] getEntriesBytes(  ); abstract public void setEntriesBytes(byte[] entries);

The use of a byte array for our profile entries may seem like an odd implementation decision. It has to do with the limitations of automatic CMP persistence. We'll return to this later, but for now just take it on faith that our profile entries need to be maintained as a byte array.

Note that, since we need to define persistent field accessors as abstract in our implementation class, the class itself must be declared as abstract as well:

 abstract public class ProfileBean implements EntityBean {

Next, we need to describe our abstract persistence schema in the EJB's deployment descriptor. This essentially amounts to specifying an abstract schema name for each EJB we are creating (thus defining the "tables") and listing the persistent properties of each EJB (defining the "columns"). A partial listing of the ejb-jar.xml file for our CMP Profile bean is shown in Example 6-8.

Example 6-8. Section of ejb-jar.xml file for CMP profile EJB
 <?xml version="1.0" encoding="UTF-8"?> <ejb-jar xmlns="http://java.sun.com/xml/ns/j2ee"          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"          xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee                              http://java.sun.com/xml/ns/j2ee/ejb-jar_2_1.xsd"          version="2.1">     <enterprise-beans>         <!-- A Profile EJB using container-managed persistence -->         <entity>             <ejb-name>CMPProfileBean</ejb-name>             <home>com.oreilly.jent.ejb.containerManaged.ProfileHome</home>             <remote>com.oreilly.jent.ejb.containerManaged.Profile</remote>             <local-home>                 com.oreilly.jent.ejb.containerManaged.ProfileLocalHome             </local-home>             <local>                 com.oreilly.jent.ejb.containerManaged.ProfileLocal             </local>             <ejb-class>                 com.oreilly.jent.ejb.containerManaged.ProfileBean             </ejb-class>             <persistence-type>Container</persistence-type>             <prim-key-class>java.lang.String</prim-key-class>             <reentrant>false</reentrant>             <cmp-version>2.x</cmp-version>             <abstract-schema-name>ProfileBean</abstract-schema-name>               <!-- Indicate which fields need to be managed persistently -->             <cmp-field>                 <description>Name of the profile owner</description>                 <field-name>name</field-name>             </cmp-field>             <cmp-field>                <description>Binary data containing profile entries.</description>                 <field-name>entriesBytes</field-name>             </cmp-field>               <!-- Since our primary key is simple (one-column), we need to specify                  to the container which field maps to the primary key -->             <primkey-field>name</primkey-field>         </entity>         ...     </enterprise-beans>     ... </ejb-jar>

Before listing our persistent fields, we provide an <abstract-schema-name> element, which labels our EJB with an abstract name within the abstract persistence schema we're defining. Think of this abstract name as a sort of virtual table name; it's a way to refer to it in the EJB query language statements that define finder and select methods.

Following this, we list the <cmp-field> elements that describe our EJB's persistent properties. Our Profile bean has two persistent properties: name and entriesBytes, represented by the corresponding accessor methods in the bean implementation. So we have two <cmp-field> elements that serve to "tag" these bean properties as ones that need to be treated as persistent state.

In addition, the container needs to know how the bean's primary key is mapped. The primary key has to be mapped to one or more of the <cmp-field> elements listed in the deployment descriptor. If it maps to a single <cmp-field>, the name of the field has to be provided in the deployment descriptor as a <primkey-field> element. If the primary key is a complex key that maps to multiple <cmp-field> elements on the EJB, no <primkey-field> is provided and instead the data members on the primary key class must have variable names that match the corresponding persistent fields in the EJB implementation class. The container automatically makes the mapping from primary key fields to persistent bean fields. In our case, the primary key is simply the bean's name property, so we've included a <primkey-field> element in our deployment descriptor to that effect.

All we've done so far in the ejb-jar.xml file is provide the basic deployment information for our EJB and list the fields in the bean implementation that need to be persisted. But the container doesn't know how to persist these fields yet. The EJB specification does not define a standard way to provide this mapping to physical data storage. One reason for not including this in the specification is that it would require assumptions about the type of persistence being used by the container. EJB containers can (ostensibly) use any kind of persistent storage to maintain the state of entity beans: relational databases, object databases, XML databases, even flat files. Undoubtedly, virtually all of the major J2EE vendors provide relational persistence support in their CMP implementations, but the EJB specification team did not want to build this assumption into the specification, so they left the mapping of abstract persistent fields to physical storage up to the container vendors.

If we're using BEA's WebLogic application server, for example, we would specify the CMP deployment parameters using some additional deployment files that are specific to WebLogic. All EJBs (session, entity, or message-driven) deployed in WebLogic need to have an additional deployment file named weblogic-ejb-jar.xml, where things such as the JNDI binding for the EJB home interface can be specified. For container-managed entity beans using relational persistence, an additional deployment file, weblogic-cmp-rdbms-jar.xml, is used to specify persistence management information for the bean.

Each EJB-enabled server has its own scheme, but the required information is roughly the same. Regardless of how you specify the CMP field mapping, the EJB container reads the information and generates the necessary code to load and store the bean's persistent data to and from the underlying persistent storage system. It's your responsibility to ensure that the underlying physical persistence schema (whether it's relational, object-oriented, or hierarchical) is compatible with the deployment information provided to the container.

6.7.8.2. Handling complex data structures

Now we can finally explain why we implemented our CMP Profile EJB using a byte array for the entry data.

Each EJB container is limited to some degree in the way that data on your bean implementation can be mapped into persistent data fields. There is no standard format defined for the data mapping a particular container supports, so it's possible a particular EJB provider won't support whatever complicated mapping you require for your bean. For the most part, however, you can expect EJB providers to limit the format to a single persistent data field being mapped to a single data member on your bean implementation. If your bean's data structures are too complicated for you to provide an explicit mapping to persistent data fields, you have to decide how to deal with this.

In our entity Profile example, the profile entries need to be a list of name/value pairs that represent the parameters in the profile. We don't know at deployment time how many entries there will be, so we can't enumerate a mapping to particular database fields. Ideally, we really want each entry in the profile to be stored in a separate entry table in our database, along with the name of the owner of the entry, which is exactly how we implemented our bean-managed implementation. But since we're using CMP, we can't count on the EJB container supporting this type of persistence mapping (and, in fact, chances are that it won't).

One option is to give up on CMP and manage it yourself in the bean implementation using BMP. Another is to make each entry in the profile its own EJB and store the entries as a list of Entry beans on the profile bean. In this case, you could use EJB relationships, described in a later section, to tell the EJB container how these two entity EJBs are related to each other (e.g., when a given profile EJB is loaded, all of its corresponding entry EJBs should also be found and loaded). This makes the implementation pretty complex, though, since we now have two entity EJBs required to manage the data associated with profiles, and the amount of deployment information we need to provide gets much more complicated. This could also turn out to be expensive in terms of interactions with the container and memory usage. Each entry in the profile would need to be managed separately by the container, with all of the relevant lifecycle notifications and the like. CMP is supposed to make things simpler and faster, not the reverse, so we would probably not take this path in a real-world situation.

Another option, and the one we used in our CMP Profile EJB, is to convert the data structure into something that can be persisted into a single database table. Since the EJB specification gives us well-defined container callbacks to mark persistence operations, we can use these to do the conversion of our internal data structures to and from simplified persistent properties. In our Profile EJB, we defined the profile entries as a persistent byte array property. In our deployment descriptors for the bean, we mapped this persistent property to the binary PROFILE_BIN.ENTRIES_BYTES column in the database.

This gives us a persistent binary field for our EJB, but we still have to serialize the Properties object into the byte array when it needs to be stored persistently and deserialize the byte array into the Properties object when the persistent data is loaded. As we mentioned before, when using CMP, the ejbLoad( ) and ejbStore( ) methods are used by the container as callbacks. The ejbStore( ) method is called just before the container performs an automatic store of the bean's persistent fields to the database, and ejbLoad( ) is called just after the container has loaded the persistent fields from the database. So we can use the ejbStore( ) method on our bean to copy the Properties object on our ProfileBean to the mEntriesBytes persistent data member. If you look at the ejbStore( ) method, you'll see that it simply calls our transferToBytes( ) method:

 // Transfer the list of entries from our Properties member to the byte // array private void transferToBytes(  ) throws IOException {   // Serialize the Properties into a byte array using an ObjectOutputStream   if (mEntries != null && !mEntries.isEmpty(  )) {     ByteArrayOutputStream byteOut = new ByteArrayOutputStream(  );     ObjectOutputStream objOut = new ObjectOutputStream(byteOut);     objOut.writeObject(mEntries);     setEntriesBytes(byteOut.toByteArray(  ));   }   else {     setEntriesBytes(null);   } }

This method uses I/O streams to serialize the mEntries Properties object into the mEntriesBytes byte array. After the container calls our ejbStore( ) method, it can write the mEnTRiesBytes data member on our bean to a raw data field in the database (e.g., a LONG BINARY field in a SQL database). On the reading end, the container will load the bytes stored in the database column into the mEntriesBytes byte array and call our ejbLoad( ) method. We can use the ejbLoad( ) method to convert the bytes loaded by the container into a Properties object; our ejbLoad( ) method simply calls TRansferToProps( ):

 // Convert the serialized byte array into our Properties entry list private void transferToProps(  ) throws IOException {   // Take the raw byte array and deserialize it   // back into a Properties object using an ObjectInputStream   try {     if (getEntriesBytes(  ) != null) {       ByteArrayInputStream byteIn =         new ByteArrayInputStream(getEntriesBytes(  ));       ObjectInputStream objIn = new ObjectInputStream(byteIn);       mEntries = (Properties)objIn.readObject(  );     }     // If no entries in database, set properties     // to a new, empty collection     else {       mEntries = new Properties(  );     }   }   catch (ClassNotFoundException cnfe) {     System.out.println(                 "Properties class not found during de-serialization");   } }

This workaround is a good example of how the ejbLoad( ) and ejbStore( ) methods can be useful for CMP entity beans. It may also seem like an ideal solution for our Profile example since it allows us to deploy our entity Profile with container-managed persistence and still have a variable-sized list of entries on a profile. But this solution comes with a price tag: it makes our database records unusable for other, non-Java applications. The data stored in the PROFILE_BIN.ENTRIES_ BYTES column is a serialized Java object, so there's no way, for example, to check on a user's profile entries using a simple SQL query or to use a standard RDBMS reporting tool to show categories of users sorted by profile entries. As we'll see in the next section, the limitations of CMP also make it more difficult to implement certain complex finder methods.

6.7.8.3. Finder and select methods

When using container-managed persistence, the EJB container also generates all of the ejbFindXXX( ) methods required for the finder methods on the home interface(s). It can automatically generate an ejbFindByPrimaryKey( ) method, based on the data mapping and primary key information you provide at deployment time (although the EJB specifications are unclear as to whether the container is required to do this). But for any other ejbFindXXX( ) methods, you need to provide the container with the logic for the methods. The container can't infer the semantics of what you're trying to do based solely on method arguments. The same is true of select methods.

As an example, suppose we want to add a finder method for our Profile EJB, called findEmptyProfiles( ) that finds all the profiles in the persistent store that have no entries in them. We would add the finder method to the home interface(s) for the Profile bean:

 public Collection findEmptyProfiles(  )                               throws RemoteException, FinderException;

The finder method returns a Collection because it could potentially find more than one empty profile in the database. Now we need to tell the EJB container how to implement this method.

The persistence logic for both finder and select methods is provided using EJB QL, a standard query language defined as part of the EJB specification. EJB QL is similar in syntax to SQL, except that queries are defined using the abstract schema elements defined in the deployment descriptors for your EJBs.

Query logic for finder and select methods is included in the ejb-jar.xml deployment descriptor, using <query> elements within the corresponding <entity> section. So to provide the query logic for the findEmptyProfiles( ) finder method on our Profile EJB, we would add the following stanza to our ejb-jar.xml file, within the <entity> element for our Profile bean:

 ... <enterprise-beans>     ...     <entity>         ...         <query>             <query-method>                 <method-name>findEmptyProfiles</method-name>                 <method-params></method-params>             </query-method>             <ejb-ql>                 <![CDATA[SELECT OBJECT(p) FROM ProfileBean AS p                          WHERE p.entriesBytes IS NULL]]>             </ejb-ql>         </query>         ...     </entity>     ... </enterprise-beans> ...

The full syntax for EJB QL is provided in Appendix C, but it should be fairly obvious in our example what we're doing. The findEmptyProfiles( ) method takes no arguments, so we specify the <query-method> (using findEmptyProfiles as its <method-name>) and an empty <method-params> element. We want this finder to return all Profiles that have no entries in the database, so we specify an EJB QL statement that selects all ProfileBeans (using the abstract "table" name we defined earlier for our Profile EJB) whose entriesBytes persistent fields are null.

Select methods are defined using the same <query> element in the deployment descriptor. We simply specify the corresponding ejbSelectXXX( ) method name in the <method-name> element.

This persistence logic, in combination with the abstract schema information provided earlier in the deployment descriptor, is used by the container to generate the concrete implementations of the finder and select methods for our bean. The EJB QL might be "compiled" into SQL for a relational database, Object Query Language (OQL) for an object-oriented database, or some other native persistence query language.

You may have noticed in our CMP bean implementation (if you peruse the source code) that we've removed the ejbFindByEntryValue( ) finder method that we had in our BMP version. Because of the way we decided to implement the persistence of the entries of the Profile bean (using a single serialized byte array stored in a single binary database column), there isn't any way to implement the logic of findByEntryValue( ). From the perspective of EJB QL, the entriesBytes "column" is simply an array of bytes, with no way to query for the data stored in the Properties object that it came from. Again, we could fix this problem by changing our persistence mappingfor example, we could create a new Entry EJB and have the Profile bean maintain a set of references to Entry beans. But as mentioned in the previous section, this would impose some significant resource needs on our profile service, since each entry in each profile would now be a full-blown EJB object with all of the requisite container management.

6.7.8.4. EJB relationships

In keeping with the idea of an abstract persistence schema, EJB relationships can be thought of as abstract foreign key constraints between our EJB "tables." Suppose, for example, that in addition to our Profile bean, we also defined a Person entity bean that provided access to more general information about a person (name, address, etc.). Within the Profile bean implementation, it might be useful to access the Person bean corresponding to the owner of a particular Profile. In the EJB 2.1 CMP model, we can do this using EJB relationships.

Since EJB relationships are a way to extend the abstract persistence schema of a set of CMP EJBs, they can be established only between EJBs that are using CMP. Session beans, message-driven beans, and BMP entity beans can't participate in EJB relationships. EJB relationships are accessible only from within the bean implementationsthey aren't exposed directly to clients. EJB relationships can be one-to-one or one-to-many. In our case, we want a single Person to be related to a single Profile, so the relationship will be one-to-one. If we plan to change our Profile bean so that it uses a new EnTRy EJB to represent its entries and we want to establish a persistence relationship between the two, we would make the relationship one-to-many.

It's important to note that EJB relationships are created using the local interfaces for EJBs. If an EJB doesn't have a local interface, it can have one-way relationships to other beans, but other EJBs can't have relationships with it.

The process for establishing an EJB relationship is similar to defining persistent fields; you define the accessor method(s) on either end of the relationship, specify the relationship in the EJB deployment descriptor, and then provide the container with the information on how the relationship is mapped to the underlying persistent storage system. In our case, we want to define a one-to-one relationship between our Profile bean and a new Person bean. Let's suppose that the Person bean has a very simple local client interface:

 import javax.ejb.*;   public interface PersonLocal extends EJBLocalObject {   // Access the person's first and last name   public String getFirstName(  );   public String getLastName(  ); }

We won't show all of the details of the implementation of this Person EJB; we'll just highlight the details relevant to the EJB relationship.

The first step in creating the relationship is defining abstract accessor methods on the implementations of the beans involved. On our ProfileBean implementation class, we would add the following accessors:

 // Get local person related to this profile abstract public PersonLocal getPersonLocal(  ); abstract public void setPersonLocal(PersonLocal person);

If we wanted the relationship to be bidirectional, we would also define accessor methods on the implementation class for our Person bean to read and write the Profile associated with the Person:

 // Get/set local Profile using CMP relationships abstract public ProfileLocal getProfileLocal(  ); abstract public void setProfileLocal(ProfileLocal profile);

Notice that these accessors need to be defined in terms of the local interfaces of the beans involved.

Now we need to specify the relationship between these two beans in the EJB deployment descriptor. Assuming that both beans are defined in the same ejb-jar.xml file, the relationship is defined like so:

 ... <ejb-jar ...>     <enterprise-beans>         <!-- A Profile EJB using container-managed persistence -->         <entity>             <ejb-name>CMPProfileBean</ejb-name>             ...         </entity>         <!-- A Person EJB using container-managed persistence -->         <entity>             <ejb-name>CMPPersonBean</ejb-name>             ...         </entity>     </enterprise-beans>     <!-- Establish EJB relationships between Person and Profile -->     <relationships>         <ejb-relation>             <ejb-relation-name>Person-Profile</ejb-relation-name>             <!-- Relation from person to profile -->             <ejb-relationship-role>                 <ejb-relationship-role-name>                     Person-has-Profile                 </ejb-relationship-role-name>                 <!-- One profile per person -->                 <multiplicity>One</multiplicity>                 <relationship-role-source>                     <ejb-name>CMPPersonBean</ejb-name>                 </relationship-role-source>                 <cmr-field>                     <cmr-field-name>profileLocal</cmr-field-name>                 </cmr-field>             </ejb-relationship-role>             <!-- Relation from profile to person -->             <ejb-relationship-role>                 <ejb-relationship-role-name>                     Profile-belongs-to-Person                 </ejb-relationship-role-name>                 <!-- One person per profile -->                 <multiplicity>One</multiplicity>                 <relationship-role-source>                     <ejb-name>CMPProfileBean</ejb-name>                 </relationship-role-source>                 <cmr-field>                     <cmr-field-name>personLocal</cmr-field-name>                 </cmr-field>             </ejb-relationship-role>         </ejb-relation>     </relationships>     ... </ejb-jar>

We've added a <relationships> element to our ejb-jar.xml file, after the <enterprise-beans> section that describes the EJBs themselves. Each <ejb-relation> element in this section defines a relationship between two EJBs. Here we have a single <ejb-relation> that defines the relationship between the Person and Profile beans described earlier in the deployment descriptor. Each "side" of the relationship is described using an <ejb-relationship-role> element. The <ejb-relationship-role> element includes a name to assign to the role, the multiplicity of the role ("one" or "many"), the source EJB of the role (using the <ejb-name> associated with the EJB), and a <cmr-field> element that specifies the property on the bean that the role is associated with. In our case, we use the profileLocal and personLocal properties for the <cmr-field> elements to correspond to the accessor methods we defined earlier on our beans.

The last thing we need to do is provide the EJB container with the information specifying how this relationship is represented in persistent storage. Again, a format for specifying this information isn't defined in the EJB specification. You need to determine what form of persistence your J2EE server supports and how it requires the persistence mapping to be defined. The details of providing this mapping will be server-specific: JBoss will do it one way, WebLogic will do it another, and so on. Consult your application server's documentation for full details.

Since we defined both read and write accessors for either end of this EJB relationship, we can alter the persistent relationship between a Person and his Profile by simply using the setPersonLocal( ) or setProfileLocal( ) methods on the Profile or Person implementations, respectively. The data in persistent storage will be adjusted accordingly by the EJB container to reflect the new relationship. In the case of RDBMS persistent storage, the foreign key column(s) point to the appropriate row in the target table.



Java Enterprise in a Nutshell
Java Enterprise in a Nutshell (In a Nutshell (OReilly))
ISBN: 0596101422
EAN: 2147483647
Year: 2004
Pages: 269

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net