/* * Databinder: a simple bridge from Wicket to Hibernate * Copyright (C) 2006 Nathan Hamblen nathan@technically.us * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package net.databinder.components.hib; import java.io.Serializable; import net.databinder.models.hib.HibernateObjectModel; import org.apache.wicket.Component; import org.apache.wicket.WicketRuntimeException; import org.apache.wicket.markup.html.link.Link; import org.apache.wicket.model.CompoundPropertyModel; import org.apache.wicket.model.IModel; import org.hibernate.Session; /** * Provides default handling for a single {@link HibernateObjectModel} nested * in a {@link CompoundPropertyModel}. This includes saving a new model object to * persistent storage and committing the current transaction when a valid form * is submitted. For forms holding multiple independent persistent objects (when * there is no single parent that cascades saves to the others), subclasses may * override {@link #savePersistentObjectIfNew()} to save all the form's * {@link HibernateObjectModel}s. (Note that automatic {@link #version} tracking * is only available for the primary model.) * <p>For very specialized forms it may be necessary to extend this class's parent, * {@link DataFormBase}.</p> * @author Nathan Hamblen */ public class DataForm<T> extends DataFormBase<T> { /** * Retains the persistent object's version field (if it has one) between * requests to detect editing conflicts between users. * @see #validate() */ private Serializable version; /** * Instantiates this form and a new, blank instance of the given class as a persistent model * object. By default the model object created is serialized and retained between requests until * it is persisted. * @param id * @param modelClass for the persistent object * @see HibernateObjectModel#setRetainUnsaved(boolean) */ public DataForm(String id, Class<T> modelClass) { super(id, new CompoundPropertyModel<T>(new HibernateObjectModel<T>(modelClass))); } public DataForm(String id, HibernateObjectModel<T> model) { super(id, new CompoundPropertyModel<T>(model)); setFactoryKey(model.getFactoryKey()); } /** * Instantiates this form with a persistent object of the given class and id. * @param id Wicket id * @param modelClass for the persistent object * @param persistentObjectId id of the persistent object */ public DataForm(String id, Class<T> modelClass, Serializable persistentObjectId) { super(id, new CompoundPropertyModel<T>(new HibernateObjectModel<T>(modelClass, persistentObjectId))); } /** * Form that is nested below a component with a compound model containing a Hibernate * model. * @param id */ public DataForm(String id) { super(id); } /** * @param key for the Hibernate session factory to be used with this component * @return this */ @Override public DataForm setFactoryKey(Object key) { super.setFactoryKey(key); getPersistentObjectModel().setFactoryKey(key); return this; } /** * @return the single persistent model for this form */ public HibernateObjectModel<T> getPersistentObjectModel() { return (HibernateObjectModel<T>) getCompoundModel().getChainedModel(); } /** * Change the persistent model object of this form. * @param object to attach to this form * @return this form, for chaining */ public DataForm setPersistentObject(T object) { getPersistentObjectModel().setObject(object); modelChanged(); return this; } /** * Updates the internal version number to the actual version number. * @see #version */ private void updateVersion() { version = getPersistentObjectModel().getVersion(); } /** Late-init version record. */ @Override protected void onBeforeRender() { super.onBeforeRender(); if (version == null) updateVersion(); } /** Calls {@link #updateVersion()} when model changes. */ @Override protected void onModelChanged() { updateVersion(); } /** * Replaces the form's model object with a new, blank (unbound) instance. Does not affect * persistent storage. * @see HibernateObjectModel#unbind() * @return this form, for chaining */ public DataForm clearPersistentObject() { getPersistentObjectModel().unbind(); modelChanged(); return this; } /** * @return the effective compound model for this form, which may be * attached to a parent component */ protected CompoundPropertyModel getCompoundModel() { IModel model = getModel(); Component cur = this; while (cur != null) { model = cur.getDefaultModel(); if (model != null && model instanceof CompoundPropertyModel) return (CompoundPropertyModel) model; cur = cur.getParent(); } throw new WicketRuntimeException("DataForm has no parent compound model"); } /** Default implementation calls {@link #commitFormIfValid()}. */ @Override protected void onSubmit() { commitFormIfValid(); } /** * Commits a valid form's data to persistent storage. If no errors are * registered for any form component, this method calls * {@link #savePersistentObjectIfNew()} * {@link #commitTransactionIfValid()}, and {@link #updateVersion()}. * @return true if committed */ protected boolean commitFormIfValid() { if (!hasError()) { savePersistentObjectIfNew(); commitTransactionIfValid(); // flush and commit session // if version is present it should have changed if (version != null) { updateVersion(); } return true; } return false; } /** * Saves persistent model object if it is not already contained in the session. * If the a sub-class is responsible for more than one {@link HibernateObjectModel}, * it may override to call {@link #saveIfNew(HibernateObjectModel)} on each. * @return true if object was newly saved */ protected boolean savePersistentObjectIfNew() { return saveIfNew(getPersistentObjectModel()); } /** * Saves model's entity if it is not already contained in the session. * @return true if object was newly saved */ protected boolean saveIfNew(HibernateObjectModel<T> model) { Session session = getHibernateSession(); if (!session.contains(model.getObject())) { onBeforeSave(model); session.save(model.getObject()); // updating binding status; though it will happen on detach // some UI components may like to know sooner. getPersistentObjectModel().checkBinding(); return true; } return false; } /** * Called before saving any new object by {@link #saveIfNew(HibernateObjectModel)}. * This is a good time to make last minute changes to new objects that * couldn't be easily serialized (adding relationships to existing persistent * entities, for example). * @param generally, the persistent model for this form (but subclasses may also call saveIfNew) */ protected void onBeforeSave(HibernateObjectModel<T> model) { }; /** * Checks that the version number, if present, is the last known version number. * If it does not match, validation fails and will continue to fail until the form is * reloaded with the updated data and version number. This allows the user to * preserve her unsaved changes while preventing overwrites. <p> <b>Note:</b> although * timestamp versions are supported, beware of rounding errors. equals() must return true * when comparing the retained version object to the one loaded from persistent storage. */ @Override protected void onValidate() { if (version != null) { Serializable currentVersion = getPersistentObjectModel().getVersion(); if (!version.equals(currentVersion)) error(getString("version.mismatch", null)); // report error // do not update version number as old data still appears in form } super.onValidate(); } /** * @return persistent storage version number if available, null otherwise */ protected Serializable getVersion() { return version; } /** * Deletes the form's model object from persistent storage. Flushes change so that * queries executed in the same request (e.g., in a HibernateListModel) will not return * this object. * @return true if the object was deleted, false if it did not exist */ protected boolean deletePersistentObject() { Session session = getHibernateSession(); Object modelObject = getPersistentObjectModel().getObject(); if (!session.contains(modelObject)) return false; session.delete(modelObject); session.flush(); return true; } /** * Instances of this nested class call #{@link DataForm#clearPersistentObject()} * on their instantiating DataForm when clicked. */ public class ClearLink extends Link { public ClearLink(String id) { super(id); } /** @return true if visible and the form's perisistent model is bound */ @Override public boolean isEnabled() { return !DataForm.this.isVisibleInHierarchy() || getPersistentObjectModel().isBound(); } @Override public void onClick() { clearPersistentObject(); DataForm.this.setVisible(true); } } }