Apache Struts 2 Documentation > Home > Tutorials > AdminApp |
TODO: Documenting updated version (currently at http://www.i-tao.com/adminapp.html). IN PROGRESS.
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:
Now, let's properly dissect the AA files:
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"/> ..
<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>
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; } } }
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.