/*
* 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.invocation;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.lang.reflect.UndeclaredThrowableException;
import java.math.BigDecimal;
import java.math.BigInteger;

import javax.transaction.Transaction;

import org.jboss.proxy.Interceptor;
import org.jboss.proxy.ClientContainer;

import org.jboss.system.Registry;
import org.jboss.util.id.GUID;
import org.jboss.remoting.serialization.IMarshalledValue;
import org.jboss.remoting.serialization.SerializationStreamFactory;
import org.jboss.serial.objectmetamodel.safecloning.SafeClone;

/**
 * A very simple implementation of it that branches to the local stuff.
 *
 * @author <a href="mailto:marc.fleury@jboss.org">Marc Fleury</a>
 * @author Scott.Stark@jboss.org
 * @version $Revision$
 */
public class InvokerInterceptor
   extends Interceptor
   implements Externalizable
{
   /** Serial Version Identifier. @since 1.2 */
   private static final long serialVersionUID = 2548120545997920357L;

   /** The value of our local Invoker.ID to detect when we are local. */
   private GUID invokerID = Invoker.ID;

   /** Invoker to the remote JMX node. */
   protected Invoker remoteInvoker;

   /** Static references to local invokers. */
   protected static Invoker localInvoker;

   /** The InvokerProxyHA class */
   protected static Class invokerProxyHA;

   static
   {
       try
       {
          // Using Class.forName() to avoid security problems in the client
          invokerProxyHA = Class.forName("org.jboss.invocation.InvokerProxyHA");
       }
       catch (Throwable ignored)
       {
       }

       try
       {
          // To guarantee backwards compatibility, we need to make sure this SerializationManager is being used
          Class.forName("org.jboss.invocation.unified.interfaces.JavaSerializationManager");
       }
       catch (Throwable ignored)
       {
       }

   }

   /**
    * Get the local invoker reference, useful for optimization.
    */
   public static Invoker getLocal()
   {
      return localInvoker;
   }

   /**
    * Set the local invoker reference, useful for optimization.
    */
   public static void setLocal(Invoker invoker)
   {
      localInvoker = invoker;
   }

   /**
    * Exposed for externalization.
    */
   public InvokerInterceptor()
   {
      super();
   }

   /**
    * Returns wether we are local to the originating container or not.
    *
    * @return true when we have the same GUID
    */
   public boolean isLocal()
   {
      return invokerID.equals(Invoker.ID);
   }

   /**
    * Whether the target is local
    *
    * @param invocation the invocation
    * @return true when the target is local
    */
   public boolean isLocal(Invocation invocation)
   {
      // No local invoker, it must be remote
      if (localInvoker == null)
         return false;

      // The proxy was downloaded from a remote location
      if (isLocal() == false)
      {
         // It is not clustered so we go remote
         if (isClustered(invocation) == false)
            return false;
      }

      // See whether we have a local target
      return hasLocalTarget(invocation);
   }

   /**
    * Whether we are in a clustered environment<p>
    *
    * NOTE: This should be future compatible under any
    * new design where a prior target chooser interceptor
    * picks a non HA target than that code being
    * inside a ha invoker.
    *
    * @param invocation the invocation
    * @return true when a clustered invoker
    */
   public boolean isClustered(Invocation invocation)
   {
      // No clustering classes
      if (invokerProxyHA == null)
         return false;

      // Is the invoker a HA invoker?
      InvocationContext ctx = invocation.getInvocationContext();
      Invoker invoker = ctx.getInvoker();
      return invoker != null && invokerProxyHA.isAssignableFrom(invoker.getClass());
   }

   /**
    * Whether there is a local target
    *
    * @param invocation
    * @return true when in the registry
    */
   public boolean hasLocalTarget(Invocation invocation)
   {
      return Registry.lookup(invocation.getObjectName()) != null;
   }

   /**
    * The invocation on the delegate, calls the right invoker.
    * Remote if we are remote, local if we are local.
    */
   public Object invoke(Invocation invocation)
      throws Exception
   {
      // optimize if calling another bean in same server VM
      if (isLocal(invocation))
         return invokeLocal(invocation);
      else
         return invokeInvoker(invocation);
   }

   /**
    * Invoke using local invoker
    *
    * @param invocation the invocation
    * @return the result
    * @throws Exception for any error
    */
   protected Object invokeLocal(Invocation invocation) throws Exception
   {
      return localInvoker.invoke(invocation);
   }

   /**
    * Invoke using local invoker and marshalled
    *
    * @param invocation the invocation
    * @return the result
    * @throws Exception for any error
    */
   protected Object invokeMarshalled(Invocation invocation) throws Exception
   {
      MarshalledInvocation mi = new MarshalledInvocation(invocation);
      MarshalledValue copy = new MarshalledValue(mi);
      Invocation invocationCopy = (Invocation) copy.get();

      // copy the Tx
      Transaction tx = invocation.getTransaction();
      invocationCopy.setTransaction(tx);

      try
      {
         Object rtnValue = localInvoker.invoke(invocationCopy);
         MarshalledValue mv = new MarshalledValue(rtnValue);
         return mv.get();
      }
      catch(Throwable t)
      {
         MarshalledValue mv = new MarshalledValue(t);
         Throwable t2 = (Throwable) mv.get();
         if( t2 instanceof Exception )
            throw (Exception) t2;
         else
            throw new UndeclaredThrowableException(t2);
      }
   }


   /** These objects are safe to reuse in callByValue operations */
   static final SafeClone safeToReuse = new SafeClone(){
                 public boolean isSafeToReuse(Object obj) {
                     if (obj==null)
                     {
                         return false;
                     }

                     if (obj instanceof ClientContainer ||
                         obj instanceof String ||
                         obj instanceof Number ||
                         obj instanceof BigDecimal ||
                         obj instanceof BigInteger ||
                         obj instanceof Byte ||
                         obj instanceof Double ||
                         obj instanceof Float ||
                         obj instanceof Integer ||
                         obj instanceof Long ||
                         obj instanceof Short)
                     {
                         return true;
                     }
                     else
                     {
                         return false;
                     }
                 }
             };


   protected IMarshalledValue createMarshalledValueForCallByValue(Object value) throws IOException
   {
	   return SerializationStreamFactory.getManagerInstance().createdMarshalledValue(value);
   }
             
   /** This method is for local calls when using pass-by-value*/
   protected Object invokeLocalMarshalled(Invocation invocation) throws Exception
   {

       IMarshalledValue value = createMarshalledValueForCallByValue(invocation.getArguments());
       MarshalledInvocation invocationCopy = createInvocationCopy(invocation, value);

      // copy the Tx
      Transaction tx = invocation.getTransaction();
      invocationCopy.setTransaction(tx);

      try
      {
         Object rtnValue = localInvoker.invoke(invocationCopy);
         IMarshalledValue mv = createMarshalledValueForCallByValue(rtnValue);
         return mv.get();
      }
      catch(Throwable t)
      {
         IMarshalledValue mv = SerializationStreamFactory.getManagerInstance().createdMarshalledValue(t);
         Throwable t2 = (Throwable) mv.get();
         if( t2 instanceof Exception )
            throw (Exception) t2;
         else
            throw new UndeclaredThrowableException(t2);
      }
   }

    /**  It is too expensive to serialize this entire object just to get class isolation
         and MarshalledValues on local calls.
         We are creating an in-memory copy without using serialization for that matter. */
    private MarshalledInvocation createInvocationCopy(Invocation invocation, IMarshalledValue value) {
        MarshalledInvocation invocationCopy = new MarshalledInvocation(invocation);
        invocationCopy.setMethod(null);
        invocationCopy.setMethodHash(MarshalledInvocation.calculateHash(invocation.getMethod()));
        invocationCopy.setMarshalledArguments(value);
        invocationCopy.setArguments(null);

        return invocationCopy;
    }

    /**
     * Invoke using invoker
     *
     * @param invocation the invocation
     * @return the result
     * @throws Exception for any error
     */
    protected Object invokeInvoker(Invocation invocation) throws Exception
    {
       InvocationContext ctx = invocation.getInvocationContext();
       Invoker invoker = ctx.getInvoker();
       return invoker.invoke(invocation);
    }

   /**
    * Externalize this instance.
    *
    * <p>
    * If this instance lives in a different VM than its container
    * invoker, the remote interface of the container invoker is
    * not externalized.
    */
   public void writeExternal(final ObjectOutput out)
      throws IOException
   {
      out.writeObject(invokerID);
   }

   /**
    * Un-externalize this instance.
    *
    * <p>
    * We check timestamps of the interfaces to see if the instance is in the original
    * VM of creation
    */
   public void readExternal(final ObjectInput in)
      throws IOException, ClassNotFoundException
   {
      invokerID = (GUID)in.readObject();
   }
}