/*
 * 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.net.axis.server;

import org.jboss.axis.AxisProperties;
import org.jboss.axis.EngineConfiguration;
import org.jboss.axis.EngineConfigurationFactory;
import org.jboss.axis.configuration.EngineConfigurationFactoryFinder;
import org.jboss.axis.deployment.wsdd.WSDDUndeployment;
import org.jboss.axis.server.AxisServer;
import org.jboss.deployment.DeploymentException;
import org.jboss.deployment.DeploymentInfo;
import org.jboss.deployment.SubDeployerSupport;
import org.jboss.metadata.MetaData;
import org.jboss.net.axis.AttacheableService;
import org.jboss.net.axis.Deployment;
import org.jboss.net.axis.XMLResourceProvider;
import org.jboss.util.xml.JBossEntityResolver;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.management.MBeanRegistration;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.rpc.server.ServiceLifecycle;
import java.net.URL;

/**
 * <p>
 * A deployer service that installs Axis and manages Web-Services 
 * within the Jboss JMX environment. It is the base for all plain axis
 * deployments, web service archive support and also (via an additional
 * layer) WS4EE functionality.
 * </p>
 * @since 27. September 2001
 * @author <a href="mailto:Christoph.Jung@infor.de">Christoph G. Jung</a>
 * @version $Revision: 1.25.2.13 $
 * @jmx.mbean
 * 	name="jboss.net:service=Axis" 
 * 	description="web service deployer"
 * 	extends="org.jboss.deployment.SubDeployerMBean"
 */

public class AxisService
        extends SubDeployerSupport
        implements AxisServiceMBean, MBeanRegistration
{

   // 
   // Attributes
   //

   /** the engine belonging to this service */
   protected AxisServer axisServer;

   /** the client configuration belonging to this service*/
   protected XMLResourceProvider clientConfiguration;

   /** the server configuration belonging to this service*/
   protected XMLResourceProvider serverConfiguration;

   /** the initial context into which we bind external web-service proxies */
   protected InitialContext initialContext;

   /** document builder */
   protected DocumentBuilder documentBuilder;

   /** whether we are validating */
   protected boolean validateDTDs;

   //
   // Constructors
   //

   /** default */
   public AxisService()
   {
   }

   //
   // Some helper methods
   //

   /** 
    * this tiny helper copies all children of the given element that
    * are elements and match the given name to the other element
    */
   protected void copyChildren(Document sourceDoc,
                               Element source,
                               String match,
                               Element target)
   {
      NodeList children = source.getChildNodes();
      for (int count = 0; count < children.getLength(); count++)
      {
         Node actNode = children.item(count);
         if (actNode instanceof Element)
         {
            if (((Element)actNode).getLocalName().equals(match))
            {
               target.appendChild(sourceDoc.importNode(actNode, true));
            }
         }
      }
   }

   /** starts a single axis deployment */
   protected void startAxisDeployment(String deploymentName, Element root, ClassLoader deploymentLoader)
           throws DeploymentException
   {

      try
      {

         // the deployment command document
         Document deployDoc = documentBuilder.newDocument();
         // the client deployment command document
         Document deployClientDoc = documentBuilder.newDocument();
         // create command
         Element deploy =
                 deployDoc.createElementNS(root.getNamespaceURI(), "deployment");
         // create command
         Element deployClient =
                 deployClientDoc.createElementNS(root.getNamespaceURI(),
                         "deployment");

         NamedNodeMap attributes = root.getAttributes();
         for (int count = 0; count < attributes.getLength(); count++)
         {
            Attr attribute = (Attr)attributes.item(count);
            deploy.setAttributeNodeNS((Attr)deployDoc.importNode(attribute, true));
            deployClient.setAttributeNodeNS((Attr)deployClientDoc.importNode(attribute, true));
         }

         // and insert the nodes from the original document
         // and sort out the ejb-ref extensions
         NodeList children = root.getChildNodes();
         for (int count = 0; count < children.getLength(); count++)
         {
            Node actNode = children.item(count);
            if (actNode instanceof Element)
            {
               Element actElement = (Element)actNode;

               if (actElement.getTagName().equals("ejb-ref"))
               {

                  String refName =
                          MetaData.getElementContent(MetaData.getUniqueChild((Element)actNode,
                                  "ejb-ref-name"));
                  String linkName =
                          MetaData.getElementContent(MetaData.getUniqueChild((Element)actNode, "ejb-link"));

                  log.warn("Web Service Deployment "
                          + deploymentName
                          + " makes use of the deprecated ejb-ref feature. "
                          + "Please adjust any ejb-providing service tag inside your web-service.xml pointing to "
                          + refName
                          + " to use the absolute "
                          + linkName
                          + " instead.");

               }
               else if (actElement.getTagName().equals("ext-service"))
               {
                  deployExternalWebService(actElement);
               }
               else
               {
                  if (!actElement.getTagName().equals("service"))
                  {
                     deployClient.appendChild(deployClientDoc.importNode(actNode, true));
                  }
                  deploy.appendChild(deployDoc.importNode(actNode, true));
               }
            }
            else
            {
               deployClient.appendChild(deployClientDoc.importNode(actNode, true));
               deploy.appendChild(deployDoc.importNode(actNode, true));
            }
         }

         // insert command into document
         deployDoc.appendChild(deploy);
         deployClientDoc.appendChild(deployClient);

         Deployment.makeSafeDeployment(deploy, deploymentLoader).deployToRegistry(((XMLResourceProvider)axisServer.getConfig()).getDeployment());
         Deployment.makeSafeDeployment(deployClient, deploymentLoader).deployToRegistry(clientConfiguration.getDeployment());
         axisServer.refreshGlobalOptions();
         axisServer.saveConfiguration();
      }
      catch (Exception e)
      {
         throw new DeploymentException(Constants.COULD_NOT_DEPLOY_DESCRIPTOR,
                 e);
      }
   }

   /** stops a single axis deployment */
   protected void stopAxisDeployment(Element root) throws DeploymentException
   {
      try
      {
         // from which we extract an undeployment counterpart
         Document undeployDoc = documentBuilder.newDocument();
         Element undeploy =
                 undeployDoc.createElementNS(root.getNamespaceURI(), "undeployment");

         // copy over administrational attributes
         NamedNodeMap attributes = root.getAttributes();
         for (int count = 0; count < attributes.getLength(); count++)
         {
            Attr attribute = (Attr)attributes.item(count);
            undeploy.setAttributeNodeNS((Attr)undeployDoc.importNode(attribute, true));
         }

         // external services are just a matter of us
         NodeList children = root.getElementsByTagName("ext-service");
         for (int count = 0; count < children.getLength(); count++)
         {
            Element actNode = (Element)children.item(count);
            undeployExternalWebService(actNode);
         }

         // all service and handler entries are copied in the
         // undeployment document
         copyChildren(undeployDoc, root, "service", undeploy);

         // axis should be able to do that on its own
         children = root.getElementsByTagName("service");
         for (int count = 0; count < children.getLength(); count++)
         {
            Element actNode = (Element)children.item(count);
            String serviceName = actNode.getAttribute("name");
            ServiceLifecycle slc = (ServiceLifecycle)axisServer.
                    getApplicationSession().get(serviceName);
            if (slc != null)
            {
               slc.destroy();
               axisServer.getApplicationSession().remove(serviceName);
            }
         }

         copyChildren(undeployDoc, root, "handler", undeploy);
         copyChildren(undeployDoc, root, "typemapping", undeploy);
         copyChildren(undeployDoc, root, "beanmapping", undeploy);

         // put command into document
         undeployDoc.appendChild(undeploy);

         // and call the administrator
         new WSDDUndeployment(undeploy).undeployFromRegistry(((XMLResourceProvider)axisServer.getConfig()).getDeployment());
         // and call the administrator
         new WSDDUndeployment(undeploy).undeployFromRegistry(clientConfiguration.getDeployment());
         axisServer.refreshGlobalOptions();
         axisServer.saveConfiguration();
      }
      catch (Exception e)
      {
         throw new DeploymentException(Constants.COULD_NOT_UNDEPLOY, e);
      }
   }

   /**
    * deploy an external web service reference
    */

   protected synchronized void deployExternalWebService(Element deployElement)
           throws DeploymentException
   {

      try
      {
         if (initialContext == null)
         {
            initialContext = new InitialContext();
         }

         NamedNodeMap attributes = deployElement.getAttributes();

         String jndiName = attributes.getNamedItem("jndiName").getNodeValue();
         String serviceClassName =
                 attributes.getNamedItem("serviceImplClass").getNodeValue();
         Object factory =
                 new AttacheableService(serviceClassName,
                         serviceName.getCanonicalName());
         initialContext.bind(jndiName, factory);
      }
      catch (NamingException e)
      {
         throw new DeploymentException("Could not deploy item " + deployElement,
                 e);
      }
   }

   /** undeploys an external web service reference */
   protected synchronized void undeployExternalWebService(Element deployElement)
   {
      try
      {
         if (initialContext == null)
         {
            initialContext = new InitialContext();
         }

         NamedNodeMap attributes = deployElement.getAttributes();

         String jndiName = attributes.getNamedItem("jndiName").getNodeValue();

         if (jndiName != null)
         {
            initialContext.unbind(jndiName);
         }
      }
      catch (NamingException e)
      {
         // what?
         log.warn("Could not undeploy " + deployElement,
                 e);
      }
   }

   //----------------------------------------------------------------------------
   // 'service' interface
   //----------------------------------------------------------------------------

   /**
    * start service means
    *  - initialise axis engine
    *  - register Axis servlet in WebContainer
    *  - contact the maindeployer
    */
   protected void startService() throws Exception
   {

      DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
      dbf.setNamespaceAware(true);
      // this will (along JAXP in conjunction with 
      // validation+namespace-awareness) enable xml schema checking. 
      // Will currently fail because some J2EE1.4/W3C schemas 
      // are still lacking.
      //dbf.setAttribute
      //   ("http://java.sun.com/xml/jaxp/properties/schemaLanguage","http://www.w3.org/2001/XMLSchema");
      dbf.setValidating(validateDTDs);

      documentBuilder = dbf.newDocumentBuilder();
      documentBuilder.setEntityResolver(new JBossEntityResolver());

      // find the global config file in classpath
      URL resource =
              getClass().getClassLoader().getResource(Constants.AXIS_SERVER_CONFIGURATION_FILE);

      if (resource == null)
      {
         log.warn("Could not find: " + Constants.AXIS_SERVER_CONFIGURATION_FILE);
         throw new Exception(Constants.COULD_NOT_FIND_AXIS_CONFIGURATION_0);
      }

      serverConfiguration = new XMLResourceProvider(resource, getClass().getClassLoader());

      axisServer = new AxisServer(serverConfiguration);

      // we annotate the server configuration with our serviceName
      serverConfiguration.getGlobalOptions().put(org.jboss.net.axis.Constants.CONFIGURATION_CONTEXT,
              serviceName.toString());

      // find the client config file in classpath
      resource =
              getClass().getClassLoader().getResource(Constants.AXIS_CLIENT_CONFIGURATION_FILE);

      if (resource == null)
      {
         log.warn("Failed to find: " + Constants.AXIS_SERVER_CONFIGURATION_FILE);
         throw new Exception(Constants.COULD_NOT_FIND_AXIS_CONFIGURATION_0);
      }

      clientConfiguration = new XMLResourceProvider(resource, getClass().getClassLoader());
      clientConfiguration.buildDeployment();
      clientConfiguration.getGlobalOptions().put(org.jboss.net.axis.Constants.CONFIGURATION_CONTEXT,
              serviceName.toString());

      // make sure that Axis/Discovery wont register any application classloaders for system lookup
      AxisProperties.getNameDiscoverer();
      // register our client configuration there 
      Class initializeThisStaticStuff =
              EngineConfigurationFactoryFinder.class;
      System.setProperty(EngineConfigurationFactory.SYSTEM_PROPERTY_NAME,
              JMXEngineConfigurationFactory.class.getName());
      super.startService();
   }

   /** what to do to stop axis temporarily --> undeploy the servlet */
   protected void stopService() throws Exception
   {
      super.stopService();
      axisServer.stop();
      documentBuilder = null;
   }

   //----------------------------------------------------------------------------
   // Deployer interface
   //----------------------------------------------------------------------------

   /* (non-javadoc)
    * @see org.jboss.deployment.SubDeployer#accepts(DeploymentInfo)
    */

   public boolean accepts(DeploymentInfo sdi)
   {
      if (sdi.shortName.endsWith("-axis.xml")
              || sdi.localCl.getResource(Constants.WEB_SERVICE_DESCRIPTOR) != null)
      {
         return true;
      }
      return false;
   }

   /* (non-javadoc)
   * @see org.jboss.deployment.SubDeployerSupport#init(DeploymentInfo)
   */
   public void init(DeploymentInfo sdi) throws DeploymentException
   {
      super.init(sdi);

      try
      {
         if (sdi.document == null)
         {
            if (sdi.documentUrl == null)
            {
               if (sdi.isXML)
               {
                  sdi.documentUrl = sdi.localUrl;
               }
               else
               {
                  sdi.documentUrl =
                          sdi.localCl.getResource(Constants.WEB_SERVICE_DESCRIPTOR);
               }
            }
            sdi.document = documentBuilder.parse(sdi.documentUrl.openStream());
         }

         // Resolve what to watch
         if (sdi.isDirectory)
         {
            sdi.watch = new URL(sdi.url, Constants.WEB_SERVICE_DESCRIPTOR);
         }
         else
         {
            // We watch the top only, no directory support
            sdi.watch = sdi.url;
         }
      }
      catch (Exception e)
      {
         throw new DeploymentException(e);
      }
   }

   /* (non-javadoc)
   * @see org.jboss.deployment.SubDeployerSupport#create(DeploymentInfo)
   */
   public void create(DeploymentInfo sdi) throws DeploymentException
   {
      log.debug("create, sdi=" + sdi);
      super.create(sdi);
   }

   /* (non-javadoc)
   * @see org.jboss.deployment.SubDeployerSupport#start(DeploymentInfo)
   */
   public void start(DeploymentInfo sdi) throws DeploymentException
   {
      log.debug("start, sdi=" + sdi);
      super.start(sdi);

      start(sdi.toString(), sdi.document, sdi.ucl);
   }

   /**
    * start method for usage with immediately known documents
    * @jmx.managed-operation
    */

   public void start(String deploymentName,
                     Document doc,
                     ClassLoader serviceLoader)
           throws DeploymentException
   {

      // the original command
      Element root = doc.getDocumentElement();

      if (root.getNodeName().equals("deployments"))
      {
         NodeList children = root.getChildNodes();

         for (int count = 0; count < children.getLength(); count++)
         {
            startAxisDeployment(deploymentName,
                    (Element)children.item(count), serviceLoader);
         }
      }
      else
      {
         startAxisDeployment(deploymentName, root, serviceLoader);
      }
   }

   /* (non-javadoc)
   * @see org.jboss.deployment.SubDeployerSupport#start(DeploymentInfo)
   */
   public void stop(DeploymentInfo sdi) throws DeploymentException
   {
      log.debug("stop, sdi=" + sdi);
      super.stop(sdi);
      stop(sdi.document);
   }

   /** stop a given deployment by document 
    * @jmx.managed-operation
    */
   public void stop(Document doc) throws DeploymentException
   {
      // this was the deployment command
      Element root = (Element)doc.getDocumentElement();
      if (root.getNodeName().equals("deployments"))
      {
         NodeList children = root.getChildNodes();
         for (int count = 0; count < children.getLength(); count++)
         {
            stopAxisDeployment((Element)children.item(count));
         }
      }
      else
      {
         stopAxisDeployment(root);
      }
   }

   /* (non-javadoc)
   * @see org.jboss.deployment.SubDeployerSupport#start(DeploymentInfo)
   */
   public void destroy(DeploymentInfo sdi) throws DeploymentException
   {
      log.debug("destroy, sdi=" + sdi);
      super.destroy(sdi);
   }

   /** 
    * return the associated client configuration 
    * @jmx.managed-operation
    */
   public EngineConfiguration getClientEngineConfiguration()
   {
      return clientConfiguration;
   }

   /** 
    * return the associated server configuration 
    * @jmx.managed-operation
    */
   public EngineConfiguration getServerEngineConfiguration()
   {
      return serverConfiguration;
   }

   /** 
    * return the associated server 
    * @jmx.managed-operation
    */
   public AxisServer getAxisServer()
   {
      return axisServer;
   }

}
