Thursday, July 26, 2007

Working with a non JTA DataSource in Toplink Essentials

As it is commonly known, Toplink Essentials, as a JPA engine, can be used both inside a Java EE 5 application server and outside an EJB container in a Java Standard Edition (Java SE) 5 application.
However, if you are working with JPA outside an EJB container, you would have known that by default you can't define a data source to work with in your persistence.xml archive. You just need to define the JDBC connection data as showed in the sample below:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
<persistence-unit name="SamplePU" transaction-type="RESOURCE_LOCAL">
<provider>oracle.toplink.essentials.ejb.cmp3.EntityManagerFactoryProvider</provider>
<class>sample.MyEntity</class>
<properties>
<property name="toplink.jdbc.url" value="someURL"/>
<property name="toplink.jdbc.driver" value="oracle.jdbc.OracleDriver"/>
<property name="toplink.jdbc.user" value="someUser"/>
<property name="toplink.jdbc.password" value="somePassword"/>
</properties>
</persistence-unit>
</persistence>
For many people not being able to use a datasource is a little frustrating. However, before you think of switching to another JPA engine (such as Hibernate EM which does allow to define non JTA datasource for a Java SE environment, i.e. Tomcat), I must tell you there is a way to define a non JTA data source in Toplink Essentials too! Just it is not straightforward.

Transparent approach

Toplink Essentials allows you to customize some JPA behavior with several extensions to the standard JPA that can be defined in the persistence.xml persistence configuration file. So, we take advantage of some of this features to achieve our goal.
The extension we need to serve our purpose can be found here.

It's important to keep in mind that this workaround doesn't compromise the application using JPA. You won't have to write any proprietary code using classes other than the JPA standard.

The only requirement for a client application is:

  1. To attach a jar archive as a library (with the proprietary setting for managing a DataSource).
    This can be done in the same way you attach your Toplink Essentials library: either in your war file (under /WEB-INF/lib) or in within your Application Server library directory (i. e. $CATALINA_HOME/common/lib for Apache Tomcat).

  2. To define a property in the persistence.xml persistence configuration file.

Create the customized code as a library

The only problem we can't define a data source in persistence.xml to be used for the engine in a Java SE environment is because the looking up of this data source is not by name by default. Fortunately we can change this behavior through the JNDIConnector class.

If you take a look to the methods of this class, you'll notice you can even setup a DataSource by invoking the setDataSource() method. However we won't go this further. The only method we need is just setLookupType() to establish the looking up type to the constant JNDIConnector.STRING_LOOKUP.

The JNDIConnector instance we need to modify is accessed through a Toplink session. Toplink Essentials allows us to customize this session before it is used by implementing a SessionCustomizer (we'll need to specify its class name as the previously mentioned property in persistence.xml).

Here is showed the one class implementation we need:
package es.claro.commons.ds;

import oracle.toplink.essentials.tools.sessionconfiguration.SessionCustomizer;
import oracle.toplink.essentials.sessions.Session;
import oracle.toplink.essentials.jndi.JNDIConnector;
public class DataSourceSessionCustomizer implements SessionCustomizer {

public DataSourceSessionCustomizer() {
}

public void customize(Session session) throws Exception {

JNDIConnector conn = (JNDIConnector)session.getLogin().getConnector();
conn.setLookupType(JNDIConnector.STRING_LOOKUP);
}
}
Note that this code is proprietary indeed, but this code is not within your web application but as a library file. You must think of it as an extension of your JPA engine files: I suggest you to compile this class (using Toplink Essentials library as part of the classpath for the compilation), pack the compiled code into a jar file and add this jar file to wherever you put the Toplink Essentials jar files.

Customizing the persistence configuration file

Now that we have already implemented the SessionCustomizer interface, we have just to define the property toplink.session.customizer in the persistence.xml persistence configuration file.

Below you can take a look to a persistence.xml where this property is defined:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
<persistence-unit name="SamplePU" transaction-type="RESOURCE_LOCAL">
<provider>oracle.toplink.essentials.ejb.cmp3.EntityManagerFactoryProvider</provider>
<non-jta-data-source>java:comp/env/jdbc/DefaultDS</non-jta-data-source>
<class>sample.MyEntity</class>
<properties>
<property name="toplink.session.customizer" value="es.claro.commons.ds.DataSourceSessionCustomizer"/>
</properties>
</persistence-unit>
</persistence>

Using the data source in your web application

Now, Toplink Essentials will use your data source when you use JPA code! With JPA you don't need to use directly any DataSource.

Note that in this sample we're using a reference jdbc/DefaultDS and not an actual data source previously defined in the Application Server (e.g. Tomcat). As you should guess, the name of the data source referenced in the persistence.xml is just a reference. This reference is defined in web.xml deployment descriptor file of your web application and mapped to an actual data source in a proprietary file. In case of Tomcat, this mapping is defined in the context.xml file as shown in the following sample:
<?xml version="1.0" encoding="UTF-8"?>
<Context path="/sample">
<ResourceLink global="jdbc/MyRealDS" name="jdbc/DefaultDS" type="javax.sql.DataSource"/>
</Context>