Friday, November 19, 2010

Business Delegate

If user has exposed services as Session façades at business layer and a developer has to call this Session Facade bean at presentation layer say in Application Controller or Servlet then presentation layer developer has to write a lot of repetitive code to access the Session Bean which increase coupling with business layer and duplicate code. In some cases developer at presentation layer doesn’t know how to call or deal with session beans. So to reduce the complexity of handling remote services and coupling developer at presentation layer can use business delegate. A business delegate is like a proxy or shadow POJO object which delegates to remote service, here a Session Façade.
Here we are going to take an example of Session façade bean which has been exposed as service on business layer and will write a Business Delegate POJO for this Session façade bean. So when a developer wants to call this Session Façade he has to just use the Business Delegate object.
Business delegate object can have some access logic like try more than one time to get the services if not available or cache some data which don’t change frequently.
Business delegate should be developed by business layer developers because they know all the functionality of business layer which can help during caching mechanism in business delegate if required.
Generally Business delegate use Service Locator to locate the services and shadow one POJO per Session Façade bean and single method for every exposed method by Session bean.
Below example is a Business Delegate object for Session façade in article http://tiwarij2eeblog.blogspot.com/2010/11/transaction-design-with-session-bean.html
And business delegate also uses the service locator at
We can implement Business Delegate as Singleton
package org.paandav.businessdelegate;

import java.util.List;

import org.paandav.relation.OpAddress;
import org.paandav.relation.OpPerson;
import org.paandav.servicelocator.ServiceLocator;
import org.paandav.session.SessionFacade;

public class SessionFacadeBusinessDelegate {
private static final String SESSIONFACADE_JNDI_KEY_NAME = "ORG.PAANDAV.SESSION.SESSIONFACADE";
private static ServiceLocator sl = null;
private static SessionFacade fac = null;
// This list changes once in month in DB or fixed at businessLayer and
// changes rarely.
private static List<String> dropList = null;

public SessionFacadeBusinessDelegate() throws Exception {
if (sl == null) {
sl = ServiceLocator.getInstance();
fac = (SessionFacade) sl.getObject(SESSIONFACADE_JNDI_KEY_NAME);
if (fac == null) {
throw new Exception("Could not initialize");
}
dropList = fac.getDropList();
}
}

public String sessionFacadeUseCaseOne(OpPerson person, OpAddress[] add)
throws Exception {

return fac.sessionFacadeUseCaseOne(person, add);
}

// No need to call again and again method on session facade bean.
public List<String> getDropList() throws Exception {
return dropList;
}
}

Wednesday, November 10, 2010

Service Locator

Configurable Service Locator: Service locator is used to hide the complexity of look up an object from centralized location.

Below is a configurable Service Locator implementation which can be used to locate EJB(3.0), JMS, DATATSOURCE. This Service locator totally depends on a properties file which will have all information about environment and jndi names. If you want to integrate a new jndi name to this locator you will have to just add a key value name pair to properties file and no changes in java code.

If you have created a JDBC JNDI name jdbc/JdbcOraclePool then you have to put it as JDBC_JNDI_ORACLE=jdbc/JdbcOraclePool in properties file. 

There are two KEY which can be changed for different application server vendor and should be defined in properties file and they are CONTEXT_PROVIDER_URL and CONTEXT_INITIAL_CONTEXT_FACTORY.

Below an example for Glassfish server

CONTEXT_PROVIDER_URL=iiop://127.0.0.1:3700
CONTEXT_INITIAL_CONTEXT_FACTORY =com.sun.appserv.naming.S1ASCtxFactory
JDBC_JNDI_NAME=jdbc/__CallFlowPool

Jav Code for PropertyReader can be found at
http://tiwarijavaj2eeblog.blogspot.com/2010/11/property-file-reader.html

This locator does not cast object after lookup so client should cast to appropriate type after getting JNDI object from Service Locator.


package org.paandav.servicelocator;
import java.util.Hashtable;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.naming.Context;
import javax.naming.InitialContext;
import org.paandav.propreader.PropertyReader;
public class ServiceLocator
{
 private static final Lock     lock        = new ReentrantLock();
 private static ServiceLocator sloc        = null;
 private Map<String, String>   propertyMap = null;
 private Context               ctx         = null;
 private Map<String, Object>   cache       = null;
 private ServiceLocator()throws ServiceLocatorException
 {
  try
  {
   propertyMap = PropertyReader.getMapForPropertiesFileKey(ServiceLocatorKeys.SERVICE_LOCATOR_FILE_KEY);
   Hashtable<String, String> ctxProp = new Hashtable<String, String>(2);
   ctxProp.put(Context.PROVIDER_URL,
               propertyMap.get(ServiceLocatorKeys.CONTEXT_PROVIDER_URL).trim());
   ctxProp.put(Context.INITIAL_CONTEXT_FACTORY,
               propertyMap.get(ServiceLocatorKeys.CONTEXT_INITIAL_CONTEXT_FACTORY).trim());
   ctx   = new InitialContext(ctxProp);
   cache = new ConcurrentHashMap<String, Object>();
  }
  catch (Exception ex)
  {
   ex.printStackTrace();
   throw new ServiceLocatorException(ex.getMessage());
  }
 }
 public static ServiceLocator getInstance()
   throws ServiceLocatorException
 {
  if (sloc == null)
  {
   try
   {
    lock.lock();
    if (sloc == null)
    {
     sloc = new ServiceLocator();
    }
   }
   finally
   {
    lock.unlock();
   }
  }
  return sloc;
 }
 public Object getObject(String jndiKeyName)
   throws ServiceLocatorException
 {
  if ((jndiKeyName == null) || "".equals(jndiKeyName))
  {
   throw new IllegalArgumentException("Invalid key name");
  }
  Object ds                 = null;
  String dataSourceJNDIName = propertyMap.get(jndiKeyName);
  if ((dataSourceJNDIName == null) || "".equals(dataSourceJNDIName))
  {
   throw new IllegalArgumentException("JNDI name not found in properties file");
  }
  if (!cache.containsKey(dataSourceJNDIName))
  {
   try
   {
    lock.lock();
    if (!cache.containsKey(dataSourceJNDIName))
    {
     ds = ctx.lookup(dataSourceJNDIName);
     if (ds != null)
     {
      cache.put(dataSourceJNDIName, ds);
     }
    }
   }
   catch (Exception ex)
   {
    throw new ServiceLocatorException(ex.getMessage());
   }
   finally
   {
    lock.unlock();
   }
  }
  else
  {
   ds = cache.get(dataSourceJNDIName);
  }
  return ds;
 }
}

Monday, November 8, 2010

Transaction Design with Session Bean (EJB 3.0) and JPA

Transaction Design with Session Bean (EJB 3.0) and JPA: This tutorial discuss about following points
1. Integration of Session Bean (at Business Layer) and JPA(at Integration layer) using annotation.
2. Use of container management transaction in Session Beans(EJB 3.0) using annotation.
3. How to design a robust transaction strategy in J2EE business layer and Integration layer using CMT(Container Mangement Transaction).


To discuss/design a transaction management we will take the following scenarios.
During application design generally transaction starts at business layer and we use Session Beans at business layer to write business logic and access DB at Integration layer. At Integration layer we can use JBDC, Entity Bean, and JPA etc to access the database.

Generally transaction boundary starts when we start processing logic at business layer and should end (commit or rollback) when we returns from business layer. So if anything happens wrong at business layer or integration layer transaction should be rolled back otherwise commit.

To complete a single use case request there may be more than one session beans are required. So to reduce the network traffic we use Session Facade. To process a single use case request if we have to call two Session Beans from client then in this case we use a Session Facade which will call these two session beans and also handle all transaction related issue. So the client which is either JSP/Servlet or other java class need to process a request it calls the Session facade bean which in turns start transaction do all processing with the help of other helper session beans and if everything is fine it commit the transaction or rollback the transaction and throws the exception to caller to indicate the failure of transaction.

In below figure there is one Session Facade bean which in turn calls HelperSessionBean1 and then calls HelperSessionBean2 and these helper session beans use JPA to access DB.


Start Design Transaction Strategy: We will use Session Beans (EJB 3.0) at business layer and JPA to access DB. Each method of Session Facade bean will be used to complete process of one use case request which in turn will call Session Bean Helper One and Two to complete the request. Here Session Facade Bean will also be responsible to handle transaction related processing. So if there is something happens wrong anywhere during processing the request it will rollback the request or commit the request processing.
In this transaction design we will use Container Management Transaction Strategy that means all transaction related processing like start transaction, commit transaction or rollback transaction will be handled by container(here Glassfish) and we will define only the transaction demarcation and transaction attributes in Session Beans.

In the below example annotations have been used to define all properties in Session Beans.

In the below example we are going to choose TransactionAttributeType.REQUIRES_NEW for each method call in Session facade bean that means every call to any method in Session Facade bean from client will need a new transaction. As per EJB specification when this transaction attribute is used then every method will run in the new transaction context. For Helper Session Beans we are going to use TransactionAttributeType.MANDATORY because the transaction will always be started by Session facade beans so helper session beans should support transaction started by Session facade bean and helper beans cannot start the transaction so MANDATORY attribute is used which means helper beans will support already started transaction and if they are being called without any transaction context will throw javax.ejb.EJBTransactionRequiredException.

So if one use case is being handled by on method of Session Façade and façade is calling other unlimited helper beans and if anything happens wrong during this request processing in nay of helper beans or façade bean transaction must be rolled back and if everything is going fine transaction must be committed. 

In the below code example we will use the following.
1. Glassfish Server version 2.1 which supports EJB3.0.
2. Oracle 11g Database.
3. Eclipse
4. Top Link JPA

Following configuration is required to run the below code.
1. Create a JBDC resource in GlassFish Server
First create a Connection Pool using Glasfish Admin Console, Resources--> JBDC--> Connection Pools
Now create a JNDI name using Resources--> JDBC--> JDBC Resources. Oracle JBDC driver should be available in Glassfish library to complete this process.

2. Create two Database tables as below
create table OP_PERSON (personid INTEGER primary key,firstname varchar2(30), lastname varchar2(30), age integer);
create table OP_ADDRESS(ADDRESSID integer primary key, ADDRESSLINE1 varchr2(30),ADDRESSLINE2 varchar2(30), CITY varchar2(10), ADDRESSTYPE varchar2(5), COUNTY varchar2(20), COUNTRY varchar2(20), STREET varchar2(20), ZIPCODE integer, PERSONID integer foreign key references op_person persionid);

3. Persistent.xml file configuration

<?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="myprojectmanager"
                   transaction-type="JTA" >
                   <provider>oracle.toplink.essentials.PersistenceProvider</provider>
                   <jta-data-source>jdbc/oraclepool</jta-data-source>              
                    <class>org.paandav.relation.OpAddress</class>
                   <class>org.paandav.relation.OpPerson</class>
                   <properties>
                             <property name="toplink.jdbc.native-sql" value="false" />
                             <property name="eclipselink.ddl-generation" value="create-tables"/>
                             <property name="eclipselink.ddl-generation.output-mode" value="both"/>
                   </properties>
          </persistence-unit>
</persistence>

In this xml we are using transaction type as JTA when we want to inject an EntityManager in Session Beans and container management transaction we must use transaction type as JTA not RESOURCE_LOCAL. jta-data-source tag value must be match to JDBC Resources JNDI name. Here we cannot use local connection properties when we are using container management transaction.
4.            I have used the following structure in Eclipse for this example
4.                  Java code for all classes
package org.paandav.relation;

import java.io.Serializable;
import java.math.BigDecimal;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "OP_ADDRESS")
public class OpAddress implements Serializable {
          @Id
          private long addressid;

          private String addresstype;

          private String street;

          private String addressline1;

          private String addressline2;

          private String city;

          private String county;

          private String country;

          private BigDecimal zipcode;

          private long personid;

          private static final long serialVersionUID = 1L;

          public OpAddress() {
                   super();
          }

          public long getAddressid() {
                   return this.addressid;
          }

          public void setAddressid(long addressid) {
                   this.addressid = addressid;
          }

          public String getAddresstype() {
                   return this.addresstype;
          }

          public void setAddresstype(String addresstype) {
                   this.addresstype = addresstype;
          }

          public String getStreet() {
                   return this.street;
          }

          public void setStreet(String street) {
                   this.street = street;
          }

          public String getAddressline1() {
                   return this.addressline1;
          }

          public void setAddressline1(String addressline1) {
                   this.addressline1 = addressline1;
          }

          public String getAddressline2() {
                   return this.addressline2;
          }

          public void setAddressline2(String addressline2) {
                   this.addressline2 = addressline2;
          }

          public String getCity() {
                   return this.city;
          }

          public void setCity(String city) {
                   this.city = city;
          }

          public String getCounty() {
                   return this.county;
          }

          public void setCounty(String county) {
                   this.county = county;
          }

          public String getCountry() {
                   return this.country;
          }

          public void setCountry(String country) {
                   this.country = country;
          }

          public BigDecimal getZipcode() {
                   return this.zipcode;
          }

          public void setZipcode(BigDecimal zipcode) {
                   this.zipcode = zipcode;
          }

          public long getPersonid() {
                   return this.personid;
          }

          public void setPersonid(long personid) {
                   this.personid = personid;
          }

}

package org.paandav.relation;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Set;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "OP_PERSON")
public class OpPerson implements Serializable {
          @Id
          private long personid;

          private String firstname;

          private String lastname;

          private BigDecimal age;

          private static final long serialVersionUID = 1L;

          public OpPerson() {
                   super();
          }

          public long getPersonid() {
                   return this.personid;
          }

          public void setPersonid(long personid) {
                   this.personid = personid;
          }

          public String getFirstname() {
                   return this.firstname;
          }

          public void setFirstname(String firstname) {
                   this.firstname = firstname;
          }

          public String getLastname() {
                   return this.lastname;
          }

          public void setLastname(String lastname) {
                   this.lastname = lastname;
          }

          public BigDecimal getAge() {
                   return this.age;
          }

          public void setAge(BigDecimal age) {
                   this.age = age;
          }
package org.paandav.session;

public class BusinessLayerException extends Exception {

          private static final long serialVersionUID = 1L;

          public BusinessLayerException() {
                   super();
          }

          public BusinessLayerException(String msg) {
                   super(msg);
          }
}

package org.paandav.session;

import org.paandav.relation.OpAddress;
import org.paandav.relation.OpPerson;
import java.util.List;
public interface SessionFacade {
          public String sessionFacadeUseCaseOne(OpPerson person, OpAddress[] add)
                             throws BusinessLayerException;
         public List<String> getDropList() throws BusinessLayerException;

}

package org.paandav.session;

import java.util.ArrayList;
import java.util.List;
import javax.ejb.EJB;
import javax.ejb.Remote;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.ejb.TransactionManagement;
import javax.ejb.TransactionManagementType;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.paandav.relation.OpAddress;
import org.paandav.relation.OpPerson;

/**
 * Session Bean implementation class SessionFacade
 */
@Stateless
@Remote(SessionFacade.class)
@TransactionManagement(TransactionManagementType.CONTAINER)
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public class SessionFacadeBean implements SessionFacade {
          @EJB
          private SessionHelperOne hone;
          @EJB
          private SessionHelperTwo htwo;
          @PersistenceContext(unitName = "myprojectmanager")
          private EntityManager em;

          @Override
          public String sessionFacadeUseCaseOne(OpPerson person, OpAddress[] add)
                             throws BusinessLayerException {
                   System.out.println("Entity Manager In Session Facade=" + em);
                   String resFronHelperOne = hone.sessionHelperOne(person);
                   String resFronHelperTwo = htwo.sessionHelperTwo(add);
                   return "Hello " + resFronHelperOne + resFronHelperTwo;
          }

         @Override
public List<String> getDropList() throws BusinessLayerException {
List<String> dropList = new ArrayList<String>(2);
dropList.add("One");
dropList.add("Two");
return dropList;
}


}

package org.paandav.session;

import org.paandav.relation.OpPerson;

public interface SessionHelperOne {
          public String sessionHelperOne(OpPerson person) throws BusinessLayerException;

}

package org.paandav.session;

import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.ejb.TransactionManagement;
import javax.ejb.TransactionManagementType;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.paandav.relation.OpPerson;

/**
 * Session Bean implementation class SessionHelperOneBean
 */
@Stateless
@TransactionManagement(TransactionManagementType.CONTAINER)
@TransactionAttribute(TransactionAttributeType.MANDATORY)
public class SessionHelperOneBean implements SessionHelperOne {

          @PersistenceContext(unitName = "myprojectmanager")
          private EntityManager em;

          @Override
          public String sessionHelperOne(OpPerson person)
                             throws BusinessLayerException {
                   em.persist(person);
                   System.out.println("Person Inserted Successfulyy");
                   return "Person Inserted Successfulyy";
          }

}

package org.paandav.session;

import org.paandav.relation.OpAddress;

public interface SessionHelperTwo {
          public String sessionHelperTwo(OpAddress[] add)
                             throws BusinessLayerException;

}

package org.paandav.session;

import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.ejb.TransactionManagement;
import javax.ejb.TransactionManagementType;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.paandav.relation.OpAddress;

/**
 * Session Bean implementation class SessionHelperOneBean
 */
@Stateless
@TransactionManagement(TransactionManagementType.CONTAINER)
@TransactionAttribute(TransactionAttributeType.MANDATORY)
public class SessionHelperTwoBean implements SessionHelperTwo {

          @PersistenceContext(unitName = "myprojectmanager")
          private EntityManager em;

          @Override
          public String sessionHelperTwo(OpAddress[] add)
                             throws BusinessLayerException {
                   if (add != null && add.length > 0) {
                             for (OpAddress ad : add) {
                                      em.persist(ad);
                             }
                   }
                   return "Addresses inserted successfully";
          }

}

import javax.naming.Context;
import javax.naming.InitialContext;

import org.paandav.relation.OpAddress;
import org.paandav.relation.OpPerson;
import org.paandav.session.SessionFacade;

public class SessionFacadeTest {

          /**
           * @param args
           */
          public static void main(String[] args) throws Exception {
                   Context ctx = new InitialContext();
                   SessionFacade fac = (SessionFacade) ctx
                                      .lookup("org.paandav.session.SessionFacade");
                   OpPerson p = new OpPerson();
                   p.setPersonid(System.nanoTime());
                   p.setFirstname("Pradeep");
                   p.setLastname("Tiwari");

                   OpAddress ad = new OpAddress();
                   ad.setAddressid(System.nanoTime());
                   ad.setAddressline1("Line1");

                   OpAddress ad2 = new OpAddress();
                   ad2.setAddressid(System.nanoTime());
                   ad2.setAddressline1("Line1");
                   ad2.setAddresstype("PPP");
                   OpAddress[] as = new OpAddress[2];
                   as[0] = ad;
                   as[1] = ad2;

                   fac.sessionFacadeUseCaseOne(p, as);
                   System.out.println();
          }
}


Running the Example:
Run the SessionFacadeTest and will get two entries in OP_ADDRESS table and one entry in OP_ADDRESS table. That means everything was fine during the flow and transaction has been committed.

Now Change ad2.setAddresstype("PPP"); to ad2.setAddresstype("PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP"); and run the test program you will get an exception and no entries either in OP_ADDRESS or OP_PERSON table.

IF we look at the flow, exception is occurred when JPA tries to insert values in OP_ADDRESS table that means helperSessionBean1 has completed its work to insert data into OP_PERSON table but helperSessionBean2 could not insert data because of more than specified value in personType field and when heleprbean2 throws an exception container automatically rollback all transactions including insertion of data in helperSessionBean1.

Here all the transaction management are being done by container (Glassfish) like injecting EntityManager and starting transaction at Session Façade’s method call and commit or rollback transaction when any exception is being thrown.

                 Mastering Enterprise Java Beans 3.0 By Rima Patel Sriganesh, Gerald Brose, Micah Silverman