Tapestry and Hibernate example application

Updated: 24 Nov 2004

Document purpose: to show some simple steps in creating a
web application using Tapestry & Hibernate.

Application: dummy application containing just two tables
(contact, user)

diagram

Download: download the code (the
libraries are missing to have a smaller archive, you need the ones
mentioned in the jarlist file).

Updates to this version (12 oct 03)

  • Added HibernateGlobal method of using Hibernate
  • Changed login method using callbacks mechanism
  • Changed the ugly green with a more supportable blue
  • Moved Hibernate config to hibernate.cfg.xml
  • Added the AppPage base page including Hibernate and Validation
    functionality
  • switched to Tapestry 3.0beta3

Changes from the previous version:

  • Hibernate migration according to the guide found on the Hibernate
    site
  • Tapestry migration
    • change ```
      PUBLIC “-//Howard Lewis Ship//Tapestry Specification 1.3//EN”

      http://tapestry.sf.net/dtd/Tapestry_1_3.dtd">



      
      to
      
      

      PUBLIC “-//Apache Software Foundation//Tapestry Specification 3.0//EN”

      http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">



      
      and similar ones for *component-specification* and  
      *script*
      
    • change parameters to new names
    • move parts from .jwc and .page files to the HTML template
      (ex: jwcid=”@Form” instead of defining a form
      component in the .page). This simplifies things.
    • update code to use org.apache.tapestry instead of net.sf.tapestry
      and new methods names such as getPageName() instead
      of getName()
    • update everything to replace /net/sf/tapestry with /org/apache/tapestry
    • remove the RequestCycleException which does not seem to
      exist anymore

Library versions: Hibernate 2.0, Tapestry 3.0beta3

1. Start with the hibernate mapping files (contact.hdb.xml
and user.hdb.xml) for the database structure and the hibernate.cfg.xml
(I am using a postgresql database).

<class name="ro.nit.contacts.data.Contact" table="nitc_contact"><br></br>
<id name="id" column="contact_id" type="long"><br></br>
<generator class="native"/><br></br>
</id><property name="name" type="string" not-null="true" length="128"/><br></br>
<property name="data" type="string" length="512"/><br></br>
<property name="company" type="string" not-null="true"
length="128"/><br></br>
<property name="status" type="string" not-null="true" length="64"/><br></br>
<property name="verifyDate" type="java.sql.Date" not-null="true"/><br></br>
<property name="note" type="string" length="512"/><br></br>
<property name="estimate" type="int"/><br></br>
<many-to-one<br></br>
name="user"<br></br>
class="ro.nit.contacts.data.User"<br></br>
not-null="true"><br></br>
<column name="user_id" /><br></br>
</many-to-one></class><class name="ro.nit.contacts.data.User" table="nitc_user"><br></br>
<id name="id" column="user_id" type="long"><br></br>
<generator class="native"/><br></br>
</id><property name="name" type="string" not-null="true" length="128"/><br></br>
<property name="pass" type="string" not-null="true" length="128"/><br></br>
<property name="email" type="string" not-null="true" length="128"/><br></br>
<property name="data" type="string" length="512"/><set<br></br>
name="contacts"<br></br>
lazy="true"<br></br>
inverse="true"<br></br>
><br></br>
<key><br></br>
<column name="user_id" /><br></br>
</key><br></br>
<one-to-many class="ro.nit.contacts.data.Contact"/><br></br>
</set><br></br>
</class>

2. Tapestry application

<application name="contacts" engine-class="ro.nit.contacts.AppEngine"><property name="org.apache.tapestry.visit-class">ro.nit.contacts.Visit</property><br></br>
<property name="org.apache.tapestry.global-class">ro.nit.contacts.HibernateGlobal</property>><br></br>
<page name="Home" specification-path="/ro/nit/contacts/Home.page"/><br></br>
<page name="Add" specification-path="/ro/nit/contacts/Add.page"/><br></br>
<page name="Update" specification-path="/ro/nit/contacts/Add.page"/>
(same page, different name)<br></br>
<page name="View" specification-path="/ro/nit/contacts/View.page"/><br></br>
<page name="Logon" specification-path="/ro/nit/contacts/Logon.page"/>
(added logon page)<component-alias type="ShowError" specification-path="/ro/nit/components/ShowError.jwc"
/><br></br>
<component-alias type="Menu" specification-path="/ro/nit/components/Menu.jwc"
/><br></br>
<component-alias type="LogonComp" specification-path="/ro/nit/components/LogonComp.jwc"
/> (logon form)<br></br>
<component-alias type="Author" specification-path="/ro/nit/components/Author.jwc"
/> (author text at the bottom right)<library id="contrib" specification-path="/org/apache/tapestry/contrib/Contrib.library"
/></application>

3. The HibernateGlobal class (the
global object) is going to contain hibernate code to obtain a Session
object

configFileURL = HibernateGlobal.class.getResource(configFilePath);<br></br>log.debug("Initializing Hibernate from " + configFilePath + "...");<br></br>configuration = (new Configuration()).configure(configFileURL);<br></br>factory = configuration.buildSessionFactory();<br></br>

4. The LogonComp object contains
authentification code

public User authenticate(){ <br></br>try { <br></br>HibernateGlobal global = <br></br>                  (HibernateGlobal)getPage().getGlobal(); <br></br>Session sess = global.openSession(); <br></br>Query q = sess.createQuery("select u from u in class ro.nit.contacts.data.User where u.name=:name and u.pass=:pass"); <br></br>q.setParameter("name",getUsername()); <br></br>q.setParameter("pass",getPassword()); List result = q.list(); <br></br>if(!result.isEmpty()){ <br></br>return (User)result.iterator().next(); <br></br>} <br></br>sess.close(); <br></br>} <br></br>catch (HibernateException e) <br></br>{ <br></br>	e.printStackTrace(); <br></br>} <br></br>return null; }         

5. The Login page is just
a simple form for authentication.

Securing the application and adding Hibernate support only require
for each page to extend the AppPage class.

public void pageValidate(PageEvent event){ <br></br>Visit visit = (Visit) getVisit(); <br></br>if (visit != null && visit.getUser()!=null){ <br></br>return; <br></br>} <br></br>IPage loginPage = getRequestCycle().getPage("Logon"); <br></br>LogonComp login = (LogonComp)loginPage.getComponent("logonComp"); <br></br>login.setCallback(new PageCallback(this)); <br></br>throw new PageRedirectException(loginPage); <br></br>}<b><br></br></b>

6. The Home page does not contains
nothing

Graphic 1:Home page

7. The Add page contains logic
for adding a contact with validation. I use some ValidField components
for checking the input entered by the user. This validation is one
of the most ugly things I can thing about when creating some dynamic
pages. This is mainly due to the repetitive, error generating code.

<form jwcid="@Form" delegate="ognl:beans.delegate" listener="ognl:listeners.formSubmit"><br></br>
<span jwcid="@ShowError" delegate="ognl:beans.delegate"/><br></br>
<table cellpadding="4"><!--<br></br>
<tr><td>Notes:</td><td><textarea
jwcid="notesText" cols="20" rows="5"/></td></tr><br></br>
--><tr class="row1"><br></br>
<td><span jwcid="@FieldLabel" field="ognl:components.nameField">Name:</span></td><br></br>
<td><input jwcid="nameField" size="20"/></td><br></br>
<td><span jwcid="@FieldLabel"
field="ognl:components.companyField">Company:</span></td><br></br>
<td><input jwcid="companyField" size="20"/></td><br></br>
</tr><br></br>
<tr class="row1"><br></br>
<td>Verify Date:</td><br></br>
<td><span jwcid="@DatePicker" value="ognl:contact.verifyDate"/></td><br></br>
<td>Status:</td><br></br>
<td><span jwcid="@PropertySelection" model="ognl:@ro.nit.contacts.Add@STATUS"
value="ognl:contact.status"/></td><br></br>
</tr><br></br>
<tr class="row1"><br></br>
<td>Data:</td><br></br>
<td><input jwcid="@TextArea" value="ognl:contact.data"
cols="20"/></td><br></br>
<td>Note:</td><br></br>
<td><input jwcid="@TextArea" value="ognl:contact.note"
cols="20"/></td><br></br>
</tr><br></br>
<tr class="row1"><br></br>
<td>&nbsp;</td><br></br>
<td>&nbsp;</td><br></br>
<td><span jwcid="@FieldLabel"
field="ognl:components.estimateField">Estimate:</span></td><br></br>
<td><input jwcid="estimateField" size="3"/>(1-10
value)</td><br></br>
</tr><tr align="right"><br></br>
<td colspan="4"><input type="submit" jwcid="@Submit"
label="ognl:page.pageName"/></td> (change the button label based on the page name)<br></br>
</tr><br></br>
</table><br></br>
</form>

The method:

public void activateExternalPage(Object[] objects, IRequestCycle <br></br>                    cycle) {<br></br>
if(objects!=null&&objects.length>=1){<br></br>
setContact((Contact)objects[0]);<br></br>
}<br></br>
}<br></br>
public void formSubmit(IRequestCycle cycle) {ValidationDelegate delegate = (ValidationDelegate)<br></br>
getBeans().getBean("delegate");// If no errors process the bid, otherwise stay on this page
and<br></br>
// let the fields show their errors.<br></br>
if (!delegate.getHasErrors()){<br></br>
try {<br></br>
Session sess = null;<br></br>
Transaction tx = null;<br></br>
try {<br></br>
sess = HUtil.getSession();<br></br>
tx = sess.beginTransaction();<br></br>
contact.setUser(((Visit)getVisit()).getUser());if("Add".equalsIgnoreCase(getPageName())) (save or update the contact based on how the page
was called)<br></br>
sess.save(contact);<br></br>
else<br></br>
sess.update(contact);tx.commit();<br></br>
cycle.activate("View");<br></br>
} catch (Exception e) {<br></br>
e.printStackTrace();<br></br>
tx.rollback();<br></br>
} finally {<br></br>
if(sess!=null){<br></br>
sess.flush();<br></br>
sess.close();<br></br>
}<br></br>
}<br></br>
} catch (HibernateException e) {<br></br>
e.printStackTrace();<br></br>
}<br></br>
contact = new Contact();<br></br>
}<br></br>
}

Observation: maybe a generator could be written.
This will take a mapping file and output a component for entering
data.


Graphic 2:Add page


Graphic 3:Add page (incomplete data)


Graphic 4:Add page called as Update

8. The View page contains just
a table with data (I stoped using the table component since it seemed
much to complicated for this case, instead I used a simple foreach)

Graphic 5:View page (the table)

<table width="600" border="1" cellpadding="1" <br></br>                  cellspacing="0" bordercolor="#999999" style="BORDER-COLLAPSE: <br></br>                  collapse"><br></br>                  <tr class="rowh"><br></br>                  <td>Name</td><br></br>                  <td>Company</td><br></br>                  <td>Status</td><br></br>                  <td>Data</td><br></br>                  <td>Verify Date</td><br></br>                  <td>Note</td><br></br>                  <td>Estimate</td><br></br>                  <td>&nbsp;</td><br></br>                  </tr> <br></br>                  <tr jwcid="@Foreach" source="ognl:contacts" value="ognl:contact" <br></br>                    element="tr" class="ognl:cssClass"><br></br>
<td><span jwcid="@Insert" value="ognl:contact.name"/></td><br></br>
<td><span jwcid="@Insert" value="ognl:contact.company"/></td><br></br>
<td><span jwcid="@Insert" value="ognl:contact.status"/></td><br></br>
<td><span jwcid="@Insert" value="ognl:contact.data"/></td><br></br>
<td><span jwcid="@Insert" value="ognl:contact.verifyDate"/></td><br></br>
<td><span jwcid="@Insert" value="ognl:contact.note"/></td><br></br>
<td><span jwcid="@Insert" value="ognl:contact.estimate"/></td><br></br>
<td><span jwcid="@ExternalLink" page="Update"
parameters="ognl:contact">Update</span></td><br></br>
</tr><br></br>
</table><span jwcid="@Author"/><br></br>

The method:

          public List getContacts(){<br></br>                  Session sess = null;<br></br>                  try {<br></br>                  sess = HUtil.getSession();<br></br>                  List results = sess.find("from c in class ro.nit.contacts.data.Contact");<br></br>                  return results;<br></br>                  } catch (Exception e) {<br></br>                  e.printStackTrace();<br></br>                  }finally{<br></br>                  if(sess!=null)<br></br>                  try {<br></br>                  sess.close();<br></br>                  } catch (Exception e) {<br></br>                  e.printStackTrace();<br></br>                  //conceal this for now<br></br>                  }<br></br>                  }<br></br>                  return null;<br></br>                  }<br></br>