io.helixservice.feature.jpa.transaction.TransactionalAspect.java Source code

Java tutorial

Introduction

Here is the source code for io.helixservice.feature.jpa.transaction.TransactionalAspect.java

Source

/*
 * @author Les Novell
 *
 *   All rights reserved. This program and the accompanying materials
 *   are made available under the terms of the Eclipse Public License v1.0
 *   and Apache License v2.0 which accompanies this distribution.
 *
 *      The Apache License v2.0 is available at
 *      http://www.opensource.org/licenses/apache2.0.php
 *
 */

package io.helixservice.feature.jpa.transaction;

import co.paralleluniverse.fibers.SuspendExecution;
import io.helixservice.feature.jpa.JpaHibernateFeature;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;
import java.lang.reflect.Field;
import java.util.EmptyStackException;

/**
 * Aspects necessary to support @Transactional annotation
 */
@Aspect
public class TransactionalAspect {
    private static Logger LOG = LoggerFactory.getLogger(TransactionalAspect.class);

    public TransactionalAspect() {
    }

    /*
     * Instruments all public methods with @Transactional annotation
     */
    @SuppressWarnings("DuplicateThrows")
    @Around(value = "(execution(public * *(..)) && @annotation(transactional))"
            + " || (execution(public * *(..)) && within(@javax.transaction.Transactional *) && @annotation(transactional))")
    public Object around(ProceedingJoinPoint pjp, Transactional transactional) throws Throwable, SuspendExecution {
        Object result;

        Signature method = pjp.getSignature();
        Object target = pjp.getTarget();

        LOG.info("Managing transaction for method=" + method.getName() + ", target=" + target.getClass().getName()
                + ", txType=" + transactional.value());

        validateTransactionalAnnotation(target, method, transactional);

        boolean commit = false;
        String persistenceContextName = beginTransaction(target);
        try {
            result = pjp.proceed();
            commit = true;
        } catch (Throwable t) {
            commit = shouldCommitOnThrowable(transactional, t);
            LOG.info("Caught throwable=" + t.getClass().getName() + ", which results in commit=" + commit);
            throw t;
        } finally {
            LOG.trace("Skipping non-transactional method=" + method.getName() + ", target="
                    + target.getClass().getName());
            endTransaction(persistenceContextName, commit);
        }

        return result;
    }

    private void validateTransactionalAnnotation(Object target, Signature method, Transactional transactional) {
        if (transactional.value() != Transactional.TxType.REQUIRED) {
            String msg = "Transaction type not supported: txType=" + transactional.value() + ", method="
                    + method.getName() + ", target=" + target.getClass().getName();
            LOG.error(msg);
            throw new IllegalArgumentException(msg);
        }
    }

    @SuppressWarnings("unchecked")
    private boolean shouldCommitOnThrowable(Transactional transactional, Throwable t) {
        Class[] dontRollbackClasses = transactional.dontRollbackOn();
        for (Class dontRollbackClass : dontRollbackClasses) {
            if (dontRollbackClass.isAssignableFrom(t.getClass())) {
                return true;
            }
        }

        Class[] rollbackClasses = transactional.rollbackOn();
        for (Class rollbackClass : rollbackClasses) {
            if (rollbackClass.isAssignableFrom(t.getClass())) {
                return false;
            }
        }

        return (RuntimeException.class.isAssignableFrom(t.getClass()));
    }

    private String beginTransaction(Object target) throws Throwable {
        String persistenceUnitName;

        try {
            Field entityManagerField = findEntityManagerField(target);
            persistenceUnitName = findPersistenceUnitName(target, entityManagerField);

            LOG.info("Beginning transaction on " + persistenceUnitName);
            EntityManager entityManager = createEntityManager(persistenceUnitName);
            EntityManagerStack.push(persistenceUnitName, entityManager);
            entityManagerField.set(target, new EntityManagerDelegate(persistenceUnitName));
        } catch (Throwable t) {
            String message = "Unable to begin transaction on entity manager in targetClass="
                    + target.getClass().getName();
            LOG.error(message, t);
            throw new IllegalStateException(message, t);
        }

        return persistenceUnitName;
    }

    private EntityManager createEntityManager(String persistenceUnitName) {
        EntityManager entityManager;

        if (EntityManagerStack.isEmpty(persistenceUnitName)) {
            EntityManagerFactory entityManagerFactory = JpaHibernateFeature
                    .entityManagerFactoryByPersistenceUnitName(persistenceUnitName);
            entityManager = entityManagerFactory.createEntityManager();
            LOG.info("Created entityManager=" + emIdentifier(entityManager) + " on threadName="
                    + Thread.currentThread().getName());

            entityManager.getTransaction().begin();
            LOG.info("Started transaction on entityManager=" + emIdentifier(entityManager));
        } else {
            entityManager = EntityManagerStack.peek(persistenceUnitName);
        }

        return entityManager;
    }

    private Field findEntityManagerField(Object target) throws IllegalAccessException {
        Field result = null;

        Class<?> targetClass = target.getClass();
        for (Field field : targetClass.getDeclaredFields()) {
            if (field.getType().equals(EntityManager.class)) {
                field.setAccessible(true);
                result = field;
            }
        }

        if (result == null) {
            String message = "EntityManager field missing on targetClass=" + targetClass.getName();
            LOG.error(message);
            throw new IllegalStateException(message);
        }

        return result;
    }

    private String findPersistenceUnitName(Object target, Field entityManagerField) {
        String persistenceUnitName = JpaHibernateFeature.DEFAULT_PERSISTENCE_UNIT_NAME;

        PersistenceContext persistenceContext = entityManagerField.getDeclaredAnnotation(PersistenceContext.class);
        if (persistenceContext == null) {
            persistenceContext = target.getClass().getAnnotation(PersistenceContext.class);
        }

        if (persistenceContext != null && persistenceContext.name() != null) {
            persistenceUnitName = persistenceContext.unitName();
        }

        return persistenceUnitName;
    }

    private void endTransaction(String persistenceUnitName, boolean commit) {

        LOG.info("Ending transaction on " + persistenceUnitName);

        EntityManager entityManager;
        try {
            entityManager = EntityManagerStack.pop(persistenceUnitName);
        } catch (EmptyStackException e) {
            throw new IllegalStateException("endTransaction() failed => EntityManagerStack is empty", e);
        }

        if (EntityManagerStack.isEmpty(persistenceUnitName)) {
            try {
                if (commit && !entityManager.getTransaction().getRollbackOnly()) {
                    entityManager.getTransaction().commit();
                    LOG.info("Transaction committed on entityManager=" + emIdentifier(entityManager));
                } else {
                    entityManager.getTransaction().rollback();
                    LOG.info("Transaction rolled back on entityManager=" + emIdentifier(entityManager));
                }
            } finally {
                safelyCloseEntityManager(entityManager);
            }
        } else {
            if (!commit) {
                LOG.info("Transaction marked for rollback only on entityManager=" + emIdentifier(entityManager));
                entityManager.getTransaction().setRollbackOnly();
            }
        }
    }

    private void safelyCloseEntityManager(EntityManager entityManager) {
        try {
            LOG.trace("Closing entityManager=" + emIdentifier(entityManager));
            try {
                EntityTransaction transaction = entityManager.getTransaction();
                if (transaction != null && transaction.isActive()) {
                    transaction.rollback();
                }
            } finally {
                entityManager.close();
            }
        } catch (Throwable unexpected) {
            // Unexpected
            LOG.error("Unexpected exception closing the entity manager", unexpected);
        }
    }

    private String emIdentifier(EntityManager entityManager) {
        return Integer.toHexString(entityManager.hashCode());
    }
}