/*
* JBoss, Home of Professional Open Source
* Copyright 2005, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.tm.usertx.server;

import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.Map;
import java.util.HashMap;
import java.util.Collections;

import javax.management.ObjectName;
import javax.naming.InitialContext;
import javax.naming.Context;

import org.jboss.system.ServiceMBeanSupport;
import org.jboss.tm.usertx.client.ClientUserTransaction;
import org.jboss.tm.usertx.interfaces.UserTransactionSessionFactory;
import org.jboss.tm.usertx.interfaces.UserTransactionSession;
import org.jboss.tm.TransactionPropagationContextUtil;
import org.jboss.invocation.Invocation;
import org.jboss.invocation.MarshalledInvocation;

/**
 *  This is a JMX service handling the serverside of UserTransaction
 *  usage for standalone clients.
 *
 * @author <a href="mailto:osh@sparre.dk">Ole Husgaard</a>
 * @author Scott.Stark@jboss.org
 * @version $Revision$
 */
public class ClientUserTransactionService
      extends ServiceMBeanSupport
      implements ClientUserTransactionServiceMBean
{
   // Constants -----------------------------------------------------
   /** The location where the javax.transaction.UserTransaction is bound on the server */
   public static String JNDI_NAME = "UserTransaction";

   // Attributes ----------------------------------------------------
   /** The UserTransactionSession and UserTransactionSessionFactory method hashes */
   private Map marshalledInvocationMapping = new HashMap();
   /** The proxy factory service used for the UserTransactionSession */
   private ObjectName txProxyName;
   /** The stateless proxy used for all UserTransactionSessions */
   private Object txProxy;

   /** Set the name of the proxy factory service used for the 
    * UserTransactionSession
    * @param proxyName
    */ 
   public void setTxProxyName(ObjectName proxyName)
   {
      this.txProxyName = proxyName;      
   }

   /** Expose UserTransactionSession and UserTransactionSessionFactory
    * interfaces via JMX to invokers.
    *
    * @jmx:managed-operation
    *
    * @param invocation    A pointer to the invocation object
    * @return              Return value of method invocation.
    *
    * @throws Exception    Failed to invoke method.
    */
   public Object invoke(Invocation invocation) throws Exception
   {
      // Set the method hash to Method mapping
      if (invocation instanceof MarshalledInvocation)
      {
         MarshalledInvocation mi = (MarshalledInvocation) invocation;
         mi.setMethodMap(marshalledInvocationMapping);
      }
      // Invoke the method via reflection
      Method method = invocation.getMethod();
      Object[] args = invocation.getArguments();
      Object value = null;
      try
      {
         if( UserTransactionSessionFactory.class.isAssignableFrom(method.getDeclaringClass()) )
         {
            // Just return the UserTransactionSession proxy as its stateless
            value = txProxy;
         }
         else if( method.getName().equals("begin") )
         {
            // Begin a new transaction
            Integer timeout = (Integer) args[0];
            UserTransactionSession session = UserTransactionSessionImpl.getInstance();
            value = session.begin(timeout.intValue());
         }
         else if( method.getName().equals("destroy"))
         {
            /* We do nothing as the tx will timeout and the tx map is shared
            across all sessions as we have no association with the txs
            a given client has started.
            */
         }
         else
         {
            UserTransactionSession session = UserTransactionSessionImpl.getInstance();
            value = method.invoke(session, args);
         }
      }
      catch(InvocationTargetException e)
      {
         Throwable t = e.getTargetException();
         if( t instanceof Exception )
            throw (Exception) t;
         else
            throw new UndeclaredThrowableException(t, method.toString());
      }

      return value;
   }

   // ServiceMBeanSupport overrides ---------------------------------
   
   protected void startService()
         throws Exception
   {
      Context ctx = new InitialContext();
      // Bind the in VM UserTransaction interface
      ctx.bind(JNDI_NAME, ClientUserTransaction.getSingleton());

      // Get the UserTransactionSession proxy
      txProxy = getServer().getAttribute(txProxyName, "Proxy");

      // Build the UserTransactionSession interface method map
      HashMap tmpMap = new HashMap(13);
      Method[] methods = UserTransactionSession.class.getMethods();
      for(int m = 0; m < methods.length; m ++)
      {
         Method method = methods[m];
         Long hash = new Long(MarshalledInvocation.calculateHash(method));
         tmpMap.put(hash, method);
      }
      // Add the UserTransactionSessionFactory interface method map
      methods = UserTransactionSessionFactory.class.getMethods();
      for(int m = 0; m < methods.length; m ++)
      {
         Method method = methods[m];
         Long hash = new Long(MarshalledInvocation.calculateHash(method));
         tmpMap.put(hash, method);
      }
      marshalledInvocationMapping = Collections.unmodifiableMap(tmpMap);
   }

   protected void stopService()
   {
      try
      {
         Context ctx = new InitialContext();
         ctx.unbind(JNDI_NAME);
      }
      catch (Exception e)
      {
         log.warn("Failed to unbind "+JNDI_NAME, e);
      }
   }

}
