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.

Time Break Up


		// this piece of code breaks up the date and time part of a Date
		java.util.Date today = java.util.Date();
		SimpleDateFormat timeFormat = new SimpleDateFormat("kk:mm");
		String timeString = timeFormat.format(today);

		try {
			onlyTime = timeFormat.parse(timeString);
		} catch (ParseException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy");
		String dateString = dateFormat.format(today);

		try {
			onlyDate = dateFormat.parse(dateString);
		} catch (ParseException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}

 


	// this snippet sets the time part of one date to another date
	public java.sql.Timestamp setDateAndTime(java.util.Date date,
			java.util.Date time) {

		Calendar calendar = Calendar.getInstance();
		Calendar calendar2 = Calendar.getInstance();
		calendar.setTime(date);
		calendar2.setTime(time);
		calendar.set(Calendar.HOUR_OF_DAY, calendar2.get(Calendar.HOUR_OF_DAY));
		calendar.set(Calendar.MINUTE, calendar2.get(Calendar.MINUTE));
		calendar.set(Calendar.SECOND, calendar2.get(Calendar.SECOND));
		java.util.Date dateWithTime = calendar.getTime();
		java.sql.Timestamp sqlTimestamp = new java.sql.Timestamp(
				dateWithTime.getTime());

		return sqlTimestamp;
	}

Struts makes File Upload Easy as Pie!


//ACTION FOR FILE UPLOAD
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.upload.FormFile;
import java.io.*;
import java.util.Properties;

public class UploadRequestAction extends Action {
	public ActionForward execute(ActionMapping mapping, ActionForm form,
			HttpServletRequest request, HttpServletResponse response)
			throws Exception {
		UploadActionForm requestForm = (UploadActionForm) form;
		FormFile f = requestForm.getRequestFile();
		String line = null;
		InputStream input = null;
		try {
			int fileSize = f.getFileSize();
			String fileName = f.getFileName();
			input = f.getInputStream();
			byte[] byteArr = new byte[fileSize];
			// Create an input stream to read the uploaded file.
			ByteArrayInputStream bytein = new ByteArrayInputStream(
					f.getFileData());
			// Load the input stream into the byte Array.
			bytein.read(byteArr);
			byte[] b = new byte[1024];
			int x = 0;
			int state = 0;
			java.io.FileOutputStream buffer = null;
			// get path from properties file or use getResourceAsStream() for relative paths
                        File propFile = new File(
					"C:\\jboss-3.2.5\\jboss-3.2.5\\bin\\.\\.environment\\mattiz.properties");
			InputStream is = new FileInputStream(propFile);
			Properties mobCache = new Properties();
			mobCache.load(is);
			String path = mobCache
					.getProperty("D:/workspace/mattiz/imageswebapp/WebRoot/images/");
			buffer = new java.io.FileOutputStream(path + fileName);
			while ((x = input.read(b, 0, 1024)) < -1) {
				buffer.write(b, 0, x);
			}
			// Close the resources
			bytein.close();
			input.close();
			buffer.close();
		} catch (IOException ex) {
			ex.printStackTrace();
		}
		request.setAttribute("msg", "File Uploaded");
		return new ActionForward("/events/postshow.jsp");
	}
}

//JSP FOR FILE UPLOAD

<html:form method="post" action="/uploadRequest.do"
enctype="multipart/form-data">
<center>
<html:file styleClass="myTextBox" name="UploadActionForm"
property="requestFile">
</html:file>
</br>
<html:submit value="Upload Your Request">
</br>
</html:submit>
</center>
</html:form>

//FORM BEAN FOR FILE UPLOAD


import org.apache.struts.action.ActionForm;
import org.apache.struts.upload.FormFile;

public class UploadActionForm extends ActionForm {
	private FormFile requestFile = null;

	public FormFile getRequestFile() {
		return requestFile;
	}

	public void setRequestFile(FormFile requestFile) {
		this.requestFile = requestFile;
	}

}

EJB with STRUTS

The following example uses a modified version of the Entity Bean Application the description of which was posted earlier.
There is no change in the contents of mysql-ds.xml, jboss.xml, jbosscmp-jdbc.xml, ejb-jar.xml and application.xml; the entity bean classes AuthorsBean.java, Authors.java. AuthorsHome.java and AuthorsKey.java. Some classes and xml-files specific to Struts and a few jsps’ have been added.

Download a binary version of Struts and unzip it into an appropriate folder. Set an environment variable STRUT_HOME pointing to this installation.

The work directory for ant has the following structure:

mysql-ds.xml
jbosscmp-jdbc.xml
ejb-jar.xml
jboss.xml
application.xml
ApplicationResources.properties
build.xml
build.properties
build.bat
struts-config.xml
web.xml
<build>
<pages>
      |authors.jsp
      |success.jsp
<src>
      |shoppingcart
            |AuthorsBean.java
            |Authors.java
            |AuthorsHome.java
            |AuthorsKey.java
            |authorsForm.java
            |authorsAction.java
<action path="/testout" type="shoppingcart.authorsAction" name="authorsForm"
	scope="request" input="/authors.jsp" validate="false">
	<forward name="successful" path="/success.jsp" />
</action>

The

<action></action>

block in the struts config.xml is the link between jsps, actions, forms and forwards.
The type=shopping.authorsAction mentions the Action class to be used in authors.jsp. The name=authorsForm mentions which form bean is to be used for authors.jsp. The form bean needs a separate definition in the struts-config.xml. This is at the beginning of the struts-config:

<form-bean name="authorsForm" type="shoppingcart.authorsForm"/>

The jsp pages mapped in the struts-config file is called thus in the action class:

return mapping.findForward("successful");

struts-config.xml looks like this:

<?xml version="1.0" encoding="ISO-8859-1" ?>

<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.1//EN"
"http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">
<struts-config>
	<form-beans type="org.apache.struts.action.ActionFormBean">
		<form-bean name="authorsForm" type="shoppingcart.authorsForm" />
	</form-beans>
	<action-mappings type="org.apache.struts.action.ActionMapping">
		<action path="/testout" type="shoppingcart.authorsAction" name="authorsForm"
			scope="request" validate="false">
			<forward name="successful" path="/success.jsp" />
		</action>
	</action-mappings>
	<message-resources parameter="ApplicationResources" />
</struts-config>

Modifying the web.xml file:
Modify the web.xml file for the web application to include a

<servlet>

element to define the controller servlet, and a

<servlet-mapping>

element to establish which request URIs are mapped to this servlet. Modify the WEB-INF/web.xml file of the web application to include tag library declarations for tld files from STRUT_HOME/lib/struts-*.tld
The action “/testout.matty” in authors.jsp ( in authors.jsp) is mapped to the path=“/testout” in the action mappings of struts-config.xml. “.matty” is the extension for all urls pointing to struts action classes and is set in web.xml by extension mapping.

web.xml looks like this:

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
	<servlet>
		<servlet-name>action</servlet-name>
		<servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
		<init-param>
			<param-name>config</param-name>
			<param-value>/WEB-INF/struts-config.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>action</servlet-name>
		<url-pattern>*.matty</url-pattern>
	</servlet-mapping>
	<welcome-file-list>
		<welcome-file>authors.jsp</welcome-file>
	</welcome-file-list>
	<taglib>
		<taglib-uri>/WEB-INF/struts-bean.tld</taglib-uri>
		<taglib-location>/WEB-INF/struts-bean.tld</taglib-location>
	</taglib>
	<taglib>
		<taglib-uri>/WEB-INF/struts-html.tld</taglib-uri>
		<taglib-location>/WEB-INF/struts-html.tld</taglib-location>
	</taglib>
	<taglib>
		<taglib-uri>/WEB-INF/struts-logic.tld</taglib-uri>
		<taglib-location>/WEB-INF/struts-logic.tld</taglib-location>
	</taglib>
</web-app>

Application Resources.properties looks like this:

isbnCode.label=Enter Isbn Code of the book
author.label=Enter name of the author(only to create a record)

When compiling the Java classes that comprise the application, include the struts.jar and commons-*.jar files on the CLASSPATH that is submitted to the compiler. I did this by specifying paths in the tag of build.xml.
Struts *.tld files and struts.jar are copied to the war file during the build process.

build.xml looks like this:

<project name="Authors" default="clean" basedir=".">

	<property environment="env" />
	<property file="./build.properties" />

	<!-- the build path -->
	<path id="build.path">
		<pathelement location="${jboss.dist}/server/default/lib" />
		<pathelement location="${jboss.dist}/server/default/lib/jboss-j2ee.jar" />
		<pathelement location="${jboss.dist}/client/javax.servlet.jar" />
		<pathelement location="${build.classes.dir}" />
		<pathelement location="${struts.dir}" />
		<pathelement location="${struts.dir}/struts.jar" />
	</path>

	<target name="war" depends="compile">
		<war warfile="${war}" webxml="web.xml">
			<fileset dir="${basedir}/pages">
				<include name="*.jsp" />
			</fileset>
			<webinf dir="${struts.dir}">
				<include name="*.tld" />
			</webinf>
			<webinf dir="${basedir}">
				<include name="struts-config.xml" />
			</webinf>
			<classes dir="${build.classes.dir}">
				<include name="shoppingcart/*.class" />
				<exclude name="shoppingcart/${appname}Bean.class" />
			</classes>
			<classes dir="${basedir}">
				<include name="ApplicationResources.properties" />
			</classes>
			<lib dir="${jboss.client.dir}">
				<include name="jboss-client.jar" />
				<include name="jnp-client.jar" />
			</lib>
			<lib dir="${struts.dir}">
				<include name="struts.jar" />
			</lib>
		</war>
	</target>

	<target name="jar" depends="compile">
		<jar jarfile="${jar}">
			<fileset dir="${build.classes.dir}">
				<include name="shoppingcart/${appname}.class" />
				<include name="shoppingcart/${appname}Home.class" />
				<include name="shoppingcart/${appname}Bean.class" />
				<include name="shoppingcart/${appname}Key.class" />
			</fileset>
			<metainf dir="${basedir}" includes="jboss.xml,ejb-jar.xml,jbosscmp-jdbc.xml" />
		</jar>
	</target>

	<!-- build all, and copy to the jboss/deploy directory -->

	<target name="ear" depends="jar,war">
		<ear earfile="${ear}" appxml="application.xml">
			<fileset dir="${basedir}" includes="${jar},${war}" />
		</ear>
	</target>

	<!-- compilation options -->

	<target name="compile">
		<mkdir dir="${build.classes.dir}" />
		<javac srcdir="${src.dir}" destdir="${build.classes.dir}" debug="on"
			deprecation="on" classpathref="build.path" optimize="off" />
	</target>
	<target name="build-all" depends="ear">
		<copy file="${ear}" todir="${jboss.deploy.dir}" />
		<copy file="mysql-ds.xml" todir="${jboss.deploy.dir}" />
	</target>
	<target name="clean">
		<delete file="${jar}" />
		<delete file="${ear}" />
		<delete file="${war}" />
		<delete file="${jboss.deploy.dir}/${ear}" />
		<delete file="${jboss.deploy.dir}/mysql-ds.xml" />
		<delete dir="${build.classes.dir}" />
	</target>
</project>

build.properties looks like this:

appname=Authors
# get JBOSS location from environment variable
dist.root=${env.JBOSS_HOME}
jboss.dist=${dist.root}
jboss.deploy.dir=${jboss.dist}/server/default/deploy
jboss.client.dir=${jboss.dist}/client

# get STRUT_HOME location from environment variable
struts.dir=${env.STRUT_HOME}/lib
src.dir=${basedir}/src
src.docroot=${src}/docroot
build.dir=${basedir}/build
build.classes.dir=${build.dir}/classes

war=${appname}.war
jar=${appname}.jar
ear=${appname}.ear

The ejb files are the same as in the entity bean example.
Two additional classes have replaced AuthorsServlet:

authorsForm.java

package shoppingcart;

import javax.servlet.http.*;

public class authorsForm extends org.apache.struts.action.ActionForm {
	String author;
	String isbnCode;

	public void setAuthor(String author) {
		this.author = author;
	}

	public String getAuthor() {
		return this.author;
	}

	public void setIsbnCode(String isbnCode) {
		this.isbnCode = isbnCode;
	}

	public String getIsbnCode() {
		return this.isbnCode;
	}
};

authorsAction.java


package shoppingcart;

import shoppingcart.*;
import javax.naming.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Hashtable;
import java.util.Properties;
import javax.naming.InitialContext;
import javax.rmi.PortableRemoteObject;
import org.apache.struts.action.*;
import java.util.*;

public class authorsAction extends Action {
	public ActionForward execute(ActionMapping mapping, ActionForm form,
			HttpServletRequest req, HttpServletResponse res)
			throws java.lang.Exception {
		String auth = ((authorsForm) form).getAuthor();
		String isbn = ((authorsForm) form).getIsbnCode();
		AuthorsHome authorsHome = null;
		Authors bean = null;
		try {
			Properties p = new Properties();
			p.setProperty("java.naming.factory.initial",
					"org.jnp.interfaces.NamingContextFactory");
			p.setProperty("java.naming.provider.url", "localhost:1099");
			p.setProperty("java.naming,factory.url.pkgs",
					"org.jboss.naming:org.jnp.interfaces");
			InitialContext jndiContext = new InitialContext(p);
			Object ref = jndiContext.lookup("shoppingcart/Authors");
			authorsHome = (AuthorsHome) PortableRemoteObject.narrow(ref,
					AuthorsHome.class);
		} catch (Exception e) {
			throw new ServletException("failed to lookup shoppingcart/Authors",
					e);
		}
		req.setAttribute("author", auth);
		req.setAttribute("isbnCode", isbn);
		if (req.getParameter("add") != null) {
			try {
				AuthorsKey key = new AuthorsKey(isbn);
				bean = authorsHome.create(key);
				if (auth != null) {
					bean.setAuthorName(auth);
				}
				req.setAttribute("author", auth);
				req.setAttribute("isbnCode", isbn);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		if (req.getParameter("find") != null) {
			try {
				if (isbn != null) {
					AuthorsKey key = new AuthorsKey(isbn);
					bean = authorsHome.findByPrimaryKey(key);
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
			req.setAttribute("author", bean.getAuthorName());
			req.setAttribute("isbnCode", isbn);
		}
		return mapping.findForward("successful");
	}
}

Two new jsps have been added

authors.jsp

<html>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html"%>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean"%>
<%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic"%>
<head>
</head>
<body>
	<html:form action="/testout.matty">
		<p>
			<html:errors />
			<br />
			<bean:message key="isbnCode.label" />
			<html:text property="isbnCode" size='10' />
			<br />
			<bean:message key="author.label" />
			<html:text property="author" size='10' />
			<html:submit value='Add' property="add" />
			<html:submit value='Find' property="find" />
	</html:form>
</body>
</html>

success.jsp

<html>
<head>
</head>
<body>
	<%
		if (request.getAttribute("author") != null) {
	%>
	<h3>
		ISBN CODE
		<%=request.getAttribute("isbnCode")%></h3>
	<%
		}
	%>
	<%
		if (request.getAttribute("isbnCode") != null) {
	%>
	<h3>
		AUTHOR
		<%=request.getAttribute("author")%></h3>
	<%
		}
	%>
</body>
</html>

The ear file deployed to jboss has the following structure:

authors.ear
      |<meta-INF>
        |application.xml
      |authors.jar
        |<meta-INF>
            |ejb-jar.xml
            |jboss.xml
            |jbosscmp-jdbc.xml
        |<shoppingcart>
            |AuthorsBean.class
            |Authors.class
            |AuthorsHome.class
            |AuthorsKey.class
      |authors.war
        |authors.jsp
        |success.jsp
        |<meta-INF>
        |<web-INF>
            |struts-config.xml
            |web.xml
            |struts-bean.tld
            |struts-html.tld
            |struts-logic.tld
            |struts-nested.tld
            |struts-logic.tld
            |<classes>
                  |ApplicationResources.properties
                  |<shoppingcart>
                        |Authors.class
                        |AuthorsKey.class
                        |AuthorsHome.class
                        |authorsForm.class
                        |authorsAction.class
            |<lib>
                  |jnp-client.java
                  |jboss-client.jar
                  |struts.jar

Start the application using the following url
http://localhost:8080/authors.jsp

EJB – 2 Creating an entity bean

This example is deprecated.

Note that what I have described is creation and deployment of an entity bean on Jboss. For other Application Servers, the configuration process might change and EJB lookup code using JNDI as well as data source lookups might vary. Jboss mostly has manual configurations so when using an IDE such as Eclipse, the data source and other configurations might prove to be much simpler. Cheers!

For creating the entity bean, we need to first create a database table.
I am using MySQL Server 4.1 with username and password: admin, admin. The driver used is mysql-connector-java-3.1.8-bin.jar.
The entity used in this example uses a table called AUTHORS in database AUTHORS which has two fields ISBN_CODE and AUTHOR.
Here are the scripts to create the database:

--user=root mysql
GRANT ALL PRIVILEGES ON *.* TO 'admin'@'localhost' IDENTIFIED BY 'admin' WITH GRANT OPTION;
create database AUTHORS;
use AUTHORS;
CREATE TABLE authors (ISBN_CODE varchar(10) NOT NULL default '0',AUTHOR varchar(30) default NULL,PRIMARY KEY  (ISBN_CODE)) ;
insert into authors (ISBN_CODE,AUTHOR )values("1e","mattiz");
---------------------------------------------------------------
| Field               | Type           | Null    | Key | Default |
----------------------------------------------------------------
| ISBN_CODE           | varchar(10)    |         | PRI | 0       |
| AUTHOR              | varchar(30)    | YES     |     | NULL    |
---------------------------------------------------------------

Here are steps how to configure a mySQL datasource in Jboss.:
1.copy mysql-connector-java-3.1.8-bin.jar and aspectjrt.jar to jboss’s lib folder
2.Add the following to login-config.xml in jboss’s conf folder

<application-policy name="mysqlDbRealm">
	<authentication>
		<login-module
			code="org.jboss.resource.security.ConfiguredIdentityLoginModule"
			flag="required">
			<module-option name="principal">admin</module-option>
			<module-option name="userName">admin</module-option>
			<module-option name="password">admin</module-option>
			<module-option name="managedConnectionFactoryName">
				jboss.jca:service=LocalTxCM,name=dbpool</module-option>
		</login-module>
	</authentication>
</application-policy>

3.create mysql-ds.xml as follows:

<?xml version="1.0" encoding="UTF-8"?>
<datasources>
	<local-tx-datasource>
		<jndi-name>dbpool</jndi-name>
		<connection-url>jdbc:mysql://localhost:3306/AUTHORS</connection-url>
		<driver-class>com.mysql.jdbc.Driver</driver-class>
		<user-name>admin</user-name>
		<password>admin</password>
		<security-domain>mysqlDbRealm</security-domain>
		<metadata>
			<type-mapping>mySQL</type-mapping>
		</metadata>
	</local-tx-datasource>
</datasources>

4.create jbosscmp-jdbc.xml as follows:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jbosscmp-jdbc PUBLIC "-//JBoss//DTD JBOSSCMP-JDBC 4.0//EN"

"http://www.jboss.org/j2ee/dtd/jbosscmp-jdbc_4_0.dtd">
<jbosscmp-jdbc>
	<defaults>
		<datasource>java:/dbpool</datasource>
		<datasource-mapping>mySQL</datasource-mapping>
	</defaults>
	<enterprise-beans>
		<entity>
			<ejb-name>AuthorsBean</ejb-name>
			<create-table>false</create-table>
			<table-name>Authors</table-name>
			<cmp-field>
				<field-name>isbnCode</field-name>
				<column-name>ISBN_CODE</column-name>
			</cmp-field>
			<cmp-field>
				<field-name>authorName</field-name>
				<column-name>AUTHOR</column-name>
			</cmp-field>
		</entity>
	</enterprise-beans>
</jbosscmp-jdbc>

My work folder for ANT has the following structure:

<build>
<src>
     |<shoppingcart>
          |AuthorsBean.java
          |Authors.java
          |AuthorsHome.java
          |AuthorsKey.java
          |AuthorsServlet.java
ejb-jar.xml
mysql-ds.xml
jbosscmp-jdbc.xml
jboss.xml
application.xml
web.xml
build.xml
build.properties
build.bat

ejb-jar.xml looks like this:

<?xml version="1.0"?>
<!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 1.1//EN" "http://java.sun.com/j2ee/dtds/ejb-jar_1_1.dtd">
<ejb-jar>
	<display-name>Authors</display-name>
	<enterprise-beans>
		<entity>
			<description>Authors Entity Bean</description>
			<ejb-name>AuthorsBean</ejb-name>
			<home>shoppingcart.AuthorsHome</home>
			<remote>shoppingcart.Authors</remote>
			<ejb-class>shoppingcart.AuthorsBean</ejb-class>
			<persistence-type>Container</persistence-type>
			<prim-key-class>shoppingcart.AuthorsKey</prim-key-class>
			<reentrant>False</reentrant>
			<cmp-field>
				<field-name>isbnCode</field-name>
			</cmp-field>
			<cmp-field>
				<field-name>authorName</field-name>
			</cmp-field>
		</entity>
	</enterprise-beans>
	<assembly-descriptor>
		<container-transaction>
			<method>
				<ejb-name>AuthorsBean</ejb-name>
				<method-name>*</method-name>
			</method>
			<trans-attribute>Required</trans-attribute>
		</container-transaction>
	</assembly-descriptor>
	<resource-ref>
		<res-ref-name>jdbc/ejbPool</res-ref-name>
		<res-type>javax.sql.DataSource</res-type>
		<res-auth>Container</res-auth>
	</resource-ref>
</ejb-jar>

The ejb-jar.xml is a deployment descriptor was introduced with EJB 2.0.
It describes the entity or session beans that are included in the application.
Every ejb-jar.xml has exactly one tag, which may contain , or tags and some other optional tags .
The contains the names of the home, remote and the bean class as well as some other info.
The tag contains the descriptive name of the bean.
The tag contains the fully qualified name of the home class.
The tag contains the fully qualified name of the remote class.
The tag contains the fully qualified name of the bean class.
The tag and the are specific to session beans and is used to….
In case of entity beans there is a tag that contains the fully qualified name for the entity bean’s primary key class.
This may be a primitive data type such as java.lang.String for a simple key or a fully qualified class name for a compound key.
The tag declares whether the bean can call another bean which calls the original bean.
The tag contains a tag that describes the cmp fields in the entity bean.
Lastly the tag contains the name of the primary key field (for simple pry key types) and is not required for compound keys.

jaws.xml is required only for entity beans.

jboss.xml looks like this:

<?xml version="1.0"?>
<jboss>
	<secure>false</secure>
	<container-configurations />
	<resource-managers />
	<enterprise-beans>
		<entity>
			<ejb-name>AuthorsBean</ejb-name>
			<jndi-name>shoppingcart/Authors</jndi-name>
		</entity>
	</enterprise-beans>
	<resource-ref>
		<res-ref-name>jdbc/ejbPool</res-ref-name>
		<resource-name>java:/dbpool</resource-name>
		<jndi-name>dbpool</jndi-name>
	</resource-ref>
</jboss>

application.xml looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE application PUBLIC '-//Sun Microsystems, Inc.//DTD J2EE Application 1.2//EN' 'http://java.sun.com/j2ee/dtds/application_1_2.dtd'>
<application>
	<display-name>Authors EJB</display-name>
	<module>
		<ejb>Authors.jar</ejb>
	</module>
	<module>
		<web>
			<web-uri>Authors.war</web-uri>
			<context-root></context-root>
		</web>
	</module>
</application>

web.xml looks like this:

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
	<servlet>
		<servlet-name>AuthorsServlet</servlet-name>
		<servlet-class>shoppingcart.AuthorsServlet</servlet-class>
	</servlet>
	<servlet-mapping>
		<servlet-name>AuthorsServlet</servlet-name>
		<url-pattern>/AuthorsServlet</url-pattern>
	</servlet-mapping>
</web-app>

build.xml looks like this:


	<project name="Authors" default="build-all" basedir=".">

		<property environment="env" />
		<property file="./build.properties" />

		<!-- the build path -->
		<path id="build.path">
			<pathelement location="${jboss.dist}/server/default/lib" />
			<pathelement location="${jboss.dist}/server/default/lib/jboss-j2ee.jar" />
			<pathelement location="${build.classes.dir}" />
			<pathelement location="${jboss.dist}/client/javax.servlet.jar" />
		</path>

		<target name="war" depends="compile">
			<war warfile="${war}" webxml="web.xml">
				<classes dir="${build.classes.dir}">
					<include name="shoppingcart/AuthorsServlet.class" />
					<include name="shoppingcart/${appname}.class" />
					<include name="shoppingcart/${appname}Home.class" />
					<include name="shoppingcart/${appname}Key.class" />
				</classes>
				<lib dir="${jboss.client.dir}">
					<include name="jboss-client.jar" />
					<include name="jnp-client.jar" />
				</lib>
			</war>
		</target>

		<target name="jar" depends="compile">
			<jar jarfile="${jar}">
				<fileset dir="${build.classes.dir}">
					<include name="shoppingcart/${appname}.class" />
					<include name="shoppingcart/${appname}Home.class" />
					<include name="shoppingcart/${appname}Bean.class" />
					<include name="shoppingcart/${appname}Key.class" />
				</fileset>
				<metainf dir="${basedir}" includes="jboss.xml,ejb-jar.xml,jbosscmp-jdbc.xml" />
			</jar>
		</target>

		<!-- build all, and copy to the jboss/deploy directory -->

		<target name="ear" depends="jar,war">
			<ear earfile="${ear}" appxml="application.xml">
				<fileset dir="${basedir}" includes="${jar},${war}" />
			</ear>
		</target>

		<!-- compilation options -->
		<target name="compile">
			<mkdir dir="${build.classes.dir}" />
			<javac srcdir="${src.dir}" destdir="${build.classes.dir}"
				debug="on" deprecation="on" classpathref="build.path" optimize="off" />
		</target>
		<target name="build-all" depends="ear">
			<copy file="${ear}" todir="${jboss.deploy.dir}" />
			<copy file="mysql-ds.xml" todir="${jboss.deploy.dir}" />
		</target>
		<!-- clean all -->
		<target name="clean">
			<delete file="${jar}" />
			<delete file="${ear}" />
			<delete file="${war}" />
			<delete file="${jboss.deploy.dir}/${ear}" />
			<delete dir="${build.classes.dir}" />
		</target>
	</project>

build.properties looks like this:

appname=Authors

# get JBOSS location from environment variable
dist.root=${env.JBOSS_HOME}
jboss.dist=${dist.root}
jboss.deploy.dir=${jboss.dist}/server/default/deploy
jboss.client.dir=${jboss.dist}/client


src.dir=${basedir}/src
src.docroot=${src}/docroot
build.dir=${basedir}/build
build.classes.dir=${build.dir}/classes

war=${appname}.war
jar=${appname}.jar
ear=${appname}.ear

AuthorsBean.java

package shoppingcart;

import shoppingcart.AuthorsKey;
import shoppingcart.Authors;
import javax.ejb.EntityBean;
import javax.ejb.EntityContext;

public class AuthorsBean implements EntityBean {
	EntityContext ctx;
	public String isbnCode;
	public String authorName;

	public AuthorsKey ejbCreate(AuthorsKey key) {
		this.isbnCode = key.getIsbnCode();
		return null;
	}

	public AuthorsKey ejbFindByPrimaryKey(AuthorsKey key) {
		return key;
	}

	public void ejbPostCreate(AuthorsKey authorsKey) {
	}

	public String getAuthorName() {
		return this.authorName;
	}

	public void setAuthorName(String authorName) {
		this.authorName = authorName;
	}

	public void setEntityContext(EntityContext ctx) {
		this.ctx = ctx;
	}

	public void unsetEntityContext() {
		ctx = null;
	}

	public void ejbActivate() {
	}

	public void ejbPassivate() {
	}

	public void ejbLoad() {
	}

	public void ejbStore() {
	}

	public void ejbRemove() {
	}
}

Authors.java

package shoppingcart;

import javax.ejb.EJBObject;
import java.rmi.RemoteException;

public interface Authors extends EJBObject {
	public String getAuthorName() throws RemoteException;

	public void setAuthorName(String authorName) throws RemoteException;
}

AuthorsHome.java

package shoppingcart;

import javax.ejb.EJBHome;
import javax.ejb.CreateException;
import javax.ejb.FinderException;
import java.rmi.RemoteException;

public interface AuthorsHome extends EJBHome {
	public Authors create(AuthorsKey authorsKey) throws CreateException,
			RemoteException;

	public Authors findByPrimaryKey(AuthorsKey authorsKey)
			throws FinderException, RemoteException;
}

AuthorsKey.java

package shoppingcart;

import java.io.Serializable;

public class AuthorsKey implements Serializable {
	public String isbnCode;

	public AuthorsKey() {
		super();
	}

	public AuthorsKey(String isbnCode) {
		this.isbnCode = isbnCode;
	}

	public String getIsbnCode() {
		return this.isbnCode;
	}

	public boolean equals(Object o) {
		if (o instanceof shoppingcart.AuthorsKey) {
			if (((shoppingcart.AuthorsKey) o).getIsbnCode().equals(
					this.isbnCode))
				return true;
			else
				return false;
		}
		return false;
	}

	public int hashCode() {
		return this.isbnCode.hashCode();
	}
}

AuthorsServlet.java

package shoppingcart;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Hashtable;
import java.util.Properties;
import javax.naming.InitialContext;
import javax.rmi.PortableRemoteObject;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import shoppingcart.Authors;
import shoppingcart.AuthorsHome;
import shoppingcart.AuthorsKey;

public class AuthorsServlet extends HttpServlet {
	private AuthorsHome authorsHome = null;

	public void init() throws ServletException {
		try {
			Properties p = new Properties();
			p.setProperty("java.naming.factory.initial",
					"org.jnp.interfaces.NamingContextFactory");
			p.setProperty("java.naming.provider.url", "localhost:1099");
			p.setProperty("java.naming,factory.url.pkgs",
					"org.jboss.naming:org.jnp.interfaces");
			InitialContext jndiContext = new InitialContext(p);
			Object ref = jndiContext.lookup("shoppingcart/Authors");
			authorsHome = (AuthorsHome) PortableRemoteObject.narrow(ref,
					AuthorsHome.class);
		} catch (Exception e) {
			throw new ServletException("failed to lookup shoppingcart/Authors",
					e);
		}
	}

	public void doPost(HttpServletRequest req, HttpServletResponse res)
			throws ServletException, IOException {
		Authors bean = null;
		String title = "Servlet Interface to EJB";
		res.setContentType("text/html");
		PrintWriter pout = res.getWriter();
		pout.println("<html><head><title>");
		pout.println(title);
		pout.println("</title></head><body>");
		pout.println("<h1>" + title + "</h1>");
		pout.println("<h2>calling ejb...</h2>");
		try {
			pout.println("parameter Add" + req.getParameter("Add") + "<br />");
			pout.println("parameter Find  " + req.getParameter("Find") + "<br />");
			pout.println("parameter isbnCode" + req.getParameter("isbnCode")
					+ "<br />");
			pout.println("paramter Author  " + req.getParameter("author")
					+ "<br />");
			pout.println("<p>");
			if (req.getParameter("Add") != null) {
				AuthorsKey key = new AuthorsKey(
						(String) req.getParameter("isbnCode"));
				bean = authorsHome.create(key);
				if (req.getParameter("author") != null)
					bean.setAuthorName(req.getParameter("author"));
				pout.println("created  " + req.getParameter("isbnCode")
						+ "entry");
			}
			if (req.getParameter("Find") != null) {
				try {
					if (req.getParameter("isbnCode") != null
							&& req.getParameter("isbnCode").length() > 0) {
						AuthorsKey key = new AuthorsKey(
								(String) req.getParameter("isbnCode"));
						bean = authorsHome.findByPrimaryKey(key);
						pout.println("found author name   "
								+ bean.getAuthorName() + "  entry");
					}
				} catch (Exception e) {
					pout.println("not found " + "<br />");
					e.printStackTrace();
				}
			}
		} catch (Exception e) {
			pout.print(e.toString());
		} finally {
			pout.println("</body></html>");
			pout.close();
		}
	}

	public void doGet(HttpServletRequest req, HttpServletResponse res)
			throws ServletException, IOException {
		PrintWriter pout = res.getWriter();
		pout.print("<html><body>");
		pout.print("<form name='AuthorsForm' method=post action='./AuthorsServlet'>");
		pout.println("<p> Isbn code:");
		pout.println("<input type='text' name='isbnCode' size='10'>");
		pout.println("<p>  Author");
		pout.println("<input type='text' name='author' size='12'>");
		pout.println("<input type='submit' name='Add' value='Add'>");
		pout.println("<input type='submit' name='Find' value='Find'>");
		pout.println("</form>");
		pout.println("</body></html>");
	}
}

Interest.ear file has the following structure on deployment to jboss:

Authors.ear
     |<meta-INF>
          |application.xml
     |Authors.jar
          |<meta-INF>
               |ejb-jar.xml
               |jboss.xml
               |jbosscmp-jdbc.xml
          |<shoppingcart>
               |AuthorsBean.class
               |Authors.class
               |AuthorsHome.class
               |AuthorsKey.class
     |Authors.war
          |<meta-INF>
          |<web-INF>
               |web.xml
               |<classes>
                         |<shoppingcart>
                              |Authors.class
                              |AuthorsHome.class
                              |AuthorsKey.class
                              |AuthorsServlet.class
               |<lib>
                              |jboss-client.jar
                              |jnp-client.jar

To run the application remember to perform the following steps:
1.set JBOSS_HOME and ANT_HOME environment variables to appropriate folders containing the folder in ant and jboss. Also set / in your path variable.
2.construct work folder as described above
3.Open dos prompt at work folder to build project – “ant build-all”
To redeploy – “ant clean” – and then – “ant build-all”
When you run build.bat at the dos prompt, the files in folder are compiled and appropriate class files are placed in folder, war, jar and ear files are created and the ear file is deployed to / folder.
Start jboss
Type the following url on the browser.
http://localhost:8080/AuthorsServlet

You will be prompted by the jsp page to create or find an entry in the authors table.
To find an entry you only need to set the ISBN_CODE
To create an entry you need to set both ISBN_CODE as well as AUTHOR
In case the above criteria are not fulfilled an exception will be thrown as the program is not sophisticated enough to handle such cases. An exception is also thrown if an attempt is made to create an entity when it already exists in the database.
After creating an entry you login to the mySQL console and check the presence of the entry.
You can run the application again and check out the existence of the entry by entering the ISBN_CODE.
The output will be something like this:

Servlet Interface to EJB
calling ejb...
parameter Add null
parameter Find Find
parameter isbnCode gogo
parameter Author
found author name martin entry

I will be using this example to demonstrate how to use struts with ejb as well as some other demos.

EJB – 1 Creating a simple session bean

This example is deprecated.

I am running jboss-4.0.3SP1 on Windows XP.
Note what I have described is creation and deployment of a stateless session bean for Jboss. For other Application Servers, the configuration process might change and EJB lookup code using JNDI might vary. Anyway the rest of the code will prove useful when creating a session EJB from scratch with minor changes at places.
My work folder for ant has the following structure.

web.xml
application.xml
ejb-jar.xml
jboss.xml
build.bat
build.xml
build.propeties
<build>
<src>
    |<gorg>
      |<jboss>
        |<docs>
          |<interest>
              |InterestBean.java
              |Interest.java
              |InterestHome.java
              |InterestServlet.java
</pre>

ejb-jar.xml looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 1.1//EN" "http://java.sun.com/j2ee/dtds/ejb-jar_1_1.dtd">
<ejb-jar>
	<description>JBoss Interest Sample Application</description>
	<display-name>Interest EJB</display-name>
	<enterprise-beans>
		<session>
			<ejb-name>Interest</ejb-name>
			<home>gorg.jboss.docs.interest.InterestHome</home>
			<remote>gorg.jboss.docs.interest.Interest</remote>
			<ejb-class>gorg.jboss.docs.interest.InterestBean</ejb-class>
			<session-type>Stateless</session-type>
			<transaction-type>Bean</transaction-type>
		</session>
	</enterprise-beans>
</ejb-jar>

The ejb-jar.xml is a deployment descriptor and was introduced with EJB 2.0.
It describes the entity or session beans that are included in the application.
Every ejb-jar.xml has exactly one tag, which may contain , or tags and some other optional tags
The contains the names of the home, remote and the bean class as well as some other info.
The tag contains the descriptive name of the bean.
The tag contains the fully qualified name of the home class.
The tag contains the fully qualified name of the remote class.
The tag contains the fully qualified name of the bean class.
The <session-type&g; tag and the are specific to session beans.
In case of entity beans there is a tag that contains the fully qualified name for the entity bean’s primary key class.
This may be a primitive data type such as java.lang.String for a simple key or a fully qualified class name for a compound key.
The tag declares whether the bean can call another bean which calls the original bean.
The tag contains a tag that describes the cmp fields in the entity bean.
Lastly the tag contains the name of the primary key field (for simple pry key types) and is not required for compound keys.
jboss.xml looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<jboss>
	<enterprise-beans>
		<session>
			<ejb-name>Interest</ejb-name>
			<jndi-name>interest/Interest</jndi-name>
		</session>
	</enterprise-beans>
</jboss>

jboss.xml is a container specific configuration file that is used to define the jndi names for the beans in the package.

application.xml looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE application PUBLIC '-//Sun Microsystems, Inc.//DTD J2EE Application 1.2//EN' 'http://java.sun.com/j2ee/dtds/application_1_2.dtd'>
<application>
	<display-name>Interest EJB</display-name>
	<module>
		<ejb>Interest.jar</ejb>
	</module>
	<module>
		<web>
			<web-uri>Interest.war</web-uri>
			<context-root></context-root>
		</web>
	</module>
</application>

web.xml looks like this:

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app     PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
	<servlet>
		<servlet-name>InterestServlet</servlet-name>
		<servlet-class>gorg.jboss.docs.interest.InterestServlet</servlet-class>
	</servlet>
	<servlet-mapping>
		<servlet-name>InterestServlet</servlet-name>
		<url-pattern>/InterestServlet</url-pattern>
	</servlet-mapping>
</web-app>

web.xml contains the descriptive names for the servlets in the application as well as the fully qualifed servlet name.
It also maps the servlet to a url pattern.
build.xml looks like this:

<project name="Interest" default="build-all" basedir=".">

	<property environment="env" />
	<property file="./build.properties" />

	<!-- the build path -->
	<path id="build.path">
		<pathelement location="${jboss.dist}/server/default/lib" />
		<pathelement location="${jboss.dist}/server/default/lib/jboss-j2ee.jar" />
		<pathelement location="${jboss.dist}/client/javax.servlet.jar" />
		<pathelement location="${build.classes.dir}" />
	</path>

	<target name="war" depends="compile">
		<war warfile="${war}" webxml="web.xml">
			<classes dir="${build.classes.dir}">
				<include name="gorg/jboss/docs/interest/InterestServlet.class" />
				<include name="gorg/jboss/docs/interest/${appname}.class" />
				<include name="gorg/jboss/docs/interest/${appname}Home.class" />
			</classes>
			<lib dir="${jboss.client.dir}">
				<include name="jboss-client.jar" />
				<include name="jnp-client.jar" />
			</lib>
		</war>
	</target>

	<target name="jar" depends="compile">
		<jar jarfile="${jar}">
			<fileset dir="${build.classes.dir}">
				<include name="gorg/jboss/docs/interest/${appname}.class" />
				<include name="gorg/jboss/docs/interest/${appname}Home.class" />
				<include name="gorg/jboss/docs/interest/${appname}Bean.class" />
			</fileset>
			<metainf dir="${basedir}" includes="jboss.xml,ejb-jar.xml" />
		</jar>
	</target>

	<!-- build all, and copy to the jboss/deploy directory -->

	<target name="ear" depends="jar,war">
		<ear earfile="${ear}" appxml="application.xml">
			<fileset dir="${basedir}" includes="${jar},${war}" />
		</ear>
	</target>

	<!-- compilation options -->

	<target name="compile">
		<mkdir dir="${build.classes.dir}" />
		<javac srcdir="${src.dir}" destdir="${build.classes.dir}" debug="on"
			deprecation="on" classpathref="build.path" optimize="off" />
	</target>
	<target name="build-all" depends="ear">
		<copy file="${ear}" todir="${jboss.deploy.dir}" />
	</target>
	<target name="clean">
		<delete file="${jar}" />
		<delete file="${ear}" />
		<delete file="${war}" />
		<delete file="${jboss.deploy.dir}/${ear}" />
		<delete dir="${build.classes.dir}" />
	</target>
</project>

build.properties looks like this:

appname=Interest
# get JBOSS location from environment variable
dist.root=${env.JBOSS_HOME}
jboss.dist=${dist.root}
jboss.deploy.dir=${jboss.dist}/server/default/deploy
jboss.client.dir=${jboss.dist}/client

src.dir=${basedir}/src
src.docroot=${src}/docroot
build.dir=${basedir}/build
build.classes.dir=${build.dir}/classes

war=${appname}.war
jar=${appname}.jar
ear=${appname}.ear

Remember, to run build.xml using the build.bat provided by ant, you need to set JBOSS_HOME environment variable pointing to the jboss folder that contains the bin folder.
When you run build.bat at the dos prompt, the files in folder are compiled and appropriate class files are placed in folder, war, jar and ear files are created and the ear file is deployed to / / folder.
In the gorgjbossdocsinterest folder I have the following source files:
InterestBean.java

package gorg.jboss.docs.interest;

import java.rmi.RemoteException;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;

public class InterestBean implements SessionBean {
	public double calculateCompoundInterest(double principle, double rate,
			double periods) {
		System.out.println("Someone called 'calculate Compound Interest!'");
		return principle * Math.pow(1 + rate, periods) - principle;
	}

	public void ejbCreate() {
	}

	public void ejbPostCreate() {
	}

	public void ejbRemove() {
	}

	public void ejbActivate() {
	}

	public void ejbPassivate() {
	}

	public void setSessionContext(SessionContext sc) {
	}
}

Interest.java

package gorg.jboss.docs.interest;

import javax.ejb.EJBObject;
import java.rmi.RemoteException;

public interface Interest extends EJBObject {
	public double calculateCompoundInterest(double principle, double rate,
			double periods) throws RemoteException;
}

InterestHome.java

package gorg.jboss.docs.interest;

import java.io.Serializable;
import java.rmi.RemoteException;
import javax.ejb.CreateException;
import javax.ejb.EJBHome;

public interface InterestHome extends EJBHome {
	Interest create() throws RemoteException, CreateException;
}

InterestServlet.java

package gorg.jboss.docs.interest;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Hashtable;
import java.util.Properties;
import javax.naming.InitialContext;
import javax.rmi.PortableRemoteObject;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import gorg.jboss.docs.interest.Interest;
import gorg.jboss.docs.interest.InterestHome;

public class InterestServlet extends HttpServlet {
	private InterestHome interestHome = null;

	public void init() throws ServletException {
		try {
			Properties p = new Properties();
			p.setProperty("java.naming.factory.initial",
					"org.jnp.interfaces.NamingContextFactory");
			p.setProperty("java.naming.provider.url", "localhost:1099");
			p.setProperty("java.naming.factory.url.pkgs",
					"org.jboss.naming:org.jnp.interfaces");
			InitialContext jndiContext = new InitialContext(p);
			Object ref = jndiContext.lookup("interest/Interest");
			interestHome = (InterestHome) PortableRemoteObject.narrow(ref,
					InterestHome.class);
		} catch (Exception e) {
			throw new ServletException(
					"Failed to lookup java:comp/env/ejb/Interest", e);
		}
	}

	public void doPost(HttpServletRequest req, HttpServletResponse res)
			throws ServletException, IOException {
		String title = "Servlet Interface to EJB";
		double principal = getValue("principal", 1000.0, req);
		double rate = getValue("rate", 10.0, req);
		double periods = getValue("periods", 2.0, req);
		res.setContentType("text/html");
		PrintWriter out = res.getWriter();
		out.println("<html><head><title>");
		out.println(title);
		out.println("</title></head><body>");
		out.println("<h1>" + title + "</h1>");
		out.println("<h2>   Calling EJB.......</h2>");
		try {
			Interest bean = interestHome.create();
			out.print("Interest on " + principal);
			out.print("units at " + rate);
			out.print("% per period, compounded over   " + periods);
			out.println(" periods is ");
			out.println(bean.calculateCompoundInterest(principal, rate / 100,
					periods));
			bean.remove();
		} catch (Exception e) {
			out.println(e.toString());
		} finally {
			out.println("</body></html>");
			out.close();
		}
	}

	private double getValue(String name, double defaultValue,
			HttpServletRequest req) {
		double value = defaultValue;
		String pvalue = req.getParameter(name);
		if (pvalue != null) {
			try {
				value = Double.valueOf(pvalue).doubleValue();
			} catch (NumberFormatException e) {
			}
		}
		return value;
	}

	public void doGet(HttpServletRequest req, HttpServletResponse res)
			throws ServletException, IOException {
		doPost(req, res);
	}
}

Use ant command “ant clean” at the dos prompt to clean compiled resources.
And ant command “ant build-all” to redeploy the folder to jboss.
The Interest.ear file has the following structure when it is deployed to jboss deploy folder”

Interest.ear
   |<meta-INF>
      |application.xml
   |Interest.jar
      |<gorg>
         |<jboss>
            |<docs>
               |<interest>
                  |InterestBean.class
                  |InterestHome.class
                  |Interest.class
      |<meta_INF>
         |ejb-jar.xml
         |jboss.xml
   |Interest.war
      |<meta-INF>
      |<web-INF>
         |web.xml
         |<classes>
            |<gorg>
               |<jboss>
                  |<docs>
                     |<interest>
                        |Interest.class
                        |InterestHome.class
                        |InterestServlet.class
         |<lib>
            |jboss-client.jar
            |jnp-client.jar

To run the application remember to perform the following steps:
1.set JBOSS_HOME and ANT_HOME environment variables to appropriate folders containing the folder in ant and jboss. Also set / in your path variable.
2.construct work folder as described above
3.Open dos prompt at work folder to build project – “ant build-all”
To redeploy – “ant clean” – and then – “ant build-all”
The ear file is deployed in servers/default/deploy in jboss

notice that
a) the parameters including environment variables are defined in a separate file, build.properties and this is simply referred to in the build.xml
b) You don’t have to copy the jboss jar files to your war lib directory to be included in the war, build.xml will simply pick it up from JBOSS_HOME/client

Start jboss
Type the following url on the browser.
http://localhost:8080/InterestServlet
You will get the following message on successful output.

Servlet Interface to EJB
Calling EJB.......
Interest on 1000.0units at 10.0% per period, compounded over 2.0 periods is 210.00000000000023

The Jboss console would show:

23:58:08,180 INFO  [STDOUT] Someone called 'calculate Compound Interest!'


Happy Programming!