package org.apache.aries.jpa.container.context.transaction.impl;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.LockModeType;
import javax.persistence.PersistenceException;
import javax.persistence.TransactionRequiredException;

import org.apache.aries.jpa.container.context.impl.NLS;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class JTAEntityManagerHandler implements InvocationHandler {
    private static final String[] TRANSACTED_METHODS = {"flush", "lock", "merge", "isJoinedToTransaction", "persist", "remove", "getLockMode", "lock", "refresh"};
    
    /**
     * Logger
     */
    private static final Logger _logger = LoggerFactory.getLogger("org.apache.aries.jpa.container.context");
    
    private Set<String> transactedMethods = new HashSet<String>(Arrays.asList(TRANSACTED_METHODS));

    /**
     * The {@link EntityManagerFactory} that can create new {@link EntityManager} instances
     */
    private final EntityManagerFactory emf;
    /**
     * The map of properties to pass when creating EntityManagers
     */
    private final Map<String, Object> props;
    /**
     * A registry for creating new persistence contexts
     */
    private final JTAPersistenceContextRegistry reg;
    /**
     * The number of EntityManager instances that are open
     */
    private final AtomicLong instanceCount;
    /**
     * A callback for when we're quiescing
     */
    private final DestroyCallback callback;


    private final ThreadLocal<AtomicInteger> activeCalls = new ThreadLocal<AtomicInteger>() {
        @Override
        protected AtomicInteger initialValue() {
            return new AtomicInteger(0);
        }
    };

    private final ThreadLocal<EntityManager> activeManager = new ThreadLocal<EntityManager>();

    private final ConcurrentLinkedQueue<EntityManager> pool = new ConcurrentLinkedQueue<EntityManager>();
    
    public JTAEntityManagerHandler(EntityManagerFactory factory,
                            Map<String, Object> properties, JTAPersistenceContextRegistry registry, AtomicLong activeCount,
                            DestroyCallback onDestroy) {
        emf = factory;
        props = properties;
        reg = registry;
        instanceCount = activeCount;
        callback = onDestroy;
    }

    public void preCall() {
        activeCalls.get().incrementAndGet();
    }

    public void postCall() {
        if (activeCalls.get().decrementAndGet() == 0) {
            EntityManager manager = activeManager.get();
            if (manager != null) {
                activeManager.set(null);
                manager.clear();
                pool.add(manager);
            }
        }
    }
    
    /**
     * Get the target persistence context
     *
     * @param forceTransaction Whether the returned entity manager needs to be bound to a transaction
     * @return
     * @throws TransactionRequiredException if forceTransaction is true and no transaction is available
     */
    private EntityManager getPersistenceContext(boolean forceTransaction) {
        if (forceTransaction) {
            EntityManager manager = activeManager.get();
            if (manager != null) {
                manager.clear();
            }
            return reg.getCurrentPersistenceContext(emf, props, instanceCount, callback);
        } else {
            if (reg.isTransactionActive()) {
                EntityManager manager = activeManager.get();
                if (manager != null) {
                    manager.clear();
                }
                return reg.getCurrentPersistenceContext(emf, props, instanceCount, callback);
            } else {
                if (!!!reg.jtaIntegrationAvailable() && _logger.isDebugEnabled())
                    _logger.debug("No integration with JTA transactions is available. No transaction context is active.");

                EntityManager manager = activeManager.get();
                if (manager == null) {
                    manager = pool.poll();
                    if (manager == null) {
                        manager = emf.createEntityManager(props);
                    }
                    activeManager.set(manager);
                }
                return manager;
            }
        }
    }

    /**
     * Called reflectively by blueprint
     */
    public void internalClose() {
        EntityManager temp;
        while ((temp = pool.poll()) != null) {
            temp.close();
        }
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        if ("close".equals(methodName)) {
            throw new IllegalStateException(NLS.MESSAGES.getMessage("close.called.on.container.manged.em"));
        }
        if ("getTransaction".equals(methodName)) {
            throw new IllegalStateException(NLS.MESSAGES.getMessage("getTransaction.called.on.container.managed.em"));
        }
        if ("isOpen".equals(methodName)) {
            return true;
        }
        if ("joinTransaction".equals(methodName)) {
            // This should be a no-op for a JTA entity manager
            return null;
        }
        
        if ("postCall".equals(methodName)) {
            postCall();
            return null;
        }
        
        if ("preCall".equals(methodName)) {
            preCall();
            return null;
        }
        
        if ("internalClose".equals(methodName)) {
            internalClose();
            return null;
        }
        
        boolean forceTransaction = transactedMethods.contains(methodName);
        
        // TODO Check if this can be reached
        if ("joinTransaction".equals(methodName) && args != null && args.length > 2 && args[2].getClass() == LockModeType.class) {
            forceTransaction = args[2] != LockModeType.NONE;
        }
        
        if ("find".equals(methodName) && args != null && args.length >= 3 && args[2].getClass() == LockModeType.class) {
            forceTransaction = args[2] != LockModeType.NONE;
        }
        
        EntityManager delegate = getPersistenceContext(forceTransaction); 
        Object res = null;
        try {
          res = method.invoke(delegate, args);
        } catch (IllegalArgumentException e) {
          new PersistenceException(NLS.MESSAGES.getMessage("wrong.JPA.version", new Object[]{
              method.getName(), delegate
          }), e);
        }
        return res;

    }

}

@@ -3465,134 +3465,31 @@
     
-EntityManager manager = activeManager.get();%0A            if (manager != null) %7B%0A                manager.clear();%0A            %7D
+clearDetachedManager();
 %0A   
@@ -3657,146 +3657,31 @@
     
-EntityManager manager = activeManager.get();%0A                if (manager != null) %7B%0A                    manager.clear();%0A                %7D
+clearDetachedManager();
 %0A   
@@ -4397,24 +4397,196 @@
    %7D%0A    %7D%0A%0A
+    private void clearDetachedManager() %7B%0A        EntityManager manager = activeManager.get();%0A        if (manager != null) %7B%0A            manager.clear();%0A        %7D%0A    %7D%0A%0A
     /**%0A    

Factor out duplicate code