TODO: Documenting updated version (currently at http://www.i-tao.com/adminapp.html). IN PROGRESS.
Introduction
This page aims at providing some additional information about the Hibernate AdminApp. The Hibernate AdminApp (hereafter referred to as AA) was created by the Hibernate developers to show a possible implementation strategy for Hibernate with Webwork. Although AA can still be used as a starting point for webapplications, most of its libraries become quite aged (WW 2.0 beta, Hibernate 2, XWork 1.0 beta). Therefore, a shiny new fork (AA2) has been created by Ron Chan.
AA2 relies on WW2.2, Hibernate 3.1, and Spring as its IoC container (rather than XWork's, which has been deprecated in WW 2.2). We'll first discuss the original AA. Later on, we'll show the differences with AA2. Ron, if you're reading this, feel free to point out any mistakes/edit this document.
Like we pointed out before, AA shows a possible implementation strategy to use Hibernate in WebWork in combination with a so-called open-session-in-view pattern (more info, even more). This pattern allows maximum flexibility in our view layer by creating a Hibernate Session object that will live till the end of the request (after the view has been rendered). This allows lazy loading of objects in our view, rather than having to preload all objects and their associations in our business layer, and yet ensures the correct disposing of the Session object.
To accomplish this, AA uses XWork's components and interceptors:
- components: XWork manages the lifecycle of objects in several scopes (application, session, request) and takes care of the IoC through the ..Aware interfaces (so called enablers). Hibernate's expensive-to-create SessionFactory will thus be created in the application scope (meaning it will only be initialised once when the application starts up), while the Session objects, used to load our models, is registered in the request scope (will be created once per request).
- interceptors: AA uses an interceptor (the HibernateInterceptor) to extract the Session from the WebWork action, so it can control the transactions, redirect/rollback on errors and properly dispose the Session after the view is rendered.
AdminApp Source Overview
Now, let's properly dissect the AA files:
- /lib: contains the various jars for our application. Nothing special here.
- /src/java/org/hibernate/admin/action: lists our WebWork actions. All actions extend an abstract AbstractAction file, which overrides the execute() method from our XWork's ActionSupport. This is where we define a setHibernateSession() method, which is the method we declared in our enabler interface (HibernateSessionAware). This will notify XWork to invoke its IoC magic to set the HibernateSession.
public String execute() throws Exception { // We go to INPUT on field and data errors if ( hasErrors() ) { LOG.debug("action not executed, field or action errors"); LOG.debug( "Field errors: " + getFieldErrors() ); LOG.debug( "Action errors: " + getActionErrors() ); return INPUT; } LOG.debug("executing action"); return go(); } protected abstract String go() throws HibernateException; public void setHibernateSession(HibernateSession session) { this.session = session; } protected Session getSession() throws HibernateException { return session.getSession(); }
In this execute() method we'll simply call a abstract go() method (which is then defined in each of the actions). When we need the Hibernate Session, we use the getSession() method, inherited from our AbstractAction. Don't worry about transactions or saving so called dirty objects (our HibernateInterceptor takes care of all that). As you can see, this totally minimizes the LoC (lines of code) needed to retrieve or manipulated our models).
public class EditUserAction extends AbstractAction { //.. ommited for brevity protected String go() throws HibernateException { .. getSession().update(user); .. return SUCCESS; } //.. getters and setters ommited }
There are 3 more *-validation.xml files in this directory containing the validation logic for the Actions. XWork will validate your request before the action gets executed, so you can decouple your (simple) validation logic from your Action. For example, take a look at the CreateUserAction-validation.xml:
.. <field name="user.name.lastName"> <field-validator type="requiredstring"> <message>You must enter a last name.</message> </field-validator> </field> <field name="user.email"> <field-validator type="email"> <message>Please correct the e-mail address.</message> </field-validator> <field-validator type="required"> <message>Please enter an e-mail address.</message> </field-validator> </field> ..
Several validator types are available. Here we rely on XWork to validate our Actions, but it's also possible to validate our object Models (see WW Validation). You will mostly use these to validate submitted forms in your webapp.
When a validator fails, you will automatically be returned to the input page with a clear indication which field failed to validate if:
a) actually provided an input type in your struts.xml file
.. <result name="input" type="dispatcher"> <param name="location">/editUser.jsp</param> </result> ..
b) you enabled the validation interceptor in your struts.xml
.. <interceptor-ref name="defaultStack"/> <interceptor-ref name="validation"/> ..
c) you use the WebWork tag library (warning: this is the old syntax):
.. <ww:form name="'createUserForm'" action="'createUser.action'" method="'POST'"> <ww:textfield label="'Username'" name="'user.handle'"/> ..
New syntax (since 2.2):
.. <ww:form name="createUserForm" action="createUser" method="POST"> <ww:textfield label="Username" name="user.handle"/> ..
- /src/java/org/hibernate/admin/component: contains the components and enablers for both the HibernateSessionFactory and the HibernateSession. These components are declared in the /src/java/components.xml file (which will be copied to the root of your compiled classes afterwards):
<components> <component> <scope>request</scope> <class>org.hibernate.admin.component.HibernateSession</class> <enabler>org.hibernate.admin.component.HibernateSessionAware</enabler> </component> <component> <scope>application</scope> <class>org.hibernate.admin.component.HibernateSessionFactory</class> <enabler>org.hibernate.admin.component.HibernateSessionFactoryAware</enabler> </component> </components>
- /src/java/org/hibernate/admin/interceptor: contains the Hibernate interceptor. Interceptors are an incredibly powerful feature of WebWork - it allows you to control invocations before and after they excute, manipulate their results, or, as in our case, extract the HibernateSession object and dispose it after the Action has been executed (and the view rendered). Because we use a try/catch/finally block, we're able to catch exceptions and yet make sure our Session gets closed properly (the number one cause of db connection leaks).
public String intercept(ActionInvocation invocation) throws Exception { Action action = invocation.getAction(); if ( !(action instanceof AbstractAction) ) return invocation.invoke(); HibernateSession hs = ( (AbstractAction) action ).getHibernateSession(); try { return invocation.invoke(); } // Note that all the cleanup is done // after the view is rendered, so we // have an open session in the view catch (Exception e) { hs.setRollBackOnly(true); if (e instanceof HibernateException) { LOG.error("HibernateException in execute()", e); return Action.ERROR; } else { LOG.error("Exception in execute()", e); throw e; } } finally { try { hs.disposeSession(); } catch (HibernateException e) { LOG.error("HibernateException in dispose()", e); return Action.ERROR; } } }
Conclusion
In this document, we tried to point out several key features in the Hibernate AdminApp. In part II, we'll have a look at the new AdminApp, which is far more up to date, and uses Spring as its IoC container. No more implements ActionSupport or Aware interfaces, resulting in even cleaner code.
AdminApp is a very good example of how a webapp can be structered, using as many advantages from the various frameworks as possible.