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

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

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

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.DOMReader;
import org.dom4j.io.SAXReader;
import org.jboss.deployment.DeploymentException;
import org.jboss.deployment.DeploymentInfo;
import org.jboss.logging.Logger;
import org.jboss.system.server.ServerConfig;
import org.jboss.webservice.metadata.WebserviceDescriptionMetaData;
import org.jboss.webservice.metadata.WebservicesMetaData;
import org.jboss.util.xml.JBossEntityResolver;

import javax.wsdl.Definition;
import javax.wsdl.Import;
import javax.wsdl.factory.WSDLFactory;
import javax.wsdl.xml.WSDLWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Iterator;
import java.util.List;

/**
 * A helper class that publishes the wsdl files and thei imports to the server/data/wsdl directory.
 *
 * @author Thomas.Diesler@jboss.org
 * @since 02-June-2004
 */
public class WSDLFilePublisher
{
   // provide logging
   private static final Logger log = Logger.getLogger(WSDLFilePublisher.class);

   // The deployment info for the web service archive
   private DeploymentInfo di;
   // The expected wsdl location in the deployment
   private String expLocation;

   public WSDLFilePublisher(DeploymentInfo di)
   {
      this.di = di;

      String archiveName = di.shortName;
      if (archiveName.endsWith(".jar"))
         expLocation = "META-INF/wsdl/";
      if (archiveName.endsWith(".war"))
         expLocation = "WEB-INF/wsdl/";

      if (expLocation == null)
         throw new IllegalStateException("Can only publish wsdl from WAR or JAR deployment");
   }

   /**
    * Publish the deployed wsdl file to the data directory
    */
   public void publishWsdlFile(WebservicesMetaData webservices) throws DeploymentException
   {
      String deploymentName = di.getCanonicalName();

      // For each webservice-description
      WebserviceDescriptionMetaData[] wsdArray = webservices.getWebserviceDescriptions();
      for (int i = 0; i < wsdArray.length; i++)
      {
         WebserviceDescriptionMetaData wsd = wsdArray[i];
         File targetFile = getPublishLocation(deploymentName, wsd);
         targetFile.getParentFile().mkdirs();

         // Get the wsdl definition and write it to the wsdl publish location
         try
         {
            Definition wsdlDefinition = wsd.getWsdlDefinition();
            WSDLFactory wsdlFactory = WSDLFactory.newInstance();
            WSDLWriter wsdlWriter = wsdlFactory.newWSDLWriter();
            FileWriter fw = new FileWriter(targetFile);
            wsdlWriter.writeWSDL(wsdlDefinition, fw);
            fw.close();

            wsd.setWsdlPublishLocation(targetFile.getCanonicalPath());
            log.info("WSDL published to: " + targetFile.toURL());

            // Process the wsdl imports
            publishWsdlImports(targetFile.toURL(), wsdlDefinition);

            // Get the dom4j representation and try to publish XMLSchema imports
            Document document = new DOMReader().read(wsdlWriter.getDocument(wsdlDefinition));
            publishSchemaImports(targetFile.toURL(), document.getRootElement());
         }
         catch (Exception e)
         {
            throw new DeploymentException("Cannot publish wsdl to: " + targetFile, e);
         }
      }
   }

   /**
    * Publish the wsdl imports for a given wsdl definition
    */
   private void publishWsdlImports(URL parentURL, Definition parentDefinition) throws Exception
   {
      String baseURI = parentURL.toExternalForm();

      Iterator it = parentDefinition.getImports().values().iterator();
      while (it.hasNext())
      {
         List list = (List)it.next();
         for (int j = 0; j < list.size(); j++)
         {
            Import wsdlImport = (Import)list.get(j);
            String locationURI = wsdlImport.getLocationURI();
            Definition subdef = wsdlImport.getDefinition();

            // its an external import, don't publish locally
            if (locationURI.startsWith("http://") == false)
            {
               URL wsdlURL = new URL(baseURI.substring(0, baseURI.lastIndexOf("/") + 1) + locationURI);
               File targetFile = new File(wsdlURL.getPath());
               targetFile.getParentFile().mkdirs();

               WSDLFactory wsdlFactory = WSDLFactory.newInstance();
               WSDLWriter wsdlWriter = wsdlFactory.newWSDLWriter();
               FileWriter fw = new FileWriter(targetFile);
               wsdlWriter.writeWSDL(subdef, fw);
               fw.close();

               log.debug("WSDL import published to: " + wsdlURL);

               // recursivly publish imports
               publishWsdlImports(wsdlURL, subdef);

               // Get the dom4j representation and try to publish XMLSchema imports
               Document document = new DOMReader().read(wsdlWriter.getDocument(subdef));
               publishSchemaImports(wsdlURL, document.getRootElement());
            }
         }
      }
   }

   /**
    * Publish the schema imports for a given wsdl definition
    * <p/>
    * wsdl4j does not have a representation for <xsd:schema> elements
    */
   private void publishSchemaImports(URL parentURL, Element element) throws Exception
   {
      String baseURI = parentURL.toExternalForm();

      Iterator it = element.elementIterator();
      while (it.hasNext())
      {
         Element childElement = (Element)it.next();
         if ("import".equals(childElement.getQName().getName()) || "include".equals(childElement.getQName().getName()))
         {
            if (childElement.attribute("schemaLocation") != null)
            {
               String locationURI = childElement.attribute("schemaLocation").getText();
               if (locationURI.startsWith("http://") == false)
               {
                  URL xsdURL = new URL(baseURI.substring(0, baseURI.lastIndexOf("/") + 1) + locationURI);
                  File targetFile = new File(xsdURL.getPath());
                  targetFile.getParentFile().mkdirs();

                  String deploymentName = di.getCanonicalName();

                  // get the resource path
                  int index = baseURI.indexOf(deploymentName);
                  String resourcePath = baseURI.substring(index + deploymentName.length());
                  resourcePath = resourcePath.substring(0, resourcePath.lastIndexOf("/"));
                  if (resourcePath.length() > 0)
                     resourcePath = resourcePath + "/";

                  resourcePath = expLocation + resourcePath + locationURI;
                  InputStream is = di.localCl.getResourceAsStream(resourcePath);
                  if (is == null)
                     throw new IllegalArgumentException("Cannot find schema import in deployment: " + resourcePath);

                  FileOutputStream fos = new FileOutputStream(targetFile);
                  copyStream(fos, is);
                  fos.close();
                  is.close();

                  log.debug("XMLSchema import published to: " + xsdURL);

                  // recursivly publish imports
                  SAXReader saxReader = new SAXReader();
                  saxReader.setEntityResolver(new JBossEntityResolver());

                  Document subdoc = saxReader.read(xsdURL);
                  publishSchemaImports(xsdURL, subdoc.getRootElement());
               }
            }
         }
         else
         {
            publishSchemaImports(parentURL, childElement);
         }
      }
   }

   /**
    * Delete the published wsdl
    */
   public void unpublishWsdlFile()
   {
      String deploymentDir = (di.parent != null ? di.parent.shortName : di.shortName);
      String dataDir = System.getProperty(ServerConfig.SERVER_DATA_DIR);
      File serviceDir = new File(dataDir + "/wsdl/" + deploymentDir);
      deleteWsdlPublishDirectory(serviceDir);
   }

   /**
    * Delete the published wsdl document, traversing down the dir structure
    */
   private void deleteWsdlPublishDirectory(File dir)
   {
      String[] files = dir.list();
      for (int i = 0; files != null && i < files.length; i++)
      {
         String fileName = files[i];
         File file = new File(dir + "/" + fileName);
         if (file.isDirectory())
         {
            deleteWsdlPublishDirectory(file);
         }
         else
         {
            if (file.delete() == false)
               log.warn("Cannot delete published wsdl document: " + file);
         }
      }

      // delete the directory as well
      dir.delete();
   }

   /**
    * Get the file publish location
    */
   private File getPublishLocation(String archiveName, WebserviceDescriptionMetaData wsd)
   {
      // Only file URLs are supported in <wsdl-publish-location>
      boolean predefinedLocation = wsd.getWsdlPublishLocation() != null && wsd.getWsdlPublishLocation().startsWith("file:");

      File publishLocation = null;
      if (predefinedLocation == false)
      {
         String dataDir = System.getProperty(ServerConfig.SERVER_DATA_DIR);
         publishLocation = new File(dataDir + "/wsdl/" + archiveName);
      }
      else
      {
         try
         {
            publishLocation = new File(new URL(wsd.getWsdlPublishLocation()).getPath());
         }
         catch (MalformedURLException e)
         {
            throw new IllegalArgumentException("Invalid publish location: " + e.getMessage());
         }
      }

      // make sure we don't have a leadig '/'
      String wsdlFile = wsd.getWsdlFile();
      if (wsdlFile.startsWith("/"))
         wsdlFile = wsdlFile.substring(1);

      if (wsdlFile.startsWith(expLocation) == false)
         throw new IllegalArgumentException("The wsdl file should be located in: " + expLocation);

      wsdlFile = wsdlFile.substring(expLocation.length());
      File wsdlLocation = new File(publishLocation + "/" + wsdlFile);
      return wsdlLocation;
   }

   /**
    * Copies the input stream to the output stream
    */
   private void copyStream(OutputStream outputStream, InputStream inputStream) throws IOException
   {
      byte[] bytes = new byte[4096];
      int read = inputStream.read(bytes, 0, 4096);
      while (read > 0)
      {
         outputStream.write(bytes, 0, read);
         read = inputStream.read(bytes, 0, 4096);
      }
   }
}