Package org.jboss.dashboard.database.hibernate

Source Code of org.jboss.dashboard.database.hibernate.HibernateTransaction

/**
* Copyright (C) 2012 JBoss 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.jboss.dashboard.database.hibernate;

import org.jboss.dashboard.factory.Factory;
import org.jboss.dashboard.factory.FactoryWork;
import org.jboss.dashboard.error.ErrorManager;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.FlushMode;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.jboss.dashboard.CoreServices;

import java.util.ArrayList;
import java.util.List;

/**
* A Hibernate transaction in the platform is always tied to a single thread.
* Every time a thread is executed an instance of this class is created
* and bounded to the current thread. The standard way to interact with the
* underlying transaction is to make use of the HibernateTxFragment idiom.
*
* @see org.jboss.dashboard.database.hibernate.HibernateTxFragment
*/
public class HibernateTransaction {

    /** Logger */
    private static transient Log log = LogFactory.getLog(HibernateTransaction.class.getName());

    /** Current active transaction per thread */
    private static transient ThreadLocal<HibernateTransaction> activeTx = new ThreadLocal<HibernateTransaction>();

    /**
     * Get the current tx associated with the calling thread.
     * This method ensures that a tx instance is always available.
     */
    public static HibernateTransaction getCurrentTx() {
        HibernateTransaction tx = activeTx.get();
        if (tx == null) activeTx.set(tx = new HibernateTransaction());
        return tx;
    }

    /**
     * The transaction identifier.
     */
    private String id;

    /**
     * The current tx fragment being processed by the transaction.
     */
    HibernateTxFragment currentFragment;

    /**
     * Children listeners that were opened within this fragment interested in transaction callbacks.
     */
    List<HibernateTxFragment> listeners;

    /**
     * List of "marked new" transactions, that will be executed AFTER this tx is commited or rolled back.
     */
    List<HibernateTxFragment> followers;

    /**
     * Hibernate session associated with the transaction.
     */
    private Session session;

    /**
     * Flag indicating i the transaction has been initiated.
     */
    private boolean active;

    /**
     * Flag indicating if the transaction has been marked as rollback only.
     */
    private boolean rollback;

    /**
     * Status flag indicating if the transaction is has started the completion phase..
     */
    private boolean completing;

    private HibernateTransaction() {
        this.id = Thread.currentThread().getName();
        this.currentFragment = null;
        this.followers = new ArrayList<HibernateTxFragment>();
        this.listeners = new ArrayList<HibernateTxFragment>();
        this.session = null;
        this.active = false;
        this.rollback = false;
        this.completing = false;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public Session getSession() {
        return session;
    }

    public boolean isRollback() {
        return rollback;
    }

    public void setRollback(boolean rollback) {
        this.rollback = rollback;
    }

    public boolean isActive() {
        return active;
    }

    /** Begin the transaction */
    public void begin() throws Exception {
        log.debug("Begin transaction. Id=" + getId());
        HibernateSessionFactoryProvider hibernateSessionFactoryProvider = CoreServices.lookup().getHibernateSessionFactoryProvider();
        SessionFactory sessionFactory = hibernateSessionFactoryProvider.getSessionFactory();
        session = sessionFactory.openSession();
        session.getTransaction().begin();
        active = true;
    }

    /** Complete the transaction */
    public void complete() {
        // Flush the session before notifying the listeners
        if (!rollback) {
            flush();
        }

        // Invoke listeners
        completing = true;
        notifyListeners(true);

        // Commit or rollback
        if (rollback) rollback();
        else commit();

        // Close the transaction and unbound it from the current thread
        completing = false;
        active = false;
        close();
        activeTx.set(null);

        // Invoke listeners
        notifyListeners(false);

        // Process queued txs.
        processFollowers();
    }

    /** Flush the transaction */
    protected void flush() {
        try {
            log.debug("Flush transaction. Id=" + getId());
            session.flush();
        } catch (Throwable e) {
            log.debug("Flush error. Id=" + getId());
            error(e);
        }
    }

    /** Flush the transaction */
    protected void close() {
        try {
            log.debug("Close transaction. Id=" + getId());
            session.close();
        } catch (Throwable e) {
            log.error("Close error. Id=" + getId());
            error(e);
        }
    }

    /** Rollback the transaction */
    protected void rollback() {
        try {
            log.debug("Rollback transaction. Id=" + getId());
            session.getTransaction().rollback();
        } catch (Throwable e) {
            log.error("Error in rollback. Id=" + getId());
            rollback = false;
            error(e);
        }
    }

    /** Commit the transaction */
    protected void commit() {
        try {
            log.debug("Commit transaction. Id=" + getId());
            session.getTransaction().commit();
        } catch (Throwable e) {
            log.error("Error in commit. Id=" + getId());
            error(e);
        }
    }

    /** Exception occurred during a transaction fragment */
    public void error(Throwable t) {
        if (!rollback) {
            // Mark the transaction as rollback only.
            rollback = true;

            // Notify the error.
            ErrorManager.lookup().notifyError(t, true);
        }
    }

    protected final void executeFragment(HibernateTxFragment fragment) throws Exception {
        FlushMode flushMode = session.getFlushMode();
        boolean flushChanged = false;
        try {
            // Change the current fragment.
            fragment.parentFragment = currentFragment;
            currentFragment = fragment;

            // Disable flush if the fragment's flush is set.
            HibernateTxFragment flusherFragment = getFlusherFragment();
            if (fragment == flusherFragment) {
                session.setFlushMode(FlushMode.COMMIT);
                flushChanged = true;
            }

            // Execute the fragment.
            fragment.txFragment(session);

            // Flush the fragment if required.
            if (fragment == flusherFragment) {
                log.debug("Flush transaction. Id=" + getId());
                session.flush();
            }
        } catch (Throwable t) {
            // Rollback the tx.
            error(t);

            // Propagate the exception.
            if (t instanceof Exception) throw (Exception) t;
            else throw new Exception(t);
        } finally {
            if (flushChanged) session.setFlushMode(flushMode);
            currentFragment = fragment.parentFragment;
            if (fragment.callbacksEnabled) {
                listeners.add(fragment);

                // When completing the tx notify the fragment right now.
                if (completing) notifyListener(true, fragment);
            }
        }
    }

    /**
     * Get the first fragment in the chain than has the flush flag enabled.
     */
    protected HibernateTxFragment getFlusherFragment() {
        HibernateTxFragment setter = null;
        HibernateTxFragment fragment = currentFragment;
        while (fragment != null) {
            if (fragment.flushAfterFinish) setter = fragment;
            fragment = fragment.parentFragment;
        }
        return setter;
    }

    /**
     * Invoke the callbacks on listeners
     */
    protected void notifyListeners(boolean before) {
        if (!listeners.isEmpty()) log.debug((before ? "Before " : "After ") + (rollback ? "rollback" : "commit"));

        // A copy of the list is needed because a listener could modify the list if it contains a txFragment with callbacks.
        for (HibernateTxFragment listener : new ArrayList<HibernateTxFragment> (listeners)) {
            boolean wasCommit = !rollback;
            notifyListener(before, listener);

            // If the listener execution aborts the tx then the notifyBeforeRollback must be sent to all the listeners.
            if (before && wasCommit && rollback) {
                notifyListeners(true);

                // The current notifyBeforeCommit notifications must be cancelled.
                break;
            }
        }
    }

    /**
     * Invoke the callbacks on a listener
     * @return true if the transaction has been aborted due to a failure in the listener execution.
     */
    protected void notifyListener(boolean before, HibernateTxFragment listener) {
        if (before) {
            if (rollback) notifyBeforeRollback(listener);
            else notifyBeforeCommit(listener);
        } else {
            if (rollback) notifyAfterRollback(listener);
            else notifyAfterCommit(listener);
        }
    }

    /**
     * Call the followers (new transactions opened within this one, with the "new tx" flag set).
     */
    private void processFollowers() {
        for (HibernateTxFragment fragment : followers) {
            try {
                log.debug("Follower");
                fragment.execute();
            } catch (Throwable e) {
                log.error("Follower error. Id=" + getId(), e);
            }
        }
    }

    protected void notifyBeforeCommit(HibernateTxFragment fragment) {
        try {
            fragment.beforeCommit();
        } catch (Throwable t) {
            // If it fails then the commit must be aborted.
            error(t);
        }
    }

    protected void notifyBeforeRollback(HibernateTxFragment fragment) {
        try {
            fragment.beforeRollback();
        } catch (Throwable e) {
            log.error("Error before rollback: ", e);
        }
    }

    protected void notifyAfterCommit(HibernateTxFragment fragment) {
        try {
            fragment.afterCommit();
        } catch (Throwable e) {
            log.error("Error after commit: ", e);
        }
    }

    protected void notifyAfterRollback(HibernateTxFragment fragment) {
        try {
            fragment.afterRollback();
        } catch (Throwable e) {
            log.error("Error after rollback: ", e);
        }
    }

    public static Object runWork(final HibernateWork work) throws Throwable {
        final Throwable[] error = new Throwable[] {null};
        final Object[] result = new Object[] {null};
        Factory.doWork(new FactoryWork() {
        public void doWork() {
            try {
                new HibernateTxFragment() {
                protected void txFragment(Session session) throws Throwable {
                    result[0] = work.doWork(session);
                }}.execute();
            } catch (Throwable e) {
                error[0] = e;
            }
        }});
        if (error[0] != null) throw error[0];
        else return result[0];
    }
}
TOP

Related Classes of org.jboss.dashboard.database.hibernate.HibernateTransaction

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.