Member Menu
 
 Monthly JBoss newsletter:
 
Hibernate Books
CaveatEmptor

A simple Hibernate + Tapestry application

Document purpose: making a simple Tapestry and Hibernate application to work.

Links:

- full document/updated

- send comments

- Hibernate(the DB persistence)

- Tapestry (the Web framework)

- me, Nemesis IT (my company)

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 for hibernate and tapestry (including contrib))

gfdgdf

The link above seems to be down, but you can still download the sample application from an alternative location.

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:

  • change

<!DOCTYPE page-specification

PUBLIC "-//Howard Lewis Ship//Tapestry Specification 1.3//EN"

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

<!DOCTYPE page-specification PUBLIC

"-//Apache Software Foundation//Tapestry Specification 3.0//EN"

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

and similar ones

  • 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 tends to simplify 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.properties (I am using a postgresql database).

<class name="ro.nit.contacts.data.Contact" table="nitc_contact">
<id name="id" column="contact_id" type="long">
<generator class="native"/>
</id>

<property name="name" type="string" not-null="true" length="128"/>
<property name="data" type="string" length="512"/>
<property name="company" type="string" not-null="true" length="128"/>
<property name="status" type="string" not-null="true" length="64"/>
<property name="verifyDate" type="java.sql.Date" not-null="true"/>
<property name="note" type="string" length="512"/>
<property name="estimate" type="int"/>
<many-to-one
name="user"
class="ro.nit.contacts.data.User"
not-null="true">
<column name="user_id" />
</many-to-one>

</class>

<class name="ro.nit.contacts.data.User" table="nitc_user">
<id name="id" column="user_id" type="long">
<generator class="native"/>
</id>

<property name="name" type="string" not-null="true" length="128"/>
<property name="pass" type="string" not-null="true" length="128"/>
<property name="email" type="string" not-null="true" length="128"/>
<property name="data" type="string" length="512"/>

<set
name="contacts"
lazy="true"
inverse="true"
>
<key>
<column name="user_id" />
</key>
<one-to-many class="ro.nit.contacts.data.Contact"/>
</set>
</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>
<page name="Home" specification-path="/ro/nit/contacts/Home.page"/>
<page name="Add" specification-path="/ro/nit/contacts/Add.page"/>
<page name="Update" specification-path="/ro/nit/contacts/Add.page"/> (same page, different name)
<page name="View" specification-path="/ro/nit/contacts/View.page"/>
<page name="Logon" specification-path="/ro/nit/contacts/Logon.page"/> (added logon page)

<component-alias type="ShowError" specification-path="/ro/nit/components/ShowError.jwc" />
<component-alias type="Menu" specification-path="/ro/nit/components/Menu.jwc" />
<component-alias type="LogonComp" specification-path="/ro/nit/components/LogonComp.jwc" /> (logon form)
<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 HUtil class is going to contain hibernate code to obtain a Session object

try {
Configuration cfg = new Configuration();
cfg.
addClass(Contact.class).
addClass(User.class);

Properties properties=new Properties();
properties.load(HUtil.class.getResourceAsStream("/hibernate.properties"));
cfg.setProperties(properties);

sessionFactory=cfg.buildSessionFactory();
} catch (Exception e) {
e.printStackTrace();
}

4. The Visit object contains authentification code

try {
Session sess = HUtil.getSession();
Query q = sess.createQuery("select u from u in class ro.nit.contacts.data.User where u.name=:name and u.pass=:pass");
q.setParameter("name",username);
q.setParameter("pass",password);
List result = q.list();
if(!result.isEmpty()){
user = (User)result.iterator().next();
password = null;
return true;
}
sess.close();
} catch (HibernateException e) {
e.printStackTrace();
}
return false;

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

Securing the application only require for each page to extend the SecuredPage class

public class SecuredPage extends BasePage{
public void validate(IRequestCycle iRequestCycle){
Visit v = (Visit)getVisit();
if(!v.isAuthenticated()){
throw new PageRedirectException("Logon");
}
super.validate(iRequestCycle);
}

public void detach(){
super.detach();
}
}

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">
<span jwcid="@ShowError" delegate="ognl:beans.delegate"/>
<table cellpadding="4">

<!--
<tr><td>Notes:</td><td><textarea jwcid="notesText" cols="20" rows="5"/></td></tr>
-->

<tr class="row1">
<td><span jwcid="@FieldLabel" field="ognl:components.nameField">Name:</span></td>
<td><input jwcid="nameField" size="20"/></td>
<td><span jwcid="@FieldLabel" field="ognl:components.companyField">Company:</span></td>
<td><input jwcid="companyField" size="20"/></td>
</tr>
<tr class="row1">
<td>Verify Date:</td>
<td><span jwcid="@DatePicker" value="ognl:contact.verifyDate"/></td>
<td>Status:</td>
<td><span jwcid="@PropertySelection" model="ognl:@ro.nit.contacts.Add@STATUS" value="ognl:contact.status"/></td>
</tr>
<tr class="row1">
<td>Data:</td>
<td><input jwcid="@TextArea" value="ognl:contact.data" cols="20"/></td>
<td>Note:</td>
<td><input jwcid="@TextArea" value="ognl:contact.note" cols="20"/></td>
</tr>
<tr class="row1">
<td>&nbsp;</td>
<td>&nbsp;</td>
<td><span jwcid="@FieldLabel" field="ognl:components.estimateField">Estimate:</span></td>
<td><input jwcid="estimateField" size="3"/>(1-10 value)</td>
</tr>

<tr align="right">
<td colspan="4"><input type="submit" jwcid="@Submit" label="ognl:page.pageName"/></td> (change the button label based on the page name)
</tr>
</table>
</form>
public void activateExternalPage(Object[] objects, IRequestCycle cycle) {
if(objects!=null&&objects.length>=1){
setContact((Contact)objects[0]);
}
}


public void formSubmit(IRequestCycle cycle) {

ValidationDelegate delegate = (ValidationDelegate)
getBeans().getBean("delegate");

// If no errors process the bid, otherwise stay on this page and
// let the fields show their errors.
if (!delegate.getHasErrors()){
try {
Session sess = null;
Transaction tx = null;
try {
sess = HUtil.getSession();
tx = sess.beginTransaction();
contact.setUser(((Visit)getVisit()).getUser());

if("Add".equalsIgnoreCase(getPageName())) (save or update the contact based on how the page was called)
sess.save(contact);
else
sess.update(contact);

tx.commit();
cycle.activate("View");
} catch (Exception e) {
e.printStackTrace();
tx.rollback();
} finally {
if(sess!=null){
sess.flush();
sess.close();
}
}
} catch (HibernateException e) {
e.printStackTrace();
}
contact = new Contact();
}
} 

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

6. 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 4:View page (the table)

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

<span jwcid="@Author"/>

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

  NEW COMMENT

missing pieces.... 12 Jan 2004, 23:47 eepstein
It would be neat if there were a deployable (war) version of this.

Also a usable build.xml file for compiling the source.

Finally some basic "how to" for installation and 
modification/experimentation.
 
Is this thread-safe? 02 Feb 2005, 21:39 CraigJones
I'm confused on 2 counts.  First, the text of the article no longer
matches the sample code (so I'm going just by what I see in the code).

Second, I don't see how this code is threadsafe.  You''ve got the
hibernate session factory in Global, which is fine, but the instantiated
session is stored in a field of AppPage (which extends BasePage). 
Correct me if I'm wrong, but if 5 users simultaneously access a page
that extends AppPage, there will only be one instance of that page class
(+ five instances of AppEngine).  So, shouldn't the session be stored in
AppEngine?
 
Tapestry pages 03 Jul 2005, 23:53 bwalding
FWIW:

Unlike servlets, only 1 thread is active in a tapestry page at any given
time.  This simplifies the programming model as you can then use class
level variables to store state as required.
 
Re: Is this thread-safe? 20 Jul 2005, 21:59 slhynju
On 02 Feb 2005 21:39, CraigJones wrote:

>Correct me if I'm wrong, but if 5 users simultaneously access a page
>that extends AppPage, there will only be one instance of that page class
>(+ five instances of AppEngine).  So, shouldn't the session be stored in
>AppEngine?

Different from Struts, there're mutiple instances of the page class.
Each instance serves for one request thread. These instances are pooled
and managed by Tapestry.

For more details, please refer to 
http://jakarta.apache.org/tapestry/UsersGuide/state.html#state.page-properties
 
© Copyright 2006, Red Hat Middleware, LLC. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc. [Privacy Policy]