Implementing a CMP Entity Bean

Just as for BMP Entity beans, implementing a CMP Entity bean involves providing an implementation for the methods of the javax.ejb.EntityBean, corresponding methods for each method in the home interface, and a method for each method in the remote interface.

Implementing javax.ejb.EntityBean

Under BMP, the setEntityContext() method was used to look up various bean home interfaces from JNDI, and the JDBC DataSource called java:comp/env/jdbc/Agency was also obtained. Because most of these relationships are now managed by the container, only a couple of home interfaces now need to be obtained, and there is no requirement to look up the DataSource. Listing 7.4 shows this.

Listing 7.4. The JobBean's setEntityContext() and unsetEntityContext() Methods
 1: package data;
 2:
 3: import javax.ejb.*;
 4: import javax.naming.*;
 5: // imports omitted
 6:
 7: public abstract class JobBean implements EntityBean {
 8:
 9:     private EntityContext ctx;
10:
11:     public void setEntityContext(EntityContext ctx) {
12:         this.ctx = ctx;
13:         InitialContext ic = null;
14:         try {
15:             ic = new InitialContext();
16:             customerHome = (CustomerLocalHome) ic.lookup("java:comp/env/ejb
/CustomerLocal");
17:             jobHome = (JobLocalHome) ic.lookup("java:comp/env/ejb/JobLocal");
18:         }
19:         catch (NamingException ex) {
20:             error("Error looking up depended EJB or resource", ex);
21:             return;
22:         }
23:     }
24:
25:     public void unsetEntityContext() {
26:         this.ctx = null;
27:         customerHome = null;
28:         jobHome = null;
29:     }
30:
31:     // code omitted
32: }
						

The lookup of the CustomerHome home interface is required because the relationship from Customer to Job is maintained by the bean.

The lookup of the JobHome home interface is required only because the ctx.getLocalHome() method returns NULL for the J2EE RI 1.3. This would appear to be a bug. The ejbHomeDeleteByCustomer() home method actually uses the bean's own home interface.

As noted previously, under CMP, the ejbLoad() and ejbStore() methods often have empty implementations. If there is derived data to be maintained, it should be managed here. Indeed, the JobBean class does need to do this, as shown in Listing 7.5.

Listing 7.5. The JobBean's ejbLoad() and ejbStore() Methods
 1: package data;
 2:
 3: import javax.ejb.*;
 4: import javax.naming.*;
 5: // imports omitted
 6:
 7: public abstract class JobBean implements EntityBean {
 8:
 9:     public void ejbLoad() {
10:         JobPK key = (JobPK)ctx.getPrimaryKey();
11:
12:         try {
13:             this.customerObj = customerHome.findByPrimaryKey(getCustomer());
14:         }
15:         catch (FinderException e) {
16:             error("Error in ejbLoad (invalid customer) for " + key, e);
17:         }
18:     }
19:
20:     public void ejbStore() { }
21:
22:    // code omitted
23: }

The ejbLoad() method is called after the bean's state has been populated, so the bean's state can be read through the accessor methods, if needed.

The findByPrimaryKey() method call on line 13 populates the customerObj field, using the value of getCustomer() accessor method. It's worth appreciating that getCustomer() returns just a String. In other words, this is the name (actually, the primary key) of a customer. To save business methods having to continually look up the actual customer that corresponds to this customer name, the ejbLoad() method does it once.

You can see that the ejbStore() method is trivial—there is nothing to do.

The ejbActivate() and ejbPassivate() methods have nothing to do with data stores, so their implementation is unchanged from the BMP version. This is shown in Listing 7.6.

Listing 7.6. The JobBean's ejbActivate() and ejbPassivate() Methods
 1: package data;
 2:
 3: import javax.ejb.*;
 4: // imports omitted
 5:
 6: public abstract class JobBean implements EntityBean {
 7:
 8:     public void ejbPassivate() {
 9:         setRef(null);
10:         setCustomer(null);
11:         customerObj = null;
12:         setDescription(null);
13:         setLocation(null);
14:     }
15:
16:     public void ejbActivate() { }
17:
18:    // code omitted
19: }
						

Implementing the Local-Home Interface Methods

The methods of the local-home interface are implemented partly in code and partly through the provision of an appropriate EJB QL query. This section shows the queries that correspond to the finder methods in the home interface; the next section (configuring a CMP Entity bean) shows how to take those EJB QL strings and put them into the correct part of the deployment descriptor.

Create and Remove Methods

Listing 7.7 shows the ejbCreate() method for Job bean under CMP.

Listing 7.7. The JobBean's ejbCreate() Method
 1: package data;
 2:
 3: import javax.ejb.*;
 4: // imports omitted
 5:
 6: public abstract class JobBean implements EntityBean {
 7:
 8:     public JobPK ejbCreate(String ref, String customer) throws CreateException {
 9:         // validate customer login is valid.
10:         try {
11:             customerObj = customerHome.findByPrimaryKey(customer);
12:         } catch (FinderException ex) {
13:             error("Invalid customer.", ex);
14:         }
15:
16:         JobPK key = new JobPK(ref,customerObj.getLogin());
17:         // for BMP, there was a workaround here, namely to call ejbFindByPrimaryKey
18:         // under CMP, cannot call since doesn't exist.
19:         // instead, use jobHome interface ...
20:         try {
21:             jobHome.findByPrimaryKey(key);
22:             throw new CreateException("Duplicate job name: "+key);
23:         }
24:         catch (FinderException ex) {}
25:
26:         setRef(ref);
27:         setCustomer(customer);
28:         setDescription(null);
29:         setLocation(null);
30:         return null;
31:     }
32:
33:     // code omitted
34: }

Note the use of accessor methods (the “setter” methods) to save the bean's state. This contrasts with the BMP equivalent where the fields were written to directly (for example, this.ref = ref).

The implementation of this method would have been even shorter if the findByPrimaryKey() call checking for duplicates had been omitted. Indeed, if an RDBMS is being used as the persistent data store (as it is for the case study), there is likely to be a unique index on the primary key on the appropriate table, meaning that any attempt to INSERT a duplicate would be detected.

Caution

Strictly, the appropriate exception to throw here is a DuplicateKeyException, not a CreateException. However, the EJB specification does not mandate this. Moreover, the specification even allows that the EJB container can defer any interaction with the persistent data store until the end of the transaction, and is not clear in this circumstance what type of exception should be raised.

As the EJB specification continues to mature, it will more fully define expected exceptions in different situations. Until then, beware that—regardless of the hype—moving from one EJB container to another may well throw up differences that will necessitate changes in your application.


The ejbCreate() method is called first, and then the container's concrete implementation persists the bean's state, and then the bean's ejbPostCreate() method is called.

Under CMP, the bean should return null from the ejbCreate() method. This compares to returning the actual primary key under BMP. Put another way, it doesn't matter what your method returns, it will be ignored. The reason for this is that the EJB container can access the information that constitutes the primary key anyway, by virtue of the cmp-fields.

Note

The technical reason that the EJB container requires that CMP Entity beans return null is so that EJB container vendors can implement CMP by effectively subclassing the CMP bean and creating a BMP Entity bean (so far as the rest of the EJB container is concerned). Indeed, if you look at the generated code, this is precisely what the J2EE RI container does:

package data;

public final class JobBean_PM extends JobBean implements
com.sun.ejb.PersistentInstance {

public data.JobPK ejbCreate(java.lang.String param0, java.lang.String param1) throws
javax.ejb.CreateException {
com.sun.ejb.Partition partition =
com.sun.ejb.PersistenceUtils.getPartition(this);
        partition.beforeEjbCreate(this);
        super.ejbCreate(param0, param1);
        return (data.JobPK) partition.afterEjbCreate(this);
    }

    // code omitted

}

You can see the call to super.ejbCreate(). The return type is ignored, but the subclass' ejbCreate() does return a primary key to the rest of the EJB container.


The ejbRemove()method for the Job bean is shown in Listing 7.8.

Listing 7.8. The JobBean's ejbRemove() Methods
 1: package data;
 2:
 3: import javax.ejb.*;
 4: // imports omitted
 5:
 6: public abstract class JobBean implements EntityBean {
 7:
 8:     public void ejbRemove() { }
 9:
10:     // code omitted
11: }

As you can see, the implementation of ejbRemove() is trivial—there is nothing to do. Nevertheless, an implementation is required.

Caution

The BMP version of ejbRemove() for Job bean reset all the fields to null. Strictly speaking, there was no direct requirement for doing this, because when the bean instance is next used from the pool, its ejbLoad() will (should) populate all fields.

When implementing CMP Entity beans, you absolutely must not reset the fields to null. Doing so will cause the EJB container to throw an exception, because the bean's state is required so that the container can remove the correct data from the persistent data store.


Finder Methods

The implementation of the finder methods is by formulating appropriate EJB QL queries. The JobLocalHome interface defines three finder methods—findByPrimaryKey(), findByCustomer(), and findByLocation().

The first bit of good news is that there is no need to define an EJB QL query for the findByPrimaryKey() method at all. You will recall that the primkey-class element is used in the deployment descriptor to indicate to the EJB container the class (either custom or pre-existing) that represents the primary key of the Entity bean. When there is a custom primary key class, the EJB container can use the structure of that class to figure out how to implement this method.

If there is no custom primary key class, an additional piece of information is required in the deployment descriptor—namely, the primkey-field element. This nominates the (single) cmp-field that represents the primary key for the bean.

If you are using a custom primary key class (such as JobPK), you do need to ensure that its public fields correspond exactly in name and type to a subset of the cmp-fields of the bean. The JobPK class is shown in Listing 7.9.

Listing 7.9. JobPK Class
 1: package data;
 2:
 3: import java.io.*;
 4: import javax.ejb.*;
 5:
 6: public class JobPK implements Serializable
 7: {
 8:     public String ref;
 9:     public String customer;
10:
11:     public JobPK() {
12:     }
13:     public JobPK(String ref, String customer) {
14:         this.ref = ref;
15:         this.customer = customer;
16:     }
17:
18:     public String getRef() {
19:         return ref;
20:     }
21:     public String getCustomer() {
22:         return customer;
23:     }
24:
25:     // code omitted
26: }

The EJB container will match the ref and customer fields with getRef()/setRef() and getCustomer()/setCustomer() accessor methods for the cmp-fields. You can see here that the fields in the primary key class must be declared to have public visibility.

Moving onto the other finder methods, the findByCustomer() method has the following signature in the local-home interface:

Collection findByCustomer(String customer) throws FinderException;

The EJB QL query for this is as follows:

SELECT OBJECT(j)
FROM Job AS j
WHERE j.customer = ?1

The other finder method is findByLocation(), whose signature is as follows:

Collection findByLocation(String location) throws FinderException;

The EJB QL query for this is as follows:

SELECT OBJECT(j)
FROM Job AS j
WHERE j.location.name = ?1

Home Methods

The last method in the local-home interface is the home method deleteByCustomer(). This is used by clients when removing a customer; all of its jobs must also be removed. You've already seen the implementation of this home method under BMP, using SQL to delete from the Job and JobSkill tables. Under CMP, the implementation is somewhat more object-oriented, as shown in Listing 7.10.

Listing 7.10. JobBean's ejbHomeDeleteByCustomer() Method
 1: package data;
 2:
 3: import java.rmi.*;
 4: import java.util.*;
 5: import javax.ejb.*;
 6: // imports omitted
 7:
 8: public abstract class JobBean implements EntityBean {
 9:
10:     public void ejbHomeDeleteByCustomer(String customer) {
11:         try {
12:             Collection col = this.jobHome.findByCustomer(customer);
13:             for (Iterator iter = col.iterator(); iter.hasNext(); ) {
14:                 JobLocal job = (JobLocal)iter.next();
15:                 // remove job from collection
16:                 iter.remove();
17:                 // remove job itself
18:                 job.remove();
19:             }
20:         }
21:         catch (FinderException e) {
22:             error("Error removing all jobs for " + customer, e);
23:         }
24:         // needed because of the explicit job.remove()
25:         catch (RemoveException e) {
26:             error("Error explicitly removing job for " + customer, e);
27:         }
28:
29:     }
30:     // code omitted
31:
32: }

The method calls the bean's own findByCustomer() method, which returns a collection of jobs for the customer. It then iterates over these jobs and removes them one-by-one.

Caution

The jobHome field holds a reference to the bean's own home interface. In principle, the entityContext.getEJBHome() method could have been called, but the J2EE RI 1.3 container seems always to return null.


If there had been no suitable finder method, this would have been a prime case for using a select method. The select method would be declared in the JobBean class as follows:

package data;

import java.rmi.*;
import java.util.*;
import javax.ejb.*;
// imports omitted

public abstract class JobBean implements EntityBean {

    public abstract Collection ejbSelectByCustomer(String customer);

    // code omitted
}

The EJB QL query would be identical to that of the finder method. The only other difference would be in how the method was called. Rather than invoking the finder method through the home interface, the ejbSelect method could be called directly. So, in the ejbHomeDeleteByCustomer() method, the line

Collection col = this.jobHome.findByCustomer(customer);

would be replaced with

Collection col = this.ejbSelectByCustomer(customer);

In fact, the BMP and CMP implementations are not quite comparable; the CMP implementation is a little more robust. As was mentioned earlier, the BMP version of this method just deleted the appropriate rows from the Job and JobSkill table (turn back to yesterday's chapter to see this, if needed). This means that the Job bean's ejbRemove() method is never called; there is no opportunity for the bean to perform clean-up processing.

Note

An alternative design again might have been to define a cascade delete relationship from Customer through to Job. The EJB Specification does require that ejbRemove() is called on every bean being deleted as a result of the cascade, so the net result is similar to the CMP implementation.

However, as has been remarked earlier, setting up a (cascade delete) relationship for Customer and Job is not easy when using the J2EE RI, because it does not easily support relationships between beans when one bean is identified by another (part of the primary key is also a foreign key).


Note

A cascade delete relationship might be preferable to the CMP implementation. A naïve EJB container implementation would work the same way as the hand-coded CMP implementation, explicitly deleting each and every child bean one-by-one. The performance hit could be substantial.

A more sophisticated EJB container implementation ought to be able to call ejbRemove() for each bean, but then delete all of the child beans using a single call to the persistent data store; in other words, combining the best of the BMP and CMP approaches.


A related issue is that if a client happens to have a reference to a Job for the Customer being deleted, they won't find out that the Job has been removed until they attempt to access that Job again. At that point, the client will receive a java.rmi.NoSuchObjectException. But note that this isn't a problem just with BMP; the same behavior will occur for CMP also.

Implementing the Local Interface Methods

Looking back at the JobLocal interface back in Listing 7.3, you can see that many of the methods in the local interface simply expose the bean's cmp-fields or cmr-fields to its clients. Of course, there is no implementation for these methods because they are implemented by the EJB container's deployment tools. Therefore, all that is required is to copy the methods over from the local interface and mark them abstract. If you cast your eyes back all the way to Listing 7.1, you'll see that this is precisely what was done.

The only method in the JobLocal interface that does not correspond to an accessor method for a cmp-field or cmr-field is the getCustomerObj() method. Its implementation is shown in Listing 7.11.

Listing 7.11. JobBean's getCustomerObj() Method
 1: package data;
 2:
 3: import javax.ejb.*;
 4: // imports omitted
 5:
 6: public abstract class JobBean implements EntityBean {
 7:     private CustomerLocal customerObj; // derived
 8:     public CustomerLocal getCustomerObj() {
 9:         return customerObj;
10:     }
11:     // code omitted
12: }

Pretty straightforward, though of course the hard work is done in ejbLoad() (Listing 7.5) and ejbCreate() (Listing 7.7). Recall that the customerObj field holds the actual reference to the “parent” Customer for the Job and is derived from the customer cmp-field that holds merely the name of the Customer. Because the customer cmp-field makes up part of the primary key, it is immutable, and so is the customerObj field—hence, no setCustomerObj() method.

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

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