net.databinder.models.hib.HibernateObjectModel.java Source code

Java tutorial

Introduction

Here is the source code for net.databinder.models.hib.HibernateObjectModel.java

Source

/*
 * 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.hib;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

import javax.persistence.Version;

import net.databinder.hib.Databinder;
import net.databinder.models.BindingModel;
import net.databinder.models.LoadableWritableModel;

import org.apache.wicket.WicketRuntimeException;
import org.hibernate.Criteria;
import org.hibernate.Hibernate;
import org.hibernate.Session;
import org.hibernate.proxy.HibernateProxyHelper;

/**
 * Model loaded and persisted by Hibernate. 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.
 * @author Nathan Hamblen
 */
public class HibernateObjectModel<T> extends LoadableWritableModel<T> implements BindingModel<T> {
    private Class objectClass;
    private Serializable objectId;
    private QueryBuilder queryBuilder;
    private CriteriaBuilder criteriaBuilder;
    /** May store unsaved objects between requests. */
    private T retainedObject;
    /** Enable retaining unsaved objects between requests. */
    private boolean retainUnsaved = true;

    private Object 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 HibernateObjectModel(Class objectClass, Serializable entityId) {
        this.objectClass = objectClass;
        this.objectId = entityId;
    }

    /**
     * Constructor for a model with no existing persistent object. This class should be
     * Serializable so that the new object can be stored in the session until it is persisted.
     * If serialization is impossible, call setRetainUnsaved(false) and the object will be discarded
     * and recreated with each request.
     * @param objectClass class to be loaded and stored by Hibernate
     */
    public HibernateObjectModel(Class objectClass) {
        this.objectClass = objectClass;
    }

    /**
     * Construct with an entity.
     * @param persistentObject should be previously persisted or Serializable for temp storage.
     */
    public HibernateObjectModel(T persistentObject) {
        setObject(persistentObject);
    }

    /**
     * Construct with a query and binder that return exactly one result. Use this for fetch
     * instructions, scalar results, or if the persistent object ID is not available.
     * Queries that return more than one result will produce exceptions. Queries that return
     * no result will produce a null object.
     * @param queryString query returning one result
     * @param queryBinder bind id or other parameters
     */
    public HibernateObjectModel(String queryString, QueryBinder queryBinder) {
        this(new QueryBinderBuilder(queryString, queryBinder));
    }

    /**
     * Construct with a class and criteria binder that return exactly one result. Use this for fetch
     * instructions, scalar results, or if the persistent object ID is not available. Criteria that
     * return more than one result will produce exceptions. Criteria that return no result
     * will produce a null object.
     * @param objectClass class of object for root criteria
     * @param criteriaBuilder builder to apply criteria restrictions
     */
    public HibernateObjectModel(Class objectClass, CriteriaBuilder criteriaBuilder) {
        this.objectClass = objectClass;
        this.criteriaBuilder = criteriaBuilder;
    }

    /**
     * Construct with a query builder that returns exactly one result, used for custom query
     * objects. Queries that return more than one result will produce exceptions.  Queries that 
     * return no result will produce a null object.
     * @param queryBuilder builder to create and bind query object
     */
    public HibernateObjectModel(QueryBuilder queryBuilder) {
        this.queryBuilder = queryBuilder;
    }

    /**
     * Construct with no object. Will return null for getObject().
     */
    public HibernateObjectModel() {
    }

    /** @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 HibernateObjectModel setFactoryKey(Object 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(T object) {
        unbind(); // clear everything but class, name
        objectClass = null;

        if (object != null) {
            objectClass = HibernateProxyHelper.getClassWithoutInitializingProxy(object);

            Session sess = Databinder.getHibernateSession(factoryKey);
            if (sess.contains(object))
                objectId = sess.getIdentifier(object);
            else if (retainUnsaved)
                retainedObject = (T) object;
            setTempModelObject(object); // skip calling load later
        }
    }

    public Serializable getIdentifier() {
        return Databinder.getHibernateSession(factoryKey).getIdentifier(getObject());
    }

    /**
     * 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.
     */
    @SuppressWarnings("unchecked")
    @Override
    protected T load() {
        if (objectClass == null && queryBuilder == null)
            return null; // can't load without one of these
        try {
            if (!isBound()) {
                if (retainUnsaved && retainedObject != null)
                    return retainedObject;
                else if (retainUnsaved)
                    try {
                        return retainedObject = (T) objectClass.newInstance();
                    } catch (ClassCastException e) {
                        throw new WicketRuntimeException(
                                "Unsaved entity must be Serializable or retainUnsaved set to false; see HibernateObjectModel javadocs.");
                    }
                else
                    return (T) objectClass.newInstance();
            }
        } catch (ClassCastException e) {
            throw new RuntimeException("Retaining unsaved model objects requires that they be Serializable.", e);
        } catch (Throwable e) {
            throw new RuntimeException("Unable to instantiate object. Does it have a default constructor?", e);
        }
        Session sess = Databinder.getHibernateSession(factoryKey);
        if (objectId != null) {
            return (T) sess.get(objectClass, objectId);
        }

        if (criteriaBuilder != null) {
            Criteria criteria = sess.createCriteria(objectClass);
            criteriaBuilder.build(criteria);
            return (T) criteria.uniqueResult();
        }

        return (T) queryBuilder.build(sess).uniqueResult();
    }

    /**
     * Checks if the model is retaining an object this has since become a
     * persistent entity. If so, the ID is fetched and the reference discarded.  
     */
    public void checkBinding() {
        if (!isBound() && retainedObject != null) {
            Session sess = Databinder.getHibernateSession(factoryKey);

            if (sess.contains(retainedObject)) {
                objectId = sess.getIdentifier(retainedObject);
                retainedObject = null;
            }
        }
    }

    /**
     * Uses version annotation to find version for this Model's object.
     * @return Persistent storage version number if available, null otherwise
     */
    public Serializable getVersion() {
        Object o = getObject();

        if (o != null) {
            Class c = Hibernate.getClass(o);
            try {
                for (Method m : c.getMethods())
                    if (m.isAnnotationPresent(Version.class) && m.getParameterTypes().length == 0
                            && m.getReturnType() instanceof Serializable)
                        return (Serializable) m.invoke(o, new Object[] {});
                for (Field f : c.getDeclaredFields())
                    if (f.isAnnotationPresent(Version.class) && f.getType() instanceof Serializable) {
                        f.setAccessible(true);
                        return (Serializable) f.get(o);
                    }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        return null;
    }

    /** Compares contained objects if present, otherwise calls super-implementation.*/
    @Override
    public boolean equals(Object obj) {
        Object target = getObject();
        if (target != null && obj instanceof HibernateObjectModel)
            return target.equals(((HibernateObjectModel) obj).getObject());
        return super.equals(obj);
    }

    /** @return hash of contained object if present, otherwise from super-implementation.*/
    @Override
    public int hashCode() {
        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 HibernateObjectModel#HibernateObjectModel(Class objectClass)
     * @see #isBound()
     */
    public void unbind() {
        objectId = null;
        queryBuilder = null;
        criteriaBuilder = null;
        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 || criteriaBuilder != null || queryBuilder != 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(boolean retainUnsaved) {
        this.retainUnsaved = retainUnsaved;
    }
}