Spring 2.0 - Struts 1.2.9, Hibernate 3.1.3 - sample application



Spring2/Struts1.2/Hibernate3 utilized in a minimal web application

Some advantages of spring: Purpose of this text is to demonstrate the smallest possible practical web application example, while still illustrating some important framework aspects. The down-to-earth functionality of this example is basicly to manage a set of attributes, ie. to Create/Retrieve/Update/Delete (CRUD).

Struts is used to perform the web-presentation and control the actions initiated by the client. Spring is used in the middle layer to manage a data source, facilitate easy DAO processing, possibly specify declarative transactional control. For convenience, I've provided the source here. It even contains all necessary third-party .jar files. Please notice, that everything is provided AS-IS with no insurance that anything may actually work for anybody etc. I might not be using the latest Spring 2.0 features etc. But this is mainly intended as simple example.

If assembling the libraries yourself, for this example you'll need: When you use the Spring framework, some things will look differently for Hibernate. Most notably you don't need a 'hibernate.cfg.xml' anymore. Instead you will specify a spring framework datasource and the Hibernate dialect in an 'applicationContext.cml' configuration file. Hibernate is also acessed indirectly and programming DAO's requires less code.



The setup consist of:


Serverside programmtic logic (really just 10k worth of code):

Webtier presentation pages (goes unchanged into a .war):

Log4j debug traceconfiguration:

Deployment descriptor:

Struts configuration:

Spring configuration (indirectly wired up in 'struts-config.xml'):


Sitemesh filter (defined in web.xml by <filter>):


Taking offset in the programmatic logic, the fundamental data object here is dk.topsecurity.hibernate.Attribute, which contains the data passed between the different layers:

Value object
dk.topsecurity.hibernate.Attribute.java

package dk.topsecurity.hibernate;

import java.util.Date;

/**
 * POJO (Plain old Java Object) which serves as Value Object according to
 * http://java.sun.com/blueprints/corej2eepatterns/Patterns/TransferObject.html
 */
public class Attribute extends JoshuaBlochBaseObject {
    private Long id;
    private String attributeName;
    private String attributeValue;
    private Date attributeDate = new Date(); //initialized at construction
    
    public Long getId() { return id; }

    public void setId(Long newId) { id = newId; }
    
    public String getAttributeName() { return attributeName; }

    public void setAttributeName(String name) { attributeName = name; }

    public String getAttributeValue() { return attributeValue; }

    public void setAttributeValue(String value) { attributeValue = value; }

    public Date getAttributeDate() { return attributeDate; }

    public void setAttributeDate(Date date) { attributeDate = date; }

}

The idea here is that the web application keeps track of (name,value) pairs (attributes), each accompanied by a timestamp showing when the attribute was last changed.

The extension of 'JoshuaBlochBaseObject' ensures a serializable value object and provides a number of convenient hash and comparing methods according the the scheemes in the book "Effective Java" by Joshua Bloch.


Value object scheemes
dk.topsecurity.hibernate.JoshuaBlochBaseObject.java

package dk.topsecurity.hibernate;

import java.io.Serializable;
import org.apache.commons.lang.builder.*; //EqualsBuilder,HashCodeBuilder,ToStringBuilder,ToStringStyle

/**
 * Template for objects offering toString, equals and hashCode methods.
 * Follows rules laid out in the book Effective Java by Joshua Bloch. 
 * Writing a good hashCode method is actually quite difficult. 
 * This class aims to simplify the process.
 *
 */
public class JoshuaBlochBaseObject implements Serializable {
    public String toString() {
        return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE); }
    public boolean equals(Object obj) { return EqualsBuilder.reflectionEquals(this, obj); }
    public int hashCode() { return HashCodeBuilder.reflectionHashCode(this); }
}

The 'Attribute' class is wired into the Hibernate database mapping relation through use of 'Attribute.hbm.xml', which is standard Hibernate strategy regardless of Spring or not.

The corresponding DAO has the interface:

DAO interface
dk.topsecurity.springdao.AttributeDAOImpl.java

package dk.topsecurity.springdao;

import java.util.List;
import dk.topsecurity.hibernate.*;

/**
 * Interface for DAO object, used by business delegate layer to access.
 * DAO according to http://java.sun.com/blueprints/corej2eepatterns/Patterns/DataAccessObject.html
 */
public interface AttributeDAO {
    public List getAttributes();
    public Attribute getAttribute(Long attributeId);
    public boolean existAttribute(String attributeName);
    public void saveAttribute(Attribute attribute);
    public void removeAttribute(Long attributeId);
}

The DAO interface is free from definition of Hibernate checked exceptions. This is because the DAO implmentation makes use of spring's HibernateDaoSupport, which handles a lot of the underlying Hibernate checked exceptions, which would normally be necessary to handle at the DAO layer. HibernateDaoSupport provides threadsafe factory access to obtain a reference to the session factory for the underlaying database layer.

The actual DAO implementation can appear as:
DAO implmentation
dk.topsecurity.springdao.AttributeDAOImpl.java

package dk.topsecurity.springdao;

import java.util.List;

import org.apache.commons.logging.*;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.hibernate.Hibernate;

import dk.topsecurity.hibernate.*;


/**
 * DAO object according to
 * http://java.sun.com/blueprints/corej2eepatterns/Patterns/DataAccessObject.html
 */
public class AttributeDAOImpl extends HibernateDaoSupport implements AttributeDAO {
    private static Log log = LogFactory.getLog(AttributeDAOImpl.class);

    public List getAttributes() { return getHibernateTemplate().find("from Attribute"); }

     //http://www.springframework.org/docs/api/org/springframework/orm/hibernate3/HibernateTemplate.html
    public boolean existAttribute(String attributeName) { 
      List l = getHibernateTemplate().find("from Attribute where name=?", attributeName); 
      return (l!=null && l.size()!=0);
    }

    public Attribute getAttribute(Long attribId) { return (Attribute) getHibernateTemplate().get(Attribute.class, attribId); }

    public void saveAttribute(Attribute attrib) {
        getHibernateTemplate().saveOrUpdate(attrib);
        log.debug("attributeId set to: " + attrib.getId());
    }

    public void removeAttribute(Long attribId) { 
      try {
        getHibernateTemplate().delete( getHibernateTemplate().load(Attribute.class, attribId) ); 
      }
      catch(Throwable tr) {
         log.error("Hibernate failed to delete entry: ");
         tr.printStackTrace();
      }
    }
}

'getHibernateTemplate()' returns a HibernateTemplate which wraps Hibernate exceptions into runtime exceptions - relieving the DAO from Hibernate exception declarations.

The DAO is wired into the context by 'applicationContext.xml':


Spring usage of DAO
./WEB-INF/applicationContext.xml

...
    <bean id="attributeDAO" class="dk.topsecurity.springdao.AttributeDAOImpl">
        <property name="sessionFactory"><ref local="sessionFactory"/></property>
    </bean>    
...

This defines sessionfactory to be obtained from getHibernateTemplate() in the AttributeDAOImpl context. If one has already been opened in the webtier context, it is continously returned on DAO requests.

The business delegate layer is represented by 'AttributeManager.java', which has an interface very similar to the DAO. Reason for introducing a business delegate layer is avoiding calls from webtier directly to system dependant DAO objects. Even though the interfaces are very similar, it still provides decoupling of business logic.


business delegate layer - interface
dk.topsecurity.businessdelegate.AttributeManager.java

package dk.topsecurity.businessdelegate;

import java.util.List;
import dk.topsecurity.hibernate.*; //Attribute

/**
 * Business delegate layer to seperate DAO from web presentation
 * according to http://www.corej2eepatterns.com/Patterns2ndEd/BusinessDelegate.htm
 */
public interface AttributeManager {
    public List getAttributes();
    public Attribute getAttribute(String attributeId);
    public boolean existAttribute(String attributeName);
    public Attribute saveAttribute(Attribute attribute);
    public void removeAttribute(String attributeId);
}

The implementation basicly consist of pass-through calls to the DAO layer. The justification of the business delegate layer is purely an abstraction from the underlying system-dependant usage of Hibernate. In fact the weblayer knows nothing about the database persistance layer - not even about Hibernate-dependant exceptions, which might occur.


business delegate layer - implementation
dk.topsecurity.businessdelegate.AttributeManagerImpl.java

package dk.topsecurity.businessdelegate;

import java.util.List;

import org.apache.commons.logging.*; //Log,LogFactory
import dk.topsecurity.springdao.*;
import dk.topsecurity.hibernate.*;


/**
 * Business delegate layer to seperate DAO from web presentation
 * according to http://www.corej2eepatterns.com/Patterns2ndEd/BusinessDelegate.htm
 */
public class AttributeManagerImpl implements AttributeManager {
    private static Log log = LogFactory.getLog(AttributeManagerImpl.class);
    private AttributeDAO dao;

    public void setAttributeDAO(AttributeDAO newDao) { dao = newDao; }

    public List getAttributes() { return dao.getAttributes(); }

    public boolean existAttribute(String attributeName) { return dao.existAttribute(attributeName); }

    public Attribute getAttribute(String attributeId) {
        Attribute attribute = dao.getAttribute(Long.valueOf(attributeId));

        if (attribute == null) {
            log.warn("AttributeId '" + attributeId + "' not found in database.");
        }

        return attribute;
    }

    public Attribute saveAttribute(Attribute attribute) {
        dao.saveAttribute(attribute);
        return attribute;
    }

    public void removeAttribute(String attributeId) {  dao.removeAttribute(Long.valueOf(attributeId)); }
}

The business delegate layer is wired into the context by 'applicationContext.xml':


Spring usage of DAO
./WEB-INF/applicationContext.xml

...
    <bean id="attributeManager" 
        class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <property name="transactionManager"><ref local="transactionManager"/></property>
        <property name="target"><ref local="attributeManagerTarget"/></property>
        <property name="transactionAttributes">
            <props>
                <prop key="save*">PROPAGATION_REQUIRED</prop>
                <prop key="remove*">PROPAGATION_REQUIRED</prop>
                <prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
            </props>
        </property>
    </bean>
    <bean id="attributeManagerTarget" class="dk.topsecurity.businessdelegate.AttributeManagerImpl">
        <property name="attributeDAO"><ref local="attributeDAO"/></property>
    </bean>
...

which enables declarative transaction to the handling of the manager - through the use of 'ProxyFactoryBean'.

According to bean-setting <property name=attributeDAO ...>, Spring figures out itself to call the method 'AttributeManagerImpl.setAttributeDAO(..)'in order to keep the DAO reference valid - and later to call 'dk.topsecurity.struts.AttributeAction.setAttributeManager(..)' to keep the action servlet reference valid to the delegate layer as well... according to bean-setting <property name="attributeManager ...> in action-servlet.xml

Now turning to the webtier presentation layer...

Assuming usage of struts, in each case where the client sends request for CRUD actions (create/retrieve/update/delete), they are all passed to the action servlet dk.topsecurity.struts.AttributeAction which performs some validation and passes the request further to the business delegate layer.

Practically the action class contains methods handling:
This should cover all aspects of the CRUD application model.
web layer programming logic
dk.topsecurity.struts.AttributeAction.java

package dk.topsecurity.struts;

import javax.servlet.http.*; //HttpServletRequest,HttpServletResponse
import java.util.Date;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts.action.*; //ActionForm,DynaActionForm,ActionMessages,ActionMessage,ActionMapping,ActionForward
import org.apache.struts.actions.*; //DispatchAction

import dk.topsecurity.hibernate.*;
import dk.topsecurity.businessdelegate.*;


/**
 * Struts framework 1.2+ action servlet support, which is wired into the
 * webapplication through 'action-servlet.xml'
 */
public class AttributeAction extends DispatchAction {
    private static Log log = LogFactory.getLog(AttributeAction.class);
    private AttributeManager mgr = null;

    public void setAttributeManager(AttributeManager attributeManager) { mgr = attributeManager; }

      //delete usecase
    public ActionForward delete(ActionMapping mapping, 
                                ActionForm form,
                                HttpServletRequest request,
                                HttpServletResponse response)
    throws Exception {
        log.debug("starting 'delete' method...");

        mgr.removeAttribute(request.getParameter("attribute.id"));
        ActionMessages messages = new ActionMessages();
        messages.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("attribute.deleted"));
        saveMessages(request, messages);

        return list(mapping, form, request, response);
    }

     //create/update usecase
    public ActionForward edit(ActionMapping mapping, 
                              ActionForm form,
                              HttpServletRequest request,
                              HttpServletResponse response)
    throws Exception {
        log.debug("starting 'edit' method...");

        DynaActionForm attributeForm = (DynaActionForm) form;
        String attributeId = request.getParameter("id");
        if (attributeId != null) { // null attributeId indicates an add
            Attribute attribute = mgr.getAttribute(attributeId);
            if (attribute == null) { //no attribute to corresponding id... 
                ActionMessages errors = new ActionMessages();
                errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("attribute.missing"));
                saveErrors(request, errors);
                return mapping.findForward("list");
            } //else update
            attribute.setAttributeDate( new Date() ); //currently updated
            attributeForm.set("attribute", attribute); //overwrite default attribute
        }

        return mapping.findForward("edit");
    }


     //retrieve usecase
    public ActionForward list(ActionMapping mapping, 
                              ActionForm form,
                              HttpServletRequest request,
                              HttpServletResponse response)
    throws Exception {
        log.debug("starting 'list' method...");

        request.setAttribute("listOfAllAttributes", mgr.getAttributes());
        return mapping.findForward("list");
    }

     //create/update usecase persistance
    public ActionForward save(ActionMapping mapping, 
                              ActionForm form,
                              HttpServletRequest request,
                              HttpServletResponse response)
    throws Exception {
        log.debug("starting 'save' method...");

          // run form validation rules - checking if field contents is valid
        ActionMessages errors = form.validate(mapping, request);
        if (!errors.isEmpty()) {
            saveErrors(request, errors);
            return mapping.findForward("edit");
        }
        DynaActionForm attributeForm = (DynaActionForm) form;
        Attribute attr=(Attribute)attributeForm.get("attribute");
        log.debug("found "+
            (attr==null ? "no":
               ("currently (id="+attr.getId()+")"))
            +" existing object");

          //forbid creation of attributes with same name
        if(attr.getId()==0 &&  //attemting to create new attribute
           mgr.existAttribute( attr.getAttributeName() ) //already attribute with same name
          ) {
          ActionMessages messages = new ActionMessages();
          messages.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("attribute.alreadyexists"));
          saveMessages(request, messages);
          return mapping.findForward("edit");
        }
        mgr.saveAttribute( attr );
        log.debug("saving at id="+attr.getId());
        ActionMessages messages = new ActionMessages();
        messages.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("attribute.saved"));
        saveMessages(request, messages);
        return list(mapping, form, request, response);
    }

      //default    
    public ActionForward unspecified(ActionMapping mapping, 
                                     ActionForm form,
                                     HttpServletRequest request,
                                     HttpServletResponse response)
	throws Exception {

    	return list(mapping, form, request, response);
    }
}

Normally when only struts are used in a web system, then the web.xml specifies usage of the standard org.apache.struts.action.ActionServlet, loading struts-config.xml, which uses a <action-mappings> to specify the actual action servlet implementation, handling affairs.

Things are a bit different when using spring. In web.xml you still specify org.apache.struts.action.ActionServlet to handle client requests - but in struts-config.xml under <action-mappings> you specify org.springframework.web.struts.DelegatingActionProxy - and then in action-servlet.xml loaded by spring you specify the actual action servlet handling affairs. The change here is the introduction of a proxy.


struts web layer
./WEB-INF/struts-config.xml

...
        <action path="/attribute" type="org.springframework.web.struts.DelegatingActionProxy" 
            name="attributeForm" scope="request" parameter="method" validate="false">
            <forward name="list" path="/attributeList.jsp"/>
            <forward name="edit" path="/attributeForm.jsp"/>
        </action>
...

Which takes us to:

spring web layer
./WEB-INF/action-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
    "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>
    <bean name="/attribute" class="dk.topsecurity.struts.AttributeAction" singleton="false">
        <property name="attributeManager"><ref bean="attributeManager"/></property>
    </bean>
</beans>

The struts form being generated from fields entered at the client goes through some validation before being turned over to the action servlet. Validation is defined in 'validation.xml', 'validator-rules.xml'. Unless both a 'name' and 'value' is entered, the client ir presented with an error message to do so. This is taken care by:


struts web layer
./WEB-INF/struts-config.xml

...
    <form-beans>
        <form-bean
            name="attributeForm" type="org.apache.struts.validator.DynaValidatorForm">
            <form-property name="attribute" type="dk.topsecurity.hibernate.Attribute"/>
        </form-bean>
    </form-beans>
...

The weblayer presentation requires text for presenting status or error messages. This is defined in 'message.properties' and should reside in classpath somewhere along with log4 property file etc.


jsp web layer texts
./WEB-INF/classes/message.properties

# CRUD application custom made messages for table labels and status messages
attribute.alreadyexists=Attribute by that name exists already and cannot be created
attribute.saved=Attribute saved successfully.
attribute.missing=Attribute with this id not found.
attribute.deleted=Attribute deleted successfully.
attribute.tabletitle=Attribute properties
attribute.id=Id
attribute.attributeName=Name
attribute.attributeValue=Value
attribute.attributeDate=Last modified

# standard error messageges necessary while using default validator rules as outlined in 'validator-rules.xml'
errors.required={0} necessary .
errors.minlength={0} no less than {1} characters.
errors.maxlength={0} no greater than {1} characters.
errors.invalid={0} is not valid.
errors.byte={0} should be a byte.
errors.short={0} should be a short.
errors.integer={0} should be an integer.
errors.long={0} should be a long.
errors.float={0} should be a float.
errors.double={0} should be a double.
errors.date={0} not a date.
errors.range={0} not in range {1} through {2}.
errors.creditcard={0} not valid credit card number.
errors.email={0} not valid e-mail address.

'message.properties' is loaded and made available to Struts via the <message-resources> element in strutsconfig.xml. From the jsp code message for 'attribute.attributeName' are trieeved using the 'http://jakarta.apache.org/struts/tags-bean' taglib and the jsp line <bean:message key="attribute.attributeName"/>. In the action servlet the messages are fx. accessed with messages.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("attribute.alreadyexists"));

The 'message.properties' is wired through the 'struts-config.xml' using:
struts web layer
./WEB-INF/struts-config.xml

...
    <message-resources parameter="messages"/>
...

Really there are mainly two jsp pages interacting with the user: The rest (error.jsp, 404.jsp, decorator/default.jsp,..) of the jsp pages are mainly for informational support.

jsp web layer - user interaction
attributeList.jsp

<%@ include file="/taglibs.jsp"%>
<title>CRUD reference application - retrieve usecase</title>
<table width="90%" border="1" cellpadding="1" cellspacing="1">
  <thead>
    <tr>
        <td colspan="4"><center><b><bean:message key="attribute.tabletitle"/></b></center></td>
    </tr>
    <tr>
      <th><bean:message key="attribute.id"/></th>
      <th><bean:message key="attribute.attributeName"/></th>
      <th><bean:message key="attribute.attributeValue"/></th>
      <th><bean:message key="attribute.attributeDate"/></th>
    </tr>
  </thead>
<!-- listOfAllAttributes is set in dispatch-action before referring to this jsp -->
  <tbody>
    <c:forEach var="attr" items="${listOfAllAttributes}" varStatus="status">
      <tr>
        <td><a href="attribute.do?method=edit&id=${attr.id}">${attr.id}</a></td>
        <td>${attr.attributeName}</td>
        <td>${attr.attributeValue}</td>
        <td>${attr.attributeDate}</td>
      </tr>
    </c:forEach>
  </tbody>
</table>
<br>
<button onclick="location.href='attribute.do?method=edit'">Add Attribute</button>
<br><br>

And the input field register page for entering new data or update existing:

jsp web layer - user interaction
attributeForm.jsp

<%@ include file="/taglibs.jsp"%>

Error page as defined in web.xml and indicated by scriptlet setting isErrorPage="true":


jsp web layer - default error page
error.jsp

<%@ page language="java" isErrorPage="true" %>

<head><title>CRUD reference application - error handling</title></head>

An Error has occurred in this application.  
<% if (exception != null) { %>
    <pre><% exception.printStackTrace(new java.io.PrintWriter(out)); %></pre>
<% } else { %>
    Exception unavailable, check log files for further info.
<% } %>

Decorator:


jsp web layer - decorator
error.jsp

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<!-- define errror page and set other stuff -->        
<%@ include file="/taglibs.jsp"%>

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head><title><decorator:title default="CRUD sample application"/></title></head>

<BODY text="#a5a5a5" bgcolor="#3c3c50" link="#ffffff" vlink="#f0f0f0" alink="#808080"> 


<a name="top"></a>

<h1><span>CRUD sample application default decorator</span></h1>

<strong>CRUD sample application</strong> aim to demonstrate a   
webapp using the 
<a href="http://www.springframework.org">Spring Framework</a> 
utilizing its build-in hibernate support, 
<a href="http://struts.apache.org">Struts</a> for presentation 
and input validation and
finally <a href="http://www.hibernate.org">Hibernate</a> for persistance.

<br><br><br>

<%@ include file="/messages.jsp"%>

<decorator:body />

</body>
</html>

Taglibs - shows what's in use:
jsp web layer
taglibs.jsp

<%@ page language="java" errorPage="/error.jsp" %>
<%@ taglib uri="http://jakarta.apache.org/struts/tags-bean" prefix="bean" %>
<%@ taglib uri="http://jakarta.apache.org/struts/tags-bean-el" prefix="bean-el" %>
<%@ taglib uri="http://jakarta.apache.org/struts/tags-html" prefix="html" %>
<%@ taglib uri="http://jakarta.apache.org/struts/tags-html-el" prefix="html-el" %>
<%@ taglib uri="http://jakarta.apache.org/struts/tags-logic" prefix="logic" %>
<%@ taglib uri="http://jakarta.apache.org/struts/tags-logic-el" prefix="logic-el" %>
<%@ taglib uri="http://jakarta.apache.org/struts/tags-tiles" prefix="tiles" %>
<%@ taglib uri="http://java.sun.com/jstl/core_rt" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jstl/fmt" prefix="fmt" %>
<%@ taglib uri="http://www.opensymphony.com/sitemesh/decorator" prefix="decorator"%>

A old and common alternative for including the taglibs in each and every jsp page would be to define a reference in 'web.xml' to 'tld's specifying the taglib specifics:


deployment descriptor - alternative taglib defintion (NOT USED!!!)
./WEB-INF/web.xml

...
  <!-- Struts Tag Library Descriptors -->
  <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>
....

Finally listing the remaining configuration files:

spring configuration
./WEB-INF/applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
    "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName"><value>org.hsqldb.jdbcDriver</value></property>
        <property name="url"><value>jdbc:hsqldb:db/crudapplication</value></property>
        <property name="username"><value>sa</value></property>
        <!-- Make sure <value> tags are on same 
             line - if they're not, 
             authentication will fail -->
        <property name="password"><value></value></property>
    </bean>
    <!-- Hibernate SessionFactory -->
    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
        <property name="dataSource"><ref local="dataSource"/></property>
        <property name="mappingResources">
            <list>
                <value>dk/topsecurity/hibernate/Attribute.hbm.xml</value>
            </list>
        </property>
        <property name="hibernateProperties">
        <props>
            <prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop>
            <prop key="hibernate.hbm2ddl.auto">create</prop>
        </props>
        </property>
    </bean>
    <!-- Alternative to JTA transaction manager for single Hibernate SessionFactory -->
    <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory"><ref local="sessionFactory"/></property>
    </bean>
    <bean id="attributeDAO" class="dk.topsecurity.springdao.AttributeDAOImpl">
        <property name="sessionFactory"><ref local="sessionFactory"/></property>
    </bean>    
    <bean id="attributeManagerTarget" class="dk.topsecurity.businessdelegate.AttributeManagerImpl">
        <property name="attributeDAO"><ref local="attributeDAO"/></property>
    </bean>
    <bean id="attributeManager" 
        class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <property name="transactionManager"><ref local="transactionManager"/></property>
        <property name="target"><ref local="attributeManagerTarget"/></property>
        <property name="transactionAttributes">
            <props>
                <prop key="save*">PROPAGATION_REQUIRED</prop>
                <prop key="remove*">PROPAGATION_REQUIRED</prop>
                <prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
            </props>
        </property>
    </bean>
</beans>

The decorator:
decorator wrapper definition
./WEB-INF/decorator.xml

<decorators defaultdir="/decorators">
    <decorator name="default" page="default.jsp">
          <pattern>/*</pattern>
    </decorator>
</decorators>

Which is also defined in the 'sitemesh.xml' - and sitemesh is activated by <filter-mapping%gt tag in web.xml):


sitemesh
./WEB-INF/sitemesh.xml

<sitemesh>
    <page-parsers>
        <parser default="true" 
            class="com.opensymphony.module.sitemesh.parser.DefaultPageParser" />
        <parser content-type="text/html" 
            class="com.opensymphony.module.sitemesh.parser.FastPageParser" />
        <parser content-type="text/html;charset=ISO-8859-1"
            class="com.opensymphony.module.sitemesh.parser.FastPageParser" />
    </page-parsers>

    <decorator-mappers>
        <mapper class="com.opensymphony.module.sitemesh.mapper.ConfigDecoratorMapper">
            <param name="config" value="/WEB-INF/decorators.xml" />
        </mapper>
    </decorator-mappers>
</sitemesh>

The entire Struts configuration file:

struts configuration
./WEB-INF/struts-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts-config PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 1.2//EN"
    "http://jakarta.apache.org/struts/dtds/struts-config_1_2.dtd">

<struts-config>

    <form-beans>
        <form-bean
            name="attributeForm" type="org.apache.struts.validator.DynaValidatorForm">
            <form-property name="attribute" type="dk.topsecurity.hibernate.Attribute"/>
        </form-bean>
    </form-beans>

    <global-forwards/>

    <action-mappings>

        <action path="/attribute" type="org.springframework.web.struts.DelegatingActionProxy" 
            name="attributeForm" scope="request" parameter="method" validate="false">
            <forward name="list" path="/attributeList.jsp"/>
            <forward name="edit" path="/attributeForm.jsp"/>
        </action>

    </action-mappings>

    <message-resources parameter="messages"/>

    <!-- load the action-servlet.xml file by default -->
    <plug-in className="org.springframework.web.struts.ContextLoaderPlugIn">
        <set-property property="contextConfigLocation" 
            value="/WEB-INF/applicationContext.xml,
                   /WEB-INF/action-servlet.xml"/>
    </plug-in>

    <plug-in className="org.apache.struts.validator.ValidatorPlugIn">
        <set-property
            property="pathnames" value="/WEB-INF/validator-rules.xml,
                                        /WEB-INF/validation.xml"/>
    </plug-in>
</struts-config>

The struts configuration makes a distinct reference for 'org.apache.struts.validator.ValidatorPlugIn' to the validation rules:


validation rule defintion
./WEB-INF/validation.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE form-validation PUBLIC 
    "-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.1//EN" 
    "http://jakarta.apache.org/commons/dtds/validator_1_1.dtd">

<form-validation>
    <formset>
        <form name="attributeForm">
            <field property="attribute.attributeName" depends="required">
                <arg0 key="attribute.attributeName"/>
            </field>
            <field property="attribute.attributeValue" depends="required">
                <arg0 key="attribute.attributeValue"/>
            </field>
        </form>
    </formset>
</form-validation>

and the 'validator-rules.xml' from a standard struts distribution:


validation rules defintion (standard struts config file contained in all major distributions)
./WEB-INF/validator-rules.xml

<!DOCTYPE form-validation PUBLIC
          "-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.1.3//EN"
          "http://jakarta.apache.org/commons/dtds/validator_1_1_3.dtd">
<!--
  $Id: validator-rules.xml 159291 2005-03-28 20:19:29Z niallp $

   This file contains the default Struts Validator pluggable validator
   definitions.  It should be placed somewhere under /WEB-INF and
   referenced in the struts-config.xml under the plug-in element
   for the ValidatorPlugIn.

      <plug-in className="org.apache.struts.validator.ValidatorPlugIn">
        <set-property property="pathnames" value="/WEB-INF/validator-rules.xml,
                                                  /WEB-INF/validation.xml"/>
      </plug-in>

   These are the default error messages associated with
   each validator defined in this file.  They should be
   added to your projects ApplicationResources.properties
   file or you can associate new ones by modifying the
   pluggable validators msg attributes in this file.

   # Struts Validator Error Messages
   errors.required={0} is required.
   errors.minlength={0} can not be less than {1} characters.
   errors.maxlength={0} can not be greater than {1} characters.
   errors.invalid={0} is invalid.

   errors.byte={0} must be a byte.
   errors.short={0} must be a short.
   errors.integer={0} must be an integer.
   errors.long={0} must be a long.
   errors.float={0} must be a float.
   errors.double={0} must be a double.

   errors.date={0} is not a date.
   errors.range={0} is not in the range {1} through {2}.
   errors.creditcard={0} is an invalid credit card number.
   errors.email={0} is an invalid e-mail address.

   Note: Starting in Struts 1.2.0 the default javascript definitions have
         been consolidated to commons-validator. The default can be overridden
         by supplying a <javascript> element with a CDATA section, just as
         in struts 1.1.
-->
<form-validation>
.................................................................................................................................................................................
.................................................................................................................................................................................
.................................................................................................................................................................................
 please refere to standard struts distributed binary for contents: ./struts-1.2.9-src/conf/share/validator-rules.xml 
.................................................................................................................................................................................
.................................................................................................................................................................................
.................................................................................................................................................................................
</form-validation>

And finally the web application deployment descriptor binding it all together:


web.xml
./WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4">

    <display-name>CRUD sample application name</display-name>
    <filter>
        <filter-name>sitemesh</filter-name>
        <filter-class>com.opensymphony.module.sitemesh.filter.PageFilter</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>sitemesh</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>FORWARD</dispatcher>
    </filter-mapping>

    <!-- enable struts actionservlet on startup -->
    <servlet>
        <servlet-name>action</servlet-name>
        <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <!-- enable struts actionservlet to handle all .do requests -->
    <servlet-mapping>
        <servlet-name>action</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>

    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>

    <error-page>
        <error-code>404</error-code>
        <location>/404.jsp</location>  
    </error-page>
    
    <error-page>
        <error-code>500</error-code>
        <location>/error.jsp</location>  
    </error-page>

</web-app>

Finally we need just the buildfile to compile and assemble the web application.

The build-file has mainly two callable targets. One for compiling and another for assembling the .war file, which should be immediately deployable on a Tomcat or equivalent servlet-continer.


compile ant file
build.xml

<?xml version="1.0"?>
<project name="crudapp" basedir="." default="all">

  <target name="init">
      <echo>+ ===================================================== +</echo>
      <echo>+                                                       +</echo>
      <echo>+  Init                                                 +</echo>
      <echo>+                                                       +</echo>
      <echo>+ ===================================================== +</echo>
      <property name="webapp.name" value="crudapp"/>
      <property name="build.java.dir" value="src"/>
      <property name="build.class.dir" value="build"/>
      <property name="build.web.dir" value="webfiles"/>
      <property name="build.war.dir" value="dist"/>

      <property environment="env"/>
      <property name="tomcat.home" value="${env.CATALINA_HOME}"/>
      <echo message="tomcat.home=${tomcat.home}"/>
  
      <path id="project.class.path">
          <fileset dir="${build.web.dir}/WEB-INF/lib">
              <include name="*.jar"/>
          </fileset>
          <fileset dir="${tomcat.home}/common/lib">
              <include name="servlet*.jar"/>
          </fileset>
          <pathelement path="${build.class.dir}"/>
      </path>
    </target>

    <target name="compile" depends="init">

	<echo>+ ===================================================== +</echo>
	<echo>+                                                       +</echo>
	<echo>+  Compiling                                            +</echo>
	<echo>+                                                       +</echo>
	<echo>+ ===================================================== +</echo>

        <mkdir dir="${build.class.dir}/classes"/>
        <javac destdir="${build.class.dir}/classes" debug="true"
            deprecation="false" optimize="false" failonerror="true">
            <src path="${build.java.dir}"/>
            <classpath refid="project.class.path"/>
        </javac>
        <!-- Copy hibernate mapping files to ${build.class.dir}/classes -->
        <copy todir="${build.class.dir}/classes">
            <fileset dir="${build.java.dir}" includes="**/*.hbm.xml"/>
        </copy>

    </target>


    <target name="war" depends="compile">
	<echo>+ ===================================================== +</echo>
	<echo>+                                                       +</echo>
	<echo>+  Building .war                                        +</echo>
	<echo>+                                                       +</echo>
	<echo>+ ===================================================== +</echo>
        <mkdir dir="${build.war.dir}"/>
        <war destfile="${build.war.dir}/${webapp.name}.war"
            webxml="${build.web.dir}/WEB-INF/web.xml">
            <classes dir="${build.class.dir}/classes"/>
            <fileset dir="${build.web.dir}">
                <include name="**/*.*"/>
                <exclude name="**/web.xml"/>
                <exclude name="**/junit.jar"/>
                <exclude name="**/*mock.jar"/>
                <exclude name="**/strutstestcase*.jar"/>
            </fileset>
        </war>
    </target>

    <target name="clean">
        <delete dir="build"/>
        <delete dir="${build.war.dir}"/>
    </target>

  <target name="all" depends="clean,compile" />

</project>

1) To get started, we need to compile using "ant" from the commandline:



2) Next, assemple the .war



3) drop it into Tomcat %CATALINA_HOME%/webapps directory.

4) start Tomcat from %CATALINA_HOME%/bin if you haven't done that already.

5) lauch the application from http://localhost:8080/crudapp or whereever you have your Tomcat running - and enter the application following link 'Start CRUD...'



6) Notice absence of data - fill in you own by pressing button 'add'



7) Fill in a set of data



8) Notice each time that attributes are added correctly

9) Notice you can't add an attribute with the same name twice (action servlet check) - and notice the message from messages.properties.



10) Edit existing attribute by following url link.



11) Change to another value.

Please notice that only the value field is editable - the name field is readonly. This is something ensured in attributeForm.jsp when generating the page. Also notice thar the delete button is visible here - but not on the add-attribute page. Finally submit by pressing the 'save' button





12) delete following the url link for the first entry and pressing delete


This pritty much concludes this scaled-down Spring/Struts/Hibernate example. It has been demonstrated, how to retrieve and manipulate data persisted in Hibernate - running in a servlet-container.



Ressources: