A simple Hibernate + Tapestry applicationDocument 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)) 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:
<!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> </td>
<td> </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> </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;
}
|