Package org.apache.myfaces.extensions.cdi.jpa.impl

Source Code of org.apache.myfaces.extensions.cdi.jpa.impl.TransactionalInterceptor

/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you 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.apache.myfaces.extensions.cdi.jpa.impl;

import org.apache.myfaces.extensions.cdi.jpa.api.Transactional;
import org.apache.myfaces.extensions.cdi.core.api.util.ClassUtils;
import org.apache.myfaces.extensions.cdi.core.impl.util.AnyLiteral;

import javax.enterprise.inject.Default;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import javax.inject.Inject;
import javax.interceptor.AroundInvoke;
import javax.interceptor.Interceptor;
import javax.interceptor.InvocationContext;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Set;
import java.util.Map;
import java.util.HashSet;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* Interceptor for wrapping transactional database requests.
*
* @author Mark Struberg
*/
@Interceptor
@Transactional
public class TransactionalInterceptor implements Serializable
{
    private static final long serialVersionUID = -812054233068559930L;

    //don't use final in interceptors
    private static String noFieldMarker = TransactionalInterceptor.class.getName() + ":DEFAULT_FIELD";

    @Inject
    private BeanManager beanManager;

    private static transient ThreadLocal<AtomicInteger> refCount = new ThreadLocal<AtomicInteger>();

    protected final Logger logger = Logger.getLogger(TransactionalInterceptor.class.getName());

    /** key=qualifier name, value= EntityManager */
    private static transient ThreadLocal<HashMap<String, EntityManager>> entityManagerMap =
            new ThreadLocal<HashMap<String, EntityManager>>();

    private static transient Map<ClassLoader, Map<String, PersistenceContextMetaEntry>> persistenceContextMetaEntries =
            new ConcurrentHashMap<ClassLoader, Map<String, PersistenceContextMetaEntry>>();

    /** 1 ms  in nanoTime ticks */
    final static long LONG_MILLISECOND = 1000000L;
    final static long LONG_RUNNING_THRESHOLD = 300L * LONG_MILLISECOND;

    @AroundInvoke
    public Object invoke(InvocationContext context) throws Exception
    {
        Transactional transactionalAnnotation = context.getMethod().getAnnotation(Transactional.class);

        if (transactionalAnnotation == null)
        {
            transactionalAnnotation = context.getTarget().getClass().getAnnotation(Transactional.class);
        }

        Class<? extends Annotation> qualifierClass = Default.class;
        if (transactionalAnnotation != null)
        {
            qualifierClass = transactionalAnnotation.qualifier();
        }

        Set<Bean<?>> entityManagerBeans = beanManager.getBeans(EntityManager.class, new AnyLiteral());
        if (entityManagerBeans == null)
        {
            entityManagerBeans = new HashSet<Bean<?>>();
        }
        Bean<EntityManager> entityManagerBean = null;

        it:
        for (Bean<?> currentEntityManagerBean : entityManagerBeans)
        {
            Set<Annotation> foundQualifierAnnotations = currentEntityManagerBean.getQualifiers();

            for (Annotation currentQualifierAnnotation : foundQualifierAnnotations)
            {
                if (currentQualifierAnnotation.annotationType().equals(qualifierClass))
                {
                    entityManagerBean = (Bean<EntityManager>) currentEntityManagerBean;
                    break it;
                }
            }
        }

        EntityManagerEntry entityManagerEntry = null;
        EntityManager entityManager;

        String entityManagerId = qualifierClass.getName();
        if (entityManagerBean == null)
        {
            //support for special add-ons which introduce backward compatibility - resolves the injected entity manager
            entityManagerEntry = tryToFindEntityManagerEntryInTarget(context.getTarget());

            if(entityManagerEntry == null)
            {
                throw unsupportedUsage(context);
            }

            entityManager = entityManagerEntry.getEntityManager();
            entityManagerId = entityManagerEntry.getPersistenceContextEntry().getUnitName();

            //might happen due to special add-ons - don't change it!
            if(entityManager == null)
            {
                //TODO log warning in project stage dev.
                return context.proceed();
            }
        }
        else
        {
            entityManager = (EntityManager) this.beanManager.getReference(entityManagerBean, EntityManager.class,
                    this.beanManager.createCreationalContext(entityManagerBean));
        }

        if (entityManagerMap.get() == null)
        {
            entityManagerMap.set(new HashMap<String, EntityManager>());
        }
        entityManagerMap.get().put(entityManagerId, entityManager);
        // log.info("growing: " + ems.get().size());

        if (refCount.get() == null)
        {
            refCount.set(new AtomicInteger(0));
        }

        EntityTransaction transaction = entityManager.getTransaction();

        if(entityManagerEntry != null)
        {
            //only in case of add-ons synchronize
            //-> nested calls (across beans) which share the same entity manager aren't supported
            //noinspection SynchronizationOnLocalVariableOrMethodParameter
            synchronized (entityManager)
            {
                return proceedMethodInTransaction(context,
                                                  entityManagerEntry,
                                                  entityManager,
                                                  transaction);
            }
        }
        //we don't have a shared entity manager
        return proceedMethodInTransaction(context,
                                          entityManagerEntry,
                                          entityManager,
                                          transaction);
    }

    private Object proceedMethodInTransaction(InvocationContext context,
                                              EntityManagerEntry entityManagerEntry,
                                              EntityManager entityManager,
                                              EntityTransaction transaction)
            throws Exception
    {
        // used to store any exception we get from the services
        Exception firstException = null;

        try
        {
            if(!transaction.isActive())
            {
                transaction.begin();
            }
            refCount.get().incrementAndGet();

            return context.proceed();

        }
        catch(Exception e)
        {
            firstException = e;

            // we only cleanup and rollback all open transactions in the outermost interceptor!
            // this way, we allow inner functions to catch and handle exceptions properly.
            if (refCount.get().intValue() == 1)
            {
                for (EntityManager currentEntityManager : entityManagerMap.get().values())
                {
                    transaction = currentEntityManager.getTransaction();
                    if (transaction != null && transaction.isActive())
                    {
                        try
                        {
                            transaction.rollback();
                        }
                        catch (Exception eRollback)
                        {
                            logger.log(Level.SEVERE, "Got additional Exception while subsequently " +
                                    "rolling back other SQL transactions", eRollback);
                        }

                    }
                }

                refCount.remove();

                // drop all EntityManagers from the ThreadLocal
                entityManagerMap.remove();
            }

            // rethrow the exception
            throw e;

        }
        finally
        {
            if (refCount.get() != null)
            {
                refCount.get().decrementAndGet();


                // will get set if we got an Exception while committing
                // in this case, we rollback all later transactions too.
                boolean commitFailed = false;

                // commit all open transactions in the outermost interceptor!
                // this is a 'JTA for poor men' only, and will not guaranty
                // commit stability over various databases!
                if (refCount.get().intValue() == 0)
                {

                    // only commit all transactions if we didn't rollback
                    // them already
                    if (firstException == null)
                    {
                        for (EntityManager currentEntityManager : entityManagerMap.get().values())
                        {
                            transaction = currentEntityManager.getTransaction();
                            if(transaction != null && transaction.isActive())
                            {
                                try
                                {
                                    if (!commitFailed)
                                    {
                                        transaction.commit();
                                    }
                                    else
                                    {
                                        transaction.rollback();
                                    }
                                }
                                catch (Exception e)
                                {
                                    firstException = e;
                                    commitFailed = true;
                                }
                            }
                        }
                    }

                    // finally remove all ThreadLocals
                    refCount.remove();
                    entityManagerMap.remove();
                    if (commitFailed)
                    {
                        //noinspection ThrowFromFinallyBlock
                        throw firstException;
                    }
                    else
                    {
                        //commit was successful and entity manager of bean was used
                        //(and not an entity manager of a producer) which isn't of type extended
                        if(entityManagerEntry != null && entityManager != null && entityManager.isOpen() &&
                                !entityManagerEntry.getPersistenceContextEntry().isExtended())
                        {
                            entityManager.clear();
                        }
                    }
                }
            }
        }
    }

    /*
     * needed for special add-ons - don't change it!
     */
    private EntityManagerEntry tryToFindEntityManagerEntryInTarget(Object target)
    {
        Map<String, PersistenceContextMetaEntry> mapping = persistenceContextMetaEntries.get(getClassLoader());

        mapping = initMapping(mapping);

        String key = target.getClass().getName();
        PersistenceContextMetaEntry persistenceContextEntry = mapping.get(key);

        if( persistenceContextEntry != null && noFieldMarker.equals(persistenceContextEntry.getFieldName()))
        {
            return null;
        }

        if(persistenceContextEntry == null)
        {
            persistenceContextEntry = findPersistenceContextEntry(target.getClass());

            if(persistenceContextEntry == null)
            {
                mapping.put(key, new PersistenceContextMetaEntry(
                        Object.class, noFieldMarker, Default.class.getName(), false));
                return null;
            }

            mapping.put(key, persistenceContextEntry);
        }

        Field entityManagerField;
        try
        {
            entityManagerField = persistenceContextEntry.getSourceClass()
                    .getDeclaredField(persistenceContextEntry.getFieldName());
        }
        catch (NoSuchFieldException e)
        {
            //TODO add logging in case of project stage dev.
            return null;
        }

        entityManagerField.setAccessible(true);
        try
        {
            EntityManager entityManager = (EntityManager)entityManagerField.get(target);
            return new EntityManagerEntry(entityManager, persistenceContextEntry);
        }
        catch (IllegalAccessException e)
        {
            //TODO add logging in case of project stage dev.
            return null;
        }
    }

    private synchronized Map<String, PersistenceContextMetaEntry> initMapping(
            Map<String, PersistenceContextMetaEntry> mapping)
    {
        if(mapping == null)
        {
            mapping = new ConcurrentHashMap<String, PersistenceContextMetaEntry>();
            persistenceContextMetaEntries.put(getClassLoader(), mapping);
        }
        return mapping;
    }

    private PersistenceContextMetaEntry findPersistenceContextEntry(Class target)
    {
        //TODO support other injection types
        Class currentParamClass = target;
        PersistenceContext persistenceContext;
        while (currentParamClass != null && !Object.class.getName().equals(currentParamClass.getName()))
        {
            for(Field currentField : currentParamClass.getDeclaredFields())
            {
                persistenceContext = currentField.getAnnotation(PersistenceContext.class);
                if(persistenceContext != null)
                {
                    return new PersistenceContextMetaEntry(
                                   currentParamClass,
                                   currentField.getName(),
                                   persistenceContext.unitName(),
                                   PersistenceContextType.EXTENDED.equals(persistenceContext.type()));
                }
            }
            currentParamClass = currentParamClass.getSuperclass();
        }

        return null;
    }

    private ClassLoader getClassLoader()
    {
        return ClassUtils.getClassLoader(null);
    }

    private IllegalStateException unsupportedUsage(InvocationContext context)
    {
        String target;

        target = context.getTarget().getClass().getName();
        if (context.getMethod().isAnnotationPresent(Transactional.class))
        {
            target += "." + context.getMethod().getName();
        }

        return new IllegalStateException("Please check your implementation! " +
                "There is no @Transactional or @Transactional(MyQualifier.class) or @PersistenceContext at "
                + target + " Please check the documentation for the correct usage or contact the mailing list. " +
                "Hint: @Transactional just allows one qualifier -> using multiple Entity-Managers " +
                "(-> different qualifiers) within ONE intercepted method isn't supported.");
    }
}
TOP

Related Classes of org.apache.myfaces.extensions.cdi.jpa.impl.TransactionalInterceptor

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.