/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2008, 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.server;

import java.rmi.MarshalledObject;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import org.jboss.invocation.Invocation;
import org.jboss.invocation.unified.interfaces.UnifiedInvokerProxy;
import org.jboss.mx.util.JMXExceptionDecoder;
import org.jboss.remoting.InvocationRequest;
import org.jboss.remoting.InvokerLocator;
import org.jboss.remoting.ServerInvocationHandler;
import org.jboss.remoting.ServerInvoker;
import org.jboss.remoting.callback.InvokerCallbackHandler;
import org.jboss.remoting.transport.ConnectorMBean;
import org.jboss.system.Registry;
import org.jboss.system.ServiceMBeanSupport;

/**
 * This is a detached invoker which sits on top of jboss remoting.
 * Since this uses remoting, the transport protocol used is defined within
 * the remoting service and this, the UnifiedInvoker, is declared as the handler.
 *
 * @author <a href="mailto:tom.elrod@jboss.com">Tom Elrod</a>
 */
public class UnifiedInvoker extends ServiceMBeanSupport implements ServerInvocationHandler, UnifiedInvokerMBean
{
   private ConnectorMBean connector;
   private ServerInvoker serverInvoker;

   private MBeanServer mbServer;

   private boolean strictRMIException = false;

   private UnifiedInvokerProxy proxy;

   private String subsystem = "invoker";

   /**
    * If set to true, this will cause the UnifiedInvokerProxy (on the client side) to
    * wrap all RemoteExceptions thrown from the server in a new ServerException.  If false,
    * will unwrap the original exception thrown from withint the RemoteException and throw that.
    * The default is false.
    *
    * @param isStrict
    */
   public void setStrictRMIException(boolean isStrict)
   {
      this.strictRMIException = isStrict;
   }

   /**
    * A return of true means that the UnifiedInvokerProxy (on the client side) will wrap all
    * RemoteExceptions within a new ServerException.  A return of false, will unwrap the original
    * exception thrown from within the RemoteException and throw that.  The default, if not explicitly set,
    * is false.
    *
    * @return
    */
   public boolean getStrictRMIException()
   {
      return strictRMIException;
   }

   /**
    * Gets the remoting subsystem being used.
    * @return
    */
   public String getSubSystem()
   {
      return subsystem;
   }

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

   /**
    * This may be called if set depends in config with optional-attribute-name.
    *
    * @param connector
    */
   public void setConnector(ConnectorMBean connector)
   {
      this.connector = connector;
   }

   protected void createService() throws Exception
   {
      if(connector != null)
      {
         try
         {
            connector.addInvocationHandler(getSubSystem(), this);
         }
         catch(Exception e)
         {
            log.error("Error adding unified invoker as handler upon connector being set.", e);
         }
      }
   }

   /**
    * Will get the invoker locator from the server invoker, start the server invoker, create the proxy,
    * and bind the proxy.
    *
    * @throws Exception
    */
   protected void startService() throws Exception
   {
      log.debug("Starting unified invoker service.");

      InvokerLocator locator = null;
      if(serverInvoker != null)
      {
         locator = serverInvoker.getLocator();
         if(!serverInvoker.isStarted())
         {
            serverInvoker.start();
         }
      }
      else if(connector != null)
      {
         locator = connector.getLocator();
      }
      else
      {
         /**
          * This will happen in one of two scenarios.  One, the unified invoker was not declared in as
          * service before the connector AND was not specified as the handler within the connector config.
          * Two, the unified invoker service config did not use the proxy-type attribute within the depends
          * tag to have the container set the connector upon creating the unified invoker.
          */
         log.error("Error referencing either remoting connector or server invoker to be used.  " +
                   "Please check configuration to make sure proper dependancies are set.");
         throw new RuntimeException("Error getting locator because server invoker is null.");
      }

      proxy = new UnifiedInvokerProxy(locator, strictRMIException);

      jmxBind();

   }

   protected void jmxBind()
   {
      Registry.bind(getServiceName(), proxy);
   }

   /**
    * Stops the server invoker.
    *
    * @throws Exception
    */
   public void stopService() throws Exception
   {
      // JBAS-5590 -- the serverInvoker is a shared resource and shouldn't
      // be stopped just because we don't want it any more
//      if(serverInvoker != null)
//      {
//         serverInvoker.stop();
//      }
   }

   /**
    * Gives this JMX service a name.
    *
    * @return The Name value
    */
   public String getName()
   {
      return "Unified-Invoker";
   }

   /**
    * Gets the invoker locator string for this server
    *
    * @return
    */
   public String getInvokerLocator()
   {
      if(serverInvoker != null)
      {
         return serverInvoker.getLocator().getLocatorURI();
      }
      else
      {
         return null;
      }
   }

   /**
    * Implementation of the server invoker handler interface.  Will take the invocation request
    * and invoke down the interceptor chain.
    *
    * @param invocationReq
    * @return
    * @throws Throwable
    */
   public Object invoke(InvocationRequest invocationReq) throws Throwable
   {
      Invocation invocation = (Invocation) invocationReq.getParameter();
      Thread currentThread = Thread.currentThread();
      ClassLoader oldCl = currentThread.getContextClassLoader();
      ObjectName mbean = null;
      try
      {
         mbean = (ObjectName) Registry.lookup(invocation.getObjectName());

         // The cl on the thread should be set in another interceptor
         Object obj = getServer().invoke(mbean,
                                         "invoke",
                                         new Object[]{invocation},
                                         Invocation.INVOKE_SIGNATURE);
         return new MarshalledObject(obj);
      }
      catch(Exception e)
      {
         Throwable th = JMXExceptionDecoder.decode(e);
         if(log.isTraceEnabled())
         {
            log.trace("Failed to invoke on mbean: " + mbean, th);
         }

         if(th instanceof Exception)
         {
            e = (Exception) th;
         }

         throw e;
      }
      finally
      {
         currentThread.setContextClassLoader(oldCl);
      }

   }

   /**
    * set the mbean server that the handler can reference
    *
    * @param server
    */
   public void setMBeanServer(MBeanServer server)
   {
      mbServer = server;
   }

   public MBeanServer getServer()
   {
      return mbServer;
   }

   /**
    * set the invoker that owns this handler
    *
    * @param invoker
    */
   public void setInvoker(ServerInvoker invoker)
   {
      /**
       * This is needed in case we need to make calls on the server invoker (for classloading
       * in particular).  Will just leave alone for now and come back to this when have
       * a use to call on it.
       */
      serverInvoker = invoker;
   }

   protected ServerInvoker getInvoker()
   {
      return serverInvoker;
   }

   /**
    * Adds a callback handler that will listen for callbacks from
    * the server invoker handler.
    * This is a no op as don't expect the detached invokers to have callbacks
    *
    * @param callbackHandler
    */
   public void addListener(InvokerCallbackHandler callbackHandler)
   {
      //NO OP - do not expect the detached invoker to have callbacks
   }

   /**
    * Removes the callback handler that was listening for callbacks
    * from the server invoker handler.
    * This is a no op as don't expect the detached invokers to have callbacks
    *
    * @param callbackHandler
    */
   public void removeListener(InvokerCallbackHandler callbackHandler)
   {
      //NO OP - do not expect the detached invoker to have callbacks
   }

}