/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.click.extras.hibernate;
import java.io.Serializable;
import org.apache.click.control.Checkbox;
import org.apache.click.control.Field;
import org.apache.click.control.Form;
import org.apache.click.control.HiddenField;
import org.apache.click.util.ClickUtils;
import org.apache.click.util.HtmlStringBuffer;
import org.apache.commons.lang.StringUtils;
import org.hibernate.EntityMode;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.metadata.ClassMetadata;
import org.hibernate.type.Type;
/**
* Provides Hibernate data aware Form control: <form method='POST'>.
*
* <table class='htmlHeader' cellspacing='10'>
* <tr>
* <td>
*
* <table class='fields'>
* <tr>
* <td align='left'><b><label>First Name:</label></b></td>
* <td align='left'><input type='text' name='name' value='' size='20' /></td>
* </tr>
* <tr>
* <td align='left'><label>Middle Names:</label></td>
* <td align='left'><input type='text' name='name' value='' size='20' /></td>
* </tr>
* <tr>
* <td align='left'><b><label>Family Name:</label></b></td>
* <td align='left'><input type='text' name='name' value='' size='20' /></td>
* </tr>
* </table>
* <table class="buttons" align='right'>
* <tr><td>
* <input type='submit' name='ok' value=' OK '/> <input type='submit' name='cancel' value='Cancel'/>
* </td></tr>
* </table>
*
* </td>
* </tr>
* </table>
*
* <a href="http://www.hibernate.org/">Hibernate</a> is an Object Relational
* Mapping (ORM) framework. The HibernateForm supports creating (inserting) and
* saving (updating) POJO instances. This form will automatically apply the
* given objects property required validation constraints to the form fields.
* <p/>
* The HibernateForm uses the thread local <tt>Session</tt> obtained via
* <tt>SessionContext.getSession()</tt> for all object for persistence
* operations. To use an alternative Session source override set the forms
* getSession() method.
* <p/>
* The example below provides a <tt>User</tt> data object creation
* and editing page. To edit an existing user object, the object is passed
* to the page as a request parameter. Otherwise a new user object will
* be created when {@link #saveChanges()} is called.
*
* <pre class="codeJava">
* <span class="kw">public class</span> UserEdit <span class="kw">extends</span> Page {
*
* <span class="kw">private</span> HibernateForm form = <span class="kw">new</span> HibernateForm(<span class="st">"form"</span>, User.<span class="kw">class</span>);
*
* <span class="kw">public</span> UserEdit() {
* form.add(<span class="kw">new</span> TextField(<span class="st">"firstName"</span>));
* form.add(<span class="kw">new</span> TextField(<span class="st">"middleNames"</span>));
* form.add(<span class="kw">new</span> TextField(<span class="st">"FamilyName"</span>));
*
* form.add(<span class="kw">new</span> Submit(<span class="st">"ok"</span>, <span class="st">" OK "</span>, <span class="kw">this</span>, <span class="st">"onOkClicked"</span>));
* form.add(<span class="kw">new</span> Submit(<span class="st">"cancel"</span>, <span class="kw">this</span>, <span class="st">"onCancelClicked"</span>));
*
* form.setButtonAlign(<span class="st">"right"</span>);
* form.setLabelRequiredPrefix(<span class="st">"<b>"</span>);
* form.setLabelRequiredSuffix(<span class="st">"</b>"</span>);
* addControl(form);
* }
*
* <span class="kw">public void</span> setUser(User user) {
* form.setValueObject(user);
* }
*
* <span class="kw">public boolean</span> onOkClicked() {
* <span class="kw">if</span> (form.isValid()) {
* <span class="kw">if</span> (form.saveChanges()) {
* setRedirect(<span class="st">"user-list.htm"</span>);
* }
* }
* <span class="kw">return true</span>;
* }
*
* <span class="kw">public boolean</span> onCancelClicked() {
* setRedirect(<span class="st">"user-list.htm"</span>);
* <span class="kw">return false</span>;
* }
* } </pre>
*
* @see SessionContext
* @see SessionFilter
*/
public class HibernateForm extends Form {
private static final long serialVersionUID = -7134198516606088333L;
/** The form value object classname parameter name. */
protected static final String FO_CLASS = "FO_CLASS";
/** The form value object id parameter name. */
protected static final String FO_ID = "FO_ID";
// ----------------------------------------------------- Instance Variables
/** The value object class name hidden field. */
protected HiddenField classField;
/** The value object identifier hidden field. */
protected HiddenField oidField;
/** The Hibernate session. */
protected Session session;
/** The Hibernate session factory. */
protected SessionFactory sessionFactory;
/**
* The flag specifying that object validation meta data has been applied to
* the form fields.
*/
protected boolean metaDataApplied = false;
// ----------------------------------------------------------- Constructors
/**
* Create a new HibernateForm with the given form name and value object
* class.
*
* @param name the form name
* @param valueClass the value object class
*/
public HibernateForm(String name, Class valueClass) {
super(name);
if (valueClass == null) {
throw new IllegalArgumentException("Null valueClass parameter");
}
classField = new HiddenField(FO_CLASS, String.class);
classField.setValue(valueClass.getName());
add(classField);
String classname = getClassname(valueClass);
ClassMetadata classMetadata =
getSessionFactory().getClassMetadata(classname);
Type identifierType = classMetadata.getIdentifierType();
oidField = new HiddenField(FO_ID, identifierType.getReturnedClass());
add(oidField);
}
// --------------------------------------------------------- Public Methods
/**
* Return the form Hibernate <tt>Session</tt>. If form session is not
* defined this method will obtain a session from the
* {@link SessionContext}.
* <p/>
* Applications using alternative Hibernate <tt>Session</tt> sources should
* set the form's session using the {@link #setSession(Session)} method.
*
* @return the form Hibernate session
*/
public Session getSession() {
if (session == null) {
session = SessionContext.getSession();
}
return session;
}
/**
* Set the user's Hibernate <tt>Session</tt>.
*
* @param session the user's Hibernate session
*/
public void setSession(Session session) {
this.session = session;
}
/**
* Return the application Hibernate <tt>SessionFactory</tt>.
* If session factory is not defined this method will obtain the session
* factory from the {@link SessionContext}.
* <p/>
* Applications using an alternative Hibernate <tt>SessionFactory</tt>
* sources should set the form's session factory using the
* {@link #setSessionFactory(SessionFactory)} method.
*
* @return the user's Hibernate session
*/
public SessionFactory getSessionFactory() {
if (sessionFactory == null) {
sessionFactory = SessionContext.getSessionFactory();
}
return sessionFactory;
}
/**
* Set the form Hibernate <tt>SessionFactory</tt>.
*
* @param sessionFactory the Hibernate SessionFactory
*/
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
/**
* Return a Hibernate value object from the form with the form field values
* copied into the object's properties.
*
* @return the Hibernate object from the form with the form field values
* applied to the object properties.
*/
public Object getValueObject() {
if (StringUtils.isNotBlank(classField.getValue())) {
try {
Class valueClass = ClickUtils.classForName(classField.getValue());
Serializable oid = (Serializable) oidField.getValueObject();
Object valueObject = null;
if (oid != null) {
valueObject = getSession().load(valueClass, oid);
} else {
valueObject = valueClass.newInstance();
}
copyTo(valueObject);
return valueObject;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return null;
}
/**
* Set the given Hibernate value object in the form, copying the object's
* properties into the form field values.
*
* @param valueObject the Hibernate value object to set
*/
public void setValueObject(Object valueObject) {
if (valueObject != null) {
String classname = getClassname(valueObject.getClass());
ClassMetadata classMetadata =
getSessionFactory().getClassMetadata(classname);
Object identifier =
classMetadata.getIdentifier(valueObject, EntityMode.POJO);
oidField.setValueObject(identifier);
copyFrom(valueObject);
}
}
/**
* Save or update the object to the database and return true.
* If a <tt>HibernateException</tt> occurs the <tt>Transaction</tt> will be
* rolled back the exception will be raised.
* <p/>
* If no object is added to the form using <tt>setValueObject()</tt>
* then this method will: <ul>
* <li>create a new instance of the Class</li>
* <li>copy the form's field values to the objects properties</li>
* <li>save the new object to the database</li>
* </ul>
* <p/>
* If an existing persistent object is added to the form using
* <tt>setValueObject()</tt> then this method will: <ul>
* <li>load the persistent object record from the database</li>
* <li>copy the form's field values to the objects properties</li>
* <li>update the object in the database</li>
* </ul>
*
* @return true if the object was saved or false otherwise
* @throws HibernateException if a persistence error occured
*/
public boolean saveChanges() throws HibernateException {
Object valueObject = getValueObject();
Transaction transaction = null;
try {
Session session = getSession();
transaction = session.beginTransaction();
session.saveOrUpdate(valueObject);
transaction.commit();
return true;
} catch (HibernateException he) {
if (transaction != null) {
try {
transaction.rollback();
} catch (HibernateException re) {
// ignore
}
}
throw he;
}
}
/**
* This method applies the object meta data to the form fields and then
* invokes the <tt>super.onProcess()</tt> method.
*
* @see Form#onProcess()
*
* @return true to continue Page event processing or false otherwise
*/
public boolean onProcess() {
applyMetaData();
return super.onProcess();
}
/**
* Render the HTML representation of the HibernateForm.
* <p/>
* This method applies the object meta data to the form fields and then
* invokes the <tt>super.toString()</tt> method.
*
* @see #toString()
*
* @param buffer the specified buffer to render the control's output to
*/
public void render(HtmlStringBuffer buffer) {
applyMetaData();
super.render(buffer);
}
// ------------------------------------------------------ Protected Methods
/**
* Applies the <tt>ClassMetadata</tt> validation database meta data to the
* form fields.
* <p/>
* The field validation attributes include:
* <ul>
* <li>required - is a mandatory field and cannot be null</li>
* </ul>
*/
protected void applyMetaData() {
if (metaDataApplied) {
return;
}
try {
Class valueClass = ClickUtils.classForName(classField.getValue());
String classname = getClassname(valueClass);
ClassMetadata metadata = getSessionFactory().getClassMetadata(classname);
String[] propertyNames = metadata.getPropertyNames();
boolean[] propertyNullability = metadata.getPropertyNullability();
for (int i = 0; i < propertyNames.length; i++) {
Field field = getField(propertyNames[i]);
if (field != null) {
boolean isMandatory = !propertyNullability[i];
if (!field.isRequired() && isMandatory) {
if (!(field instanceof Checkbox)) {
field.setRequired(true);
}
}
}
}
} catch (ClassNotFoundException cnfe) {
throw new RuntimeException(cnfe);
}
metaDataApplied = true;
}
/**
* Return the original classname for the given class removing any CGLib
* proxy information.
*
* @param aClass the class to obtain the original name from
* @return the original classname for the given class
*/
protected String getClassname(Class aClass) {
String classname = aClass.getName();
if (classname.indexOf("$$") != -1) {
classname = classname.substring(0, classname.indexOf("$"));
}
return classname;
}
}