/*
 * JBoss, the OpenSource J2EE webOS
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */

// $Id: ServiceDeployerJSE.java,v 1.12.2.6 2005/03/22 13:21:56 tdiesler Exp $
package org.jboss.webservice;

// $Id: ServiceDeployerJSE.java,v 1.12.2.6 2005/03/22 13:21:56 tdiesler Exp $

import org.dom4j.Document;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
import org.jboss.deployment.DeploymentException;
import org.jboss.deployment.DeploymentInfo;
import org.jboss.logging.Logger;
import org.jboss.webservice.metadata.PortComponentMetaData;
import org.jboss.webservice.metadata.WebserviceDescriptionMetaData;
import org.jboss.webservice.metadata.WebservicesMetaData;
import org.jboss.webservice.server.ServiceEndpointServletJSE;
import org.jboss.util.xml.JBossEntityResolver;

import javax.management.ObjectName;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;

/**
 * A deployer service that manages WS4EE compliant Web-Services for WAR
 *
 * @author Thomas.Diesler@jboss.org
 * @jmx.mbean name="jboss.ws4ee:service=ServiceDeployerJSE"
 * description="Webservice WAR deployer"
 * extends="org.jboss.webservice.ServiceDeployerMBean"
 * @since 15-April-2004
 */
public class ServiceDeployerJSE extends ServiceDeployer implements ServiceDeployerJSEMBean
{
   // provide logging
   private final Logger log = Logger.getLogger(ServiceDeployer.class);

   // service name of the EJB deployer
   private ObjectName warDeployer;

   /**
    * Set the service name of the WAR deployer
    *
    * @jmx.managed-attribute
    */
   public void setWARDeployer(ObjectName deployerName)
   {
      this.warDeployer = deployerName;
   }

   /**
    * Register this service as NotificationListener to the WARDeployer
    */
   protected void startService() throws Exception
   {
      super.startService();
      registerNotificationListener(warDeployer);
   }

   /**
    * Unregister this service as NotificationListener from the WARDeployer
    */
   protected void stopService() throws Exception
   {
      unregisterNotificationListener(warDeployer);
      super.stopService();
   }

   /**
    * Get the resource name of the webservices.xml descriptor.
    */
   protected URL getWebservicesDescriptor(DeploymentInfo di)
   {
      return di.localCl.findResource("WEB-INF/webservices.xml");
   }

   /** Is called when the parent deployer sends the CREATE_NOTIFICATION.
    *
    * This implemantation modifies the servlet entries in web.xml
    */
   protected void createWebservice(DeploymentInfo di) throws DeploymentException
   {
      // parse webservices.xml and put it in the local registry.
      super.createWebservice(di);

      WebservicesMetaData webservices = (WebservicesMetaData)webservicesMap.get(di.url);
      modifyWebXML(di, webservices);
   }

   /** Modify the deployed web.xml
    */
   private void modifyWebXML(DeploymentInfo di, WebservicesMetaData webservices)
           throws DeploymentException
   {
      if (webservices == null)
         throw new DeploymentException("webservices.xml not registerd");

      File warFile = new File(di.localUrl.getFile());
      if (warFile.isDirectory() == false)
         throw new DeploymentException("Expected a war directory: " + di.localUrl);

      File webXML = new File(di.localUrl.getFile() + "/WEB-INF/web.xml");
      if (webXML.isFile() == false)
         throw new DeploymentException("Cannot find web.xml: " + webXML);

      FileOutputStream fos = null;
      try
      {
         SAXReader saxReader = new SAXReader();
         saxReader.setEntityResolver(new JBossEntityResolver());

         Document doc = saxReader.read(webXML);

         if (modifyWebXMLDocument(doc, di, webservices))
         {
            // After redeployment there might be a stale copy of the original web.xml.org, we delete it
            File orgWebXML = new File(webXML.getCanonicalPath() + ".org");
            orgWebXML.delete();

            // Rename the web.xml
            if (webXML.renameTo(orgWebXML) == false)
               throw new DeploymentException("Cannot rename web.xml: " + orgWebXML);

            OutputFormat format = OutputFormat.createPrettyPrint();
            fos = new FileOutputStream(webXML);
            XMLWriter writer = new XMLWriter(fos, format);
            writer.write(doc);
         }
      }
      catch (DeploymentException e)
      {
         throw e;
      }
      catch (Exception e)
      {
         throw new DeploymentException(e);
      }
      finally
      {
         if (fos != null)
         {
            try
            {
               fos.close();
            }
            catch (IOException e)
            {
               log.warn("Unexpected IOException on file close", e);
            }
         }
      }
   }

   /** Modify the web.xml document.
    *
    * The web.xml containes the service implementation class in the <servlet-class> element, this should be
    * replaced by a propper servlet.
    */
   private boolean modifyWebXMLDocument(Document doc, DeploymentInfo di, WebservicesMetaData webservices) throws DeploymentException
   {
      boolean modified = false;

      WebserviceDescriptionMetaData[] wsDescriptions = webservices.getWebserviceDescriptions();
      for (int i = 0; i < wsDescriptions.length; i++)
      {
         WebserviceDescriptionMetaData wsDescription = wsDescriptions[i];
         PortComponentMetaData[] portComponents = wsDescription.getPortComponents();
         for (int j = 0; j < portComponents.length; j++)
         {
            PortComponentMetaData pcMetaData = portComponents[j];
            PortComponentInfo pcInfo = new PortComponentInfo(di, pcMetaData);

            // get the servlet link
            String servletLink = pcMetaData.getServletLink();
            if (servletLink == null)
               throw new DeploymentException("Cannot find servlet-link in port-component: " + pcMetaData.getPortComponentName());

            // modify the servlet-class
            if (modifyServletConfig(doc, servletLink, pcInfo))
               modified = true;
         }
      }

      return modified;
   }

   /** Override to return the name of the service endpoint servlet */
   protected String getServiceEndpointServletName()
   {
      return ServiceEndpointServletJSE.class.getName();
   }
}