/* * 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.models.jpa; import java.io.Serializable; import java.lang.reflect.Field; import java.lang.reflect.Method; import javax.persistence.Version; import net.databinder.jpa.Databinder; import net.databinder.models.BindingModel; import net.databinder.models.LoadableWritableModel; import org.apache.wicket.WicketRuntimeException; /** * Model loaded and persisted via JPA. This central Databinder class can be initialized with an * entity ID, different types of queries, or an existing persistent object. As a writable Wicket model, * the object it contains may be swapped at any time for a different persistent object, a Serializable * object, or null. * * @param <T> Type of the Model * * @author rhansen@kindleit.net */ public class JPAObjectModel<T> extends LoadableWritableModel<T> implements BindingModel<T> { private static final long serialVersionUID = -8469845951034582593L; private final Class<T> objectClass; private final Serializable objectId; /** May store unsaved objects between requests. */ private T retainedObject; /** Enable retaining unsaved objects between requests. */ private boolean retainUnsaved = true; private String factoryKey; /** * Create a model bound to the given class and entity id. If nothing matches * the id the model object will be null. * @param objectClass class to be loaded and stored by Hibernate * @param entityId id of the persistent object */ public JPAObjectModel(final Class<T> objectClass, final Serializable entityId) { this.objectClass = objectClass; this.objectId = entityId; } /** @return session factory key, or null for the default factory */ public Object getFactoryKey() { return factoryKey; } /** * Set a factory key other than the default (null). * @param key session factory key * @return this, for chaining */ public JPAObjectModel<T> setFactoryKey(final String key) { this.factoryKey = key; return this; } /** * Change the persistent object contained in this model. * Because this method establishes a persistent object ID, queries and binders * are removed if present. * @param object must be an entity contained in the current Hibernate session, or Serializable, or null */ public void setObject(final T object) { throw new UnsupportedOperationException("Cannot set object"); } public Serializable getIdentifier() { return objectId; } /** * Load the object through Hibernate, contruct a new instance if it is not * bound to an id, or use unsaved retained object. Returns null if no * criteria needed to load or construct an object are available. */ @Override protected T load() { try { if (!isBound()) { if (retainUnsaved && retainedObject != null) { return retainedObject; } else if (retainUnsaved) { try { return retainedObject = objectClass.newInstance(); } catch (final ClassCastException e) { throw new WicketRuntimeException("Unsaved entity must be Serializable or retainUnsaved set to false; see JPAObjectModel javadocs."); } } else { return objectClass.newInstance(); } } } catch (final ClassCastException e) { throw new RuntimeException("Retaining unsaved model objects requires that they be Serializable.", e); } catch (final Throwable e) { throw new RuntimeException("Unable to instantiate object. Does it have a default constructor?", e); } return Databinder.getEntityManager(factoryKey).find(objectClass, objectId); } /** * Uses version annotation to find version for this Model's object. * @return Persistent storage version number if available, null otherwise */ public Serializable getVersion() { final Object o = getObject(); if (o != null) { try { for (final Method m : objectClass.getMethods()) { if (m.isAnnotationPresent(Version.class) && m.getParameterTypes().length == 0) { return (Serializable) m.invoke(o, new Object[] {}); } } for (final Field f : objectClass.getDeclaredFields()) { if (f.isAnnotationPresent(Version.class)) { f.setAccessible(true); return (Serializable) f.get(o); } } } catch (final Exception e) { throw new RuntimeException(e); } } return null; } /** Compares contained objects if present, otherwise calls super-implementation.*/ @SuppressWarnings("unchecked") @Override public boolean equals(final Object obj) { final Object target = getObject(); if (target != null && obj instanceof JPAObjectModel) { return target.equals(((JPAObjectModel)obj).getObject()); } return super.equals(obj); } /** @return hash of contained object if present, otherwise from super-implementation.*/ @Override public int hashCode() { final Object target = getObject(); if (target == null) { return super.hashCode(); } return target.hashCode(); } /** * Disassociates this object from any persistent object, but retains the class * for constructing a blank copy if requested. * @see #isBound() */ public void unbind() { retainedObject = null; detach(); } /** * "bound" models are those that can be loaded from persistent storage by a known id or * query. When bound, this model discards its temporary model object at the end of every * request cycle and reloads it via Hiberanate when needed again. When unbound, its * behavior is dictated by the value of retanUnsaved. * @return true if information needed to load from Hibernate (identifier, query, or criteria) is present */ public boolean isBound() { return objectId != null; } /** * When retainUnsaved is true (the default) and the model is not bound, * the model object must be Serializable as it is retained in the Web session between * requests. See isBound() for more information. * @return true if unsaved objects should be retained between requests. */ public boolean getRetainUnsaved() { return retainUnsaved; } /** * Unsaved Serializable objects can be retained between requests. * @param retainUnsaved set to true to retain unsaved objects */ public void setRetainUnsaved(final boolean retainUnsaved) { this.retainUnsaved = retainUnsaved; } }