org.j2free.jpa.Controller.java Source code

Java tutorial

Introduction

Here is the source code for org.j2free.jpa.Controller.java

Source

/*
 * Controller.java
 *
 * Copyright 2011 FooBrew, Inc.
 *
 * Licensed 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.j2free.jpa;

import java.io.Serializable;

import java.util.Collection;
import java.util.List;

import javax.naming.InitialContext;
import javax.naming.NamingException;

import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;

import javax.transaction.HeuristicMixedException;
import javax.transaction.HeuristicRollbackException;
import javax.transaction.NotSupportedException;
import javax.transaction.RollbackException;
import javax.transaction.Status;
import javax.transaction.SystemException;
import javax.transaction.UserTransaction;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.queryParser.MultiFieldQueryParser;
import org.apache.lucene.queryParser.ParseException;

import org.hibernate.CacheMode;
import org.hibernate.Criteria;
import org.hibernate.FlushMode;
import org.hibernate.PropertyValueException;
import org.hibernate.SQLQuery;
import org.hibernate.ScrollMode;
import org.hibernate.ScrollableResults;
import org.hibernate.Session;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Example;
import org.hibernate.criterion.NaturalIdentifier;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.ejb.EntityManagerImpl;
import org.hibernate.exception.ConstraintViolationException;
import org.hibernate.search.FullTextSession;
import org.hibernate.search.jpa.FullTextEntityManager;
import org.hibernate.search.jpa.Search;
import org.hibernate.validator.InvalidStateException;
import org.hibernate.validator.InvalidValue;

import org.j2free.util.LaunderThrowable;
import org.j2free.util.KeyValuePair;

/**
 *
 * @author Ryan Wilson
 */
public final class Controller {
    private final Log log = LogFactory.getLog(Controller.class);

    private static Log getLog() {
        return LogFactory.getLog(Controller.class);
    }

    /**
     *
     */
    public static final String ATTRIBUTE_KEY = "controller";

    /*****************************************************
     * ThreadLocal for associating a Controller with each 
     * thread.
     ****************************************************/
    private static final ThreadLocal<Controller> threadLocal = new ThreadLocal<Controller>();

    /**
     * @return The <tt>Controller</tt> associated with the current <tt>Thread</tt>,
     *         or if there wasn't one, a new <tt>Controller</tt> that will
     *         be associated with the current <tt>Thread</tt>. The returned
     *         <tt>Controller</tt> will have an open transaction.
     *
     * Code using <tt>Controller.get()</tt> MUST make sure to call
     * <tt>release()</tt> when finished with the controller to avoid
     * a memory leak.
     *
     * e.g.
     * <pre>
     *      try {
     *          Controller controller = Controller.get();
     *          // ... do some business ... or play ...
     *      } finally {
     *          Controller.release();
     *      }
     * </pre>
     */
    public static Controller get() {
        return get(true);
    }

    /**
     * @param create if true, and there is not already a Controller associated
     *        with this <tt>Thread</tt>, a new Controller will be created. 
     * 
     * @return The <tt>Controller</tt> associated with the current <tt>Thread</tt>
     *         with an active <tt>UserTransaction</tt> open. If there wasn't a
     *         <tt>Controller</tt> with this thread and <tt>create</tt> is true, 
     *         then a new <tt>Controller</tt> will be created and associated with 
     *         the current <tt>Thread</tt>.  If <tt>create</tt> is false, 
     *         <tt>null</tt> will be returned if there was not already a 
     *         <tt>Controller</tt> associated with the current <tt>Thread</tt>.
     *
     *         On the whole, this function guarantees to do one of four things:
     *         If <tt>create == false</tt>:
     *             - return a <tt>Controller</tt> with an open transaction that was 
     *               previously created and associated with the current thread.
     *             - return null, if no code previously called <tt>get(true)</tt>
     *         If <tt>create == true</tt>:
     *             - return a <tt>Controller</tt> with an open transaction that was 
     *               created as a result of this call to <tt>get(true)</tt>
     *             - throw a <tt>RuntimeException</tt>, if a new <tt>Controller</tt>
     *               could not be created, or if there was an exception thrown while
     *               starting a transaction in the new <tt>Controller</tt>
     *
     * @throws RuntimeException if create is <tt>true</tt> and there is an error
     *         creating the controller
     *
     * Exceptions normally thrown by beginning a UserTransaction are laundered
     * from checked exceptions to unchecked RuntimeExceptions so that code calling
     * Controller.get() does not ALWAYS have to catch the exception.  However,
     * code using <tt>Controller.get()</tt> MUST make sure to call
     * <tt>release()</tt> when finished with the controller to avoid
     * a memory leak.
     *
     * e.g.
     * <pre>
     *      try {
     *          Controller controller = Controller.get();
     *          // ... do some business ... or play ...
     *      } finally {
     *          Controller.release();
     *      }
     * </pre>
     */
    public static Controller get(boolean create) {
        Controller controller = threadLocal.get(); // Get the controller associated with this Thread
        if (controller != null) {
            if (controller.isTransactionOpen())
                return controller; // short-circuit, saves a branch
            else {
                threadLocal.remove(); // Sanity check, make sure an old controller isn't stuck on the Thread
                controller = null;
            }
        }
        // At this point, controller == null
        if (create) // If the user requested one be created
        {
            try {
                controller = new Controller(true); // Try to create one... (specify true to start the TX)
                threadLocal.set(controller); // and associate it with the current thread after we know the tx was opened successfully
            } catch (Exception e) {
                throw new RuntimeException(e); // Wrap any problems in a RuntimeException
            }
        }

        return controller;
    }

    /**
     * @return a new <tt>Controller</tt> instance that is not associated with
     *         any Thread.  Callers MUST call <tt>end()</tt> on the controller. 
     *         This controller does not have a transaction open; it's up to
     *         the caller to manage the transaction.
     *
     * @throws RuntimeException if there is an error creating the controller
     */
    public static Controller getIsolatedInstance() {
        try {
            // Only NamingException will be caught here, since the constructor
            // does not throw the other exceptions unless true is specified as
            // the argument.
            return new Controller(false);
        } catch (Exception e) {
            throw new RuntimeException("Error creating isolated Controller", e);
        }
    }

    /**
     * Releases a Controller associated with the current thread, if there was one.
     * 
     * <tt>release()</tt> will internally call <tt>end()</tt> on the instance
     * associated with the current thread, if it was found.
     *
     * @throws RuntimeException if there is an exception ending the UserTransaction
     */
    public static void release() {
        Controller controller = threadLocal.get(); // Get the controller associated with the current-thread
        if (controller != null) // If there was one,
        {
            try {
                controller.end(); // end the transaction
            } catch (RollbackException re) {
                getLog().debug("transaction marked for rollback");
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                threadLocal.remove(); // and ALWAYS disassociate it with the current-thread
            }
        }
    }

    //------------------------------------------------------------//
    // Instance implementation
    //------------------------------------------------------------//

    /**
     *
     */
    protected UserTransaction tx;
    /**
     *
     */
    protected EntityManager em;
    /**
     *
     */
    protected FullTextEntityManager fullTextEntityManager;

    /**
     *
     */
    protected Throwable problem;
    /**
     *
     */
    protected InvalidValue[] errors;

    private Controller(boolean beginTX) throws NamingException, NotSupportedException, SystemException {
        InitialContext ctx = new InitialContext();

        tx = (UserTransaction) ctx.lookup("UserTransaction");
        em = (EntityManager) ctx.lookup("java:comp/env/persistence/EntityManager");

        problem = null;

        if (beginTX)
            begin();
    }

    /**
     * @return The CMT <tt>UserTransaction</tt>
     */
    public UserTransaction getUserTransaction() {
        return tx;
    }

    /**
     * @return The underlying <tt>EntityManager</tt>
     */
    public EntityManager getEntityManager() {
        return em;
    }

    /**
     * Begins the container managed <tt>UserTransaction</tt> and clears fields used to
     * store problems / errors.
     *
     * @throws NotSupportedException
     * @throws SystemException
     */
    public void begin() throws NotSupportedException, SystemException {
        // Make sure a transaction isn't already in progress
        if (tx.getStatus() == Status.STATUS_ACTIVE)
            return;

        // Start the transaction
        tx.begin();

        // make sure the entity manager knows the transaction has begun
        em.joinTransaction();

        // make sure that a transaction always starts clean
        problem = null;
        errors = null;
    }

    /**
     * Ends the contatiner managed <tt>UserTransaction</tt>, committing
     * or rolling back as necessary.
     *
     * @throws SystemException
     * @throws RollbackException
     * @throws HeuristicMixedException
     * @throws HeuristicRollbackException
     */
    public void end()
            throws SystemException, RollbackException, HeuristicMixedException, HeuristicRollbackException {
        try {
            switch (tx.getStatus()) {
            case Status.STATUS_MARKED_ROLLBACK:
                tx.rollback();
                break;
            case Status.STATUS_ACTIVE:
                tx.commit();
                break;
            case Status.STATUS_COMMITTED:
                // somebody called end() twice
                break;
            case Status.STATUS_COMMITTING:
                log.warn("uh oh, concurrency problem! end() called when transaction already committing");
                break;
            case Status.STATUS_ROLLEDBACK:
                // somebody called end() twice
                break;
            case Status.STATUS_ROLLING_BACK:
                log.warn("uh oh, concurrency problem! end() called when transaction already rolling back");
                break;
            default:
                throw new IllegalStateException("Unknown status in endTransaction: " + getTransactionStatus());
            }

            problem = null;
            errors = null;
        } catch (InvalidStateException ise) {
            problem = ise;
            this.errors = ise.getInvalidValues();
        }
    }

    /**
     * @return true if <tt>begin</tt> has been called, otherwise false
     */
    public boolean isTransactionOpen() {
        try {
            return tx.getStatus() == Status.STATUS_ACTIVE;
        } catch (SystemException ex) {
            return false;
        }
    }

    /**
     * Clears the persistence context.
     * @see {@link EntityManager} clear
     */
    public void clear() {
        em.clear();
    }

    /**
     * Flush the persistence context, after which
     * @see {@link EntityManager} flush
     */
    public void flush() {
        try {
            em.flush();
            problem = null;
        } catch (InvalidStateException ise) {
            this.errors = ise.getInvalidValues();
        }
    }

    /**
     * Equivalent to:
     * <pre>
     *      controller.flush();
     *      controller.clear();
     * </pre>
     */
    public void flushAndClear() {
        try {
            em.flush();
            em.clear();
            problem = null;
        } catch (InvalidStateException ise) {
            this.errors = ise.getInvalidValues();
        }
    }

    /**
     * Marks the current transaction to be rolled back.
     */
    public void markForRollback() {
        try {
            tx.setRollbackOnly();
        } catch (Exception e) {
            throw LaunderThrowable.launderThrowable(e);
        }
    }

    /**
     * @return true if the current transaction has been
     *         marked for rollback, otherwise false
     */
    public boolean isMarkedForRollback() {
        try {
            return tx.getStatus() == Status.STATUS_MARKED_ROLLBACK;
        } catch (Exception e) {
            throw LaunderThrowable.launderThrowable(e);
        }
    }

    /**
     * @return a <tt>FullTextEntityManager</tt> for Hibernate Search
     */
    public FullTextEntityManager getFullTextEntityManager() {

        if (fullTextEntityManager == null || !fullTextEntityManager.isOpen()) {
            fullTextEntityManager = Search.getFullTextEntityManager(getEntityManager());
        }
        return fullTextEntityManager;
    }

    /**
     * @return the Hibernate Session
     */
    public Session getSession() {
        return ((EntityManagerImpl) em.getDelegate()).getSession();
    }

    /**
     * 
     * @param mode
     * @throws SystemException
     */
    public void setCacheMode(CacheMode mode) throws SystemException {
        if (!isTransactionOpen()) {
            return;
        }

        getSession().setCacheMode(mode);
    }

    /**
     * 
     * @param collectionName
     * @param primaryKey
     */
    public void evictCollection(String collectionName, Serializable primaryKey) {
        getSession().getSessionFactory().evictCollection(collectionName, primaryKey);
    }

    /**
     * 
     * @param clazz
     * @param primaryKey
     */
    public void evict(Class clazz, Serializable primaryKey) {
        getSession().getSessionFactory().evict(clazz, primaryKey);
    }

    /**
     * 
     * @param collection
     */
    public void evictCollection(String collection) {
        getSession().getSessionFactory().evictCollection(collection);
    }

    /**
     * 
     * @param clazz
     */
    public void evict(Class clazz) {
        getSession().getSessionFactory().evict(clazz);
    }

    /**
     * 
     * @param query
     * @return
     */
    public Query createQuery(String query) {
        return em.createQuery(query);
    }

    /**
     * 
     * @param query
     * @return
     */
    public Query createNativeQuery(String query) {
        return em.createNativeQuery(query);
    }

    /**
     * @param <T> The type of entity to fetch
     * @param entityClass The class of the entity to fetch
     * @param entityId The id of the entity to fetch
     * @return The entity, or null if it was not found
     */
    public <T> T findPrimaryKey(Class<T> entityClass, Object entityId) {
        return (T) em.find(entityClass, entityId);
    }

    /**
     * Equivalent to:
     * <pre>
     *      list(entityClass, -1, -1);
     * </pre>
     *
     * @param <T> The type of entity to fetch
     * @param entityClass The class of the entity to fetch
     * @return a <tt>List</tt> of entities found
     */
    public <T> List<T> list(Class<T> entityClass) {
        return list(entityClass, -1, -1);
    }

    /**
     * @param <T> The type of entity to fetch
     * @param entityClass The class of the entity to fetch
     * @param start The first entity to fetch
     * @param limit How many entities to fetch
     * @return a <tt>List</tt> of entities found
     */
    public <T> List<T> list(Class<T> entityClass, int start, int limit) {
        Query query = em.createQuery("SELECT e FROM " + entityClass.getSimpleName() + " e");

        if (start > 0) {
            query.setFirstResult(start);
        }
        if (limit > 0) {
            query.setMaxResults(limit);
        }

        return (List<T>) query.getResultList();
    }

    /**
     * @param <T> The type of entity to fetch
     * @param entityClass The class of the entity to count
     * @return The number of entities of the specified type
     */
    public <T> int count(Class<T> entityClass) {

        Object o = null;

        try {
            Query query = em.createQuery("SELECT COUNT(e) FROM " + entityClass.getSimpleName() + " e");

            o = query.getSingleResult();

            return o == null ? -1 : ((Long) o).intValue();

        } catch (ClassCastException cce) {
            return o == null ? -1 : ((java.math.BigInteger) o).intValue();
        }
    }

    /**
     * @param <T> The type of entity to fetch
     * @param query
     * @param parameters the {@link KeyValuePair}s for the variables referenced in the query
     * @return The number of entities found
     */
    public <T> int count(Query query, KeyValuePair<String, ? extends Object>... parameters) {
        int count = -1;
        Object o = null;
        if (parameters != null) {
            for (KeyValuePair<String, ? extends Object> parameter : parameters) {
                query.setParameter(parameter.key, parameter.value);
            }
        }
        try {
            o = query.getSingleResult();
            count = ((Long) o).intValue();
        } catch (NoResultException nre) {
            count = 0;
        } catch (ClassCastException cce) {
            count = ((java.math.BigInteger) o).intValue();
        }
        return count;
    }

    /**
     * @param queryString A JPQL/HQL query string
     * @param parameters the {@link KeyValuePair}s for the variables referenced in the query
     * @return The number of entities found
     */
    public int count(String queryString, KeyValuePair<String, ? extends Object>... parameters) {
        return count(em.createQuery(queryString), parameters);
    }

    /**
     *
     * @param <T>
     * @param entityClass
     * @param namedQuery
     * @param parameters
     * @return
     */
    public <T> int namedCount(Class<T> entityClass, String namedQuery,
            KeyValuePair<String, ? extends Object>... parameters) {
        return count(em.createNamedQuery(entityClass.getSimpleName() + "." + namedQuery), parameters);
    }

    /**
     * 
     * @param <T>
     * @param entityClass
     * @param entityId
     * @return
     */
    public <T> T proxy(Class<T> entityClass, Object entityId) {
        return (T) em.getReference(entityClass, entityId);
    }

    /**
     * 
     * @param <T>
     * @param entity
     * @return
     */
    public <T> T merge(T entity) {
        entity = (T) em.merge(entity);
        return entity;
    }

    /**
     * 
     * @param <T>
     * @param entity
     */
    public <T> void refresh(T entity) {
        em.refresh(entity);
    }

    /**
     * 
     * @param <T>
     * @param entityClass
     * @param entityId
     */
    public <T> void remove(Class<T> entityClass, Object entityId) {
        T entity = (T) findPrimaryKey(entityClass, entityId);

        if (entity == null)
            throw new IllegalStateException("Error removing " + entityClass.getSimpleName() + " with id = "
                    + entityId + ". Entity not found!");

        em.remove(entity);
    }

    /**
     * 
     * @param <T>
     * @param entity
     */
    public <T> void remove(T entity) {

        if (entity == null)
            throw new IllegalStateException("Cannot remove null entity!");

        em.remove(entity);
    }

    /**
     * 
     * @param <T>
     * @param entity
     * @return
     */
    public <T> T persist(T entity) {
        return persist(entity, false);
    }

    /**
     * 
     * @param <T>
     * @param entity
     * @param flush
     * @return
     */
    public <T> T persist(T entity, boolean flush) {
        try {

            em.persist(entity);

            this.errors = null;

            if (flush) {
                em.flush();
            }

        } catch (InvalidStateException ise) {
            this.problem = ise;
            this.errors = ise.getInvalidValues();

            if (log.isDebugEnabled()) {
                for (InvalidValue error : ise.getInvalidValues())
                    log.warn("Invalid Value: " + error.getBeanClass() + "." + error.getPropertyName() + " = "
                            + error.getValue() + " | " + error.getMessage());
            }

            markForRollback();

        } catch (ConstraintViolationException cve) {
            this.problem = cve;
            markForRollback();
        } catch (PropertyValueException pve) {
            this.problem = pve;
            markForRollback();
        }
        return entity;
    }

    /**
     * 
     * @return
     */
    public boolean hasErrors() {
        return this.errors != null || this.problem != null;
    }

    /**
     * 
     * @return
     */
    public InvalidValue[] getErrors() {
        return this.errors;
    }

    /**
     * 
     */
    public void clearErrors() {
        this.errors = null;
    }

    /**
     * 
     * @return
     */
    public Throwable getLastException() {
        return problem;
    }

    /**
     * 
     * @param delimiter
     * @param debug
     * @return
     */
    public String getErrorsAsString(String delimiter, boolean debug) {
        StringBuilder errorStringBuilder = new StringBuilder();
        boolean first = true;
        for (InvalidValue error : this.errors) {
            if (debug) {
                errorStringBuilder.append((first ? "" : delimiter) + "Invalid Value: " + error.getBeanClass() + "."
                        + error.getPropertyName() + " = " + error.getValue() + ", message = " + error.getMessage());
            } else {
                errorStringBuilder.append((first ? "" : delimiter) + error.getMessage());
            }

            if (first) {
                first = false;
            }
        }
        return errorStringBuilder.toString();
    }

    /**
     * Find all objects matching criteria
     * 
     * Sample criteria include:
     * Restrictions.eq("property", variable)
     * Restrictions.eq("subclass.property", variable)
     * Order.desc("property")
     * 
     * See for more examples:
     * http://www.hibernate.org/hib_docs/v3/api/org/hibernate/criterion/Restrictions.html
     * 
     *
     * @param <T>
     * @param entityClass
     * @param criteria 
     * @return
     */
    public <T> List<T> listByCriterions(Class<T> entityClass, Object... criteria) {
        return listByCriterions(entityClass, -1, -1, criteria);
    }

    /**
     * Find numResults objects matching criteria
     * 
     * Sample criteria include:
     * Restrictions.eq("property", variable)
     * Restrictions.eq("subclass.property", variable)
     * Order.desc("property")
     * 
     * See for more examples:
     * http://www.hibernate.org/hib_docs/v3/api/org/hibernate/criterion/Restrictions.html
     * 
     *
     * @param <T>
     * @param entityClass
     * @param numResults
     * @param criteria
     * @return
     */
    public <T> List<T> listByCriterions(Class<T> entityClass, int numResults, Object... criteria) {
        return listByCriterions(entityClass, 0, numResults, criteria);
    }

    /**
     * Find numResults objects matching criteria starting at firstResult
     * 
     * Sample criteria include:
     * Restrictions.eq("property", variable)
     * Restrictions.eq("subclass.property", variable)
     * Order.desc("property")
     * 
     * See for more examples:
     * http://www.hibernate.org/hib_docs/v3/api/org/hibernate/criterion/Restrictions.html
     * 
     *
     * @param <T>
     * @param entityClass
     * @param firstResult
     * @param criteria
     * @param numResults
     * @return
     */
    public <T> List<T> listByCriterions(Class<T> entityClass, int firstResult, int numResults, Object... criteria) {
        try {
            Criteria search = getSession().createCriteria(entityClass);
            for (Object c : criteria) {
                if (c instanceof Criterion || c.getClass() == Criterion.class) {
                    search.add((Criterion) c);
                }
                if (c instanceof Order || c.getClass() == Order.class) {
                    search.addOrder((Order) c);
                }
            }
            if (firstResult > 0) {
                search.setFirstResult(firstResult);
            }
            if (numResults > 0) {
                search.setMaxResults(numResults);
            }
            return (List<T>) search.list();
        } catch (NoResultException e) {
            return null;
        }
    }

    /**
     * Find numResults objects matching criteria starting at firstResult
     * 
     * See for more examples:
     * http://www.hibernate.org/hib_docs/v3/api/org/hibernate/criterion/Restrictions.html
     * 
     *
     * @param <T>
     * @param entityClass
     * @param numResults
     * @param firstResult
     * @param criteria
     * @return
     */
    public <T> List<T> listByCriterea(Class<T> entityClass, int firstResult, int numResults, Criteria criteria) {
        if (firstResult > 0) {
            criteria.setFirstResult(firstResult);
        }
        if (numResults > 0) {
            criteria.setMaxResults(numResults);
        }
        return listByCriteria(entityClass, criteria);
    }

    /**
     * Find numResults objects matching criteria starting at firstResult
     * 
     * See for more examples:
     * http://www.hibernate.org/hib_docs/v3/api/org/hibernate/criterion/Restrictions.html
     * 
     *
     * @param <T>
     * @param entityClass
     * @param criteria
     * @return
     */
    public <T> List<T> listByCriteria(Class<T> entityClass, Criteria criteria) {
        try {
            return (List<T>) criteria.list();
        } catch (NoResultException e) {
            return null;
        }
    }

    /**
     * Find a single object matching criteria
     * 
     * Sample criteria include:
     * Restrictions.eq("property", variable)
     * Restrictions.eq("subclass.property", variable)
     * Order.desc("property")
     * 
     * See for more examples:
     * http://www.hibernate.org/hib_docs/v3/api/org/hibernate/criterion/Restrictions.html
     * 
     *
     * @param <T>
     * @param entityClass
     * @param criteria
     * @return
     */
    public <T> T findByCriterion(Class<T> entityClass, Object... criteria) {
        try {
            Criteria search = getSession().createCriteria(entityClass);
            for (Object c : criteria) {
                if (c instanceof Criterion || c.getClass() == Criterion.class) {
                    search.add((Criterion) c);
                }
                if (c instanceof Order || c.getClass() == Order.class) {
                    search.addOrder((Order) c);
                }
            }
            return (T) search.setMaxResults(1).uniqueResult();
        } catch (NoResultException e) {
            return null;
        }
    }

    /**
     * Find a single object matching criteria using a naturalId lookup with the queryByFormula cache
     * 
     * Sample criteria include:
     * Restrictions.naturalId().set("email",request.getRemoteUser());
     * 
     * See for more examples:
     * http://www.hibernate.org/hib_docs/reference/en/html/querycriteria.html#queryByFormula-criteria-naturalid
     * @param <T> 
     * @param entityClass
     * @param naturalId 
     * @return
     */
    public <T> T findNaturalId(Class<T> entityClass, NaturalIdentifier naturalId) {
        try {
            return (T) (getSession().createCriteria(entityClass).add(naturalId).setCacheable(true).uniqueResult());
        } catch (NoResultException e) {
            return null;
        }
    }

    /**
     * Find the count of object matching the criteria
     * 
     * Sample criteria include:
     * Restrictions.eq("property", variable)
     * Restrictions.eq("subclass.property", variable)
     * Order.desc("property")
     * 
     * See for more examples:
     * http://www.hibernate.org/hib_docs/v3/api/org/hibernate/criterion/Restrictions.html
     * 
     *
     * @param <T>
     * @param entityClass
     * @param criteria
     * @return
     */
    public <T> int count(Class<T> entityClass, Object... criteria) {
        try {
            Criteria search = getSession().createCriteria(entityClass);
            for (Object c : criteria) {
                if (c instanceof Criterion || c.getClass() == Criterion.class) {
                    search.add((Criterion) c);
                }
            }
            search.setProjection(Projections.rowCount());
            return ((Integer) search.list().get(0)).intValue();
        } catch (NoResultException e) {
            return 0;
        }
    }

    /**
     * 
     * @param <T>
     * @param exampleInstance
     * @param excludeProperties
     * @return
     */
    public <T> List<T> listByExample(T exampleInstance, String... excludeProperties) {
        try {
            Example example = Example.create(exampleInstance);
            for (String prop : excludeProperties) {
                example.excludeProperty(prop);
            }

            return (List<T>) getSession().createCriteria(exampleInstance.getClass()).add(example).list();
        } catch (NoResultException e) {
            return null;
        }
    }

    /**
     * 
     * @param <T>
     * @param entityClass
     * @param example
     * @return
     */
    public <T> List<T> listByExample(Class<T> entityClass, Example example) {
        try {
            return (List<T>) getSession().createCriteria(entityClass).add(example).list();
        } catch (NoResultException e) {
            return null;
        }
    }

    /**
     * 
     * @param <T>
     * @param formula
     * @return
     */
    public <T> T queryByFormula(QueryFormula formula) {
        return (T) query(em.createQuery(formula.getQuery()), formula.getParametersAsPairArray());
    }

    /**
     * 
     * @param <T>
     * @param queryString
     * @param parameters
     * @return
     */
    public <T> T query(String queryString, KeyValuePair<String, ? extends Object>... parameters) {
        Query query = em.createQuery(queryString);
        return (T) query(query, parameters);
    }

    /**
     * 
     * @param <T>
     * @param query
     * @param parameters
     * @return
     */
    public <T> T query(Query query, KeyValuePair<String, ? extends Object>... parameters) {
        if (parameters != null) {
            for (KeyValuePair<String, ? extends Object> parameter : parameters) {
                query.setParameter(parameter.key, parameter.value);
            }
        }
        try {
            return (T) query.setMaxResults(1).getSingleResult();
        } catch (NoResultException nre) {
            return null;
        }
    }

    /**
     *
     * @param <T>
     * @param entityClass
     * @param queryString
     * @param parameters
     * @return
     */
    public <T> T query(Class<T> entityClass, String queryString,
            KeyValuePair<String, ? extends Object>... parameters) {
        try {
            return (T) query(em.createQuery(queryString), parameters);
        } catch (NoResultException e) {
            return null;
        }
    }

    /**
     *
     * @param <T>
     * @param entityClass
     * @param namedQuery
     * @param parameters
     * @return
     */
    public <T> T namedQuery(Class<T> entityClass, String namedQuery,
            KeyValuePair<String, ? extends Object>... parameters) {
        try {
            return (T) query(em.createNamedQuery(entityClass.getSimpleName() + "." + namedQuery), parameters);
        } catch (NoResultException e) {
            return null;
        }
    }

    /**
     *
     * @param <T>
     * @param returnClass
     * @param namedQuery
     * @param parameters
     * @return
     */
    public <T> T namedScaler(Class<T> returnClass, String namedQuery,
            KeyValuePair<String, ? extends Object>... parameters) {
        try {
            return (T) query(em.createNamedQuery(namedQuery), parameters);
        } catch (NoResultException e) {
            return null;
        }
    }

    /**
     * 
     * @param <T>
     * @param queryString
     * @param parameters
     * @return
     */
    public <T> List<T> list(String queryString, KeyValuePair<String, ? extends Object>... parameters) {
        return (List<T>) list(queryString, 0, -1, parameters);
    }

    /**
     *
     * @param <T>
     * @param queryString
     * @param start
     * @param limit
     * @param parameters
     * @return
     */
    public <T> List<T> list(String queryString, int start, int limit,
            KeyValuePair<String, ? extends Object>... parameters) {
        Query query = em.createQuery(queryString);
        return (List<T>) list(query, start, limit, parameters);
    }

    /**
     * 
     * @param <T>
     * @param query
     * @param parameters
     * @return
     */
    public <T> List<T> list(Query query, KeyValuePair<String, ? extends Object>... parameters) {
        return (List<T>) list(query, 0, -1, parameters);
    }

    /**
     *
     * @param <T>
     * @param query
     * @param start
     * @param limit
     * @param parameters
     * @return
     */
    public <T> List<T> list(Query query, int start, int limit,
            KeyValuePair<String, ? extends Object>... parameters) {
        if (parameters != null) {
            for (KeyValuePair<String, ? extends Object> parameter : parameters) {
                query.setParameter(parameter.key, parameter.value);
            }
        }
        if (start > 0) {
            query.setFirstResult(start);
        }
        if (limit > 0) {
            query.setMaxResults(limit);
        }
        try {
            return (List<T>) query.getResultList();
        } catch (NoResultException nre) {
            return null;
        }
    }

    /**
     * 
     * @param namedQuery
     * @param parameters
     * @return
     */
    public List<Object[]> namedList(String namedQuery, KeyValuePair<String, ? extends Object>... parameters) {
        return namedList(namedQuery, -1, -1, parameters);
    }

    /**
     *
     * @param namedQuery
     * @param start
     * @param limit
     * @param parameters
     * @return
     */
    public List<Object[]> namedList(String namedQuery, int start, int limit,
            KeyValuePair<String, ? extends Object>... parameters) {
        Query query = em.createNamedQuery(namedQuery);
        if (parameters != null) {
            for (KeyValuePair<String, ? extends Object> parameter : parameters) {
                query.setParameter(parameter.key, parameter.value);
            }
        }
        if (start > 0) {
            query.setFirstResult(start);
        }
        if (limit > 0) {
            query.setMaxResults(limit);
        }
        try {
            return (List<Object[]>) query.getResultList();
        } catch (NoResultException nre) {
            return null;
        }
    }

    /**
     * 
     * @param namedQuery
     * @param parameters
     * @return
     */
    public List<Object[]> namedNativeList(String namedQuery, KeyValuePair<String, ? extends Object>... parameters) {
        return namedNativeList(namedQuery, -1, -1, parameters);
    }

    /**
     *
     * @param namedQuery
     * @param start
     * @param limit
     * @param parameters
     * @return
     */
    public List<Object[]> namedNativeList(String namedQuery, int start, int limit,
            KeyValuePair<String, ? extends Object>... parameters) {
        Query query = em.createNativeQuery(namedQuery);
        if (parameters != null) {
            for (KeyValuePair<String, ? extends Object> parameter : parameters) {
                query.setParameter(parameter.key, parameter.value);
            }
        }
        if (start > 0) {
            query.setFirstResult(start);
        }
        if (limit > 0) {
            query.setMaxResults(limit);
        }
        try {
            return (List<Object[]>) query.getResultList();
        } catch (NoResultException nre) {
            return null;
        }
    }

    /**
     *
     * @param <T>
     * @param entityClass
     * @param queryString
     * @param parameters
     * @return
     */
    public <T> List<T> nativeList(Class<T> entityClass, String queryString,
            KeyValuePair<String, ? extends Object>... parameters) {

        Session session = getSession();

        SQLQuery query = session.createSQLQuery(queryString).addEntity(entityClass);

        if (parameters != null) {
            for (KeyValuePair<String, ? extends Object> parameter : parameters) {
                query.setParameter(parameter.key, parameter.value);
            }
        }

        try {
            return (List<T>) query.list();
        } catch (NoResultException nre) {
            return null;
        }
    }

    /**
     *
     * @param <T>
     * @param scalarType
     * @param queryString
     * @param parameters
     * @return
     */
    public <T> List<T> nativeScalerList(Class<T> scalarType, String queryString,
            KeyValuePair<String, ? extends Object>... parameters) {
        Query query = em.createNativeQuery(queryString);
        if (parameters != null) {
            for (KeyValuePair<String, ? extends Object> parameter : parameters) {
                query.setParameter(parameter.key, parameter.value);
            }
        }
        try {
            return (List<T>) query.getResultList();
        } catch (NoResultException nre) {
            return null;
        }
    }

    /**
     * 
     * @param queryString
     * @param parameters
     * @return
     */
    public List<Object[]> nativeList(String queryString, KeyValuePair<String, ? extends Object>... parameters) {
        Query query = em.createNativeQuery(queryString);
        if (parameters != null) {
            for (KeyValuePair<String, ? extends Object> parameter : parameters) {
                query.setParameter(parameter.key, parameter.value);
            }
        }
        try {
            return (List<Object[]>) query.getResultList();
        } catch (NoResultException nre) {
            return null;
        }
    }

    /**
     * 
     * @param queryString
     * @param parameters
     * @return
     */
    public Object nativeScalar(String queryString, KeyValuePair<String, ? extends Object>... parameters) {
        Query query = em.createNativeQuery(queryString);
        if (parameters != null) {
            for (KeyValuePair<String, ? extends Object> parameter : parameters) {
                query.setParameter(parameter.key, parameter.value);
            }
        }
        try {
            return query.getSingleResult();
        } catch (NoResultException nre) {
            return null;
        }
    }

    /**
     * 
     * @param queryString
     * @param parameters
     * @return
     */
    public int nativeCount(String queryString, KeyValuePair<String, ? extends Object>... parameters) {
        Query query = em.createNativeQuery(queryString);

        int count = 0;
        Object o = null;
        if (parameters != null) {
            for (KeyValuePair<String, ? extends Object> parameter : parameters) {
                query.setParameter(parameter.key, parameter.value);
            }
        }

        try {
            o = query.getSingleResult();
            count = ((Long) o).intValue();
        } catch (NoResultException nre) {
            count = 0;
        } catch (ClassCastException cce) {
            count = ((java.math.BigInteger) o).intValue();
        }

        return count;
    }

    /**
     *
     * @param <T>
     * @param entityClass
     * @param queryString
     * @param parameters
     * @return
     */
    public <T> List<T> list(Class<T> entityClass, String queryString,
            KeyValuePair<String, ? extends Object>... parameters) {
        return list(entityClass, queryString, -1, -1, parameters);
    }

    /**
     *
     * @param <T>
     * @param entityClass
     * @param namedQuery
     * @param parameters
     * @return
     */
    public <T> List<T> namedList(Class<T> entityClass, String namedQuery,
            KeyValuePair<String, ? extends Object>... parameters) {
        return namedList(entityClass, namedQuery, -1, -1, parameters);
    }

    /**
     *
     * @param <T>
     * @param entityClass
     * @param queryString
     * @param start
     * @param limit
     * @param parameters
     * @return
     */
    public <T> List<T> list(Class<T> entityClass, String queryString, int start, int limit,
            KeyValuePair<String, ? extends Object>... parameters) {
        try {
            return (List<T>) list(em.createQuery(queryString), start, limit, parameters);
        } catch (NoResultException e) {
            return null;
        }
    }

    /**
     *
     * @param <T>
     * @param entityClass
     * @param namedQuery
     * @param start
     * @param limit
     * @param parameters
     * @return
     */
    public <T> List<T> namedList(Class<T> entityClass, String namedQuery, int start, int limit,
            KeyValuePair<String, ? extends Object>... parameters) {
        try {
            return (List<T>) list(em.createNamedQuery(entityClass.getSimpleName() + "." + namedQuery), start, limit,
                    parameters);
        } catch (NoResultException e) {
            return null;
        }
    }

    /**
     * @param <T> 
     * @param objects
     * @deprecated
     * will cause memory problems when mapping large numbers of entities, use the alternate version with batch sizes
     **/
    public <T> void hibernateSearchIndex(List<T> objects) {
        this.getFullTextEntityManager();
        for (T o : objects) {
            fullTextEntityManager.index(o);
        }
    }

    /**
     * It is critical that batchSize matches the hibernate.search.worker.batch_size you set
     *
     * @param <T>
     * @param entityClass
     * @param batchSize
     */
    public <T> void hibernateSearchIndex(Class<T> entityClass, int batchSize) {
        FullTextSession fullTextSession = org.hibernate.search.Search.getFullTextSession(getSession());
        fullTextSession.setFlushMode(FlushMode.MANUAL);
        fullTextSession.setCacheMode(CacheMode.IGNORE);

        ScrollableResults results = fullTextSession.createCriteria(entityClass).setFetchSize(batchSize)
                .scroll(ScrollMode.FORWARD_ONLY);

        try {
            int index = 0;
            while (results.next()) {
                index++;
                fullTextSession.index(results.get(0)); //index each element

                //clear every batchSize since the queue is processed
                if (index % batchSize == 0) {
                    fullTextSession.flushToIndexes();
                    fullTextSession.clear();
                }
            }
        } finally {
            results.close();
        }
    }

    /**
     * It is critical that batchSize matches the hibernate.search.worker.batch_size you set
     *
     * @param <T>
     * @param entityClass
     * @param batchSize
     */
    public <T> void hibernateSearchClearAndIndex(Class<T> entityClass, int batchSize) {
        FullTextSession fullTextSession = org.hibernate.search.Search.getFullTextSession(getSession());
        fullTextSession.purgeAll(entityClass);
        hibernateSearchIndex(entityClass, batchSize);
        fullTextSession.getSearchFactory().optimize(entityClass);
    }

    /**
     * 
     * @param <T>
     * @param entityClass
     * @param entityId
     */
    public <T> void hibernateSearchRemove(Class<T> entityClass, int entityId) {
        FullTextSession fullTextSession = org.hibernate.search.Search.getFullTextSession(getSession());
        fullTextSession.purge(entityClass, entityId);
        fullTextSession.flush(); //index are written at commit time
    }

    /**
     * 
     * @param <T>
     * @param entityClass
     * @param query
     * @param fields
     * @return
     * @throws ParseException
     */
    public <T> List<T> hibernateSearchResults(Class<T> entityClass, String query, String[] fields)
            throws ParseException {
        return hibernateSearchResults(entityClass, query, -1, -1, fields);
    }

    /**
     *
     * @param <T>
     * @param entityClass
     * @param query
     * @param limit
     * @param fields
     * @return
     * @throws ParseException
     */
    public <T> List<T> hibernateSearchResults(Class<T> entityClass, String query, int limit, String[] fields)
            throws ParseException {
        return hibernateSearchResults(entityClass, query, -1, limit, fields);
    }

    /**
     *
     * @param <T>
     * @param entityClass
     * @param query
     * @param start
     * @param limit
     * @param fields
     * @return
     * @throws ParseException
     */
    public <T> List<T> hibernateSearchResults(Class<T> entityClass, String query, int start, int limit,
            String[] fields) throws ParseException {
        try {
            MultiFieldQueryParser parser = new MultiFieldQueryParser(fields, new StandardAnalyzer());

            //org.apache.lucene.search.Query luceneQuery = parser.parse(query.trim().replaceAll(" ","~ ").replaceAll(" [Aa][Nn][Dd]~ "," AND ").replaceAll(" [Oo][Rr]~ "," OR ") + "~");
            //org.apache.lucene.search.Query luceneQuery = parser.parse(query.trim());
            org.apache.lucene.search.Query luceneQuery;

            query = filterLuceneQuery(query);
            luceneQuery = parser.parse(query);

            org.hibernate.search.jpa.FullTextQuery hibQuery = getFullTextEntityManager()
                    .createFullTextQuery(luceneQuery, entityClass);

            if (start >= 0 && limit > 0) {
                return hibQuery.setFirstResult(start).setMaxResults(limit).getResultList();
            } else if (limit > 0) {
                return hibQuery.setMaxResults(limit).getResultList();
            } else if (start >= 0) {
                return hibQuery.setFirstResult(start).getResultList();
            } else {
                return hibQuery.getResultList();
            }

        } catch (NoResultException e) {
            return null;
        }
    }

    /**
     * 
     * @param <T>
     * @param entityClass
     * @param query
     * @param fields
     * @return
     * @throws ParseException
     */
    public <T> int hibernateSearchCount(Class<T> entityClass, String query, String[] fields) throws ParseException {
        try {
            MultiFieldQueryParser parser = new MultiFieldQueryParser(fields, new StandardAnalyzer());

            //org.apache.lucene.search.Query luceneQuery = parser.parse(query.trim().replaceAll(" ","~ ").replaceAll(" [Aa][Nn][Dd]~ "," AND ").replaceAll(" [Oo][Rr]~ "," OR ") + "~");
            //org.apache.lucene.search.Query luceneQuery = parser.parse(query.trim());
            org.apache.lucene.search.Query luceneQuery;

            query = filterLuceneQuery(query);
            luceneQuery = parser.parse(query);

            org.hibernate.search.jpa.FullTextQuery hibQuery = getFullTextEntityManager()
                    .createFullTextQuery(luceneQuery, entityClass);
            return hibQuery.getResultSize();

        } catch (NoResultException e) {
            return 0;
        }
    }

    private String filterLuceneQuery(String queryOrig) {

        String query = queryOrig;

        // Make words wildcard for partial matches
        //query = query.trim().replaceAll("\\s+", "* ");
        // Make last word wildcard for partial matches
        //query += "*";

        // Escape all the special chars that shouldnt be next to a *
        /*
        query = query.replaceAll(" [Aa][Nn][Dd]\\* ", " AND ").replaceAll(" [Oo][Rr]\\* ", " OR ").replaceAll("[-]\\*","-").
            replaceAll("[)]\\*", ")").replaceAll("[(]\\*", "(").replaceAll("[!]\\*", "!").replaceAll("[?]\\*", "?").
            replaceAll("[:]\\*", ":").replaceAll("[+]\\*", "+");
        */

        query = query.replaceAll("[-]", "\\\\-").replaceAll("[)]", "\\\\)").replaceAll("[(]", "\\\\(")
                .replaceAll("[!]", "\\\\!").replaceAll("[?]", "\\\\?").replaceAll("[:]", "\\\\:")
                .replaceAll("[+]", "\\\\+");

        return query;
    }

    /**
     * 
     * @param <T>
     * @param entityClass
     * @return
     */
    public <T> Criteria createCriteria(Class<T> entityClass) {
        return getSession().createCriteria(entityClass);
    }

    /**
     * 
     * @param <T>
     * @param entityClass
     * @param alias
     * @return
     */
    public <T> Criteria createCriteria(Class<T> entityClass, String alias) {
        return getSession().createCriteria(entityClass, alias);
    }

    /**
     * 
     * @param queryString
     * @param parameters
     * @return
     */
    public int update(String queryString, KeyValuePair<String, ? extends Object>... parameters) {
        Query query = em.createQuery(queryString);
        return update(query, parameters);
    }

    /**
     * 
     * @param queryString
     * @param parameters
     * @return
     */
    public int nativeUpdate(String queryString, KeyValuePair<String, ? extends Object>... parameters) {
        Query query = em.createNativeQuery(queryString);
        return update(query, parameters);
    }

    /**
     * 
     * @param query
     * @param parameters
     * @return
     */
    public int update(Query query, KeyValuePair<String, ? extends Object>... parameters) {
        if (parameters != null) {
            for (KeyValuePair<String, ? extends Object> parameter : parameters) {
                query.setParameter(parameter.key, parameter.value);
            }
        }
        return query.executeUpdate();
    }

    /**
     * 
     * @param <T>
     * @param collection
     * @param filterString
     * @return
     */
    public <T> Object filterSingle(Collection<T> collection, String filterString) {
        List<Object> list = filter(collection, filterString, 0, 1);
        return list.size() > 0 ? list.get(0) : null;
    }

    /**
     * 
     * @param <T>
     * @param collection
     * @param filterString
     * @param params
     * @return
     */
    public <T> Object filterSingle(Collection<T> collection, String filterString,
            KeyValuePair<String, ? extends Object>... params) {
        List<Object> list = filter(collection, filterString, 0, 1, params);
        return list.size() > 0 ? list.get(0) : null;
    }

    /**
     * 
     * @param <T>
     * @param collection
     * @param filterString
     * @return
     */
    public <T> List filter(Collection<T> collection, String filterString) {
        return filter(collection, filterString, 0, -1);
    }

    /**
     * 
     * @param <T>
     * @param collection
     * @param filterString
     * @param start
     * @param limit
     * @return
     */
    public <T> List filter(Collection<T> collection, String filterString, int start, int limit) {
        org.hibernate.Query query = getSession().createFilter(collection, filterString).setFirstResult(start);
        if (limit > 0) {
            query.setMaxResults(limit);
        }

        return query.list();
    }

    /**
     * 
     * @param <T>
     * @param collection
     * @param filterString
     * @param params
     * @return
     */
    public <T> List filter(Collection<T> collection, String filterString,
            KeyValuePair<String, ? extends Object>... params) {
        return filter(collection, filterString, 0, -1, params);
    }

    /**
     *
     * @param <T>
     * @param collection
     * @param filterString
     * @param start
     * @param limit
     * @param params
     * @return
     */
    public <T> List filter(Collection<T> collection, String filterString, int start, int limit,
            KeyValuePair<String, ? extends Object>... params) {
        org.hibernate.Query query = getSession().createFilter(collection, filterString).setFirstResult(start);
        if (limit > 0) {
            query.setMaxResults(limit);
        }

        for (KeyValuePair<String, ? extends Object> param : params) {
            query.setParameter(param.key, param.value);
        }

        return query.list();
    }

    /**
     * 
     * @return
     * @throws SystemException
     */
    public String getTransactionStatus() throws SystemException {
        if (tx == null) {
            return "Null transaction";
        }

        switch (tx.getStatus()) {
        case Status.STATUS_ACTIVE:
            return "Active";
        case Status.STATUS_COMMITTED:
            return "Committed";
        case Status.STATUS_COMMITTING:
            return "Committing";
        case Status.STATUS_MARKED_ROLLBACK:
            return "Marked for rollback";
        case Status.STATUS_NO_TRANSACTION:
            return "No Transaction";
        case Status.STATUS_PREPARED:
            return "Prepared";
        case Status.STATUS_ROLLEDBACK:
            return "Rolledback";
        case Status.STATUS_ROLLING_BACK:
            return "Rolling back";
        case Status.STATUS_UNKNOWN:
            return "Declared Unknown";
        default:
            return "Undeclared Unknown Status";
        }
    }
}