/**
 * Copyright (C) 2000-2010 Atomikos <info@atomikos.com>
 *
 * This code ("Atomikos TransactionsEssentials"), by itself,
 * is being distributed under the
 * Apache License, Version 2.0 ("License"), a copy of which may be found at
 * http://www.atomikos.com/licenses/apache-license-2.0.txt .
 * You may not use this file except in compliance with the License.
 *
 * While the License grants certain patent license rights,
 * those patent license rights only extend to the use of
 * Atomikos TransactionsEssentials by itself.
 *
 * This code (Atomikos TransactionsEssentials) contains certain interfaces
 * in package (namespace) com.atomikos.icatch
 * (including com.atomikos.icatch.Participant) which, if implemented, may
 * infringe one or more patents held by Atomikos.
 * It should be appreciated that you may NOT implement such interfaces;
 * licensing to implement these interfaces must be obtained separately from Atomikos.
 *
 * 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.
 */

package com.atomikos.jdbc;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import com.atomikos.datasource.TransactionalResource;
import com.atomikos.datasource.xa.XAResourceTransaction;
import com.atomikos.icatch.CompositeTransaction;
import com.atomikos.icatch.CompositeTransactionManager;
import com.atomikos.icatch.jta.TransactionManagerImp;
import com.atomikos.icatch.system.Configuration;

/**
 * 
 * 
 * 
 * 
 * 
 * A dynamic proxy to allow external pooling, and also to allow late enlisting
 * of resources with transactions.
 */

class ConnectionProxy implements InvocationHandler
{
	
	private final static List NON_TRANSACTIONAL_METHOD_NAMES = Arrays.asList(new String[] {
			"equals",
			"hashCode",
			"notify",
			"notifyAll",
			"toString",
			"wait"
			});

   
	
	static Set getAllImplementedInterfaces ( Class clazz )
    {
    		Set ret = null;
    		
    		if ( clazz.getSuperclass() != null ) {
    			//if superclass exists: first add the superclass interfaces!!!
    			ret = getAllImplementedInterfaces ( clazz.getSuperclass() ); 
    		}
    		else {
    			//no superclass: start with empty set
    			ret = new HashSet();
    		} 
    		
    		//add the interfaces in this class
    		Class[] interfaces = clazz.getInterfaces();
    		for ( int i = 0 ; i < interfaces.length ; i++ ) {
    			ret.add ( interfaces[i] );
    		}
    		
    		return ret;
    }

    static Object newInstance ( DTPPooledConnection pc , java.sql.Connection c ,
            TransactionalResource resource ) throws SQLException
    {
        Object ret = null;

        ConnectionProxy proxy = new ConnectionProxy ( c, resource, pc );

        Set interfaces = getAllImplementedInterfaces ( c.getClass() );
        Class[] interfaceClasses = ( Class[] ) interfaces.toArray ( new Class[0] );
        ret = java.lang.reflect.Proxy.newProxyInstance ( c.getClass ()
                .getClassLoader (), interfaceClasses, proxy );

        return ret;
    }

    private Connection wrapped_;
    private TransactionalResource resource_;
    private DTPPooledConnection pc_;

    private ConnectionProxy ( Connection wrapped ,
            TransactionalResource resource , DTPPooledConnection pc )
    {
        wrapped_ = wrapped;
        resource_ = resource;
        pc_ = pc;
    }

    /**
     * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object,
     *      java.lang.reflect.Method, java.lang.Object[])
     */
    public Object invoke ( Object o , Method m , Object[] args )
            throws Throwable
    {
        if (NON_TRANSACTIONAL_METHOD_NAMES.contains(m.getName())) {
        	if ( Configuration.isDebugLoggingEnabled() ) Configuration.logDebug ("Calling non-transactional method '" + m.getName() + "' on connection proxy, bypassing enlistment");
        	return m.invoke ( wrapped_, args );
        }

        Object ret = null;
        XAResourceTransaction restx = null;
        CompositeTransaction ct = null;
        CompositeTransactionManager ctm = null;
        ctm = Configuration.getCompositeTransactionManager ();
        boolean inTx = false;
        // false if resume should be called (first invocation in tx)

        if ( ctm != null )
            ct = ctm.getCompositeTransaction ();

        inTx = pc_.isInResourceTransaction ();
        if ( ct != null && ct.getProperty (  TransactionManagerImp.JTA_PROPERTY_NAME ) != null && !pc_.isDiscarded () ) {
            // only enlist if there is a NORMAL tx
            // AND if the pc is not yet discarded (i.e. closed event
            // without a new getConnection() event)
            if ( !inTx ) {
                // not yet enlisted
                // IMPORTANT: we should enlist ONLY ONCE,
                // since we don't have wrappers for each
                // type of dependent object (statements,...)
                // where the real action is
                // Also, for the same reason we can only
                // delist at close time, since that is the
                // earliest detectable moment in time
                // when all these dependent objects are no
                // longer accessible.
                // VITAL ASSERTION FOR THIS TO WORK:
                // after close, the ResourceTransaction association
                // of the pooled connection MUST be null, or the
                // same restx will be reused in different
                // JTA transactions!
                restx = (XAResourceTransaction) resource_
                        .getResourceTransaction ( ct );
                pc_.setResourceTransaction ( restx );
                restx.resume ();
                Configuration
                        .logDebug ( "JDBC ConnectionProxy: using resource transaction: "
                                + restx.getXid () );

            }

        }

        try {
            if ( Configuration.isDebugLoggingEnabled() ) Configuration.logDebug ( "JDBC ConnectionProxy: delegating "
                    + m.getName () + " to connection " + wrapped_.toString () );
//            if ("close".equals(m.getName())) {
//            	
//            }
            ret = m.invoke ( wrapped_, args );
        } catch ( InvocationTargetException i ) {
            pc_.setInvalidated ();
            pc_.close ();
            if ( Configuration.isDebugLoggingEnabled() ) Configuration.logDebug (
                    "Exception in pooled connection - closing it", i );
            AtomikosSQLException.throwAtomikosSQLException ( i.getMessage() , i );
        } catch ( Exception e ) {
            String msg = "Exception in pooled connection: unexpected error - closing it";
            Configuration.logWarning ( msg, e );
            pc_.setInvalidated ();
            pc_.close ();
            AtomikosSQLException.throwAtomikosSQLException ( e.getMessage() , e );
        }

        return ret;

    }

}
