EJB Inheritance, Part 3
Step 2. Business Interfaces
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.
Step 3. Home Interfaces
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.
Step 4. Lifecycle Methods
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.
Factories
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:
- A caller creates a class of the hierarchy using the factory. This is shown
here in bold.
- A caller can know the type of object easily by using the
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:
- A Business Delegate or Service Locator (client-side object that handles
access to the EJBs).
- A Value List Handler (server-side object that handles lists of objects).
- The home method of an entity bean base class.
- Etc.
Stateful Session Beans
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.
Easy Does It
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.