/*
 * JBoss, Home of Professional Open Source
 * Copyright 2007, Red Hat Middleware LLC, and individual contributors
 * as indicated by the @author tags.
 * See the copyright.txt in the distribution for a full listing
 * of individual contributors.
 * This copyrighted material is made available to anyone wishing to use,
 * modify, copy, or redistribute it subject to the terms and conditions
 * of the GNU General Public License, v. 2.0.
 * This program is distributed in the hope that it will be useful, but WITHOUT A
 * 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,
 * v. 2.0 along with this distribution; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA  02110-1301, USA.
 *
 * (C) 2007,
 * @author JBoss Inc.
 */
package org.jboss.txbridge.ba.aspect;

import org.apache.log4j.Logger;
import org.jboss.aop.joinpoint.FieldReadInvocation;
import org.jboss.aop.joinpoint.FieldWriteInvocation;
import org.jboss.aop.joinpoint.MethodInvocation;
import org.jboss.txbridge.ba.BATransactionManager;
import org.jboss.txbridge.ba.datamgmt.DataManagerImpl;
import org.jboss.txbridge.ba.datamgmt.DataManagerProvider;
import org.jboss.txbridge.ba.data.TaskDescription;

import java.lang.reflect.Method;

/**
 * This aspect provides three advices:
 * - process() - intercepts a call to a Business Activity service and applies necessary 
 * transaction-related mechanisms (handling a single invocation). 
 * - access() - used for transparently injecting the datamgmt manager associated
 * with the current thread of execution.
 * - access() - used to prevent from setting the datamgmt manager object.
 *
 * @author Maciej P. Machulak (mmachulak@redhat.com)
 * @version 0.1
 */
public class TransactionProcessingAspect
{
    // Logger
    private static Logger log = Logger.getLogger(TransactionProcessingAspect.class);

    // BA Transaction Manager
    private static BATransactionManager baTransactionManager = BATransactionManager.getSingletonInstance();

    // Compensation Manager Provider
    private static DataManagerProvider cmp = DataManagerProvider.getSingletonInstance();

    /**
     * Advice that processes methods execution according to the BA management.
     *
     * @param invocation is the method's invocation.
     * @return the object returned by the method.
     * @throws Throwable is any exception that should be propagated.
     */
    public Object process(MethodInvocation invocation) throws Throwable
    {

        log.info("process()");

        // Check if this invocation is within the scope of a Business Activity
        log.info("Checking if there is a BA");
        if (!baTransactionManager.isBAPresent())
        {
            log.info("No Business Activity - proceeding with normal invocation");
            return internalProcess(invocation);
        }
        else
        {
            log.info("Business Activity present - proceeding");
            
            // #### IMPLEMENTATION SPECIFIC PART - BEGIN
            Method method = invocation.getMethod();
            log.info("Method: " + method.getName());
            Object[] arguments = invocation.getArguments();
            if (arguments == null)
            {
                log.info("Method without any arguments.");
            }
            else
            {
                log.info("No of arguments: " + arguments.length);
            }
            // #### IMPLEMENTATION SPECIFIC PART - END



            // Handle the invocation
            log.info("Handling invocation");
            TaskDescription taskDesc = baTransactionManager.handleInvocation(method);

            // Associate datamgmt manager with thread of execution
            Long threadId = Thread.currentThread().getId();
            log.info("Associating datamgmt manager for thread: " + threadId);
            cmp.associateManager(threadId,new DataManagerImpl(taskDesc.getTaskId(),taskDesc.getParticipant()));

            try
            {
                // #### IMPLEMENTATION SPECIFIC PART - BEGIN
                // Invoke the service
                log.info("Invoking the service");
                Object returnObject = invocation.invokeNext();
                // #### IMPLEMENTATION SPECIFIC PART - END

                // Dissassociate the thread
                log.info("Disassociating datamgmt manager for thread: " + threadId);
                cmp.removeManager(threadId);

                log.info("No exception - proceeding");

                // Complete the execution within this transaction
                log.info("Complete the invocation");
                baTransactionManager.completeInvocation(taskDesc,arguments,returnObject);

                return returnObject;
            }
            catch (Exception e)
            {
                // Dissassociate the thread
                log.info("Disassociating datamgmt manager for thread: " + threadId);
                cmp.removeManager(threadId);

                // Clean the transaction
                log.info("Cleaning information about the transaction");
                baTransactionManager.cleanInvocation(taskDesc);

                // Pass the exception
                throw new Exception(e);
            }
        }
    }

    /**
     * Advice that processes methods execution without applying any transactional
	 * mechanisms.
     *
     * @param invocation is the method's invocation.
     * @return the object returned by the method.
     * @throws Throwable is any exception that should be propagated.
     */
    private Object internalProcess(MethodInvocation invocation) throws Throwable
    {
        log.info("internalProcess()");
        return invocation.invokeNext();
    }

    /**
     * Advice that returns the required DataManager object for every read
     * access to the DataManager object annotated by the @BADataManagement
	 * annotation.
     *
     * @param invocation is the read access of the DataManager field.
     * @return the object returned by the method.
     * @throws Throwable is any exception that should be propagated.
     */
    public Object access(FieldReadInvocation invocation) throws Throwable
    {
        log.info("Returning datamgmt manager for thread: " + Thread.currentThread().getId());
        return cmp.getManager(Thread.currentThread().getId());
    }

    /**
     * Advice that prevents from setting the annotated DataManager object.
     *
     * @param invocation is the write access of the DataManager field.
     * @return the object returned by the method.
     * @throws Throwable is any exception that should be propagated.
     */
    public Object access(FieldWriteInvocation invocation) throws Throwable
    {
        throw new RuntimeException("Setting a @BADataManagement variable is illegal.");
    }

}
