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)) 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;
}
|