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

// $Id: ServiceDeployerEJB.java,v 1.10.2.9 2005/07/08 17:41:10 tdiesler Exp $
package org.jboss.webservice;

// $Id: ServiceDeployerEJB.java,v 1.10.2.9 2005/07/08 17:41:10 tdiesler Exp $

import org.dom4j.Document;
import org.dom4j.DocumentFactory;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import org.jboss.deployment.DeploymentException;
import org.jboss.deployment.DeploymentInfo;
import org.jboss.logging.Logger;
import org.jboss.metadata.ApplicationMetaData;
import org.jboss.metadata.AssemblyDescriptorMetaData;
import org.jboss.metadata.BeanMetaData;
import org.jboss.metadata.EjbPortComponentMetaData;
import org.jboss.system.server.ServerConfig;
import org.jboss.system.server.ServerConfigLocator;
import org.jboss.webservice.metadata.PortComponentMetaData;
import org.jboss.webservice.metadata.WebserviceDescriptionMetaData;
import org.jboss.webservice.metadata.WebservicesMetaData;
import org.jboss.webservice.server.ServiceEndpointServletEJB;

import javax.management.ObjectName;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;

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

   // The key in the deployment info context for url the generated webapp has been deployed to
   public static final String EJB_ENDPOINT_WEBAPP_URL = "ejb-endpoint-webapp-url";

   // service name of the EJB deployer
   private ObjectName ejbDeployer;
   // service name of the EJB deployer
   private ObjectName mainDeployer;

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

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

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

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

   protected void deployWebservices(DeploymentInfo di, WebservicesMetaData webservices)
           throws DeploymentException
   {
      super.deployWebservices(di, webservices);

      Document webDoc = createWebXML(di, webservices);
      Document jbossDoc = createJBossWebXML(di);

      ServerConfig config = ServerConfigLocator.locate();
      File tmpWar = null;
      try
      {
         File tmpdir = new File(config.getServerTempDir().getCanonicalPath() + "/deploy");

         String deploymentName = di.getCanonicalName().replace('/', '-') + "-ws";
         tmpWar = File.createTempFile(deploymentName, ".war", tmpdir);
         tmpWar.delete();
         File webInf = new File(tmpWar, "WEB-INF");
         webInf.mkdirs();

         File webXml = new File(webInf, "web.xml");
         FileWriter fw = new FileWriter(webXml);
         OutputFormat fmt = OutputFormat.createPrettyPrint();
         XMLWriter writer = new XMLWriter(fw, fmt);
         writer.write(webDoc);
         writer.close();
         fw.close();

         File jbossWebXml = new File(webInf, "jboss-web.xml");
         fw = new FileWriter(jbossWebXml);
         writer = new XMLWriter(fw, fmt);
         writer.write(jbossDoc);
         writer.close();
         fw.close();
      }
      catch (IOException e)
      {
         throw new DeploymentException("Failed to create webservice.war", e);
      }

      // Deploy the webservice.war
      try
      {
         URL warURL = tmpWar.toURL();
         Object[] args = {warURL};
         String[] sig = {"java.net.URL"};
         server.invoke(mainDeployer, "deploy", args, sig);
         di.context.put(EJB_ENDPOINT_WEBAPP_URL, warURL);
      }
      catch (Exception e)
      {
         throw new DeploymentException("Failed to deploy webservice.war", e);
      }
   }

   private Document createWebXML(DeploymentInfo di, WebservicesMetaData webservices)
           throws DeploymentException
   {
      ApplicationMetaData applMetaData = (ApplicationMetaData)di.metaData;
      DocumentFactory factory = DocumentFactory.getInstance();

      Document webDoc = factory.createDocument();
      Element webApp = webDoc.addElement("web-app");

      /*
      <servlet>
        <servlet-name>
        <servlet-class>
      </servlet>
      */
      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 pc = portComponents[j];
            String ejbLink = pc.getEjbLink();
            if (ejbLink == null)
               throw new DeploymentException("Cannot find ejb-link in port-component: " + pc.getPortComponentName());

            Element servlet = webApp.addElement("servlet");
            Element servletName = servlet.addElement("servlet-name");
            servletName.addText(ejbLink);
            Element servletClass = servlet.addElement("servlet-class");
            servletClass.addText(ServiceEndpointServletEJB.class.getName());
         }
      }

      /*
      <servlet-mapping>
        <servlet-name>
        <url-pattern>
      </servlet-mapping>
      */
      ArrayList urlPatters = new ArrayList();
      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 pc = portComponents[j];
            String ejbLink = pc.getEjbLink();

            Element servletMapping = webApp.addElement("servlet-mapping");
            Element servletName = servletMapping.addElement("servlet-name");
            servletName.addText(ejbLink);
            Element urlPatternElement = servletMapping.addElement("url-pattern");

            BeanMetaData ejbMetaData = (BeanMetaData)applMetaData.getBeanByEjbName(ejbLink);
            EjbPortComponentMetaData pcMetaData = ejbMetaData.getPortComponent();

            String urlPattern = (pcMetaData != null ? pcMetaData.getURLPattern() : "/*");
            if (urlPatters.contains(urlPattern))
               throw new IllegalArgumentException("Cannot use the same url-pattern with different endpoints, " +
                       "check your <port-component-uri> in jboss.xml");

            urlPatternElement.addText(urlPattern);
            urlPatters.add(urlPattern);
         }
      }

      String authMethod = null;

      // Add web-app/security-constraint for each port component
      for (int i = 0; i < wsDescriptions.length; i++)
      {
         WebserviceDescriptionMetaData wdmd = wsDescriptions[i];
         PortComponentMetaData[] ports = wdmd.getPortComponents();
         for (int j = 0; j < ports.length; j++)
         {
            PortComponentMetaData port = ports[j];
            String portName = port.getPortComponentName();
            String ejbLink = port.getEjbLink();
            BeanMetaData smd = (BeanMetaData)applMetaData.getBeanByEjbName(ejbLink);
            EjbPortComponentMetaData portMetaData = smd.getPortComponent();
            if (portMetaData != null)
            {
               /*
               <security-constraint>
                 <web-resource-collection>
                   <web-resource-name>TestUnAuthPort</web-resource-name>
                   <url-pattern>/HSTestRoot/TestUnAuth/*</url-pattern>
                 </web-resource-collection>
                 <auth-constraint>
                   <role-name>*</role-name>
                 </auth-constraint>
                 <user-data-constraint>
                   <transport-guarantee>NONE</transport-guarantee>
                 </user-data-constraint>
               </security-constraint>
               */
               Element securityConstraint = webApp.addElement("security-constraint");
               Element wrc = securityConstraint.addElement("web-resource-collection");
               Element wrName = wrc.addElement("web-resource-name");
               wrName.addText(portName);
               Element pattern = wrc.addElement("url-pattern");
               String uri = portMetaData.getURLPattern();
               pattern.addText(uri);
               Element method = wrc.addElement("http-method");
               method.addText("GET");
               method = wrc.addElement("http-method");
               method.addText("POST");
               
               // Optional auth-constraint
               if (portMetaData.getAuthMethod() != null)
               {
                  // Only the first auth-method gives the war login-config/auth-method
                  if (authMethod == null)
                     authMethod = portMetaData.getAuthMethod();
                  Element authConstraint = securityConstraint.addElement("auth-constraint");
                  Element roleName = authConstraint.addElement("role-name");
                  roleName.addText("*");
               }
               // Optional user-data-constraint
               if (portMetaData.getTransportGuarantee() != null)
               {
                  Element userData = securityConstraint.addElement("user-data-constraint");
                  Element transport = userData.addElement("transport-guarantee");
                  transport.addText(portMetaData.getTransportGuarantee());
               }
            }
         }
      }

      // Optional login-config/auth-method
      if (authMethod != null)
      {
         Element loginConfig = webApp.addElement("login-config");
         Element method = loginConfig.addElement("auth-method");
         method.addText(authMethod);
         Element realm = loginConfig.addElement("realm-name");
         realm.addText("EJBServiceEndpointServlet Realm");
         
         // Fix: http://jira.jboss.org/jira/browse/JBWS-309
         AssemblyDescriptorMetaData assemblyDescriptor = applMetaData.getAssemblyDescriptor();
         if (assemblyDescriptor != null)
         {
            Map securityRoles = assemblyDescriptor.getSecurityRoles();
            if (securityRoles != null)
            {
               Iterator it = securityRoles.keySet().iterator();
               while (it.hasNext())
               {
                  Element securityRole = webApp.addElement("security-role");
                  Element roleName = securityRole.addElement("role-name");
                  roleName.addText((String)it.next());
               }
            }
         }
      }

      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];
            String ejbLink = pcMetaData.getEjbLink();
            PortComponentInfo pcInfo = new PortComponentInfo(di, pcMetaData);
            modifyServletConfig(webDoc, ejbLink, pcInfo);
         }
      }

      return webDoc;
   }

   private Document createJBossWebXML(DeploymentInfo di)
   {
      ApplicationMetaData amd = (ApplicationMetaData)di.metaData;
      DocumentFactory factory = DocumentFactory.getInstance();

      /* Create a jboss-web
      <jboss-web>
         <security-domain>java:/jaas/cts</security-domain>
         <context-root>/ws4ee/ejbN/</context-root>
      </jboss-web>
      */
      Document jbossDoc = factory.createDocument();
      Element jbossWeb = jbossDoc.addElement("jboss-web");

      String securityDomain = amd.getSecurityDomain();
      if (securityDomain != null)
      {
         Element secDomain = jbossWeb.addElement("security-domain");
         secDomain.addText(securityDomain);
      }
      String contextRoot = amd.getWebServiceContextRoot();
      Element root = jbossWeb.addElement("context-root");
      root.addText(contextRoot);
      return jbossDoc;
   }

   protected void undeployWebservices(DeploymentInfo di, WebservicesMetaData webservices)
   {
      // Undeploy the webservice.war
      try
      {
         URL warURL = (URL)di.context.get(EJB_ENDPOINT_WEBAPP_URL);
         if (warURL != null)
         {
            Object[] args = {warURL};
            String[] sig = {"java.net.URL"};
            server.invoke(mainDeployer, "undeploy", args, sig);
         }

      }
      catch (Exception e)
      {
         log.error("Failed to undeploy webservice.war", e);
      }

      super.undeployWebservices(di, webservices);
   }

   /**
    * Get the resource name of the webservices.xml descriptor.
    */
   protected URL getWebservicesDescriptor(DeploymentInfo di)
   {
      if (di.localCl != null) 
         return di.localCl.findResource("META-INF/webservices.xml");
      else
         return null;
   }

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