com.eucalyptus.entities.Entities.java Source code

Java tutorial

Introduction

Here is the source code for com.eucalyptus.entities.Entities.java

Source

/*************************************************************************
 * Copyright 2009-2013 Eucalyptus Systems, Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 3 of the License.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see http://www.gnu.org/licenses/.
 *
 * Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta
 * CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need
 * additional information or have any questions.
 *
 * This file may incorporate work covered under the following copyright
 * and permission notice:
 *
 *   Software License Agreement (BSD License)
 *
 *   Copyright (c) 2008, Regents of the University of California
 *   All rights reserved.
 *
 *   Redistribution and use of this software in source and binary forms,
 *   with or without modification, are permitted provided that the
 *   following conditions are met:
 *
 *     Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *     Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer
 *     in the documentation and/or other materials provided with the
 *     distribution.
 *
 *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 *   FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 *   COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 *   INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 *   BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *   LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 *   CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 *   LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 *   ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *   POSSIBILITY OF SUCH DAMAGE. USERS OF THIS SOFTWARE ACKNOWLEDGE
 *   THE POSSIBLE PRESENCE OF OTHER OPEN SOURCE LICENSED MATERIAL,
 *   COPYRIGHTED MATERIAL OR PATENTED MATERIAL IN THIS SOFTWARE,
 *   AND IF ANY SUCH MATERIAL IS DISCOVERED THE PARTY DISCOVERING
 *   IT MAY INFORM DR. RICH WOLSKI AT THE UNIVERSITY OF CALIFORNIA,
 *   SANTA BARBARA WHO WILL THEN ASCERTAIN THE MOST APPROPRIATE REMEDY,
 *   WHICH IN THE REGENTS' DISCRETION MAY INCLUDE, WITHOUT LIMITATION,
 *   REPLACEMENT OF THE CODE SO IDENTIFIED, LICENSING OF THE CODE SO
 *   IDENTIFIED, OR WITHDRAWAL OF THE CODE CAPABILITY TO THE EXTENT
 *   NEEDED TO COMPLY WITH ANY SUCH LICENSES OR RIGHTS.
 ************************************************************************/

package com.eucalyptus.entities;

import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.UUID;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.LockModeType;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.transaction.Synchronization;
import org.apache.log4j.Logger;
import org.hibernate.Criteria;
import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.collection.internal.AbstractPersistentCollection;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Example;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.exception.ConstraintViolationException;
import org.hibernate.exception.LockAcquisitionException;
import org.hibernate.proxy.HibernateProxy;
import com.eucalyptus.bootstrap.Databases;
import com.eucalyptus.configurable.ConfigurableClass;
import com.eucalyptus.configurable.ConfigurableField;
import com.eucalyptus.records.Logs;
import com.eucalyptus.system.Ats;
import com.eucalyptus.system.Threads;
import com.eucalyptus.util.Classes;
import com.eucalyptus.util.Exceptions;
import com.eucalyptus.util.HasNaturalId;
import com.eucalyptus.util.LogUtil;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Predicate;
import com.google.common.base.Strings;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import static com.eucalyptus.util.Parameters.checkParam;
import static org.hamcrest.Matchers.notNullValue;

@ConfigurableClass(root = "bootstrap.tx", description = "Parameters controlling transaction behaviour.")
public class Entities {
    @ConfigurableField(description = "Maximum number of times a transaction may be retried before giving up.", initial = "5")
    public static Integer CONCURRENT_UPDATE_RETRIES = 10;
    private static final boolean CLEANUP_TX_SESSION = Boolean
            .valueOf(System.getProperty("com.eucalyptus.entities.cleanupTxSession", "true"));
    private static Cache<String, String> txLog = CacheBuilder.newBuilder().weakKeys().softValues().build(); // No softKeys available for CacheBuilder
    private static Logger LOG = Logger.getLogger(Entities.class);
    private static ThreadLocal<String> txRootThreadLocal = new ThreadLocal<String>();
    private static ThreadLocal<ConcurrentMap<String, CascadingTx>> txStateThreadLocal = new ThreadLocal<ConcurrentMap<String, CascadingTx>>() {

        @Override
        protected ConcurrentMap<String, CascadingTx> initialValue() {
            return Maps.newConcurrentMap();
        }

    };

    static String lookatPersistenceContext(final Object obj) throws RuntimeException {
        final Class type = Classes.typeOf(obj);
        final Ats ats = Ats.inClassHierarchy(type);
        PersistenceContext persistenceContext = null;
        if (!ats.has(PersistenceContext.class)) {
            throw new RuntimeException("Attempting to create an entity wrapper instance for non persistent type: "
                    + type + ".  Class hierarchy contains: \n" + ats.toString());
        } else {
            persistenceContext = ats.get(PersistenceContext.class);
        }
        return persistenceContext.name();
    }

    private static boolean hasTransaction() {
        return !txStateThreadLocal.get().isEmpty();
    }

    public static boolean hasTransaction(final Object obj) {
        final String ctx = lookatPersistenceContext(obj);
        final CascadingTx tx = txStateThreadLocal.get().get(ctx);
        if (tx == null) {
            return false;
        } else if (tx.isActive()) {
            return true;
        } else {
            cleanStrandedTx(tx);
            return false;
        }
    }

    private static CascadingTx getTransaction(final Object obj) {
        if (hasTransaction(obj)) {
            return txStateThreadLocal.get().get(lookatPersistenceContext(obj));
        } else {
            throw new NoSuchElementException("Failed to find active transaction for persistence context: "
                    + lookatPersistenceContext(obj) + " and object: " + obj);
        }
    }

    public static void removeTransaction(final CascadingTx tx) {
        final String txId = makeTxRootName(tx);
        txLog.invalidate(txStateThreadLocal.toString() + tx.getRecord().getPersistenceContext());
        txStateThreadLocal.get().remove(tx.getRecord().getPersistenceContext());
        if (txId.equals(txStateThreadLocal.get())) {
            for (final Entry<String, CascadingTx> e : txStateThreadLocal.get().entrySet()) {
                cleanStrandedTx(e.getValue());
            }
            txStateThreadLocal.get().clear();
            txStateThreadLocal.remove();
        }
    }

    private static void cleanStrandedTx(final CascadingTx txValue) {
        LOG.error("Found stranded transaction: " + txValue.getRecord().getPersistenceContext() + " started at: "
                + txValue.getRecord().getStack());
        try {
            txValue.rollback();
        } catch (final Exception ex) {
            LOG.trace(ex, ex);
        }
    }

    private static String makeTxRootName(final CascadingTx tx) {
        return txStateThreadLocal.toString() + tx.getRecord().getPersistenceContext();
    }

    private static CascadingTx createTransaction(final Object obj)
            throws RecoverablePersistenceException, RuntimeException {
        final String ctx = lookatPersistenceContext(obj);
        final CascadingTx ret = new CascadingTx(ctx);
        try {
            ret.begin();
            if (txRootThreadLocal.get() == null) {
                final String txId = makeTxRootName(ret);
                LOG.trace("Creating root entry for transaction tree: " + txId + " at: \n"
                        + Threads.currentStackString());
                txRootThreadLocal.set(txId);
            }
            txStateThreadLocal.get().put(ctx, ret);
            return ret;
        } catch (RuntimeException ex) {
            ret.rollback();
            throw ex;
        }
    }

    public static EntityTransaction get(final Object obj) {
        if (hasTransaction(obj)) {
            final CascadingTx tx = getTransaction(obj);
            final EntityTransaction etx = tx.join();
            return etx;
        } else {
            return createTransaction(obj);
        }
    }

    public static <T> void flush(final T object) {
        getTransaction(object).txState.getEntityManager().flush();
    }

    /**
     * WARNING: This method uses wildcard matching
     * @see #query(T,QueryOptions)
     */
    public static <T> List<T> query(final T example) {
        return query(example, false);
    }

    /**
     * WARNING: This method uses wildcard matching
     * @see #query(T,QueryOptions)
     */
    @SuppressWarnings({ "unchecked", "cast" })
    public static <T> List<T> query(final T example, final boolean readOnly) {
        final Example qbe = Example.create(example).enableLike(MatchMode.EXACT);
        final List<T> resultList = (List<T>) getTransaction(example).getTxState().getSession()
                .createCriteria(example.getClass()).setReadOnly(readOnly)
                .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY).setCacheable(true).add(qbe).list();
        return Lists.newArrayList(Sets.newHashSet(resultList));
    }

    /**
     * Query items matching the given example restricted by the given criterion.
     *
     * <P>The caller must have an active transaction for the entity.</P>
     *
     * <P>WARNING: This method uses wildcard matching</P>
     * 
     * @param example The example object
     * @param readOnly Use True if the results will not be modified
     * @param criterion Additional restrictions for the query
     * @param aliases Any aliases necessary for the given criterion
     * @param <T> The entity type
     * @return The result list
     */
    @SuppressWarnings({ "unchecked", "cast" })
    public static <T> List<T> query(final T example, final boolean readOnly, final Criterion criterion,
            final Map<String, String> aliases) {
        final Example qbe = Example.create(example).enableLike(MatchMode.EXACT);
        final Criteria criteria = getTransaction(example).getTxState().getSession()
                .createCriteria(example.getClass()).setReadOnly(readOnly)
                .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY).setCacheable(true).add(qbe).add(criterion);
        for (final Map.Entry<String, String> aliasEntry : aliases.entrySet()) {
            criteria.createAlias(aliasEntry.getKey(), aliasEntry.getValue()); // inner join by default
        }
        final List<T> resultList = (List<T>) criteria.list();
        return Lists.newArrayList(Sets.newHashSet(resultList));
    }

    /**
     * WARNING: This method uses wildcard matching
     * @see #query(T,QueryOptions)
     */
    @SuppressWarnings({ "unchecked", "cast" })
    public static <T> List<T> query(final T example, final boolean readOnly, final int maxResults) {
        final Example qbe = Example.create(example).enableLike(MatchMode.EXACT);
        final List<T> resultList = (List<T>) getTransaction(example).getTxState().getSession()
                .createCriteria(example.getClass()).setReadOnly(readOnly)
                .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY).setCacheable(true).add(qbe)
                .setMaxResults(maxResults).setFetchSize(maxResults).list();
        return Lists.newArrayList(Sets.newHashSet(resultList));
    }

    @SuppressWarnings("unchecked")
    public static <T> List<T> query(final T example, final QueryOptions options) {
        final Example qbe = setOptions(Example.create(example), options);
        final List<T> resultList = (List<T>) setOptions(
                getTransaction(example).getTxState().getSession().createCriteria(example.getClass()), options)
                        .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY).add(qbe).list();
        return Lists.newArrayList(Sets.newLinkedHashSet(resultList));
    }

    private static Criteria setOptions(final Criteria criteria, final QueryOptions options) {
        final Integer maxResults = options.getMaxResults();
        if (maxResults != null) {
            criteria.setMaxResults(maxResults);
        }
        final Integer fetchSize = options.getFetchSize();
        if (fetchSize != null) {
            criteria.setFetchSize(fetchSize);
        }
        final Boolean cacheable = options.getCacheable();
        if (cacheable != null) {
            criteria.setCacheable(cacheable);
        }
        final Boolean readonly = options.getReadonly();
        if (readonly != null) {
            criteria.setReadOnly(readonly);
        }
        final Criterion criterion = options.getCriterion();
        if (criterion != null) {
            criteria.add(criterion);
        }
        return criteria;
    }

    private static Example setOptions(final Example example, final QueryOptions options) {
        if (options.getMatchMode() != null) {
            example.enableLike(options.getMatchMode());
        }
        return example;
    }

    public static QueryOptionsBuilder queryOptions() {
        return new QueryOptionsBuilder();
    }

    public static interface QueryOptions {
        @Nullable
        MatchMode getMatchMode();

        @Nullable
        Integer getMaxResults();

        @Nullable
        Integer getFetchSize();

        @Nullable
        Boolean getCacheable();

        @Nullable
        Boolean getReadonly();

        @Nullable
        Criterion getCriterion();
    }

    public static final class QueryOptionsBuilder {
        private MatchMode matchMode;
        private Integer maxResults;
        private Integer fetchSize;
        private Boolean cacheable;
        private Boolean readonly;
        private Criterion criterion;

        public QueryOptionsBuilder withMatchMode(final MatchMode matchMode) {
            this.matchMode = matchMode;
            return this;
        }

        public QueryOptionsBuilder withMaxResults(final Integer maxResults) {
            this.maxResults = maxResults;
            return this;
        }

        public QueryOptionsBuilder withFetchSize(final Integer fetchSize) {
            this.fetchSize = fetchSize;
            return this;
        }

        public QueryOptionsBuilder withCacheable(final Boolean cacheable) {
            this.cacheable = cacheable;
            return this;
        }

        public QueryOptionsBuilder withReadonly(final Boolean readonly) {
            this.readonly = readonly;
            return this;
        }

        public QueryOptionsBuilder withCriterion(final Criterion criterion) {
            this.criterion = criterion;
            return this;
        }

        public QueryOptions build() {
            return new QueryOptions() {
                @Override
                public MatchMode getMatchMode() {
                    return matchMode;
                }

                @Override
                public Integer getMaxResults() {
                    return maxResults;
                }

                @Override
                public Integer getFetchSize() {
                    return fetchSize;
                }

                @Override
                public Boolean getCacheable() {
                    return cacheable;
                }

                @Override
                public Boolean getReadonly() {
                    return readonly;
                }

                @Override
                public Criterion getCriterion() {
                    return criterion;
                }
            };
        }
    }

    public static <T> T uniqueResult(final T example) throws TransactionException, NoSuchElementException {
        try {
            final Object pk = resolvePrimaryKey(example);
            final String natId = resolveNaturalId(example);
            if (pk != null) {
                return maybePrimaryKey(example);
            } else if (natId != null) {
                return maybeNaturalId(example);
            } else {
                return maybeDefinitelyExample(example);
            }
        } catch (final NoSuchElementException ex) {
            throw ex;
        } catch (final RuntimeException ex) {
            Logs.extreme().trace(ex, ex);
            final Exception newEx = PersistenceExceptions.throwFiltered(ex);
            throw new TransactionInternalException(newEx.getMessage(), newEx);
        }
    }

    public static void evict(final Object obj) {
        getTransaction(obj).getTxState().getSession().evict(obj);
    }

    private static <T> String resolveNaturalId(final T example) {
        if ((example instanceof HasNaturalId) && (((HasNaturalId) example).getNaturalId() != null)) {
            return ((HasNaturalId) example).getNaturalId();
        } else {
            return null;
        }
    }

    private static <T> T maybeDefinitelyExample(final T example) throws HibernateException, NoSuchElementException {
        @SuppressWarnings("unchecked")
        final T ret = (T) Entities.getTransaction(example).getTxState().getSession()
                .createCriteria(example.getClass()).add(Example.create(example).enableLike(MatchMode.EXACT))
                .setCacheable(true).setMaxResults(1).setFetchSize(1).setFirstResult(0).uniqueResult();
        if (ret == null) {
            throw new NoSuchElementException("example: " + LogUtil.dumpObject(example));
        }
        return ret;
    }

    private static <T> T maybeNaturalId(final T example) throws HibernateException, NoSuchElementException {
        final String natId = ((HasNaturalId) example).getNaturalId();
        @SuppressWarnings("unchecked")
        final T ret = (T) Entities.getTransaction(example).getTxState().getSession().byNaturalId(example.getClass())
                .using("naturalId", natId).load();
        if (ret == null) {
            throw new NoSuchElementException("@NaturalId: " + natId);
        }
        return ret;
    }

    private static <T> T maybePrimaryKey(final T example) throws NoSuchElementException {
        final Object id = resolvePrimaryKey(example);
        if (id == null) {
            return null;
        } else {
            final T res = (T) Entities.getTransaction(example).getTxState().getEntityManager()
                    .find(example.getClass(), id);
            if (res == null) {
                throw new NoSuchElementException("@Id: " + id);
            } else {
                return res;
            }
        }
    }

    private static <T> Object resolvePrimaryKey(final T example) {
        return Entities.getTransaction(example).getTxState().getEntityManager().getEntityManagerFactory()
                .getPersistenceUnitUtil().getIdentifier(example);
    }

    public static Criteria createCriteria(final Class class1) {
        return getTransaction(class1).getTxState().getSession().createCriteria(class1);
    }

    public static Criteria createCriteriaUnique(final Class class1) {
        return getTransaction(class1).getTxState().getSession().createCriteria(class1).setCacheable(true)
                .setFetchSize(1).setMaxResults(1).setFirstResult(0);
    }

    /**
     * Invokes underlying persist implementation per jsr-220
     * 
     * @throws ConstraintViolationException
     * @see http://opensource.atlassian.com/projects/hibernate/browse/HHH-1273
     */
    public static <T> T persist(final T newObject) throws ConstraintViolationException {
        try {
            getTransaction(newObject).getTxState().getEntityManager().persist(newObject);
            return newObject;
        } catch (final RuntimeException ex) {
            PersistenceExceptions.throwFiltered(ex);
            throw ex;
        }
    }

    /**
     * <table>
     * <tbody>
     * <tr valign="top">
     * <th>Scenario</th>
     * <th><tt>EntityManager.persist</tt></th>
     * <th><tt>EntityManager.merge</tt></th>
     * <th><tt>SessionManager.saveOrUpdate</tt></th>
     * </tr>
     * <tr valign="top">
     * <th>Object passed was never persisted</th>
     * 
     * <td>1. Object added to persistence context as new entity<br>
     * 2. New entity inserted into database at flush/commit</td>
     * <td>1. State copied to new entity.<br>
     * 2. New entity added to persistence context<br>
     * 3. New entity inserted into database at flush/commit<br>
     * 4. New entity returned</td>
     * <td>1. Object added to persistence context as new entity<br>
     * 2. New entity inserted into database at flush/commit</td>
     * </tr>
     * <tr valign="top">
     * <th>Object was previously persisted, but not loaded in this persistence context</th>
     * <td>1. <tt>EntityExistsException</tt> thrown (or a <tt>PersistenceException</tt> at
     * flush/commit)</td>
     * 
     * <td>2. Existing entity loaded.<br>
     * 2. State copied from object to loaded entity<br>
     * 3. Loaded entity updated in database at flush/commit<br>
     * 4. Loaded entity returned</td>
     * <td>1. Object added to persistence context<br>
     * 2. Loaded entity updated in database at flush/commit</td>
     * </tr>
     * <tr valign="top">
     * <th>Object was previously persisted and already loaded in this persistence context</th>
     * <td>1. <tt>EntityExistsException</tt> thrown (or a <tt>PersistenceException</tt> at flush or
     * commit time)</td>
     * 
     * <td>1. State from object copied to loaded entity<br>
     * 2. Loaded entity updated in database at flush/commit<br>
     * 3. Loaded entity returned</td>
     * <td>1. <tt>NonUniqueObjectException</tt> thrown</td>
     * </tr>
     * </tbody>
     * </table>
     * 
     * @throws ConstraintViolationException
     * @throws NoSuchElementException
     */
    public static <T> T merge(final T newObject) throws ConstraintViolationException {
        if (!isPersistent(newObject)) {
            try {
                return uniqueResult(newObject);
            } catch (final Exception ex) {
                return persist(newObject);
            }
        } else {
            try {
                final T persistedObject = getTransaction(newObject).getTxState().getEntityManager()
                        .merge(newObject);
                return persistedObject == newObject ? newObject : persistedObject;
            } catch (final RuntimeException ex) {

                PersistenceExceptions.throwFiltered(ex);
                throw ex;
            }
        }
    }

    /**
     * Invokes underlying merge implementation per jsr-220
     *
     * @param object The object to merge
     * @param <T> The return type
     * @return The persistent instance for the object
     */
    public static <T> T mergeDirect(final T object) {
        try {
            return getTransaction(object).getTxState().getEntityManager().merge(object);
        } catch (final RuntimeException ex) {
            PersistenceExceptions.throwFiltered(ex);
            throw ex;
        }
    }

    public <T> T lookupAndClose(final T example) throws NoSuchElementException {
        EntityTransaction db;
        T ret = null;
        if (!hasTransaction(example)) {
            db = get(example);
        } else {
            db = getTransaction(example).join();
        }
        try {
            ret = uniqueResult(example);
            db.commit();
        } catch (final TransactionException ex) {
            db.rollback();
            throw new NoSuchElementException(ex.getMessage());
        }
        return ret;
    }

    public static <T> Function<T, T> merge() {
        return new Function<T, T>() {

            @Override
            public T apply(final T arg0) {
                return Entities.merge(arg0);
            }
        };
    }

    public static <T> void refresh(final T newObject) throws ConstraintViolationException {
        try {
            getTransaction(newObject).getTxState().getEntityManager().refresh(newObject, (LockModeType) null);
        } catch (final RuntimeException ex) {
            PersistenceExceptions.throwFiltered(ex);
            throw ex;
        }
    }

    public static <T> void refresh(final T newObject, final LockModeType lockMode)
            throws ConstraintViolationException {
        try {
            getTransaction(newObject).getTxState().getEntityManager().refresh(newObject, lockMode);
        } catch (final RuntimeException ex) {
            PersistenceExceptions.throwFiltered(ex);
            throw ex;
        }
    }

    /**
     * {@inheritDoc Session}
     */
    public static boolean isPersistent(final Object obj) {
        if (!hasTransaction(obj)) {
            return false;
        } else {
            return getTransaction(obj).getTxState().getSession().contains(obj);
        }
    }

    /**
     * Check if a collection or proxy is initialized.
     *
     * @param obj The object to test
     * @return True if initialized
     */
    public static boolean isInitialized(@Nullable final Object obj) {
        return obj != null && Hibernate.isInitialized(obj);
    }

    /**
     * Check if a collection or proxy is readable.
     *
     * <p>A lazy object is readable if it has an active transaction or if it is
     * already initialized.</p>
     *
     * @param obj The object to test
     * @return True if readable
     */
    public static boolean isReadable(@Nullable final Object obj) {
        final SessionImplementor sessionImplementor = getSession(obj);
        return obj != null && ((sessionImplementor != null && sessionImplementor.isOpen()) || isInitialized(obj));
    }

    private static SessionImplementor getSession(@Nullable final Object obj) {
        SessionImplementor session = null;
        if (obj instanceof AbstractPersistentCollection) {
            session = ((AbstractPersistentCollection) obj).getSession();
        } else if (obj instanceof HibernateProxy) {
            session = ((HibernateProxy) obj).getHibernateLazyInitializer().getSession();
        }
        return session;
    }

    /**
     * Initialize a lazy proxy / collection, etc.
     *
     * <p>Force loading of a lazy object.</p>
     *
     * <p>This WILL NOT initialize an entity.</p>
     *
     * @param obj The proxy / collection to initialize.
     */
    public static void initialize(@Nullable final Object obj) {
        Hibernate.initialize(obj);
    }

    /**
     *
     */
    public static <T> void delete(final T deleteObject) {
        getTransaction(deleteObject).getTxState().getEntityManager().remove(deleteObject);
    }

    /**
     * Delete all entities for the given class.
     *
     * @param <T> The entity type
     * @param deleteClass The entity class
     */
    public static <T> int deleteAll(final Class<T> deleteClass) {
        return deleteAllMatching(deleteClass, null, Collections.<String, Object>emptyMap());
    }

    /**
     * Delete all matching entities for the given class.
     *
     * @param <T> The entity type
     * @param deleteClass The entity class
     * @param condition The condition to match
     */
    public static <T> int deleteAllMatching(final Class<T> deleteClass, final String condition,
            final Map<String, ?> parameters) {
        try {
            final Query query = getTransaction(deleteClass).getTxState().getEntityManager()
                    .createQuery("DELETE FROM " + deleteClass.getName() + " " + Strings.nullToEmpty(condition));
            for (final Entry<String, ?> entry : parameters.entrySet()) {
                query.setParameter(entry.getKey(), entry.getValue());
            }
            return query.executeUpdate();
        } catch (Exception e) {
            LOG.error(deleteClass, e);
            throw Exceptions.toUndeclared(e);
        }
    }

    /**
     * Count the matching entities for the given example.
     * 
     * @param example The example entity
     * @return The number of matching entities
     */
    public static long count(final Object example) {
        return count(example, Restrictions.conjunction(), Collections.<String, String>emptyMap());
    }

    /**
     * Count the matching entities for the given example.
     *
     * @param example The example entity
     * @param criterion Additional restrictions for the query
     * @param aliases Any aliases necessary for the given criterion
     * @return The number of matching entities
     */
    public static long count(final Object example, final Criterion criterion, final Map<String, String> aliases) {
        final Example qbe = Example.create(example);
        final Criteria criteria = getTransaction(example).getTxState().getSession()
                .createCriteria(example.getClass()).setReadOnly(true).setCacheable(false).add(qbe).add(criterion)
                .setProjection(Projections.rowCount());
        for (final Map.Entry<String, String> aliasEntry : aliases.entrySet()) {
            criteria.createAlias(aliasEntry.getKey(), aliasEntry.getValue()); // inner join by default
        }
        final Number count = (Number) criteria.uniqueResult();
        return count.longValue();
    }

    /**
     * TODO REMOVE THIS, DO NOT USE
     * @deprecated
     */
    @Deprecated
    public static void registerClose(final Class<?> emClass) {
        final EntityManager entityManager = getTransaction(emClass).getTxState().getEntityManager();
        Entities.registerSynchronization(emClass, new Synchronization() {
            @Override
            public void afterCompletion(final int i) {
                entityManager.close();
            }

            @Override
            public void beforeCompletion() {
            }
        });
    }

    public static <T> void registerSynchronization(final Class<T> syncClass,
            final Synchronization synchronization) {
        final Session session = getTransaction(syncClass).getTxState().getSession();
        final Transaction transaction = session.getTransaction();
        transaction.registerSynchronization(synchronization);
    }

    /**
     * Private for a reason.
     */
    private static class CascadingTx implements EntityTransaction {
        private final TxRecord record;
        private TxState txState;

        /**
         * Private for a reason.
         * 
         * @see {@link CascadingTx#get(Class)}
         * @param persistenceContext
         * @throws RecoverablePersistenceException
         */
        @SuppressWarnings("unchecked")
        CascadingTx(final String ctx) throws RecoverablePersistenceException {
            final StackTraceElement ste = Threads.currentStackFrame(4);
            final String uuid = UUID.randomUUID().toString();
            this.record = new TxRecord(ctx, uuid, ste);
            try {
                this.txState = new TxState(ctx);
            } catch (final RuntimeException ex) {
                Logs.extreme().error(ex, ex);
                this.rollback();
                throw PersistenceExceptions.throwFiltered(ex);
            }
        }

        /**
         * @delegate Do not change semantics here.
         * @see javax.persistence.EntityTransaction#getRollbackOnly()
         */
        @Override
        public boolean getRollbackOnly() throws RecoverablePersistenceException {
            return this.txState == null ? false : this.txState.getRollbackOnly();
        }

        /**
         * @delegate Do not change semantics here.
         * @see javax.persistence.EntityTransaction#setRollbackOnly()
         */
        @Override
        public void setRollbackOnly() throws RecoverablePersistenceException {
            if (this.txState != null) {
                this.txState.setRollbackOnly();
            }
        }

        /**
         * @delegate Do not change semantics here.
         * @see javax.persistence.EntityTransaction#isActive()
         */
        @Override
        public boolean isActive() throws RecoverablePersistenceException {
            return this.txState == null ? false : this.txState.isActive();
        }

        /**
         * @delegate Do not change semantics here.
         * @see javax.persistence.EntityTransaction#begin()
         */
        @Override
        public void begin() throws RecoverablePersistenceException {
            try {
                this.txState.begin();
            } catch (final RecoverablePersistenceException ex) {
                PersistenceExceptions.throwFiltered(ex);
                removeTransaction(this);
            } catch (final RuntimeException ex) {
                PersistenceExceptions.throwFiltered(ex);
                removeTransaction(this);
                throw ex;
            }
        }

        /**
         * @delegate Do not change semantics here.
         * @see javax.persistence.EntityTransaction#rollback()
         */
        @Override
        public void rollback() throws RecoverablePersistenceException {
            removeTransaction(this);
            if ((this.txState != null) && this.txState.isActive()) {
                try {
                    this.txState.rollback();
                    this.txState = null;
                } catch (final RuntimeException ex) {
                    Logs.extreme().error(ex);
                    //          throw PersistenceExceptions.throwFiltered( ex );
                }
            } else {
                Logs.extreme().debug("Duplicate call to rollback( )");
            }
        }

        /**
         * @delegate Do not change semantics here.
         * @see javax.persistence.EntityTransaction#commit()
         */
        @Override
        public void commit() throws RecoverablePersistenceException {
            removeTransaction(this);
            if ((this.txState != null) && this.txState.isActive()) {
                try {
                    this.txState.commit();
                } catch (final RuntimeException ex) {
                    throw PersistenceExceptions.throwFiltered(ex);
                }
            } else {
                Logs.extreme().error("Duplicate call to commit( ): " + Threads.currentStackString());
            }
        }

        TxState getTxState() {
            return this.txState;
        }

        public EntityTransaction join() {
            return new EntityTransaction() {
                @Override
                public void setRollbackOnly() {
                }

                @Override
                public void rollback() {
                }

                @Override
                public void commit() {
                }

                @Override
                public void begin() {
                }

                @Override
                public boolean isActive() {
                    return CascadingTx.this.isActive();
                }

                @Override
                public boolean getRollbackOnly() {
                    return CascadingTx.this.getRollbackOnly();
                }
            };
        }

        TxRecord getRecord() {
            return this.record;
        }

        private class TxState implements EntityTransaction {
            private EntityManager em;
            private EntityTransaction transaction;
            private final WeakReference<Session> sessionRef;

            public TxState(final String ctx) {
                try {
                    final EntityManagerFactory anemf = PersistenceContexts.getEntityManagerFactory(ctx);
                    checkParam(anemf, notNullValue());
                    this.em = anemf.createEntityManager();
                    checkParam(this.em, notNullValue());
                    this.transaction = this.em.getTransaction();
                    checkParam(this.transaction, notNullValue());
                    this.sessionRef = new WeakReference<Session>((Session) this.em.getDelegate());
                } catch (final RuntimeException ex) {
                    this.doCleanup();
                    throw ex;
                }
            }

            private void doCleanup() {
                // transaction
                if (this.transaction != null && this.transaction.isActive())
                    try {
                        this.transaction.rollback();
                    } catch (final RuntimeException ex) {
                        LOG.warn(ex);
                        Logs.extreme().warn(ex, ex);
                    }
                this.transaction = null;

                if (CLEANUP_TX_SESSION) {
                    // sessionRef
                    if (this.sessionRef != null && (this.sessionRef.get() != null)) {
                        this.sessionRef.clear();
                    }

                    //em
                    if (this.em != null && this.em.isOpen())
                        try {
                            this.em.close();
                        } catch (final RuntimeException ex) {
                            LOG.warn(ex);
                            Logs.extreme().warn(ex, ex);
                        }
                    this.em = null;
                }
            }

            EntityManager getEntityManager() {
                return this.em;
            }

            Session getSession() {
                return this.sessionRef.get();
            }

            /**
             * @delegate Do not change semantics here.
             * @see javax.persistence.EntityTransaction#begin()
             */
            @Override
            public void begin() {
                try {
                    this.transaction.begin();
                } catch (final RuntimeException ex) {
                    LOG.warn(ex);
                    Logs.extreme().warn(ex, ex);
                    doCleanup();
                    throw ex;
                }
            }

            /**
             * @delegate Do not change semantics here.
             * @see javax.persistence.EntityTransaction#commit()
             */
            @Override
            public void commit() {
                try {
                    this.transaction.commit();
                } catch (final RuntimeException ex) {
                    LOG.trace(ex, ex);
                    Logs.extreme().warn(ex, ex);
                    throw ex;
                } finally {
                    doCleanup();
                }
            }

            /**
             * @delegate Do not change semantics here.
             * @see javax.persistence.EntityTransaction#getRollbackOnly()
             */
            @Override
            public boolean getRollbackOnly() {
                return this.transaction.getRollbackOnly();
            }

            /**
             * @delegate Do not change semantics here.
             * @see javax.persistence.EntityTransaction#isActive()
             */
            @Override
            public boolean isActive() {
                return this.transaction != null && this.transaction.isActive();
            }

            /**
             * @delegate Do not change semantics here.
             * @see javax.persistence.EntityTransaction#rollback()
             */
            @Override
            public void rollback() {
                try {
                    this.transaction.rollback();
                } catch (final RuntimeException ex) {
                    LOG.error(ex, ex);
                    throw ex;
                } finally {
                    doCleanup();
                }
            }

            /**
             * @delegate Do not change semantics here.
             * @see javax.persistence.EntityTransaction#setRollbackOnly()
             */
            @Override
            public void setRollbackOnly() {
                this.transaction.setRollbackOnly();
            }

        }
    }

    public static class TxRecord {
        private final String persistenceContext;
        private final String uuid;
        private final Long startTime;
        private final StackTraceElement ste;
        private final String stack;

        TxRecord(final String persistenceContext, final String uuid, final StackTraceElement ste) {
            this.persistenceContext = persistenceContext;
            this.uuid = uuid;
            this.ste = ste;
            this.stack = Threads.currentStackString();
            this.startTime = System.currentTimeMillis();
        }

        public Long getStartTime() {
            return this.startTime;
        }

        public String getPersistenceContext() {
            return this.persistenceContext;
        }

        public String getUuid() {
            return this.uuid;
        }

        public StackTraceElement getSte() {
            return this.ste;
        }

        String getStack() {
            return this.stack;
        }

    }

    private static class TransactionalFunction<E, D, R> implements Function<D, R> {
        private Class<E> entityType;
        private Function<D, R> function;
        private Integer retries = CONCURRENT_UPDATE_RETRIES;

        TransactionalFunction(Class<E> entityType, Function<D, R> function, Integer retries) {
            this.entityType = entityType;
            this.function = function;
            this.retries = retries;
        }

        @Override
        public R apply(final D input) {
            RuntimeException rootCause = null;
            for (int i = 0; i < retries; i++) {
                EntityTransaction db = Entities.get(this.entityType);
                try {
                    R ret = this.function.apply(input);
                    db.commit();
                    return ret;
                } catch (RuntimeException ex) {
                    db.rollback();
                    if (Exceptions.isCausedBy(ex, OptimisticLockException.class)) {
                        rootCause = Exceptions.findCause(ex, OptimisticLockException.class);
                    } else if (Exceptions.isCausedBy(ex, LockAcquisitionException.class)) {
                        rootCause = Exceptions.findCause(ex, LockAcquisitionException.class);
                    } else {
                        rootCause = ex;
                        Logs.extreme().error(ex, ex);
                        throw ex;
                    }
                    try {
                        TimeUnit.MILLISECONDS.sleep(20);
                    } catch (InterruptedException ex1) {
                        Exceptions.maybeInterrupted(ex1);
                    }
                    continue;
                }
            }
            throw (rootCause != null ? rootCause
                    : new NullPointerException(
                            "BUG: Transaction retry failed but root cause exception is unknown!"));
        }

    }

    public static <E, T> Predicate<T> asTransaction(final Predicate<T> predicate) {
        final List<Class> generics = Classes.genericsToClasses(predicate);
        for (final Class<?> type : generics) {
            if (PersistenceContexts.isPersistentClass(type)) {
                return asTransaction(type, predicate);
            }
        }
        throw new IllegalArgumentException(
                "Failed to find generics for provided predicate, cannot make into transaction: "
                        + Threads.currentStackString());
    }

    public static <E, T> Predicate<T> asTransaction(final Class<E> type, final Predicate<T> predicate) {
        return asTransaction(type, predicate, CONCURRENT_UPDATE_RETRIES);
    }

    public static <E, T> Predicate<T> asTransaction(final Class<E> type, final Predicate<T> predicate,
            final Integer retries) {
        final Function<T, Boolean> funcionalized = Functions.forPredicate(predicate);
        final Function<T, Boolean> transactionalized = Entities.asTransaction(type, funcionalized, retries);
        return new Predicate<T>() {

            @Override
            public boolean apply(T input) {
                return transactionalized.apply(input);
            }

        };
    }

    public static <T, R> Function<T, R> asTransaction(final Function<T, R> function) {
        if (function instanceof TransactionalFunction) {
            return function;
        } else {
            final List<Class> generics = Classes.genericsToClasses(function);
            for (final Class<?> type : generics) {
                if (PersistenceContexts.isPersistentClass(type)) {
                    return asTransaction(type, function);
                }
            }
            throw new IllegalArgumentException(
                    "Failed to find generics for provided function, cannot make into transaction: "
                            + Threads.currentStackString());
        }
    }

    public static <E, T, R> Function<T, R> asTransaction(final Class<E> type, final Function<T, R> function) {
        if (function instanceof TransactionalFunction) {
            return function;
        } else {
            return asTransaction(type, function, CONCURRENT_UPDATE_RETRIES);
        }
    }

    public static <E, T, R> Function<T, R> asTransaction(final Class<E> type, final Function<T, R> function,
            final int retries) {
        if (function instanceof TransactionalFunction) {
            return function;
        } else {
            return new TransactionalFunction<E, T, R>(type, function, retries);
        }
    }

    public static void commit(EntityTransaction tx) {
        if (tx.getRollbackOnly()) {
            tx.rollback();
        } else if (Databases.isVolatile()) {
            tx.rollback();
        } else {
            tx.commit();
        }
    }

}