Architecting a standard J2EE Struts Application

Let us consider the case of login authentication as a simple example.
The control flow is as follows
Login.jsp ->(LoginForm) ->LoginAction -> LoginDelegate -> LoginBean (Session EJB) -> LoginDAO
The login page submits to an action class.
The action class might have a private method like createUserVO() in which it populates the request parameters from the FormBean to the fields of a value object, which holds data finally used for database interaction.
Skeleton code for the action class follows:

LoginAction.java

package com.mattiz.action;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionMapping;

import com.mattiz.actionform.LoginForm;
import com.mattiz.businessdelegate.LoginDelegate;
import com.mattiz.exception.UserUnAuthenticatedException;
import com.mattiz.valueobject.LoginVO;
import com.mattiz.valueobject.UserVO;
import com.mattiz.exception.MattizException;

public class LoginAction extends Action {
	String flow;
	LoginForm loginForm;
	LoginVO loginVO;
	UserVO userVO;
	LoginDelegate loginDelegate;

	public org.apache.struts.action.ActionForward execute(
			ActionMapping mapping, ActionForm form, HttpServletRequest request,
			HttpServletResponse response) throws java.lang.Exception {
		loginForm = (LoginForm) form;
		loginVO = createLoginVO(loginForm);
		try {
			loginDelegate = new LoginDelegate();
			userVO = loginDelegate.authenticate(loginVO);
			flow = "success";
		} catch (UserUnAuthenticatedException uae) {
			flow = "failure";
		} catch (MattizException me) {
			flow = "error";
		}
		HttpSession session = request.getSession(true);
		session.setAttribute("UserObj", userVO);
		return mapping.findForward(flow);
	}

	private LoginVO createLoginVO(LoginForm loginForm) {
		LoginVO loginVO = new LoginVO();
		loginVO.setUserName(loginForm.getUserName());
		loginVO.setPassWord(loginForm.getPassWord());
		return loginVO;
	}

}

Code for the value objects used follows:

LoginVO.java


package com.mattiz.valueobject;

import java.io.Serializable;

public class LoginVO implements Serializable {

	private String userName;
	private String passWord;

	public String getPassWord() {
		return passWord;
	}

	public String getUserName() {
		return userName;
	}

	public void setPassWord(String passWord) {
		this.passWord = passWord;
	}

	public void setUserName(String userName) {
		this.userName = userName;
	}

}

UserVO.java


package com.mattiz.valueobject;

import java.io.Serializable;

public class UserVO implements Serializable {
	private String detailOne;
	private String detailTwo;
	private String detailThree;

	public String getDetailOne() {
		return detailOne;
	}

	public String getDetailThree() {
		return detailThree;
	}

	public String getDetailTwo() {
		return detailTwo;
	}

	public void setDetailOne(String detailOne) {
		this.detailOne = detailOne;
	}

	public void setDetailThree(String detailThree) {
		this.detailThree = detailThree;
	}

	public void setDetailTwo(String detailTwo) {
		this.detailTwo = detailTwo;
	}

}

LoginVO is a holder for data passed from one class to another and finally to the DAO (Data Access Object) class where database interaction takes place.
UserVO is a holder for data that relates to the user, which is retrieved from the database when a successful match is found during authentication in the DAO class and passes up the hierarchy to the action class, which then persists it to the session for later use.
Note that both the value objects must implement Serializable as they will be passed as parameters to EnterpriseJavaBeans or will be returned from them.

LoginForm is basically an Action Form Bean that contains form data and is automatically populated when the JSP page is submitted. Every field on a JSP page that implements struts must have a matching field in the form bean class. Here we shall suppose that login page contains two struts elements called userName and passWord.
Code follows:

LoginForm.java


package com.mattiz.actionform;

import javax.servlet.http.HttpServletRequest;

import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.validator.ValidatorActionForm;

public class LoginForm extends ActionForm {

	private String userName;
	private String passWord;

	public String getPassWord() {
		return passWord;
	}

	public String getUserName() {
		return userName;
	}

	public void setPassWord(String passWord) {
		this.passWord = passWord;
	}

	public void setUserName(String userName) {
		this.userName = userName;
	}

}

The action class instantiates a business delegate class and invokes its authenticate() method.
You can use a single delegate for a lot of action classes that can be grouped together logically.

What is the purpose of the BusinessDelegate class?

Instead of action class talking to EJB we use another class in between- the business delegate. The logic is that JDBC code is separated from the EJB structure so if you decide to go non- EJB later on, you still can use the DAO. It is just another layer. In J2EE, when in doubt, add more layers. If you decide not to use EJBs at all then you don’t need to clean out EJB code from action classes you just have to change the delegate class.

The business delegate has the following structure:

LoginDelegate.java

package com.mattiz.businessdelegate;

import java.rmi.RemoteException;

import javax.ejb.CreateException;
import javax.naming.NamingException;

import com.mattiz.exception.MattizException;
import com.mattiz.exception.UserUnAuthenticatedException;
import com.mattiz.locator.HomeFactory;
import com.mattiz.beans.Login;
import com.mattiz.beans.LoginHome;
import com.mattiz.valueobject.LoginVO;
import com.mattiz.valueobject.UserVO;

public class LoginDelegate {
	public UserVO authenticate(LoginVO loginVO)
			throws UserUnAuthenticatedException {
		UserVO userVO = null;
		Login login = null;
		LoginHome loginHome = null;
		try {
			loginHome = (LoginHome) HomeFactory.instance().getHome(
					"ejb/LoginHome");
			login = loginHome.create();
			userVO = login.authenticate(loginVO);
		} catch (RemoteException re) {
			throw new MattizException("Fatal Exception");
		} catch (CreateException ce) {
			throw new MattizException("Fatal Exception");
		} catch (NamingException ne) {
			throw new MattizException("Fatal Exception");
		}
		return userVO;
	}
}

The Exceptions used here are UserUnAuthenticatedException, which is thrown when a user is not authenticated; and the MattizException which is thrown whenever a critical error occurs. The MattizException unlike the UserUnAuthenticatedException extends Runtime Exception and hence need not be declared by the methods that throw it. It goes up the hierarchy of calls and is finally caught in the action class where the user is directed to the error page. Since MattizException extends runtime exception the EJB Container will roll back the transaction when this exception occurs and hence there will be no half written database entries with incomplete data.

When a UserUnAuthenticatedException is thrown there is no UserVO value object coming back from the DAO. Note that the DAO class throws this exception. The method that calls the DAO method needs to declare that it too throws the UserUnAuthenticatedException and so must any other method that calls this method. If the exception is thrown, it passes control to the bean, then the delegate and so on and so forth up the chain till it is caught in the Action class where user is redirected to the login page.

MattizException.java

package com.mattiz.exception;

public class MattizException extends RuntimeException {

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

}

UserUnAuthenticatedException.java


package com.mattiz.exception;

public class UserUnAuthenticatedException extends Exception {

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

The BusinessDelegate class uses a helper class to look up an EJB, that on being passed its JNDI name returns an object that you can cast back to an EJB Home object. All the ugly code you need to look up an EJB that was written in the servlet/action class, is now abstracted out to this helper class. The way to use it is like this

SomeEJBHome someEJBHome = (SomeEJBHome)HomeFactory.instance().getHome("ejb/SomeEJBHome");
EJBHome.class mentioned in it is the parent class of all EJB homes.

Code follows:

HomeFactory.java

package com.mattiz.locator;

import javax.ejb.EJBHome;
import javax.naming.NamingException;
import javax.naming.InitialContext;
import java.util.Hashtable;

import com.mattiz.exception.MattizException;

public class HomeFactory {
	private static HomeFactory homeFactory = new HomeFactory();
	private InitialContext initialContext = null;
	private static Hashtable homeCache = new Hashtable();

	protected HomeFactory() {
		super();
		try {
			initialContext = new InitialContext();
		} catch (NamingException ex) {
			ex.printStackTrace();
		}
	}

	public static HomeFactory instance() {
		return homeFactory;
	}

	public Object getHome(String ejbRef) throws NamingException {
		EJBHome ejbHome = (EJBHome) homeCache.get(ejbRef);

		if (ejbHome != null) {
			return ejbHome;
		}

		if (initialContext != null) {
			Object nsObject = initialContext.lookup(ejbRef);
			ejbHome = (EJBHome) javax.rmi.PortableRemoteObject.narrow(
					(org.omg.CORBA.Object) nsObject, EJBHome.class);
			homeCache.put(ejbRef, ejbHome);
			return ejbHome;
		} else {
			throw new NamingException("HomeFactory: no InitialContext");
		}
	}

}

The code for the session bean that is called by the business delegate follows:
The Remote Class

package com.mattiz.beans;

import com.mattiz.exception.UserUnAuthenticatedException;
import com.mattiz.valueobject.LoginVO;
import com.mattiz.valueobject.UserVO;

public interface Login extends javax.ejb.EJBObject {
	public UserVO authenticate(LoginVO loginVO)
			throws java.rmi.RemoteException, UserUnAuthenticatedException;
}

The Home class:

package com.mattiz.beans;

public interface LoginHome extends javax.ejb.EJBHome {

	public com.mattiz.beans.Login create() throws javax.ejb.CreateException,
			java.rmi.RemoteException;
}

The Bean class:

package com.mattiz.beans;

import com.mattiz.DAO.LoginDAO;
import com.mattiz.exception.UserUnAuthenticatedException;
import com.mattiz.valueobject.LoginVO;
import com.mattiz.valueobject.UserVO;

public class LoginBean implements javax.ejb.SessionBean {

	private javax.ejb.SessionContext mySessionCtx;

	public UserVO authenticate(LoginVO loginVO)
			throws UserUnAuthenticatedException {
		LoginDAO loginDAO = new LoginDAO();
		UserVO userVO = loginDAO.findUserValid(loginVO);
		return userVO;
	}

	public javax.ejb.SessionContext getSessionContext() {
		return mySessionCtx;
	}

	public void setSessionContext(javax.ejb.SessionContext ctx) {
		mySessionCtx = ctx;
	}

	public void ejbCreate() throws javax.ejb.CreateException {
	}

	public void ejbActivate() {
	}

	public void ejbPassivate() {
	}

	public void ejbRemove() {
	}
}

The bean creates an instance of a DAO class. The DAO class does not contain any business logic and contains the JDBC of the application. It returns a ValueObject in this case, which may contain user data that may need to be accessed later on by other classes.
Code for the DAO class follows:

LoginDAO.java

package com.mattiz.DAO;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import com.mattiz.exception.MattizException;
import com.mattiz.exception.UserUnAuthenticatedException;
import com.mattiz.locator.DSLookup;
import com.mattiz.locator.HomeFactory;
import com.mattiz.valueobject.LoginVO;
import com.mattiz.valueobject.UserVO;

public class LoginDAO {

	public UserVO findUserValid(LoginVO loginVO)
			throws UserUnAuthenticatedException {
		PreparedStatement ps = null;
		Connection conn = null;
		ResultSet rs = null;
		String detailOne = null;
		String detailTwo = null;
		String detailThree = null;
		UserVO userVO = null;
		DSLookup dsLookup = null;
		try {
			dsLookup = new DSLookup();
			conn = dsLookup.getDBConnection();
			String query = "SELECT detailOne, detailTwo, detailThree FROM user"
					+ " WHERE login_name= ? AND password= ?";
			ps = conn.prepareStatement(query);
			ps.setString(1, loginVO.getUserName());
			ps.setString(2, loginVO.getPassWord());
			rs = ps.executeQuery(query);
			if (rs.next()) // user exists //get user details from database to
							// store to session
			{
				detailOne = rs.getString("detailOne");
				detailTwo = rs.getString("detailTwo");
				detailThree = rs.getString("detailThree");
				userVO = new UserVO();
				userVO.setDetailOne(detailOne);
				userVO.setDetailTwo(detailTwo);
				userVO.setDetailThree(detailThree);
			} else {
				throw new UserUnAuthenticatedException("Not valid user");
			}
		} catch (SQLException se) {
			System.out.print(se.toString());
			throw new MattizException("Fatal Exception");
		} finally {
			try {
				if (conn != null) {
					conn.close();
				}
				if (ps != null) {
					ps.close();
				}
				if (rs != null) {
					rs.close();
				}
			} catch (SQLException e) {
				throw new MattizException("Fatal Exception");
			}
		}
		return userVO;
	}
}

This class uses a helper class to get Database connections from the datasource.
Code follows:

DSLookup.java

package com.mattiz.locator;

import java.sql.Connection;
import java.sql.SQLException;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;
import javax.naming.NamingException;

import com.mattiz.exception.MattizException;

public class DSLookup {
	private InitialContext initialContext = null;
	private static final String dbUserName = new String("admin");
	private static final String dbPwd = new String("admin");

	public Connection getDBConnection() {
		Context ic = null;
		DataSource ds = null;
		Connection dbCon = null;
		try {
			ic = new InitialContext();
			ds = (DataSource) ic.lookup("mattiz_ds");
		} catch (NamingException e) {
			System.out.println("Unable to find Datasource..");
			e.printStackTrace();
			throw new MattizException("Fatal Exception");
		}
		try {
			dbCon = ds.getConnection(dbUserName, dbPwd);
		} catch (SQLException se) {
			throw new MattizException("Fatal Exception");
		}
		return dbCon;
	}
}

Tip: Place only components like action, form, business delegate in web project module; and DAO, session, exceptions, locator, value objects in the EJB module. Put the web module on the classpath of your EJB module. Keep the EJB module independent of other modules.

About cuppajavamattiz
Matty Jacob - Avid technical blogger with interests in J2EE, Web Application Servers, Web frameworks, Open source libraries, Relational Databases, Web Services, Source control repositories, ETL, IDE Tools and related technologies.

Comments are closed.

%d bloggers like this: