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

import java.util.StringTokenizer;

import javax.management.MBeanRegistration;
import javax.management.MBeanServer;
import javax.management.ObjectName;

import org.jboss.remoting.InvokerLocator;
import org.jboss.remoting.InvokerRegistry;
import org.jboss.remoting.ServerInvocationHandler;
import org.jboss.remoting.ServerInvoker;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * Connector is an implementation of the ConnectorMBean interface.
 *
 * A transport connector is configured via <tt>*-service.xml</tt> such as:
 *
 * <CODE><PRE>
 *          &lt;mbean code="org.jboss.remote.transport.Connector"
 *                    name="jboss.remoting:type=Connector, transport=SOAP"&gt;
 *              &lt;attribute name="InvokerLocator"&gt;soap://localhost&lt;/attribute&gt;
 *              &lt;attribute name="Configuration"&gt;
 *                 &lt;handlers&gt;
 *                          &lt;handler subsystem="JMX"&gt;org.jboss.mx.remoting.JMXSubsystemInvocationHandler&lt;/handler&gt;
 *                 &lt;/handlers&gt;
 *              &lt;/attribute&gt;
 *          &lt;/mbean&gt;
 * </PRE></CODE>
 *
 * @author <a href="mailto:jhaynie@vocalocity.net">Jeff Haynie</a>
 * @author <a href="mailto:adrian.brock@happeningtimes.com">Adrian Brock</a>
 * @author <a href="mailto:d_jencks@users.sourceforge.net">David Jencks</a>
 * @author <a href="mailto:juha@jboss.org">Juha Lindfors</a>
 *
 * @version $Revision: 1.14.2.3 $
 *
 * @todo I think exposing the ServerInvoker directly might be wise, so it could have configuration
 *
 * @jmx.mbean    description = "An MB
 * ean wrapper around a ServerInvoker."
 * @jboss.xmbean
 */
public class Connector implements MBeanRegistration, ConnectorMBean
{
   protected ServerInvoker invoker;

   private String locatorURI;

   private Element xml;

   private InvokerLocator locator;

   private MBeanServer server;

   /**
    * This method is called by the MBeanServer before registration takes
    * place. The MBean is passed a reference of the MBeanServer it is
    * about to be registered with. The MBean must return the ObjectName it
    * will be registered with. The MBeanServer can pass a suggested object
    * depending upon how the MBean is registered.<p>
    *
    * The MBean can stop the registration by throwing an exception.The
    * exception is forwarded to the invoker wrapped in an
    * MBeanRegistrationException.
    *
    * @param server the MBeanServer the MBean is about to be
    * registered with.
    * @param name the suggested ObjectName supplied by the
    * MBeanServer.
    * @return the actual ObjectName to register this MBean with.
    * @exception Exception for any error, the MBean is not registered.
    */
   public ObjectName preRegister(MBeanServer server, ObjectName name)
         throws Exception
   {
      this.server = server;
      return name;
   }

   /**
    * This method is called by the MBeanServer after registration takes
    * place or when registration fails.
    *
    * @param registrationDone the MBeanServer passes true when the
    * MBean was registered, false otherwise.
    */
   public void postRegister(Boolean registrationDone)
   {
   }

   /**
    * This method is called by the MBeanServer before deregistration takes
    * place.<p>
    *
    * The MBean can throw an exception, this will stop the deregistration.
    * The exception is forwarded to the invoker wrapped in
    * an MBeanRegistrationException.
    */
   public void preDeregister()
         throws Exception
   {
   }

   /**
    * This method is called by the MBeanServer after deregistration takes
    * place.
    */
   public void postDeregister()
   {
   }

   /**
    * Starts the connector.
    *
    * @jmx.managed-operation
    *      description = "Start sets up the ServerInvoker we are wrapping."
    *      impact      = "ACTION"
    */
   public void start()
         throws Exception
   {
      if (locatorURI == null)
      {
         throw new IllegalStateException("Connector not configured with LocatorURI");
      }

      locator = new InvokerLocator(locatorURI);
      ClassLoader cl = Thread.currentThread().getContextClassLoader();

      if (cl == null)
      {
         cl = getClass().getClassLoader();
      }

      if (invoker == null)
      {
         // create the server invoker
         invoker = InvokerRegistry.createServerInvoker(this.locator);

         // now set the config, if any, in the invoker
         invoker.setConfiguration(server,xml);

         // this can change
         locator = invoker.getLocator();
      }

      if (invoker.isStarted() == false)
      {
         invoker.start();
      }

      if (xml != null)
      {
         NodeList handlersNodes = xml.getElementsByTagName("handler");

         if (handlersNodes == null || handlersNodes.getLength() <= 0)
         {
            throw new IllegalArgumentException("required 'handler' element not found");
         }

         int len = handlersNodes.getLength();

         for (int c = 0; c < len; c++)
         {
            Node node = handlersNodes.item(c);
            Node subNode = node.getAttributes().getNamedItem("subsystem");

            if (subNode == null)
            {
               throw new IllegalArgumentException("Required 'subsystem' attribute on 'handler' element");
            }

            String handlerClass = node.getFirstChild().getNodeValue();
            ServerInvocationHandler handler = (ServerInvocationHandler) cl.loadClass(handlerClass).newInstance();
            StringTokenizer tok = new StringTokenizer(subNode.getNodeValue(), ",");

            while (tok.hasMoreTokens())
            {
               String subsystem = tok.nextToken();
               addInvocationHandler(subsystem, handler);
            }
         }
      }

   }

   /**
    * Stops the connector.
    *
    * @jmx.managed-operation
    *      description = "Stop tears down the ServerInvoker we are wrapping."
    *      impact      = "ACTION"
    */
   public void stop()
   {
      if (invoker != null)
      {
         invoker.stop();
         invoker = null;
      }
   }

   /**
    * Creates the connector.
    *
    * @jmx.managed-operation
    */
   public void create()
         throws Exception
   {
   }

   /**
    * Destroys the connector.
    *
    * @jmx.managed-operation
    */
   public void destroy()
   {
//       if (invoker!=null)
//       {
//          invoker.destroy();
//          invoker=null;
//       }
   }

   /**
    * Returns the locator to the connector. Locator is the actual InvokerLocator
    * object used to identify and get the ServerInvoker we are wrapping.
    *
    * @jmx.managed-attribute
    *    description = "Locator is the actual InvokerLocator object used to
    *                   identify and get the ServerInvoker we are wrapping."
    *    access      = "read-only"
    */
   public InvokerLocator getLocator()
   {
      return locator;
   }

   /**
    * Sets the invoker locator. InvokerLocator is the string URI representation
    * of the InvokerLocator used to get and identify the ServerInvoker
    * we are wrapping.
    *
    * @jmx.managed-attribute
    *    description = "InvokerLocator is the string URI representation of the
    *                   InvokerLocator used to get and identify the ServerInvoker
    *                   we are wrapping."
    *     access     = "read-write"
    */
   public void setInvokerLocator(String locator)
         throws Exception
   {
      locatorURI = locator;
   }


   /**
    * Returns the invoker locator. InvokerLocator is the string URI representation
    * of the InvokerLocator used to get and identify the ServerInvoker
    * we are wrapping.
    *
    * @jmx.managed-attribute
    */
   public String getInvokerLocator() throws Exception
   {
      return locatorURI;
   }

   /**
    * Configuration is an xml element indicating subsystems to be registered
    * with the ServerInvoker we wrap. Using mbean subsystems that call
    * registerSubsystem is more flexible.
    *
    * @jmx.managed-attribute
    *    description = "Configuration is an xml element indicating subsystems
    *                   to be registered with the ServerInvoker we wrap. Using
    *                   mbean subsystems that call registerSubsystem is more
    *                   flexible."
    *     access     = "read-write"
    */
   public void setConfiguration(Element xml)
         throws Exception
   {
      this.xml = xml;
   }

   /**
    * Configuration is an xml element indicating subsystems to be registered
    * with the ServerInvoker we wrap. Using mbean subsystems that call
    * registerSubsystem is more flexible.
    *
    * @jmx.managed-attribute
    */
   public Element getConfiguration()
   {
      return xml;
   }

   /**
    * Adds an invocation handler for the named subsystem to the invoker we
    * manage, and sets the mbean server on the invocation handler.
    *
    * @todo why don't we just make all the invocation handlers mbeans,
    *       so they already know what mbean server they use?
    *
    * @jmx.managed-operation
    *    description = "Add a subsystem invocation handler to the ServerInvoker
    *                   we wrap, identified by the subsystem parameter."
    *    impact      = "ACTION"
    *
    * @jmx.managed-parameter
    *    name        = "subsystem"
    *    type        = "java.lang.String"
    *    description = "The subsystem this handler is for."
    *
    * @jmx.managed-parameter
    *    name        = "handler"
    *    type        = "org.jboss.remoting.ServerInvocationHandler"
    *    description = "The ServerInvocationHandler we are registering
    *                   for the subsystem"
    */
   public void addInvocationHandler(String subsystem, ServerInvocationHandler handler)
         throws Exception
   {
      if (invoker == null)
      {
         throw new IllegalStateException("You may only add handlers when the Connector is started");
      }

      handler.setMBeanServer(server);
      invoker.addInvocationHandler(subsystem, handler);
   }

   /**
    * Removes an invocation handler for the supplied subsystem from the invoker
    * we manage, and unsets the MBeanServer on the handler.
    *
    * @jmx.managed-operation
    *    description = "Remove a subsystem invocation handler to the
    *                   ServerInvoker we wrap, identified by the subsystem
    *                   parameter."
    *    impact      = "ACTION"
    *
    * @jmx.managed-parameter
    *    name        = "subsystem"
    *    type        = "java.lang.String"
    *    description = "The subsystem this handler is for."
    */
   public void removeInvocationHandler(String subsystem) throws Exception
   {
      ServerInvocationHandler handler = invoker.removeInvocationHandler(subsystem);

      if (handler != null)
         handler.setMBeanServer(null);
   }

}
