Skip to content

6. Version 2: Architecture OpenEJB / JPA

6.1. Introduction to Porting Principles

Here we present the principles that will govern the porting of a JPA / Spring / Hibernate application to a JPA / OpenEJB / EclipseLink application. We will wait until Section 6.2 to create the Maven projects.

6.1.1. The two architectures

The current implementation with Spring / Hibernate

The implementation to be built with OpenEJB / EclipseLink

6.1.2. Project libraries

  • The [DAO] and [metier] layers are no longer instantiated by Spring. They are instantiated by the OpenEJB container.
  • The Spring container libraries and its configuration are removed in favor of the OpenEJB container libraries and its configuration.
  • The libraries of the JPA / Hibernate layer are replaced by those of the JPA / EclipseLink layer

  • The [META-INF/persistence.xml] file configuring the JPA layer becomes the following:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.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_2_0.xsd">
  <persistence-unit name="pam-openejb-ui-metier-dao-jpa-eclipselinkPU" transaction-type="JTA">
     <!-- entities JPA -->
    <class>jpa.Cotisation</class>
    <class>jpa.Employe</class>
    <class>jpa.Indemnite</class>
     <!-- the supplier JPA is EclipseLink -->
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
     <!-- provider properties -->
    <properties>
      <property name="eclipselink.ddl-generation" value="create-tables"/>
    </properties>
  </persistence-unit>
</persistence>
  • Line 3: Transactions in a EJB container are of type JTA (Java Transaction API). They were of type RESOURCE_LOCAL with Spring.
  • line 9: the JPA implementation used is EclipseLink
  • Lines 5–7: Entities managed by the JPA layer
  • Lines 11–13: Properties of the EclipseLink provider
  • line 12: the tables will be created on each execution

The characteristics JDBC of the data source JTA used by the container OpenEJB will be specified by the following configuration file [conf/openejb.conf]:

1
2
3
4
5
6
7
8
<?xml version="1.0"?>
<openejb>
  <Resource id="Default JDBC Database">
    JdbcDriver com.mysql.jdbc.Driver
    JdbcUrl jdbc:mysql://localhost:3306/dbpam_eclipselink
    UserName root
  </Resource>
</openejb>
  • Line 3: We use the id Default JDBC Database when working with a OpenEJB container embedded in the application itself.
  • line 5: we use a MySQL [dbpam_eclipselink] database

6.1.4. Implementation of the [DAO] layer by EJB

  • The classes implementing the [DAO] layer become EJB. Let’s take the example of the [CotisationDao] class:

The [ICotisationDao] interface in the version Spring package was as follows:

package dao;

import java.util.List;
import jpa.Cotisation;

public interface ICotisationDao {
   // create a new contribution
  Cotisation create(Cotisation cotisation);
   // modify an existing contribution
  Cotisation edit(Cotisation cotisation);
   // delete an existing contribution
  void destroy(Cotisation cotisation);
   // search for a specific contribution
  Cotisation find(Long id);
   // get all objects Contribution
  List<Cotisation> findAll();

}

EJB will implement this same interface in two different forms: a local one and a remote one. The local interface can be used by a client running in the same JVM, while the remote interface can be used by a client running in another JVM.

The local interface:

1
2
3
4
5
6
7
package dao;

import javax.ejb.Local;

@Local
public interface ICotisationDaoLocal extends ICotisationDao{
}
  • Line 6: The [ICotisationDaoLocal] interface inherits from the [ICotisationDao] interface to adopt all of its methods. It does not add any new ones.
  • Line 5: The @Local annotation makes this a local interface for EJB, which will implement it.

The remote interface:

1
2
3
4
5
6
7
package dao;

import javax.ejb.Remote;

@Remote
public interface ICotisationDaoRemote extends ICotisationDao{
}
  • Line 6: The [ICotisationDaoRemote] interface inherits from the [ICotisationDao] interface to adopt all of its methods. It does not add any new ones.
  • line 5: the @Remote annotation makes it a remote interface for EJB, which will implement it.

The [DAO] layer is implemented by a EJB that implements both interfaces (this is not mandatory):

1
2
3
4
5
6
@Stateless()
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class CotisationDao implements ICotisationDaoLocal, ICotisationDaoRemote {

  @PersistenceContext
  private EntityManager em;
  • line 1: the @Stateless annotation, which makes the class a EJB
  • line 2: the @TransactionAttribute annotation, which ensures that every method in the class will execute within a transaction.
  • line 5: the @PersistenceContext annotation that injects the EntityManager from the JPA layer into the [CotisationDao] class. It is identical to what we had in the version Spring.

When the local interface of the [DAO] layer is used, the client of this interface runs in the same JVM.

Above, the [metier] and [DAO] layers exchange objects by reference. When one layer changes the shared object, the other layer sees this change.

When the remote interface of layer [DAO] is used, the client of this interface usually runs in another JVM.

In the example above, the [metier] and [DAO] layers exchange objects by value (serialization of the exchanged object). When one layer changes a shared object, the other layer sees this change only if the modified object is sent back to it.

6.1.5. Implementation of the [metier] layer by a EJB

  • The class implementing the [metier] layer also becomes a EJB implementing a local and remote interface. The initial [IMetier] interface was as follows:
package metier;

import java.util.List;
import jpa.Employe;

public interface IMetier {
   // get your payslip
  FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
   // list of employees
  List<Employe> findAllEmployes();
}

We create a local interface and a remote interface based on the previous interface:

1
2
3
4
5
6
7
package metier;

import javax.ejb.Local;

@Local
public interface IMetierLocal extends IMetier{
}
1
2
3
4
5
6
7
package metier;

import javax.ejb.Remote;

@Remote
public interface IMetierRemote extends IMetier{
}

The EJB class in the [metier] layer implements these two interfaces:

@Stateless()
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class Metier implements IMetierLocal, IMetierRemote {

   // reference to local [DAO] layers
  @EJB
  private ICotisationDaoLocal cotisationDao = null;
  @EJB
  private IEmployeDaoLocal employeDao = null;
  @EJB
  private IIndemniteDaoLocal indemniteDao = null;
  • Lines 1-2: define a EJB where each method runs in a transaction.
  • Line 7: A reference to the local interface of EJB [CotisationDao].
  • Line 6: The @EJB annotation instructs the EJB container to inject a reference to the local interface of EJB [CotisationDao].
  • Lines 8–11: We do the same thing for the local interfaces of EJB, [EmployeDao], and [IndemniteDao].

Ultimately, when EJB and [Metier] are instantiated, the fields on lines 7, 9, and 11 will be initialized with references to the local interfaces of the three EJB instances in the [DAO] layer. We therefore assume here that the [metier] and [DAO] layers will run within the same JVM.

6.1.6. The clients layers of the EJB

In the diagram above, to communicate with layer [metier], layer [ui] must obtain a reference to the remote interface of EJB from layer [metier].

In the diagram above, to communicate with layer [metier], layer [ui] must obtain a reference to the local interface of EJB from layer [metier]. The method for obtaining these references differs from one container to another. For the OpenEJB container, you can proceed as follows:

Reference on the local interface:

1
2
3
4
5
6
7
8
9
     // configure the embedded Open EJB container
    Properties properties = new Properties();
    properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
      // initialization of JNDI context with previous properties
    InitialContext initialContext = new InitialContext(properties);
     // instantiation layers DAO
    employeDao = (IEmployeDaoLocal) initialContext.lookup("EmployeDaoLocal");
    cotisationDao = (ICotisationDaoLocal) initialContext.lookup("CotisationDaoLocal");
indemniteDao = (IIndemniteDaoLocal) initialContext.lookup("IndemniteDaoLocal");
  • lines 2–5: the OpenEJB container is initialized.
  • line 5: we have a JNDI context (Java Naming and Directory Interface) that allows us to obtain references to the EJB objects. Each EJB is designated by a JNDI name:
  • (continued)
    • for the local interface, we add "Local" to the name of the EJB (lines 7–9)
    • for the remote interface, "Remote" is added to the name of the EJB

With Java EE 5, these rules change depending on the container EJB. This is a challenge. Java 6 introduced a notation that is portable across all application servers.

The previous code retrieves references to local EJB interfaces via their JNDI names. We mentioned earlier that these can also be obtained via the @EJB annotation. So you might want to write:

@EJB
private IemployeDaoLocal employeDaoLocal ;

The @EJB annotation is honored only if it belongs to a class loaded by the EJB container. This will be the case for the [Metier] class, for example. The code above, however, belongs to a console class that will not be loaded by the EJB container. We are therefore required to use the names JNDI and EJB.

Below is the code to obtain a reference to the remote interface of EJB and [Metier]:

1
2
3
4
5
6
7
8
     // configure the embedded Open EJB container
    Properties properties = new Properties();
    properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
     // context initialization JNDI of container EJB
    InitialContext initialContext = new InitialContext(properties);

     // remote business layer instantiation
metier = (IMetierRemote) initialContext.lookup("MetierRemote");

6.2. Practical Exercise

We propose to port the Netbeans Spring / Hibernate application to a OpenEJB / EclipseLink architecture.

The current implementation with Spring / Hibernate

The implementation to be built with OpenEJB / EclipseLink

If it does not exist, create the database MySQL [dbpam_eclipselink]. If it exists, delete all its tables. Create a connection Netbeans to this database as described in section 6.2.1.

6.2.2. Initial configuration of the Netbeans project

  • Load the Maven project [mv-pam-spring-hibernate]
  • Create a new Java Maven project [mv-pam-openejb-eclipselink] [1]
  • In the [Files] [2] tab, create a folder named [conf] [3] under the project root
  • Place the following [openejb.conf] [4] file in this folder:
1
2
3
4
5
6
7
8
<?xml version="1.0"?>
<openejb>
  <Resource id="Default JDBC Database">
    JdbcDriver com.mysql.jdbc.Driver
    JdbcUrl jdbc:mysql://localhost:3306/dbpam_eclipselink
    UserName root
  </Resource>
</openejb>
  • Create the folder [src / main/ resources/ META-INF] [5]
  • Place the following [persistence.xml] [6] file in it:

<?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="dbpam_eclipselinkPU" transaction-type="JTA">
    <!-- the supplier JPA is EclipseLink -->
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <!-- jpa entities -->
    <class>jpa.Cotisation</class>
    <class>jpa.Employe</class>
    <class>jpa.Indemnite</class>
    <!-- properties provider EclipseLink -->
    <properties>
      <property name="eclipselink.logging.level" value="FINE"/>
      <property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
    </properties>
  </persistence-unit>
</persistence>
  • line 12: fine-grained logs are requested for EclipseLink,
  • line 13: tables will be created when the JPA layer is instantiated,
  • add the libraries OpenEJB, EclipseLink, and the driver JDBC from MySQL to the [pom.xml] file in the project:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
 
  <groupId>istia.st</groupId>
  <artifactId>mv-pam-openejb-eclipselink</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>
 
  <name>mv-pam-openejb-eclipselink</name>
  <url>http://maven.apache.org</url>
 
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
 
  <dependencies>
    <dependency>
      <groupId>org.apache.openejb</groupId>
      <artifactId>openejb-core</artifactId>
      <version>4.0.0</version>
    </dependency>                
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.10</version>
      <scope>test</scope>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>org.eclipse.persistence</groupId>
      <artifactId>eclipselink</artifactId>
      <version>2.3.0</version>
    </dependency>
    <dependency>
      <groupId>org.eclipse.persistence</groupId>
      <artifactId>javax.persistence</artifactId>
      <version>2.0.3</version>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.6</version>
    </dependency>
    <dependency>
      <groupId>org.swinglabs</groupId>
      <artifactId>swing-layout</artifactId>
      <version>1.0.3</version>
    </dependency>
  </dependencies>
 
  <repositories>
    <repository>
      <url>http://download.eclipse.org/rt/eclipselink/maven.repo/</url>
      <id>eclipselink</id>
      <layout>default</layout>
      <name>Repository for library Library[eclipselink]</name>
    </repository>
  </repositories>
 
</project>
  • lines 18–22: the dependency OpenEJB,
  • lines 30–39: the EclipseLink dependencies,
  • lines 41-44: the JDBC driver dependency of MySQL

6.2.3. Porting the [DAO] layer

We will port the [DAO] layer by copying packages from the [mv-pam-spring-hibernate] project to the [mv-pam-openejb-eclipselink] project.

  • Copy the [dao, exception, jpa] packages
 

The errors reported above are due to the fact that the copied [DAO] layer uses Spring, and the Spring libraries are no longer part of the project.

6.2.3.1. EJB [CotisationDao]

We create the local and remote interfaces for the future EJB [CotisationDao]:

The local interface ICotisationDaoLocal:

1
2
3
4
5
6
7
package dao;

import javax.ejb.Local;

@Local
public interface ICotisationDaoLocal extends ICotisationDao{
}

To get the correct package imports, run [clic droit sur le code / Fix Imports].

The remote interface ICotisationDaoRemote:

1
2
3
4
5
6
7
package dao;

import javax.ejb.Remote;

@Remote
public interface ICotisationDaoRemote extends ICotisationDao{
}

Then we modify the [CotisationDao] class to make it a EJB:

...
import javax.persistence.PersistenceContext;
import jpa.Cotisation;

@Stateless
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class CotisationDao implements ICotisationDaoLocal, ICotisationDaoRemote {

  @PersistenceContext
  private EntityManager em;
...  

The import that this class made on the Spring framework is removed. Run [Clean and Build] on the project:

In [1], there are no more errors in the [CotisationDao] class.

6.2.3.2. The EJB, [EmployeDao], and [IndemniteDao]

We repeat the same process for the other elements of the [DAO] layer:

  • interfaces IEmployeDaoLocal and IEmployeDaoRemote derived from IEmployeDao
  • EJB and EmployeDao, which implement these two interfaces
  • interfaces IIndemniteDaoLocal, IIndemniteDaoRemote derived from IIndemniteDao
  • EJB IndemniteDao implementing these two interfaces

Once this is done, there are no more errors in the [2] project.

6.2.3.3. The [PamException] class

The [PamException] class remains the same as before, with one minor change:

package exception;

import javax.ejb.ApplicationException;

@ApplicationException(rollback=true)
public class PamException extends RuntimeException {

   // error code
  private int code;
...

Line 5 has been added. To get the correct imports, use [Fix imports].

To understand the annotation on line 5, remember that every method in EJB from our [DAO] layer:

  • runs within a transaction started and ended by the EJB container
  • throws a [PamException] exception as soon as something goes wrong

When the [metier] layer calls a method M of the [DAO] layer, this call is intercepted by the EJB container. It is as if there were an intermediate class between the [metier] layer and the [DAO] layer—here called [Proxy EJB]—intercepting all calls to the [DAO] layer. When the call to method M of layer [DAO] is intercepted, the EJB proxy starts a transaction and then hands control over to method M of layer [DAO], which then executes within that transaction. The M method completes with or without an exception.

  • If method M completes without an exception, execution returns to the EJB proxy, which terminates the transaction by committing it. Execution flow is then returned to the calling method of the [metier] layer
  • If method M terminates with an exception, execution returns to proxy EJB, which terminates the transaction by rolling it back. Additionally, it encapsulates this exception in a EJBException type. Execution flow is then returned to the calling method of the [metier] layer, which therefore receives a EJBException. The annotation on line 5 above prevents this encapsulation. The [metier] layer will therefore receive a PamException. Furthermore, the rollback=true attribute instructs the EJB proxy that when it receives a PamException, it must roll back the transaction.

6.2.3.4. Testing the [DAO] layer

Our [DAO] layer, implemented by EJB, can now be tested. We start by copying the [dao] package from [Test Packages] in the [mv-pam-springhibernate] project into the [1] project currently under construction:

We keep only the [JUnitInitDB] test, which initializes the database with some [2] data. We rename the [ JUnitInitDbLocal] class to [3]. The [JUnitInitDBLocal] class will use the local interface of EJB from the [DAO] layer.

First, we modify the [JUnitInitDBLocal] class as follows:

public class JUnitInitDBLocal {

  static private IEmployeDaoLocal employeDao = null;
  static private ICotisationDaoLocal cotisationDao = null;
  static private IIndemniteDaoLocal indemniteDao = null;

  @BeforeClass
  public static void init() throws Exception {
     // configure the embedded Open EJB container
    Properties properties = new Properties();
    properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
      // initialization of JNDI context with previous properties
    InitialContext initialContext = new InitialContext(properties);
     // instantiation of local DAO layers
    employeDao = (IEmployeDaoLocal) initialContext.lookup("EmployeDaoLocal");
    cotisationDao = (ICotisationDaoLocal) initialContext.lookup("CotisationDaoLocal");
    indemniteDao = (IIndemniteDaoLocal) initialContext.lookup("IndemniteDaoLocal");
}

...
  • lines 3-5: references to the local interfaces of EJB in the [DAO] layer
  • line 7: @BeforeClass annotates the method executed when the JUnit test starts
  • lines 10-13: initialization of the OpenEJB container. This initialization is proprietary and changes with each EJB container.
  • line 13: there is a JNDI context (Java Naming and Directory Interface) that allows access to EJB via names. With OpenEJB, the local interface of a EJB E is designated by ELocal and the remote interface by ERemote.
  • Lines 15–17: The JNDI context is queried for a reference to the local interfaces of EJB and [EmployeDao, CotisationDao, IndemniteDao].

Build the project, start the MySQL server if necessary, and run the JUnitInitDBLocal test. Note that the [persistence.xml] file has been configured to recreate the tables on each run. Before running the test, it is best to delete any tables from the MySQL and [dbpam_eclipselink] databases.

  • In [1], on the [Services] tab, delete the tables from the Netbeans connection established in section 6.2.1.
  • In [2], the database [dbpam_eclipselink] no longer has any tables
  • In [3], the project is built
  • In [4], the test JUnitInitDBLocal is executed
  • in [5], the test was successful
  • in [6], the connection is refreshed Netbeans
  • in [7], we see the 4 tables created by the layer JPA. The purpose of the test was to populate them. We view the contents of one of them
  • in [8], the contents of the table [EMPLOYES]

The OpenEJB container displayed logs in the console:

Infos - PersistenceUnit(name=dbpam_eclipselinkPU, provider=org.eclipse.persistence.jpa.PersistenceProvider) - provider time 396ms
Infos - Jndi(name=CotisationDaoLocal) --> Ejb(deployment-id=CotisationDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/CotisationDao!dao.ICotisationDaoLocal) --> Ejb(deployment-id=CotisationDao)
Infos - Jndi(name=CotisationDaoRemote) --> Ejb(deployment-id=CotisationDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/CotisationDao!dao.ICotisationDaoRemote) --> Ejb(deployment-id=CotisationDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/CotisationDao) --> Ejb(deployment-id=CotisationDao)
Infos - Jndi(name=EmployeDaoLocal) --> Ejb(deployment-id=EmployeDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/EmployeDao!dao.IEmployeDaoLocal) --> Ejb(deployment-id=EmployeDao)
Infos - Jndi(name=EmployeDaoRemote) --> Ejb(deployment-id=EmployeDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/EmployeDao!dao.IEmployeDaoRemote) --> Ejb(deployment-id=EmployeDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/EmployeDao) --> Ejb(deployment-id=EmployeDao)
Infos - Jndi(name=IndemniteDaoLocal) --> Ejb(deployment-id=IndemniteDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/IndemniteDao!dao.IIndemniteDaoLocal) --> Ejb(deployment-id=IndemniteDao)
Infos - Jndi(name=IndemniteDaoRemote) --> Ejb(deployment-id=IndemniteDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/IndemniteDao!dao.IIndemniteDaoRemote) --> Ejb(deployment-id=IndemniteDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/IndemniteDao) --> Ejb(deployment-id=IndemniteDao)
Infos - existing thread singleton service in SystemInstance() org.apache.openejb.cdi.ThreadSingletonServiceImpl@624a240d
Infos - OpenWebBeans Container is starting...
Infos - Adding OpenWebBeansPlugin : [CdiPlugin]
Infos - All injection points were validated successfully.
Infos - OpenWebBeans Container has started, it took [70] ms.
Infos - Created Ejb(deployment-id=IndemniteDao, ejb-name=IndemniteDao, container=Default Stateless Container)
Infos - Created Ejb(deployment-id=EmployeDao, ejb-name=EmployeDao, container=Default Stateless Container)
Infos - Created Ejb(deployment-id=CotisationDao, ejb-name=CotisationDao, container=Default Stateless Container)
Infos - Started Ejb(deployment-id=IndemniteDao, ejb-name=IndemniteDao, container=Default Stateless Container)
Infos - Started Ejb(deployment-id=EmployeDao, ejb-name=EmployeDao, container=Default Stateless Container)
Infos - Started Ejb(deployment-id=CotisationDao, ejb-name=CotisationDao, container=Default Stateless Container)
Infos - Deployed Application(path=D:\data\istia-1112\netbeans\glassfish\mv-pam\tmp\mv-pam-openejb-eclipselink\classpath.ear)
  • lines 2-3: the two names JNDI from EJB and [CotisationDaoLocal],
  • lines 4-5: the two names JNDI from EJB and [CotisationDaoRemote],
  • lines 7–8: the two names JNDI and EJB [EmployeDaoLocal],
  • lines 9-10: the two names JNDI from EJB and [EmployeDaoRemote],
  • lines 12-13: the two names JNDI from EJB and [IndemniteDaoLocal],
  • lines 14-15: the two names JNDI from EJB and [EmployeDaoRemote].

We repeat the same test, this time using the remote interface of EJB.

In [1], the [JUnitInitDBLocal] class has been duplicated (copy/paste) into [JUnitInitDBRemote]. In this class, we replace the local interfaces with the remote interfaces:

public class JUnitInitDBRemote {

  static private IEmployeDaoRemote employeDao = null;
  static private ICotisationDaoRemote cotisationDao = null;
  static private IIndemniteDaoRemote indemniteDao = null;

  @BeforeClass
  public static void init() throws Exception {
     // configure the embedded Open EJB container
    Properties properties = new Properties();
    properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
      // initialization of JNDI context with previous properties
    InitialContext initialContext = new InitialContext(properties);
     // instantiation of remote DAO layers
    employeDao = (IEmployeDaoRemote) initialContext.lookup("EmployeDaoRemote");
    cotisationDao = (ICotisationDaoRemote) initialContext.lookup("CotisationDaoRemote");
    indemniteDao = (IIndemniteDaoRemote) initialContext.lookup("IndemniteDaoRemote");
}

Once this is done, the new test class can be executed. Before doing so, using the connection Netbeans [dbpam_eclipselink], delete the tables from the database [dbpam_eclipselink].

 

With the Netbeans and [dbpam_eclipselink] connections, verify that the database has been populated.

6.2.4. Porting the [metier] layer

We will migrate the [metier] layer by copying packages from the [mv-pam-spring-hibernate] project to the [mv-pam-openejb-eclipselink] project.

The errors reported above for [1] are due to the fact that the copied layer [metier] uses Spring, and the Spring libraries are no longer part of the project.

6.2.4.1. EJB [Metier]

We follow the same procedure as described for EJB and [CotisationDao]. First, we create the local and remote interfaces for the future EJB [Metier] in [2]. Both are derived from the initial interface [IMetier].

1
2
3
4
5
6
7
8
package metier;

import javax.ejb.Local;

@Local
public interface IMetierLocal extends IMetier{

}
1
2
3
4
5
6
7
8
package metier;

import javax.ejb.Remote;

@Remote
public interface IMetierRemote extends IMetier{

}

Once this is done, in [3] we modify the [Metier] class so that it becomes a EJB:

@Stateless
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class Metier implements IMetierLocal, IMetierRemote {

   // references to local [DAO] layer
  @EJB
  private ICotisationDaoLocal cotisationDao = null;
  @EJB
  private IEmployeDaoLocal employeDao = null;
  @EJB
  private IIndemniteDaoLocal indemniteDao = null;

   // get your payslip
  public FeuilleSalaire calculerFeuilleSalaire(String SS,
          double nbHeuresTravaillées, int nbJoursTravaillés) {
     // retrieve employee information
...
  • line 1: the @Stateless annotation makes the class a EJB
  • line 2: each method of the class will execute within a transaction
  • line 3: the EJB [Metier] implements the two interfaces (local and remote) that we just defined
  • line 7: EJB [Metier] will use EJB [CotisationDao] via its local interface. This means that the [metier] and [DAO] layers must run within the same JVM.
  • Line 6: The @EJB annotation causes the EJB container to inject the reference itself into the local interface of EJB [CotisationDao]. The other approach we encountered is to use a JNDI context.
  • Lines 8–11: The same mechanism is used for the other two EJB instances in the [DAO] layer.

6.2.4.2. Testing the [metier] layer

Our [metier] layer, implemented by a EJB, can be tested. We start by copying the [metier] package from [Test Packages] in the [mv-pam-spring-hibernate] project into the [1] project currently under construction:

  • In [1], the result of the copy
  • to [2], the first test is deleted
  • in [3], the remaining test is renamed to [JUnitMetierLocal]

The class [JUnitMetierLocal] becomes the following:

public class JUnitMetierLocal {

// local business layer
  static private IMetierLocal metier;

  @BeforeClass
  public static void init() throws NamingException {
     // configure the embedded Open EJB container
    Properties properties = new Properties();
    properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
     // initialization of JNDI context with previous properties
    InitialContext initialContext = new InitialContext(properties);

     // instantiation of local DAO layers
    IEmployeDaoLocal employeDao = (IEmployeDaoLocal) initialContext.lookup("EmployeDaoLocal");
    ICotisationDaoLocal cotisationDao = (ICotisationDaoLocal) initialContext.lookup("CotisationDaoLocal");
    IIndemniteDaoLocal indemniteDao = (IIndemniteDaoLocal) initialContext.lookup("IndemniteDaoLocal");
     // local business layer instantiation
    metier = (IMetierLocal) initialContext.lookup("MetierLocal");

     // empty the base
...
}
  • line 4: a reference to the local interface of EJB [Metier]
  • lines 8-12: configuration of the OpenEJB container identical to that performed in the [DAO] layer test
  • lines 15–19: the JNDI context from line 12 is requested, for references to the 3 EJBs in the [DAO] layer and to the EJB in the [metier] layer. The EJB in the [DAO] layer will be used to initialize the database, and the EJB in the [metier] layer to perform payroll calculation tests.

Running the [JUnitMetierLocal] test yields the following result in [1]:

In [2], we duplicate [JUnitMetierLocal] into [JUnitMetierRemote] to test the remote interface of EJB [Metier] this time. The code for [JUnitMetierRemote] is modified to use this remote interface. The rest remains unchanged.

public class JUnitMetierRemote {

   // remote business layer
  static private IMetierRemote metier;

  @BeforeClass
  public static void init() throws NamingException {
     // configure the embedded Open EJB container
    Properties properties = new Properties();
    properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
     // initialization of JNDI context with previous properties
    InitialContext initialContext = new InitialContext(properties);

     // instantiation of remote DAO layers
    IEmployeDaoRemote employeDao = (IEmployeDaoRemote) initialContext.lookup("EmployeDaoRemote");
    ICotisationDaoRemote cotisationDao = (ICotisationDaoRemote) initialContext.lookup("CotisationDaoRemote");
    IIndemniteDaoRemote indemniteDao = (IIndemniteDaoRemote) initialContext.lookup("IndemniteDaoRemote");
     // remote business layer instantiation
    metier = (IMetierRemote) initialContext.lookup("MetierRemote");

     // empty the base
    for(Employe employe:employeDao.findAll()){
      employeDao.destroy(employe);
    }
    for(Cotisation cotisation:cotisationDao.findAll()){
      cotisationDao.destroy(cotisation);
    }
    for(Indemnite indemnite : indemniteDao.findAll()){
      indemniteDao.destroy(indemnite);
    }
     // fill it
    Indemnite indemnite1=new Indemnite(1,1.93,2,3,12);
    Indemnite indemnite2=new Indemnite(2,2.1,2.1,3.1,15);
    indemnite1=indemniteDao.create(indemnite1);
    indemnite2=indemniteDao.create(indemnite2);
    employeDao.create(new Employe("254104940426058","Jouveinal","Marie","5 rue des oiseaux","St Corentin","49203",indemnite2));
    employeDao.create(new Employe("260124402111742","Laverti","Justine","La brûlerie","St Marcel","49014",indemnite1));
    cotisationDao.create(new Cotisation(3.49,6.15,9.39,7.88));
  }
}
  • lines 4 and 19: we use the remote interface of EJB [Metier].
  • lines 15-17: we use the remote interfaces of the [DAO] layer
  • lines 34–35: Because with remote interfaces, objects exchanged between the client and the server are passed by value, we must retrieve the result returned by the create(Indemnite i) method. This was not necessary with local interfaces, where objects are passed by reference.

Once this is done, the project can be built and the [JUnitMetierRemote] test executed:

  

6.2.5. Porting the [console] layer

We will port the [console] layer by copying packages from the [mv-pam-spring-hibernate] project to the [mv-pam-openejb-eclipselink] project.

The errors reported above for [1] are due to the fact that the copied [metier] layer uses Spring, and the Spring libraries are no longer part of the project. In [2], the class [Main] is renamed to [MainLocal]. It will use the local interface of EJB [Metier].

The code for the [MainLocal] class changes as follows:

  public static void main(String[] args) {
     // local data
    final String syntaxe = "pg num_securite_sociale nb_heures_travaillées nb_jours_travaillés";
...
     // mistakes?
    if (erreurs.size() != 0) {
      for (int i = 0; i < erreurs.size(); i++) {
        System.err.println(erreurs.get(i));
      }
      return;
    }
     // it's OK - we can ask for the payslip at the [trade] layer
    IMetierLocal metier = null;
    FeuilleSalaire feuilleSalaire = null;
    try {
       // configure the embedded Open EJB container
      Properties properties = new Properties();
      properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
       // initialization of JNDI context with previous properties
      InitialContext initialContext = new InitialContext(properties);
       // local business layer instantiation
      metier = (IMetierLocal) initialContext.lookup("MetierLocal");
       // wage sheet calculation
      feuilleSalaire = metier.calculerFeuilleSalaire(args[0], nbHeuresTravaillées, nbJoursTravaillés);
    } catch (PamException ex) {
      System.err.println("L'erreur suivante s'est produite : " + ex.getMessage());
      return;
    } catch (Exception ex) {
      System.err.println("L'erreur suivante s'est produite : " + ex.toString());
      return;
    }
     // detailed display
    String output = "Valeurs saisies :\n";
    output += ajouteInfo("N° de sécurité sociale de l'employé", args[0]);
....

The changes are located in lines 13–25. This is how to obtain a reference to the [metier] layer, which has changed (lines 17–22). We will not explain the new code, as it has already been covered in previous examples. Once these changes are made, the project no longer has any errors (see [3]).

We configure the project to run with arguments [1]:

For the console application to run normally, there must be data in the database. To do this, you must modify the file [META-INF/persistence.xml]:


<?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="dbpam_eclipselinkPU" transaction-type="JTA">
    <!-- the supplier JPA is EclipseLink -->
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <!-- jpa entities -->
    <class>jpa.Cotisation</class>
    <class>jpa.Employe</class>
    <class>jpa.Indemnite</class>
    <!-- properties provider EclipseLink -->
    <properties>
      <property name="eclipselink.logging.level" value="FINE"/>
      <!--
      <property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
      -->
    </properties>
  </persistence-unit>
</persistence>

Line 14, which caused the database tables to be recreated on every run, is commented out. The project must be rebuilt (Clean and Build) for this change to take effect. Once this is done, the program can be run. If all goes well, the console output will look something like the following:

.......
INFO - Created EJB(deployment-id=IndemniteDao, ejb-name=IndemniteDao, container=Default Stateless Container)
INFO - Deployed Application(path=classpath.ear)
[EL Info]: 2009-09-30 15:09:21.109--ServerSession(16658781)--EclipseLink, version: Eclipse Persistence Services - 1.1.2.v20090612-r4475
[EL Info]: 2009-09-30 15:09:21.937--ServerSession(16658781)--file:/C:/temp/09-09-28/pam-console-metier-dao-openejb-eclipselink-0910/build/classes/-jpa login successful
Valeurs saisies :
N° de sécurité sociale de l'employee: 254104940426058
Nombre d'hours worked: 150
Nombre de jours travaillés : 20

Informations Employé : 
Nom : Jouveinal
Prénom : Marie
Adresse : 5 rue des oiseaux
Ville : St Corentin
Code Postal : 49203
Indice : 2

Informations Cotisations : 
CSGRDS : 3.49 %
CSGD : 6.15 %
Retraite : 7.88 %
Sécurité sociale : 9.39 %

Informations Indemnités : 
Salaire horaire : 2.1 euro
Entretien/jour : 2.1 euro
Repas/jour : 3.1 euro
Congés Payés : 15.0 %

Informations Salaire : 
Salaire de base : 362.25 euro
Cotisations sociales : 97.48 euro
Indemnités d'maintenance: 42.0 euro
Indemnités de repas : 62.0 euro
Salaire net : 368.77 euro

BUILD SUCCESSFUL (total time: 4 seconds)

Here we used the local interface of layer [metier]. We are now using its remote interface in a second console class:

In [1], the [MainLocal] class has been duplicated into [MainRemote]. The code in [MainRemote] is modified to use the remote interface of layer [metier]:

// it's okay - you can ask for the payslip in the [metier] layer
    IMetierRemote metier = null;
    FeuilleSalaire feuilleSalaire = null;
    try {
       // configure the embedded Open EJB container
...
       // remote business layer instantiation
      metier = (IMetierRemote) initialContext.lookup("MetierRemote");
       // wage sheet calculation
      feuilleSalaire = metier.calculerFeuilleSalaire(args[0], nbHeuresTravaillées, nbJoursTravaillés);
    } catch (PamException ex) {
...
    } catch (Exception ex) {
...
    }

Changes have been made to lines 2 and 8. The project is configured as [2] to execute the class [MainRemote]. Running it produces the same results as before.

6.3. Conclusion

We have demonstrated how to port a Spring/Hibernate architecture to a OpenEJB/EclipseLink architecture.

The Spring/Hibernate architecture

The OpenEJB / EclipseLink architecture

The porting was completed without too much difficulty because the initial application had been structured in layers. This point is important to understand.