com.eucalyptus.entities.EntityWrapper.java Source code

Java tutorial

Introduction

Here is the source code for com.eucalyptus.entities.EntityWrapper.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 static com.eucalyptus.util.Parameters.checkParam;
import static org.hamcrest.Matchers.*;
import java.lang.ref.WeakReference;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.UUID;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceException;
import org.apache.commons.lang.time.StopWatch;
import org.apache.log4j.Logger;
import org.hibernate.Criteria;
import org.hibernate.LockMode;
import org.hibernate.NonUniqueResultException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionException;
import org.hibernate.criterion.Example;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Restrictions;
import org.hibernate.ejb.EntityManagerFactoryImpl;
import org.hibernate.exception.ConstraintViolationException;
import com.eucalyptus.event.ClockTick;
import com.eucalyptus.event.Event;
import com.eucalyptus.event.EventListener;
import com.eucalyptus.records.EventType;
import com.eucalyptus.records.Logs;
import com.eucalyptus.system.Threads;
import com.eucalyptus.util.EucalyptusCloudException;
import com.eucalyptus.util.HasNaturalId;
import com.eucalyptus.util.LogUtil;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;

/**
 * @deprecated
 */
@Deprecated
public class EntityWrapper<TYPE> {
    private static Logger LOG = Logger.getLogger(EntityWrapper.class);
    private TransactionState tx;
    private final String txStart;

    enum TxEvent {
        CREATE, COMMIT, ROLLBACK, UNIQUE, QUERY;
        public String getMessage() {
            if (Logs.isExtrrreeeme()) {
                return Threads.currentStackString();
            } else {
                return "n.a";
            }
        }
    }

    enum TxWatchdog implements EventListener {
        INSTANCE;
        @Override
        public void fireEvent(final Event event) {
            if (event instanceof ClockTick) {
                //TODO:GRZE:tx monitoring here.
            }
        }
    }

    enum TxStep {
        BEGIN, END, FAIL;
        public String event(final TxEvent e) {
            return e.name() + ":" + this.name();
        }
    }

    /**
       * @see {@link Entities#get(Class)
       * @see {@link CascadingTx}
       */
    @Deprecated
    public static <T> EntityWrapper<T> get(final Class<T> type) {
        return new EntityWrapper(Entities.lookatPersistenceContext(type));
    }

    /**
       * @see {@link Entities#get(Object)
       * @see {@link CascadingTx}
       */
    @SuppressWarnings("unchecked")
    @Deprecated
    public static <T> EntityWrapper<T> get(final T obj) {
        return new EntityWrapper(Entities.lookatPersistenceContext(obj));
    }

    /**
     * Private for a reason.
     * 
     * @see {@link EntityWrapper#get(Class)}
     * @param persistenceContext
     */
    @SuppressWarnings("unchecked")
    private EntityWrapper(final String persistenceContext) {
        this.tx = new TransactionState(persistenceContext);
        this.txStart = Threads.currentStackString();
    }

    @SuppressWarnings({ "unchecked", "cast" })
    public <T> List<T> query(final T example) {
        return query(example, false);
    }

    @SuppressWarnings({ "unchecked", "cast" })
    public <T> List<T> query(final T example, final boolean readOnly) {
        final Example qbe = Example.create(example).enableLike(MatchMode.EXACT);
        final List<T> resultList = this.getSession().createCriteria(example.getClass()).setLockMode(LockMode.NONE)
                .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY).setCacheable(true).add(qbe)
                .setReadOnly(readOnly).list();
        return Lists.newArrayList(Sets.newHashSet(resultList));
    }

    // Fix for EUCA-3453
    /**
     * Returns a list of results from the database that exactly match <code>example</code>. This method does not use <i><code>enableLike</code></i> match while the 
     * {@link #query(Object)} does. <i><code>enableLike</code></i> criteria trips hibernate when special characters are involved. So it has been replaced by "=" (equals to)
     * 
     * @param example
     * @return
     */
    @SuppressWarnings({ "unchecked", "cast" })
    public <T> List<T> queryEscape(final T example) {
        final Example qbe = Example.create(example);
        final List<T> resultList = this.getSession().createCriteria(example.getClass()).setLockMode(LockMode.NONE)
                .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY).setCacheable(true).add(qbe).list();
        return Lists.newArrayList(Sets.newHashSet(resultList));
    }

    public <T> T lookupAndClose(final T example) throws NoSuchElementException {
        T ret = null;
        try {
            ret = this.getUnique(example);
            this.commit();
        } catch (final EucalyptusCloudException ex) {
            this.rollback();
            throw new NoSuchElementException(ex.getMessage());
        }
        return ret;
    }

    @SuppressWarnings("unchecked")
    public <T> T uniqueResult(final T example) throws TransactionException {
        try {
            return this.recast((Class<T>) example.getClass()).getUnique(example);
        } catch (final RuntimeException ex) {
            throw new TransactionInternalException(ex.getMessage(), ex);
        } catch (final EucalyptusCloudException ex) {
            throw new TransactionExecutionException(ex.getMessage(), ex);
        }
    }

    //Fix for EUCA-3453
    /**
     * Returns the unique result from the database that exactly matches <code>example</code>. This method is same as {@link #uniqueResult(Object)}
     * but calls {@link #getUniqueEscape(Object)} instead of {@link #getUnique(Object)}
     * 
     * @param example
     * @return
     * @throws TransactionException
     */
    @SuppressWarnings("unchecked")
    public <T> T uniqueResultEscape(final T example) throws TransactionException {
        try {
            return this.recast((Class<T>) example.getClass()).getUniqueEscape(example);
        } catch (final RuntimeException ex) {
            throw new TransactionInternalException(ex.getMessage(), ex);
        } catch (final EucalyptusCloudException ex) {
            throw new TransactionExecutionException(ex.getMessage(), ex);
        }
    }

    @SuppressWarnings("unchecked")
    public <T> T getUnique(final T example) throws EucalyptusCloudException {
        try {
            Object id = null;
            try {
                id = this.getEntityManager().getEntityManagerFactory().getPersistenceUnitUtil()
                        .getIdentifier(example);
            } catch (final Exception ex) {
            }
            if (id != null) {
                final T res = (T) this.getEntityManager().find(example.getClass(), id);
                if (res == null) {
                    throw new NoSuchElementException("@Id: " + id);
                } else {
                    return res;
                }
            } else if ((example instanceof HasNaturalId) && (((HasNaturalId) example).getNaturalId() != null)) {
                final String natId = ((HasNaturalId) example).getNaturalId();
                final T ret = (T) this.createCriteria(example.getClass()).setLockMode(LockMode.NONE)
                        .setCacheable(true).setMaxResults(1).setFetchSize(1).setFirstResult(0)
                        .add(Restrictions.naturalId().set("naturalId", natId)).uniqueResult();
                if (ret == null) {
                    throw new NoSuchElementException("@NaturalId: " + natId);
                }
                return ret;
            } else {
                final T ret = (T) this.createCriteria(example.getClass()).setLockMode(LockMode.NONE)
                        .setCacheable(true).setMaxResults(1).setFetchSize(1).setFirstResult(0)
                        .add(Example.create(example).enableLike(MatchMode.EXACT)).uniqueResult();
                if (ret == null) {
                    throw new NoSuchElementException("example: " + LogUtil.dumpObject(example));
                }
                return ret;
            }
        } catch (final NonUniqueResultException ex) {
            throw new EucalyptusCloudException(
                    "Get unique failed for " + example.getClass().getSimpleName() + " because " + ex.getMessage(),
                    ex);
        } catch (final NoSuchElementException ex) {
            throw new EucalyptusCloudException(
                    "Get unique failed for " + example.getClass().getSimpleName() + " using " + ex.getMessage(),
                    ex);
        } catch (final Exception ex) {
            final Exception newEx = PersistenceExceptions.throwFiltered(ex);
            throw new EucalyptusCloudException("Get unique failed for " + example.getClass().getSimpleName()
                    + " because " + newEx.getMessage(), newEx);
        }
    }

    //Fix for EUCA-3453
    /**
     * Returns the unique result from the database that exactly matches <code>example</code>. The differences between this method and {@link #getUnique(Object)} are:
     * <ol><li>{@link #getUnique(Object)} uses <i><code>enableLike</code></i> match and this method does not. <i><code>enableLike</code></i> criteria trips hibernate when 
     * special characters are involved. So it has been replaced by exact "=" (equals to)</li>  
     * <li>Unique result logic is correctly implemented in this method. If the query returns more than one result, this method correctly throws an exception 
     * wrapping <code>NonUniqueResultException</code>. {@link #getUnique(Object)} does not throw an exception in this case and returns a result as long as it finds
     * one or more matching results (because of the following properties set on the query: <code>setMaxResults(1)</code>, <code>setFetchSize(1)</code> and 
     * <code>setFirstResult(0)</code>)</li></ol>
     * 
     * @param example
     * @return
     * @throws EucalyptusCloudException
     */
    @SuppressWarnings("unchecked")
    public <T> T getUniqueEscape(final T example) throws EucalyptusCloudException {
        try {
            Object id = null;
            try {
                id = this.getEntityManager().getEntityManagerFactory().getPersistenceUnitUtil()
                        .getIdentifier(example);
            } catch (final Exception ex) {
            }
            if (id != null) {
                final T res = (T) this.getEntityManager().find(example.getClass(), id);
                if (res == null) {
                    throw new NoSuchElementException("@Id: " + id);
                } else {
                    return res;
                }
            } else if ((example instanceof HasNaturalId) && (((HasNaturalId) example).getNaturalId() != null)) {
                final String natId = ((HasNaturalId) example).getNaturalId();
                final T ret = (T) this.createCriteria(example.getClass()).setLockMode(LockMode.NONE)
                        .setCacheable(true).add(Restrictions.naturalId().set("naturalId", natId)).uniqueResult();
                if (ret == null) {
                    throw new NoSuchElementException("@NaturalId: " + natId);
                }
                return ret;
            } else {
                final T ret = (T) this.createCriteria(example.getClass()).setLockMode(LockMode.NONE)
                        .setCacheable(true).add(Example.create(example)).uniqueResult();
                if (ret == null) {
                    throw new NoSuchElementException("example: " + LogUtil.dumpObject(example));
                }
                return ret;
            }
        } catch (final NonUniqueResultException ex) {
            throw new EucalyptusCloudException(
                    "Get unique failed for " + example.getClass().getSimpleName() + " because " + ex.getMessage(),
                    ex);
        } catch (final NoSuchElementException ex) {
            throw new EucalyptusCloudException(
                    "Get unique failed for " + example.getClass().getSimpleName() + " using " + ex.getMessage(),
                    ex);
        } catch (final Exception ex) {
            final Exception newEx = PersistenceExceptions.throwFiltered(ex);
            throw new EucalyptusCloudException("Get unique failed for " + example.getClass().getSimpleName()
                    + " because " + newEx.getMessage(), newEx);
        }
    }

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

    /**
     * Calls {@link #persist(Object)}; here for legacy, and is deprecated in favor of persist
     * 
     * @see http://opensource.atlassian.com/projects/hibernate/browse/HHH-1273
     * @param newObject
     */
    @Deprecated
    public <T> T add(final T newObject) {
        return this.persist(newObject);
    }

    /**
     * <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>
     * 
     * @param newObject
     */
    public <T> T merge(final T newObject) {
        try {
            return this.getEntityManager().merge(newObject);
        } catch (final RuntimeException ex) {
            PersistenceExceptions.throwFiltered(ex);
            throw ex;
        }
    }

    /**
     * @see EntityWrapper#merge(Object)
     * @param newObject
     * @throws PersistenceException
     */
    public <T> T mergeAndCommit(T newObject) {
        try {
            newObject = this.getEntityManager().merge(newObject);
            this.commit();
            return newObject;
        } catch (final RuntimeException ex) {
            try {
                PersistenceExceptions.throwFiltered(ex);
                throw ex;
            } finally {
                this.rollback();
            }
        }
    }

    public void delete(final Object deleteObject) {
        this.getEntityManager().remove(deleteObject);
    }

    public void rollback() {
        if (this.tx != null) {
            this.tx.rollback();
            this.tx = null;
        }
    }

    public void commit() throws ConstraintViolationException {
        if (this.tx != null) {
            this.tx.commit();
            this.tx = null;
        } else {
            throw new SessionException("Attempt to commit session which is already closed:  " + this.txStart);
        }
    }

    public Criteria createCriteria(final Class class1) {
        return this.getSession().createCriteria(class1);
    }

    /** package default on purpose **/
    EntityManager getEntityManager() {
        return this.tx.getEntityManager();
    }

    /** :| should also be package default **/
    Session getSession() {
        return this.tx.getSession();
    }

    @SuppressWarnings("unchecked")
    public <N> EntityWrapper<N> recast(final Class<N> c) {
        return (com.eucalyptus.entities.EntityWrapper<N>) this;
    }

    @SuppressWarnings("unchecked")
    public <N> EntityWrapper<N> recast() {
        return (com.eucalyptus.entities.EntityWrapper<N>) this;
    }

    public static StackTraceElement getMyStackTraceElement() {
        int i = 0;
        for (final StackTraceElement ste : Thread.currentThread().getStackTrace()) {
            if ((i++ < 2) || ste.getClassName().matches(".*EntityWrapper.*")
                    || ste.getClassName().matches(".*TxHandle.*")
                    || ste.getMethodName().equals("getEntityWrapper")) {
                continue;
            } else {
                return ste;
            }
        }
        throw new RuntimeException("BUG: Reached bottom of stack trace without finding any relevent frames.");
    }

    public Query createSQLQuery(final String sqlQuery) {
        return this.getSession().createSQLQuery(sqlQuery);
    }

    public boolean isActive() {
        return this.tx != null && this.tx.isActive();
    }

    public static <TYPE> EntityWrapper<TYPE> create(final PersistenceContext persistence) {
        return new EntityWrapper<TYPE>(persistence.name());
    }

    protected void cleanUp() {
        try {
            LOG.error("Cleaning up stray entity wrapper: " + this.tx);
            this.tx.rollback();
        } catch (final Exception ex) {
            LOG.error(ex, ex);
        }
    }

    static class TransactionState implements Comparable<TransactionState>, EntityTransaction {
        private static ConcurrentNavigableMap<String, TransactionState> outstanding = new ConcurrentSkipListMap<String, TransactionState>();

        private EntityManager em;
        private final WeakReference<Session> session;
        private EntityTransaction transaction;
        private final String owner;
        private final Long startTime;
        private final String txUuid;
        private final StopWatch stopWatch;
        private volatile long splitTime = 0l;

        TransactionState(final String ctx) {
            this.startTime = System.currentTimeMillis();
            this.txUuid = String.format("%s:%s", ctx, UUID.randomUUID().toString());
            this.stopWatch = new StopWatch();
            this.stopWatch.start();
            this.owner = Logs.isExtrrreeeme() ? Threads.currentStackString() : "n/a";
            try {
                this.eventLog(TxStep.BEGIN, TxEvent.CREATE);
                final EntityManagerFactory anemf = (EntityManagerFactoryImpl) PersistenceContexts
                        .getEntityManagerFactory(ctx);
                checkParam(anemf, notNullValue());
                this.em = anemf.createEntityManager();
                checkParam(this.em, notNullValue());
                this.transaction = this.em.getTransaction();
                this.transaction.begin();
                this.session = new WeakReference<Session>((Session) this.em.getDelegate());
                this.eventLog(TxStep.END, TxEvent.CREATE);
            } catch (final Throwable ex) {
                Logs.exhaust().error(ex, ex);
                this.eventLog(TxStep.FAIL, TxEvent.CREATE);
                this.rollback();
                throw new RuntimeException(PersistenceExceptions.throwFiltered(ex));
            } finally {
                outstanding.put(this.txUuid, this);
            }
        }

        private boolean isExpired() {
            final long splitTime = this.split();
            return (splitTime - 30000) > this.startTime;
        }

        private long split() {
            this.stopWatch.split();
            this.splitTime = this.stopWatch.getSplitTime();
            this.stopWatch.unsplit();
            return this.splitTime;
        }

        private final void eventLog(final TxStep txState, final TxEvent txAction) {
            if (Logs.isExtrrreeeme()) {
                final long oldSplit = this.splitTime;
                this.stopWatch.split();
                this.splitTime = this.stopWatch.getSplitTime();
                this.stopWatch.unsplit();
                final Long split = this.splitTime - oldSplit;
                Logs.exhaust().debug(Joiner.on(":").join(EventType.PERSISTENCE, txState.event(txAction),
                        Long.toString(split), this.getTxUuid()));
            }
        }

        @Override
        public void rollback() {
            this.eventLog(TxStep.BEGIN, TxEvent.ROLLBACK);
            try {
                if ((this.transaction != null) && this.transaction.isActive()) {
                    this.transaction.rollback();
                }
                this.eventLog(TxStep.END, TxEvent.ROLLBACK);
            } catch (final Throwable e) {
                this.eventLog(TxStep.FAIL, TxEvent.ROLLBACK);
                PersistenceExceptions.throwFiltered(e);
            } finally {
                this.cleanup();
            }
        }

        private void cleanup() {
            try {
                if ((this.transaction != null) && this.transaction.isActive()) {
                    this.transaction.rollback();
                }
                this.transaction = null;
                if ((this.session != null) && (this.session.get() != null)) {
                    this.session.clear();
                }
                if ((this.em != null) && this.em.isOpen()) {
                    this.em.close();
                }
                this.em = null;
            } finally {
                outstanding.remove(this.txUuid);
            }
        }

        @Override
        public void commit() {
            this.eventLog(TxStep.BEGIN, TxEvent.COMMIT);
            try {
                this.transaction.commit();
                this.eventLog(TxStep.END, TxEvent.COMMIT);
            } catch (final RuntimeException e) {
                this.rollback();
                this.eventLog(TxStep.FAIL, TxEvent.COMMIT);
                PersistenceExceptions.throwFiltered(e);
                throw e;
            } finally {
                this.cleanup();
            }
        }

        public String getTxUuid() {
            this.split();
            return this.txUuid;
        }

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

        @Override
        public boolean isActive() {
            final boolean hasEm = (this.em != null) && this.em.isOpen();
            final boolean hasSession = (this.session.get() != null) && this.session.get().isOpen();
            final boolean hasTx = (this.transaction != null) && this.transaction.isActive();
            if (hasEm && hasSession && hasTx) {
                return true;
            } else {
                this.cleanup();
                return false;
            }
        }

        @Override
        public void setRollbackOnly() {
            this.transaction.setRollbackOnly();
        }

        private Session getSession() {
            if (this.isActive()) {
                return this.session.get();
            } else {
                throw new SessionException("This session is no longer active: " + this.getTxUuid());
            }
        }

        private EntityManager getEntityManager() {
            if (this.isActive()) {
                return this.em;
            } else {
                throw new SessionException("This session is no longer active: " + this.getTxUuid());
            }
        }

        @Override
        public void begin() {
            this.transaction.begin();
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((this.owner == null) ? 0 : this.owner.hashCode());
            result = prime * result + ((this.startTime == null) ? 0 : this.startTime.hashCode());
            return result;
        }

        @Override
        public boolean equals(final Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (this.getClass() != obj.getClass())
                return false;
            final TransactionState other = (TransactionState) obj;
            if (this.owner == null) {
                if (other.owner != null)
                    return false;
            } else if (!this.owner.equals(other.owner))
                return false;
            if (this.startTime == null) {
                if (other.startTime != null)
                    return false;
            } else if (!this.startTime.equals(other.startTime))
                return false;
            return true;
        }

        @Override
        public int compareTo(final TransactionState that) {
            return this.txUuid.compareTo(that.txUuid);
        }

        @Override
        public String toString() {
            return String.format("TxHandle:txUuid=%s:startTime=%s:splitTime=%s:owner=%s", this.txUuid,
                    this.startTime, this.splitTime, Logs.isExtrrreeeme() ? this.owner : "n/a");
        }

        public static class TxWatchdog implements EventListener {

            @Override
            public void fireEvent(final Event event) {
                if (event instanceof ClockTick) {
                    for (final TransactionState tx : TransactionState.outstanding.values()) {
                        if (tx.isExpired()) {
                            tx.cleanup();
                            LOG.error("Found expired TxHandle: " + tx);
                            LOG.error(tx.owner);
                        }
                    }
                }
            }
        }

    }

}