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)
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">
PUBLIC “-//Apache Software Foundation//Tapestry Specification 3.0//EN”to
“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
- change ```
PUBLIC “-//Howard Lewis Ship//Tapestry Specification 1.3//EN”
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> </td><br></br>
<td> </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> </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>