Editor's Note: This is Part 3 of a multi-part series on EJB inheritance. Part 1 deals with inheritance and entity beans and Part 2 focuses on table mapping.
Inheritance is not only for entity beans. Session beans may also take advantage of inheritance.
A session bean's life revolves around pure business logic. Implementing session bean inheritance is nowhere near as hard as it is with entity beans. Home interfaces are plain, containing no tricky business logic. The problems we had with entity beans were regarding access or lifecycle of the bean, not the actual bean invocation. There were issues also regarding the mapping of in-memory objects to database tables. These problems are gone in the case of session beans.
Still, I feel it is important to show you how to do session bean inheritance. I want to make sure people have the right technique, even though this technique may seem obvious. Secondly, I would like to mention the importance of factories when dealing with inheritance. Message-driven bean inheritance will be the topic of a future article.
|
A note about the example source code Download the source code for this article. This zip file contains a WebLogic Server domain, EJB source code, and a PointBase database. Install this under C:\inherit. My two previous articles were based on WLS 7.0, but this article is based on WLS 7.0 Service Pack 1. The start scripts contain the newest PointBase drivers. For the administration console, the username is still |
Why would one want to use session bean inheritance? For the same reasons as stated in my previous articles: abstraction, reuse, and maintainability. But in this case, we don't want to project data onto a hierarchy; we want to do that with pure business logic. When is this useful? Here are a few examples:
I'm sure you can think of many more examples. Let's now define a business story for our session bean examples.
|
In This Series
EJB Free and Open Source Tools Summary
EJB Inheritance, Part 4
EJB Inheritance, Part 2
EJB Inheritance, Part 1 |
In the previous article, points could be redeemed for air flights using simplistic rules. A trip from New York to Chicago would cost the same number of points as a trip from New York to San Diego. But now the requirements have changed. A business decision was made that the number of points needed to buy a trip would be proportional to the distance traveled. An approximation of this distance can be calculated based on the zip codes of the departure and the destination airports. This only applies in the USA. For flights to Canada, there's a flat rate. For overseas flights, time zones are taken into consideration, instead.
On top of that, there is an administrative charge of 1,000 points, which applies to all regions except overseas, where the charge is 1,500 points. In some countries a tourism tax applies, also charged as points. Regular customers pay the full price. Gold customers get 15 percent off (before fees). Platinum customers get 30 percent off (before fees). The following table contains a summary of the rules used to calculate point redemption.
Table 1. New business logic requirements
| Region | Rules | Administrative fees |
| USA | Subtract the departure airport's zip code from the arrival airport's. Keep the absolute value of this value. This new value represents the number of points needed for this flight. | 1000 points |
| Canada | The flat fee is 10,000 points. | 1000 points |
| Overseas | Count the number of time slots between the departure airport and the arrival airport. Multiply this value by 3,000. Add the country's tourism tax fees. | 1500 points |
As with our previous examples, these are completely bogus rules and calculations, and should not be used for any serious business logic; they will serve, however, to illustrate the question at hand: how do you implement session bean inheritance?
You may have guessed from looking at the code of our previous example application
(look for a big if else if ... block) that objects are
well suited for this calculation.
The design of this system is straightforward: the base class is PointCalculator,
with subclasses CanadaPointCalculator and OverseasPointCalculator.
Note that the points themselves are not objects in this system, but the point calculators are.
They are, of course, session beans. Here's the class diagram:

Figure 1. Point calculator classes diagram
Here's a quick overview of the various steps required for my technique. We will then cover each step in more detail.
SessionBean. Write bean subclasses
extending base bean class.EJBObject or EJBLocalObject.
Create sub-interfaces extending base business interface.EJBHome or EJBLocalHome.
Write home interfaces for subclass, extending EJBHome or EJBLocalHome as usual.
Don't extend home interface of base class.setSessionContext(), ejbCreate(), ejbPassivate(), ejbActivate(), and ejbRemove() methods. These can be inherited. Overridden methods should call the corresponding super method first.Note: In my previous articles, I forgot to mention the other methods of the bean lifecycle: set...Context(), ejbRemove(), ejbPassivate(), ejbActivate(), ejbLoad(), and ejbStore(). These should be treated similarly to ejbCreate methods. They can be inherited, but if they're overridden, they must call the corresponding methods in super first. Also, pay close attention to the scope of class members. It would be a good idea to make EntityContext objects protected instead of private.
First let's implement the bean classes. The base class is PointCalculatorBean,
implementing SessionBean. The subclasses are CanadaPointCalculatorBean and OverseesPointCalculatorBean, extending PointCalculatorBean. We will leave the implementation of create and remove methods for later. Here's a code snippet that will help you understand what this step involves:
Example 1. PointCalculatorBean
//Removed for readability: package, imports.
public class PointCalculatorBean implements SessionBean {
// Removed for readability: constructor, session
// context, activate, passivate, create, remove.
public int getPoints(String departureAirport, String destinationAirport) {
try {
Connection c = ds.getConnection();
Statement s = c.createStatement();
s.execute("SELECT ZIP FROM AIRPORT WHERE ID = '" + departureAirport + "'" );
ResultSet rs = s.getResultSet();
if(!rs.next()) throw new Exception("Airport code not found for departure.");
int zipDeparture = rs.getInt(1);
s.execute("SELECT ZIP FROM AIRPORT WHERE ID = '" + destinationAirport + "'" );
rs = s.getResultSet();
if(!rs.next()) throw new Exception("Airport code not found for destination.");
int zipDestination = rs.getInt(1);
rs.close();
s.close();
c.close();
return Math.abs(zipDeparture-zipDestination);
} catch (Exception e) {
System.out.println("ERROR! Can't get zip codes. " + e);
e.printStackTrace();
}
return 0;
}
public int getAdministrativeFees() {
return 1000;
}
}
That piece of code requires some time to digest. But how about this one?
Example 2. PointCalculatorBean
//Removed for readability: package, import
public class CanadaPointCalculatorBean extends
basepointcalculator.PointCalculatorBean {
public int getPoints(String departureAirport, String destinationAirport) {
return 10000;
}
}
A lot smaller don't you think? This is reuse at its best!
|
Now we will write the business interfaces. These will follow a similar structure
to the bean classes: the base interface is PointCalculator, extending EJBObject,
and the subinterfaces are CanadaPointCalculator and OverseesPointCalculator,
extending PointCalculator. Here is the remote interfaces code of the example
I wrote:
Example 3. PointCalculator
public interface PointCalculator extends EJBObject {
public int getPoints(String departureAirport, String destinationAirport)
throws RemoteException;
public int getAdministrativeFees() throws RemoteException;
}
Example 4. OverseasPointCalculator
public interface OverseasPointCalculator extends
basepointcalculator.PointCalculator {
public int getTax(String destinationAirport) throws RemoteException;
}
I didn't show you the CanadaPointCalculator interface because it is empty.
The getTax() method is a specialization of the base interface.
You may be wondering why I used remote interfaces instead of local ones here. There's no design reason; I just wanted to demonstrate that my technique works with both.
Now let's write the home interfaces, except in this case we'll have three
unrelated interfaces. These are PointCalculatorHome, CanadaPointCalculatorHome,
and OverseesPointCalculatorHome. All three interfaces should extend EJBHome.
Don't make the CarLoanHome and MortgageHome extend
PointCalculatorHome. This is because -- as stated in my previous articles --
the return type of the inherited
create method(s) would be wrong for subinterfaces. Here's the code I came up
with:
Example 5. PointCalculatorHome
public interface PointCalculatorHome extends EJBHome {
PointCalculator create() throws CreateException, RemoteException;
}
Example 6. OverseasPointCalculatorHome
public interface OverseasPointCalculatorHome extends EJBHome {
OverseasPointCalculator create() throws CreateException, RemoteException;
}
These code fragments are not that interesting, so let's move on.
The last step is to implement the lifecycle methods: setSessionContext(),
ejbCreate(), ejbRemove(), ejbPassivate(), and ejbActivate(). Write them in the base class; they'll be inherited in the subclasses. If you override a method, simply have the method call the right method of super. In our example, all lifecycle methods are in
the base class, and inherited in the subclasses.
Example 7. PointCalculatorBean lifecycle methods
public class PointCalculatorBean implements SessionBean {
protected SessionContext ctx;
protected DataSource ds=null;
public void ejbActivate() { }
public void ejbPassivate() { }
public void setSessionContext(SessionContext ctx) {
this.ctx = ctx;
}
public void ejbCreate () throws CreateException {
try {
InitialContext jndiCtx = new InitialContext();
ds = (DataSource)jndiCtx.lookup("inheritDS");
} catch (Exception e) {
System.out.println("ERROR! The initial context
could not be retrieved. " + e);
e.printStackTrace();
}
}
public void ejbRemove() { }
//...
}
Note how the ejbCreate() method is used in the base class to create the data
source object used by all three beans.
In my previous articles, entity objects were created by the client code. This may be acceptable in some situations. But sometimes the creation of objects depends on involved business logic, which doesn't belong on the client.
Factories play a key role when using hierarchies of beans. A factory is an object used to create objects of a hierarchy. A factory can be used to create a single object or a list of objects. In any case, each object produced can be of any type in that hierarchy, but it is cast to a base class before being returned to the caller. That caller doesn't have to know what type of object it is. We can use a factory to keep the creation business logic out of the client code and on the server side. In our RTM example, a factory is used for creating the right point calculator object, as shown here:
Example 8. PCFactoryBean, creational business logic
public class PCFactoryBean implements SessionBean {
public PointCalculator getPointCalculator(String departureAirport,
String destinationAirport) {
try {
Connection c = ds.getConnection();
Statement s = c.createStatement();
s.execute("SELECT COUNTRY FROM AIRPORT
WHERE ID = '" + destinationAirport + "'" );
ResultSet rs = s.getResultSet();
if(!rs.next()) throw new Exception("Airport code not found for destination.");
String country = rs.getString(1);
rs.close();
s.close();
c.close();
if( country.equalsIgnoreCase("USA") ) {
return pcHome.create();
} else if( country.equalsIgnoreCase("Canada") ) {
return canadaPCHome.create();
} else {
return overseasPCHome.create();
}
} catch (Exception e) {
System.out.println("ERROR! Can't create point calculator. " + e);
e.printStackTrace();
}
return null;
}
//...
}
Then a client can use this factory like this:
Example 9. Using PCFactoryBean
public int redeemPoints(String departureAirport, String destinationAirport) {
PCFactoryHome pcHome;
InitialContext jndiCtx=null;
int pts = 0;
try {
jndiCtx = new InitialContext();
pcHome = (PCFactoryHome)jndiCtx.lookup("PCFactoryEJB");
PCFactory factory = pcHome.create();
PointCalculator pc = factory.getPointCalculator
(departureAirport,destinationAirport);
pts = pc.getPoints(departureAirport, destinationAirport);
pts -= (int) ( pts * getRebate() );
if(pc instanceof OverseasPointCalculator) {
//In the special case of overseas a tax may apply.
OverseasPointCalculator opc = (OverseasPointCalculator)pc;
int tax = opc.getTax(destinationAirport);
pts += tax;
}
int fees = pc.getAdministrativeFees();
pts += fees;
if(getPoints() < pts) return 0;
setPoints(getPoints()-pts);
return pts;
} catch (...) {
//...
}
}
The two things to notice in this piece of code are:
instanceof keyword.
This enables the client to use specialization methods. An example of this
is above in italics.Note that I chose to implement my factory using a stateless session bean. But there are other ways. One could implement a factory using:
|
Related Reading
|
So far we've only seen stateless session beans. The exact same technique can apply to stateful session beans. The difference is that the clients have access to each object instance and keep this access.
It is easy to modify this example slightly so that the client can book multiple
flights. The session bean would keep track of which airports are visited along
the way. A method could be added to append a new airport code to the list. Then
we could have a method, getPoints(), with no arguments, which
would perform the calculation and keep the results. The client can then access
all of this information.
Not too painful, is it? This process should work with most J2EE application servers, as the EJB specification openly tries to make inheritance of session beans possible. This was not the case with entity beans because, for example, finders are not polymorphic. Simplicity of the session bean concept helps a lot, too.
As we've seen, the usage of factories is a very natural addition to EJB inheritance.
In the next article, we'll see how to implement inheritance using message-driven beans.
Emmanuel Proulx is an expert in J2EE and Enterprise JavaBeans, and is a certified WebLogic Server 7.0 engineer. He works in the fields of telecommunications and web development.
Return to ONJava.com.
Copyright © 2007 O'Reilly Media, Inc.