/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2006, Red Hat Middleware LLC, and individual contributors
 * as indicated by the @author tags. See the copyright.txt file 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.unified.interfaces;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.StreamCorruptedException;
import java.rmi.MarshalledObject;
import java.rmi.RemoteException;
import java.rmi.ServerException;
import org.jboss.invocation.Invocation;
import org.jboss.invocation.Invoker;
import org.jboss.logging.Logger;
import org.jboss.remoting.Client;
import org.jboss.remoting.InvokerLocator;
import org.jboss.remoting.serialization.IMarshalledValue;

/**
 * This represents the client side of the EJB invoker.  This invoker uses
 * the remoting framework for making invocations.
 *
 * @author <a href="mailto:tom.elrod@jboss.com">Tom Elrod</a>
 */
public class UnifiedInvokerProxy implements Invoker, Externalizable
{
    static
    {
        try
        {
            Class.forName("org.jboss.invocation.unified.interfaces.JavaSerializationManager");
        }
        catch (Exception e)
        {
        }
    }

   static final long serialVersionUID = -1108158470271861548L;

   private transient Client client;

   private InvokerLocator locator;

   private boolean strictRMIException = false;

   private String subsystem = "invoker";

   protected final Logger log = Logger.getLogger(getClass());

   static final int VERSION_5_0 = 500;
   static final int CURRENT_VERSION = VERSION_5_0;


   public UnifiedInvokerProxy()
   {
      super();
   }

   public UnifiedInvokerProxy(InvokerLocator locator)
   {
      init(locator);
   }

   public UnifiedInvokerProxy(InvokerLocator locator, boolean isStrictRMIException)
   {
      this.strictRMIException = isStrictRMIException;
      init(locator);
   }

   protected void init(InvokerLocator locator)
   {
      this.locator = locator;

      try
      {
         client = new Client(locator, getSubSystem());
         client.connect();
      }
      catch(Exception e)
      {
         log.fatal("Could not initialize UnifiedInvokerProxy.", e);
      }

   }

   public String getSubSystem()
   {
      return subsystem;
   }

   public void setSubSystem(String subsystem)
   {
      this.subsystem = subsystem;
   }


   public boolean isStrictRMIException()
   {
      return strictRMIException;
   }

   protected Client getClient()
   {
      return client;
   }

   protected InvokerLocator getLocator()
   {
      return locator;
   }

   protected void setLocator(InvokerLocator locator)
   {
      this.locator = locator;
   }

   protected void setStrictRMIException(boolean strictRMIException)
   {
      this.strictRMIException = strictRMIException;
   }


   /**
    * A free form String identifier for this delegate invoker, can be clustered or target node
    * This should evolve in a more advanced meta-inf object.
    * <p/>
    * This will return the host supplied by the invoker locator if locator is not null.  Otherwise, if the locator is null, will
    * return null.
    */
   public String getServerHostName() throws Exception
   {
      if(locator != null)
      {
         return locator.getHost();
      }
      else
      {
         return null;
      }
   }

   /**
    * @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
   {
      Object response = null;

      invocation.getInvocationContext().setValue("SERIALIZATION_TYPE",locator.findSerializationType());

      try
      {
         response = client.invoke(invocation, null);

         if(response instanceof Exception)
         {
            throw ((Exception) response);
         }
         if(response instanceof MarshalledObject)
         {
            return ((MarshalledObject) response).get();
         }
         if (response instanceof IMarshalledValue)
         {
             return ((IMarshalledValue)response).get();
         }
         return response;

      }
      catch(RemoteException aex)
      {
         // per Jira issue JBREM-61
         if(strictRMIException)
         {
            throw new ServerException(aex.getMessage(), aex);
         }
         else
         {
            throw aex;
         }
      }
      catch(Throwable throwable)
      {
         // this is somewhat of a hack as remoting throws throwable,
         // so will let Exception types bubble up, but if Throwable type,
         // then have to wrap in new Exception, as this is the signature
         // of this invoke method.
         if(throwable instanceof Exception)
         {
            throw (Exception) throwable;
         }
         throw new Exception(throwable);
      }
   }

   /**
    * Externalize this instance and handle obtaining the remoteInvoker stub
    */
   public void writeExternal(final ObjectOutput out)
         throws IOException
   {
      out.writeInt(CURRENT_VERSION);

      out.writeUTF(locator.getOriginalURI());
      out.writeBoolean(strictRMIException);
   }

   /**
    * Un-externalize this instance.
    */
   public void readExternal(final ObjectInput in)
         throws IOException, ClassNotFoundException
   {
      int version = in.readInt();
      // Read in and map the version of the serialized data seen
      switch(version)
      {
         case VERSION_5_0:
            locator = new InvokerLocator(in.readUTF());
            strictRMIException = in.readBoolean();
            init(locator);
            break;
         default:
            throw new StreamCorruptedException("Unknown version seen: " + version);
      }
   }

}