|
Oracle JDeveloper Tip
Creating a CMT Stateless SessionBean Facade for an Application Module
Author: Steve Muench, BC4J Development Team
Date: July 15, 2004
Contents
Overview
Our Application Module
Creating the Stateless Session Bean Wrapper
Aggregating a BC4J Application Module
Building a Test Client
Running the Sample
Conclusion
Code Listing
Overview
BC4J provides automatic facilities for deploying
any application module as a stateful EJB session bean. If you want to leverage
the features of your BC4J application module from a
stateless EJB session bean, it's not automatic but it is
straightforward to implement. This howto article explains the details.
| NOTE: |
You can download the statelesscmtsessionbean.zip
workspace to try the example described in this paper in your own JDeveloper
environment. See the README.txt file in the project for further
tips.
|
For our example, we will create a stateless EJB
session bean that uses a container-managed transaction. To keep things simple,
let's assume our service has a single business method named
createOrUpdateDepartment() with the following
signature:
public boolean createOrUpdateDepartment(int id, String name, String loc)
The goal of this article is to illustrate how to use
the BC4J application module named com.example.hr.HRApp as
part of the implementation of this createOrUpdateDepartment
method on our stateless enterprise bean. Let's assume that the
HRApp application module has a view object member named
Departments, based on the
com.example.hr.DeptView view object, based on the familiar
DEPT table and related to the
com.example.hr.Dept entity object so our view can be
updateable.
Our Application Module
We start with a BC4J ApplicationModule component named
HRApp in the com.example.hr package.
We've written a custom createOrUpdateDepartment() method in
our HRAppImpl.java class and we've visited the
ApplicationModule editor's Client Methods panel and
shuttled createOrUpdateDepartment method into the list of
selected methods to include on our application module's client interface. The
method implementation tries to find an existing row in the Departments view
object having the id passed in as its primary key, then either updates that
existing row or creates a new row appropriately. It returns boolean
true if a new row gets created. The code looks like
this:
/*
* Returns true if new row was created.
*/
public boolean createOrUpdateDepartment(int id, String name, String loc) {
boolean createdNewRow = false;
/*
* Use the "Departments" view object instance of this AM
*/
ViewObject departments = getDepartments();
/*
* Check to see if the Department with this ID already exists
*/
Number deptId = new Number(id);
Key k = new Key(new Object[]{deptId});
Row[] found = departments.findByKey(k,1);
if (found.length > 0) {
/*
* If we found it, then update it's Dname and Loc
*/
Row existingDept = found[0];
existingDept.setAttribute("Dname", name);
existingDept.setAttribute("Loc", loc);
}
else {
createdNewRow = true;
/*
* Otherwise create a new Dept row
*/
Row newDept = departments.createRow();
/*
* Add the new row to the view object's default rowset
*/
departments.insertRow(newDept);
/*
* Populate the attributes from the parameter arguments.
*/
newDept.setAttribute("Deptno", new Number(id));
newDept.setAttribute("Dname", name);
newDept.setAttribute("Loc", loc);
}
return createdNewRow;
}
By writing our custom code inside the lightweight
application module component, we gain two benefits:
- We can reuse this lightweight component both as a simple Java
Bean as well as an EJB SessionBean
- We can more
easily test the lightweight component outside of the EJB
container
Creating the Stateless Session Bean Wrapper
We can start by using the
JDeveloper Enterprise Bean wizard to create a new stateless session bean called
StatelessSample implemented by:
- com.example.StatelessSampleBean (Bean
class)
- com.example.StatelessSampleHome (Home
interface)
- com.example.StatelessSample (Remote interface)
We then use the EJB Class Editor to add the
createOrUpdateDepartment method to the
StatelessSample bean's remote interface with the signature
above. We edit the remote interface to make sure that it also reflects that the
createOrUpdateDepartment method thows the
AppException like this:
package com.example;
import javax.ejb.EJBObject;
import java.rmi.RemoteException;
public interface StatelessSample extends EJBObject {
boolean createOrUpdateDepartment(int id, String name, String loc)
throws RemoteException, AppException;
}
The AppException is an example of an
application-specific exception that our method will throw if any problems arise
during its execution. Its code looks like this:
package com.example;
public class AppException extends Exception {
public AppException(String msg) {super(msg);}
}
Before we start adding BC4J into the picture for our
Stateless SessionBean wrapper implementation, our
StatelessSampleBean class looks like this:
package com.example;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;
public class StatelessSampleBean implements SessionBean {
public void ejbCreate(){}
public void ejbActivate(){}
public void ejbPassivate(){}
public void ejbRemove(){}
public void setSessionContext(SessionContext ctx){
}
public boolean createOrUpdateDepartment(int id, String name, String loc)
throws AppException {
// TODO: Implement method here
}
}
We need to mark the
createOrUpdateDepartment method as requiring a transaction.
To do this, we can use the EJB Module editor to click on the StatelessSample
bean, select the createOrUpdateDepartment method, and then
use the Container Transaction Attribute drop-down list on
the Remote Properties tab to pick the value
Required from the list.
| NOTE: |
If you have
included both Remote and Local interfaces on your bean as the example project
has done, make sure that you visit both the Local
Properties and the Remote Properties tab to set
the Container Transaction Attribute to
Required in both places!
|
If you
double-click on the ejb-jar.xml file in the sample project
you can see the results of setting the transaction attribute in the EJB Module
Editor. Look for the <transaction-attribute> tag in the
<container-transaction> sections in the file.
<ejb-jar>
<enterprise-beans>
<session>
<description>Session Bean ( Stateless )</description>
<display-name>StatelessSample</display-name>
<ejb-name>StatelessSample</ejb-name>
<home>com.example.StatelessSampleHome</home>
<remote>com.example.StatelessSample</remote>
<local-home>com.example.StatelessSampleLocalHome</local-home>
<local>com.example.StatelessSampleLocal</local>
<ejb-class>com.example.StatelessSampleBean</ejb-class>
<session-type>Stateless</session-type>
<transaction-type>Container</transaction-type>
</session>
</enterprise-beans>
<assembly-descriptor>
<container-transaction>
<method>
<ejb-name>StatelessSample</ejb-name>
<method-intf>Local</method-intf>
<method-name>createOrUpdateDepartment</method-name>
<method-params>
<method-param>int</method-param>
<method-param>java.lang.String</method-param>
<method-param>java.lang.String</method-param>
</method-params>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
<container-transaction>
<method>
<ejb-name>StatelessSample</ejb-name>
<method-intf>Remote</method-intf>
<method-name>createOrUpdateDepartment</method-name>
<method-params>
<method-param>int</method-param>
<method-param>java.lang.String</method-param>
<method-param>java.lang.String</method-param>
</method-params>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
</assembly-descriptor>
</ejb-jar>
Aggregating a BC4J Application Module
With the EJB aspects of our bean setup, we can proceed to
implementing the BC4J application module aggregation.
The first
thing we do is add private variables to hold the EJB
SessionContext and the instance of the aggregated BC4J
ApplicationModule, like this:
/*
* Place to hold onto the aggregated appmodule instance.
*
* NOTE: We're using the custom interface com.example.hr.common.HRApp
* (which extends oracle.jbo.ApplicationModule) that the BC4J
* design time creates for us when we exposed the
* createOrUpdateDepartment() method on our application module's
* client interface.
*/
transient private HRApp _am = null;
/*
* Remember the SessionContext that the EJB container provides us
*/
private SessionContext _ctx = null;
and we modify the default, empty implementation of the
setSessionContext() method to remember the session context
like this:
public void setSessionContext(SessionContext ctx){ _ctx = ctx; }
We add additional constants that hold the names of the
transactional and non-transactional J2EE datasources that we want BC4J to use,
as well as the fully-qualified name of the BC4J application module that we'll
be aggregating:
/*
* JNDI resource name for the J2EE datasource to use for application work
* For OC4J, this is the name of the 'ejb-location' name from the OC4J
* element in the data-source.xml file.
*/
private static final String TRANSACTIONAL_DATASOURCE = "jdbc/scottDS";
/*
* JNDI resource name for the non-transactional 2EE datasource to be
* used for AM snapshot passivation if your bean uses that feature.
* This datasource will be used lazily by BC4J, but it must be set.
*
* For OC4J, this is the name of the 'location' name from the OC4J
* <data-source> element in the data-source.xml file.
*/
private static final String NONTRANSACTIONAL_DATASOURCE = "jdbc/scottCoreDS";
/*
* Fully-qualified BC4J application module name to aggregate
*/
private static final String APPMODNAME = "com.example.hr.HRApp";
| NOTE: |
When you create a connection named
scott in the JDeveloper connection manager, at runtime
JDeveloper automatically configures the data-sources.xml
file to be used by the embedded OC4J instance to have that
scott connection as a JDBC datasource with the JNDI
ejb-location of jdbc/scottDS and the
JNDI location of jdbc/scottCoreDS. The former
ejb-location named datasource is transactional and
participates in the CMT transaction. The latter location
named datasource is a non-transactional datasource and does not participate in
CMT transaction management. When you deploy an EJB JAR file in JDeveloper, it
bundles up this data-sources.xml file and includes it in
your *.ejbjar file as well. Later versions of JDeveloper
provide declarative control over whether you want this automatic connection
deployment or not.
|
We expand the now-empty
ejbCreate() and ejbRemove() methods to
create and destroy the aggregated instance of the BC4J application module that
we'll use for the lifetime of the stateless session bean. When we're done,
ejbCreate() it looks like this:
public void ejbCreate() throws CreateException {
try {
/*
* Setup a hashtable of environment parameters for JNDI initial context
*/
Hashtable env = new Hashtable();
env.put(JboContext.INITIAL_CONTEXT_FACTORY,JboContext.JBO_CONTEXT_FACTORY);
/*
* NOTE: we want to use the BC4J app module in local mode as a simple Java class!
*/
env.put(JboContext.DEPLOY_PLATFORM, JboContext.PLATFORM_LOCAL);
/*
* We set the internal datasource to be some convenient,
* non-transactional datasource name.
*/
env.put(PropertyConstants.INTERNAL_CONNECTION_PARAMS,NONTRANSACTIONAL_DATASOURCE);
/*
* Create an initial context, using this hashtable of environment params
*/
InitialContext ic = new InitialContext(env);
/*
* Lookup a home interface for the application module
*/
ApplicationModuleHome home = (ApplicationModuleHome)ic.lookup(APPMODNAME);
/*
* Using the home, create the instance of the appmodule we'll use
*/
_am = (HRApp)home.create();
/*
* Register the BC4J factory to handle EJB container-managed transactions
*/
registerContainerManagedTransactionHandlerFactory();
}
catch(Exception ex) {
ex.printStackTrace();
throw new CreateException(ex.getMessage());
}
}
and ejbRemove() looks like
this:
public void ejbRemove() {
try {
// Cleanup any appmodule resources before getting shutdown
_am.remove();
}
catch(JboException ex) { /* Ignore */ }
}
The helper method named
reigsterContainerManagedTransactionHandlerFactory() looks
like this:
/*
* Handle setting up the correct BC4J transaction handler
* for participating in CMT transactions.
*/
private void registerContainerManagedTransactionHandlerFactory() {
SessionImpl session = (SessionImpl)_am.getSession();
session.setTransactionHandlerFactory(
new TransactionHandlerFactory() {
public TransactionHandler createTransactionHandler() {
return new ContainerManagedTxnHandlerImpl();
}
public JTATransactionHandler createJTATransactionHandler() {
return new ContainerManagedTxnHandlerImpl();
}
}
);
}
It utilizes some built-in classes in the BC4J framework
to handle the interaction between the EJB container managed transaction and the
BC4J application module's transaction. The last detail is to implement the
createOrUpdateDepartment() method in the SessionBean. The
implementation needs to:
- Connect the AM to
the transactional datasource we want to use
- Delegate to the createOrUpdateDepartment method on the
HRApp interface of the application module
- Post the pending changes in the application module component's
unit of work, but do not commit them.
| NOTE: |
The EJB container's
CMT transaction will orchestrate to the commit and the
ContainerManagedTxnHandlerImpl we used above in our wrapper
bean implementation will insure that the BC4J application module's pending work
gets committed then.
|
- Disconnect the AM
from its datasource
In order to be good
CMT "citizens" in the transaction, if any JboException is raised during the
execution of the method, we perform the required step of calling:
_ctx.setRollbackOnly();
on the session context to signal to the container that
something went wrong in posting our pending changes so that the overall
container transaction should not proceed.
The final createOrUpdateDepartment() wrapper code to perform these
steps looks like this:
public boolean createOrUpdateDepartment(int id,
String name,
String loc)
throws AppException {
boolean createdNewRow = false;
try {
/*
* Connect the AM to the datasource we want to use for
* the duration of this single method call.
*/
_am.getTransaction().connectToDataSource(null,
TRANSACTIONAL_DATASOURCE,
false);
/*
* Delegate the method call to the implementation of
* the custom HRApp interface of our HRApp application
* module.
*/
createdNewRow = _am.createOrUpdateDepartment(id,name,loc);
/*
* Post all changes in the AM, but we don't commit them.
* The EJB container managed transaction handles
* the commit.
*/
_am.getTransaction().postChanges();
return createdNewRow;
}
catch(JboException ex) {
/*
* To be good EJB Container-Managed Transaction "citizens"
* we have to mark the transaction as needing a rollback
* if there are problems
*/
_ctx.setRollbackOnly();
throw new AppException("Error creating dept "+ id
+"\n"+ex.getMessage());
}
finally {
try {
/*
* Disconnect the AM from the datasource we're using
*/
_am.getTransaction().disconnect();
}
catch(Exception ex) { /* Ignore */ }
}
}
Building a Test Client
With the EJB-Tier work done, we can build a sample client program to test
this new stateless EJB Session Bean by selecting the bean in the JDeveloper IDE
and choosing "Create Sample Java Client" from the right-mouse menu.
| NOTE: |
This option appears only if your bean supports remote
interfaces.
|
When the "Sample EJB Client Details" dialog
appears, we take the defaults of connecting to embedded OC4J container.
Clicking the (OK) button generates the following test class:
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.InitialContext;
import com.example.StatelessSampleEJB;
import com.example.StatelessSampleEJBHome;
public class SampleStatelessSampleEJBClient {
public static void main(String [] args) {
SampleStatelessSampleEJBClient sampleStatelessSampleEJBClient =
new SampleStatelessSampleEJBClient();
try {
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.evermind.server.rmi.RMIInitialContextFactory");
env.put(Context.SECURITY_PRINCIPAL, "admin");
env.put(Context.SECURITY_CREDENTIALS, "welcome");
env.put(Context.PROVIDER_URL,
"ormi://localhost:23891/current-workspace-app");
Context ctx = new InitialContext(env);
StatelessSampleEJBHome statelessSampleEJBHome =
(StatelessSampleEJBHome)ctx.lookup("StatelessSampleEJB");
StatelessSampleEJB statelessSampleEJB;
// Use one of the create() methods below to create a new instance
// statelessSampleEJB = statelessSampleEJBHome.create();
// Call any of the Remote methods below to access the EJB
// statelessSampleEJB.createDepartment( int id,
// String name,
// String loc );
}
catch(Throwable ex) {
ex.printStackTrace();
}
}
}
I modified the sample client slightly to produce one
that attempts to test our createOrUpdateDepartment method in a few different
ways, and which includes the appropriate PROVIDER_URL values for connecting to
the SessionBean running on the embbed OC4J container as well as an external
OC4J container:
package com.example;
import com.example.StatelessSample;
import com.example.StatelessSampleHome;
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.rmi.PortableRemoteObject;
public class StatelessSampleClient {
public static void main(String[] args) throws Throwable {
StatelessSampleClient statelessSampleClient =
new StatelessSampleClient();
Context context = getInitialContext();
/*
* Lookup the Home Interface
*/
StatelessSampleHome statelessSampleHome =
(StatelessSampleHome) PortableRemoteObject.narrow(
context.lookup("StatelessSample"),
StatelessSampleHome.class);
/*
* Use one of the create() methods below to create a new instance
*/
StatelessSample statelessSampleEJB = statelessSampleHome.create();
/*
* Call any of the Remote methods below to access the EJB
*/
boolean created = false;
try {
created = statelessSampleEJB.createOrUpdateDepartment(13, "Test1", "Loc1");
System.out.println((created ? "Created" : "Updated")+" department 13");
}
catch (AppException ax) {
System.err.println("AppException: " + ax.getMessage());
}
try {
statelessSampleEJB.createOrUpdateDepartment(14, "Test2", "Loc2");
System.out.println((created ? "Created" : "Updated")+" department 14");
}
catch (AppException ax) {
System.err.println("AppException: " + ax.getMessage());
}
try {
/*
* Try setting a department id that is too large!
*/
statelessSampleEJB.createOrUpdateDepartment(23456, "Test3", "Loc3");
System.out.println((created ? "Created" : "Updated")+" department 23456");
}
catch (AppException ax) {
System.err.println("AppException: " + ax.getMessage());
}
try {
/*
* Try setting a location of "Loc5" which will fail during
* post-time if you have run the ExampleTriggerToMakeUpdateFail.sql
* script in this project.
*/
statelessSampleEJB.createOrUpdateDepartment(14, "Test2", "Loc5");
System.out.println((created ? "Created" : "Updated")+" department 14");
}
catch (AppException ax) {
System.err.println("AppException: " + ax.getMessage());
}
}
private static Context getInitialContext() throws NamingException {
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.evermind.server.rmi.RMIInitialContextFactory");
env.put(Context.SECURITY_PRINCIPAL, "admin");
env.put(Context.SECURITY_CREDENTIALS, "welcome");
/*
* Use this URL if you want to test against the stateless session
* bean running on the embedded OC4J container.
*
* NOTE: Remember to "Run" the session bean first to start the
* ---- embedded container before running this client, or you'll
* get an expected "Cannot connect" error since the server
* bean won't be running and available for client connections.
*/
String provider_url = "ormi://localhost:23891/current-workspace-app";
/*
* Use this URL instead if you want to test on an externally deployed
* OC4J instance.
*/
// String provider_url = "ormi://localhost/bc4jcmtslsb";
env.put(Context.PROVIDER_URL,provider_url);
return new InitialContext(env);
}
}
Running the Sample
To run
the example inside the JDeveloper IDE (9.0.3 or later) you need to:
- Have previously created a named JDeveloper
database connection named "scott" (case-sensitive!), pointing to the familiar
SCOTT/TIGER account where the
DEPT table lives.
To
run the example on the embedded OC4J container:
- Run the StatelessSample SessionBean on the embedded
OC4J container by clicking on StatelessSample Session Bean
in the navigator and chosing Run from the
right-mouse context menu.
- With the embedded
OC4J server running your session bean, run your
StatelessSampleClient.java class by again clicking on it and
picking Run from the context
menu.
| NOTE: |
If you make any changes to the
implementation of the sample EJB SessionBean, note that you'll need to
terminate the embedded OC4J container, and rerun your modified SessionBean in
order for the client to "see" the changed server-side
implementation.
|
To run the example on the external,
standalone OC4J container:
- Use the
JDEVHOME/jdev/bin/start_oc4j.bat
script to start the external OC4J instance.
- Define an Application Server connection, for example named
localoc4j, and use the admin username
with password of welcome as credentials.
- Use the ejbjar.deploy deployment profile in
the project to deploy the EJB to the external server, by clicking on
ejbjar.deploy and selecting Deploy to >
localoc4j
-
Comment out the line
in StatelessSampleClient that sets the
PROVIDER_URL for the embedded OC4J container...
// String provider_url = "ormi://localhost:23891/current-workspace-app";
and uncomment the line below with the value for the external container:
String provider_url = "ormi://localhost/bc4jcmtslsb"
| NOTE: |
If you make any changes
to the implementation of the sample EJB SessionBean, note that you'll need to
redeploy the EJB to the external OC4J container in order for the client to
"see" the changed server-side implementation.
|
When you run
the StatelessSampleClient the first time, you'll see output
like:
Created department 13
Created department 14
AppException: Error creating dept 23456
JBO-27010: Attribute set with value 23456 for Deptno in Dept has invalid precision/scale
Updated department 14
If you want to experiment with failures that can occur
during an update, you might experiment by running the
ExampleTriggerToMakeUpdateFail.sql script against your
SCOTT account and then try running the
StatelessSampleClient again. This scripts creates the simple
database trigger below which rejects any attempt to insert or update a row in
the DEPT table with a LOC value of "Loc5"
(case-insensitive):
create trigger reject_dept_loc5
before insert or update on dept for each row
begin
if (upper(:new.loc) = 'LOC5') then
raise_application_error(-20001,'Cannot have location of LOC5');
end if;
end;
If you create this trigger and rerun the
StatelessSampleClient, you'll see output like this:
Updated department 13
Updated department 14
AppException: Error creating dept 23456
JBO-27010: Attribute set with value 23456 for Deptno
in Dept has invalid precision/scale
AppException: Error creating dept 14
JBO-26041: Failed to post data to database during "Update":
SQL Statement " UPDATE DEPT Dept SET DNAME=:1,LOC=:2 WHERE DEPTNO=:3".
Conclusion
Hopefully
this article has illustrated that it is straightforward to utilize the full
power of BC4J in local mode as part of your EJB Stateless Session Beans using
container-managed transaction. This example illustrated delegating to the
single createOrUpdateDepartment method in the application
module, but by replicating the same code pattern above, we could easily expose
many methods on the Stateless SessionBean in the same way.
| NOTE: |
The steps discussed here are valid for BC4J in JDeveloper 9.0.3 and
9.0.4, as well as for ADF Business Components in JDeveloper
10g.
|
Code Listing
The full
code listing for the StatelessSampleBean bean implementation
class looks like this:
/**
* StatelessSampleBean
*
* Illustrates how to use an aggregated BC4J application module
* in local mode as part of the implementation of a stateless
* EJB session bean using container-managed transaction.
*
* HISTORY
* smuench 15-JUL-2004 Updated
* smuench/dmutreja 14-FEB-2002 Created
*/package com.example;
import com.example.hr.common.*;
import java.util.Hashtable;
import javax.ejb.CreateException;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;
import javax.naming.InitialContext;
import oracle.jbo.ApplicationModuleHome;
import oracle.jbo.JboContext;
import oracle.jbo.JboException;
import oracle.jbo.common.PropertyConstants;
import oracle.jbo.server.JTATransactionHandler;
import oracle.jbo.server.SessionImpl;
import oracle.jbo.server.TransactionHandler;
import oracle.jbo.server.TransactionHandlerFactory;
import oracle.jbo.server.ejb.ContainerManagedTxnHandlerImpl;
public class StatelessSampleBean implements SessionBean {
/*
* Place to hold onto the aggregated appmodule instance.
*
* NOTE: We're using the custom interface com.example.hr.common.HRApp
* (which extends oracle.jbo.ApplicationModule) that the BC4J
* design time creates for us when we exposed the
* createOrUpdateDepartment() method on our application module's
* client interface.
*/
transient private HRApp _am = null;
/*
* Remember the SessionContext that the EJB container provides us
*/
private SessionContext _ctx = null;
/*
* JNDI resource name for the J2EE datasource to use for application work
* For OC4J, this is the name of the 'ejb-location' name from the OC4J
* element in the data-source.xml file.
*/
private static final String TRANSACTIONAL_DATASOURCE = "jdbc/scottDS";
/*
* JNDI resource name for the non-transactional 2EE datasource to be
* used for AM snapshot passivation if your bean uses that feature.
* This datasource will be used lazily by BC4J, but it must be set.
*
* For OC4J, this is the name of the 'location' name from the OC4J
* element in the data-source.xml file.
*/
private static final String NONTRANSACTIONAL_DATASOURCE = "jdbc/scottCoreDS";
/*
* Fully-qualified BC4J application module name to aggregate
*/
private static final String APPMODNAME = "com.example.hr.HRApp";
public void ejbCreate() throws CreateException {
try {
/*
* Setup a hashtable of environment parameters for JNDI initial context
*/
Hashtable env = new Hashtable();
env.put(JboContext.INITIAL_CONTEXT_FACTORY,JboContext.JBO_CONTEXT_FACTORY);
/*
* NOTE: we want to use the BC4J app module in local mode as a simple Java class!
*/
env.put(JboContext.DEPLOY_PLATFORM, JboContext.PLATFORM_LOCAL);
/*
* We set the internal datasource to be some convenient,
* non-transactional datasource name.
*/
env.put(PropertyConstants.INTERNAL_CONNECTION_PARAMS,NONTRANSACTIONAL_DATASOURCE);
/*
* Create an initial context, using this hashtable of environment params
*/
InitialContext ic = new InitialContext(env);
/*
* Lookup a home interface for the application module
*/
ApplicationModuleHome home = (ApplicationModuleHome)ic.lookup(APPMODNAME);
/*
* Using the home, create the instance of the appmodule we'll use
*/
_am = (HRApp)home.create();
/*
* Register the BC4J factory to handle EJB container-managed transactions
*/
registerContainerManagedTransactionHandlerFactory();
}
catch(Exception ex) {
ex.printStackTrace();
throw new CreateException(ex.getMessage());
}
}
public void ejbActivate() {}
public void ejbPassivate() {}
public void ejbRemove() {
try {
/*
* Cleanup any appmodule resources before getting shutdown
*/
_am.remove();
}
catch(JboException ex) { /* Ignore */ }
}
public void setSessionContext(SessionContext ctx){ _ctx = ctx; }
/*
* Returns true if new row was created.
*/
public boolean createOrUpdateDepartment(int id, String name, String loc)
throws AppException {
boolean createdNewRow = false;
try {
/*
* Connect the AM to the datasource we want to use for the duration
* of this single method call.
*/
_am.getTransaction().connectToDataSource(null,TRANSACTIONAL_DATASOURCE,false);
/*
* Delegate the method call to the implementation of the custom
* HRApp interface of our HRApp application module.
*/
createdNewRow = _am.createOrUpdateDepartment(id,name,loc);
/*
* Post all changes in the AM, but we don't commit them.
* The EJB container managed transaction handles the commit.
*/
_am.getTransaction().postChanges();
return createdNewRow;
}
catch(JboException ex) {
/*
* To be good EJB Container-Managed Transaction "citizens" we have
* to mark the transaction as needing a rollback if there are problems
*/
_ctx.setRollbackOnly();
throw new AppException("Error creating dept "+ id +"\n"+ex.getMessage());
}
finally {
try {
/*
* Disconnect the AM from the datasource we're using
*/
_am.getTransaction().disconnect();
}
catch(Exception ex) { /* Ignore */ }
}
}
/*
* Handle setting up the correct BC4J transaction handler
* for participating in CMT transactions.
*/
private void registerContainerManagedTransactionHandlerFactory() {
SessionImpl session = (SessionImpl)_am.getSession();
session.setTransactionHandlerFactory(
new TransactionHandlerFactory() {
public TransactionHandler createTransactionHandler() {
return new ContainerManagedTxnHandlerImpl();
}
public JTATransactionHandler createJTATransactionHandler() {
return new ContainerManagedTxnHandlerImpl();
}
}
);
}
}
|