/*
 * JBoss, the OpenSource J2EE webOS
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */
package org.jboss.webservice.metadata;

// $Id: WebserviceDescriptionMetaData.java,v 1.19.4.4 2005/04/06 03:07:58 nihility Exp $

import org.jboss.logging.Logger;
import org.jboss.webservice.ServiceDeployer;
import org.jboss.webservice.WSDLDefinitionFactory;
import org.jboss.webservice.metadata.jaxrpcmapping.JavaWsdlMapping;
import org.jboss.webservice.metadata.jaxrpcmapping.JavaWsdlMappingFactory;

import javax.wsdl.Definition;
import javax.wsdl.Port;
import javax.wsdl.Service;
import javax.wsdl.WSDLException;
import javax.wsdl.extensions.soap.SOAPAddress;
import javax.xml.rpc.JAXRPCException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;

/**
 * XML Binding element for <code>webservices/webservice-description</code>
 *
 * @author Thomas.Diesler@jboss.org
 * @version $Revision: 1.19.4.4 $
 * @since 15-April-2004
 */
public class WebserviceDescriptionMetaData
{
   // provide logging
   private static final Logger log = Logger.getLogger(WebserviceDescriptionMetaData.class);

   // The parent <webservices> element
   private WebservicesMetaData webservices;

   // The required <webservice-description-name> element
   private String webserviceDescriptionName;
   // The required <wsdl-file> element
   private String wsdlFile;
   // The required <jaxrpc-mapping-file> element
   private String jaxrpcMappingFile;
   // The required <port-component> elements
   private ArrayList portComponents = new ArrayList();

   // Where the wsdl file is published to
   private String wsdlPublishLocation;

   public WebserviceDescriptionMetaData(WebservicesMetaData webservices)
   {
      this.webservices = webservices;
   }

   public WebservicesMetaData getWebservices()
   {
      return webservices;
   }

   public void addPortComponent(PortComponentMetaData portComponent)
   {
      portComponents.add(portComponent);
   }

   public PortComponentMetaData[] getPortComponents()
   {
      PortComponentMetaData[] array = new PortComponentMetaData[portComponents.size()];
      portComponents.toArray(array);
      return array;
   }

   /**
    * Lookup a PortComponentMetaData by wsdl-port local part
    *
    * @param name - the wsdl-port local part
    * @return PortComponentMetaData if found, null otherwise
    */
   public PortComponentMetaData getPortComponentByWsdlPort(String name)
   {
      ArrayList pcNames = new ArrayList();
      Iterator it = portComponents.iterator();
      while (it.hasNext())
      {
         PortComponentMetaData pc = (PortComponentMetaData)it.next();
         String wsdlPortName = pc.getWsdlPort().getLocalPart();
         if (wsdlPortName.equals(name))
         {
            return pc;
         }
         pcNames.add(wsdlPortName);
      }

      log.error("Cannot get port component name '" + name + "', we have: " + pcNames);
      return null;
   }

   /**
    * Lookup a PortComponentMetaData by port-component-name
    *
    * @param name - the port-component-name
    * @return PortComponentMetaData if found, null otherwise
    */
   public PortComponentMetaData getPortComponentByName(String name)
   {
      ArrayList pcNames = new ArrayList();
      Iterator it = portComponents.iterator();
      while (it.hasNext())
      {
         PortComponentMetaData pc = (PortComponentMetaData)it.next();
         String portName = pc.getPortComponentName();
         if (portName.equals(name))
         {
            return pc;
         }
         pcNames.add(portName);
      }

      log.error("Cannot get port component name '" + name + "', we have: " + pcNames);
      return null;
   }

   public String getWebserviceDescriptionName()
   {
      return webserviceDescriptionName;
   }

   public void setWebserviceDescriptionName(String webserviceDescriptionName)
   {
      this.webserviceDescriptionName = webserviceDescriptionName;
   }

   public String getWsdlFile()
   {
      return wsdlFile;
   }

   public void setWsdlFile(String wsdlFile)
   {
      this.wsdlFile = wsdlFile;
   }

   public String getWsdlPublishLocation()
   {
      return wsdlPublishLocation;
   }

   public void setWsdlPublishLocation(String wsdlPublishLocation)
   {
      this.wsdlPublishLocation = wsdlPublishLocation;
   }

   public String getJaxrpcMappingFile()
   {
      return jaxrpcMappingFile;
   }

   public JavaWsdlMapping getJavaWsdlMapping()
   {
      JavaWsdlMapping javaWsdlMapping = (JavaWsdlMapping)webservices.jaxrpcMappingFileMap.get(jaxrpcMappingFile);
      if (javaWsdlMapping == null)
      {
         try
         {
            // setup the XML binding Unmarshaller
            URL location = webservices.getResourceLoader().findResource(jaxrpcMappingFile);
            JavaWsdlMappingFactory mappingFactory = JavaWsdlMappingFactory.newInstance();
            javaWsdlMapping = mappingFactory.parse(location);
            webservices.jaxrpcMappingFileMap.put(jaxrpcMappingFile, javaWsdlMapping);
         }
         catch (Exception e)
         {
            throw new JAXRPCException("Cannot unmarshal jaxrpc-mapping-file: " + jaxrpcMappingFile, e);
         }
      }
      return javaWsdlMapping;
   }

   public void setJaxrpcMappingFile(String jaxrpcMappingFile)
   {
      this.jaxrpcMappingFile = jaxrpcMappingFile;
   }

   /**
    * Get the wsdl definition that corresponds to the wsdl-file element.
    */
   public Definition getWsdlDefinition()
   {
      WSDLMapEntry wsdlEntry = (WSDLMapEntry)webservices.wsdlFileMap.get(wsdlFile);
      if (wsdlEntry == null)
      {
         try
         {
            URL wsdlLocation = webservices.getResourceLoader().findResource(wsdlFile);
            if (wsdlLocation == null)
               throw new IllegalArgumentException("Cannot find wsdl in deployment: " + wsdlFile);

            WSDLDefinitionFactory factory = WSDLDefinitionFactory.newInstance();
            Definition wsdlDefinition = factory.parse(wsdlLocation);

            wsdlEntry = new WSDLMapEntry(wsdlDefinition);
            webservices.wsdlFileMap.put(wsdlFile, wsdlEntry);
         }
         catch (WSDLException e)
         {
            throw new IllegalStateException("Cannot obtain WSDL definition, cause: " + e.toString());
         }
      }

      return wsdlEntry.wsdlDefinition;
   }

   public static boolean isValidServiceUrl(String url)
   {
      URI uri;
      try
      {
          uri = new URI(url);
      }
      catch (URISyntaxException e)
      {
         return false;
      }

      String scheme = uri.getScheme();
      if (! "http".equals(scheme) && ! "https".equals(scheme))
         return false;

      if (uri.getHost() == null)
         return false;

      return true;
   }

   /**
    * Replace the port location with the actual one in JBoss
    */
   public void updateServiceAddress(ServiceDeployer.ServiceLocationResolver locationResolver)
   {
      // make sure we have the document
      Definition wsdlDefinition = getWsdlDefinition();

      WSDLMapEntry wsdlEntry = (WSDLMapEntry)webservices.wsdlFileMap.get(wsdlFile);
      if (wsdlEntry.locationReplaced == false)
      {
         replaceAddressLocations(wsdlDefinition, locationResolver);
         wsdlEntry.locationReplaced = true;
      }
   }

   /**
    * Replace the address locations for all ports that that are referenced from this webservices.xml
    */
   private void replaceAddressLocations(Definition wsdlDefinition, ServiceDeployer.ServiceLocationResolver locationResolver)
   {
      // process all webservice-description(s) that use the same wsdl-file
      WebserviceDescriptionMetaData[] wsdArr = webservices.getWebserviceDescriptions();
      for (int i = 0; i < wsdArr.length; i++)
      {
         WebserviceDescriptionMetaData wsdMetaData = wsdArr[i];
         if (wsdlFile.equals(wsdMetaData.wsdlFile))
         {
            PortComponentMetaData[] pcarr = wsdMetaData.getPortComponents();
            for (int pcIndex = 0; pcIndex < pcarr.length; pcIndex++)
            {
               PortComponentMetaData portComponent = pcarr[pcIndex];
               String pcWsdlPortName = portComponent.getWsdlPort().getLocalPart();
               Port wsdlPort = null;

               Iterator itServices = wsdlDefinition.getServices().values().iterator();
               while (wsdlPort == null && itServices.hasNext())
               {
                  Service wsdlService = (Service)itServices.next();

                  Map wsdlPorts = wsdlService.getPorts();
                  Iterator itPorts = wsdlPorts.keySet().iterator();
                  while (wsdlPort == null && itPorts.hasNext())
                  {
                     String wsdlPortName = (String)itPorts.next();
                     if (wsdlPortName.equals(pcWsdlPortName))
                     {
                        wsdlPort = (Port)wsdlPorts.get(wsdlPortName);
                        Iterator itElements = wsdlPort.getExtensibilityElements().iterator();
                        while (itElements.hasNext())
                        {
                           Object obj = itElements.next();
                           if (obj instanceof SOAPAddress)
                           {
                              SOAPAddress address = (SOAPAddress)obj;
                              String wsdlURI = address.getLocationURI();
                              String addressUrl = null;
                              String schema = null;

                              if (wsdlURI.startsWith("http://") || wsdlURI.startsWith("https://"))
                                 schema = wsdlURI.substring(0, wsdlURI.indexOf("://") + 3);

                              String endpointUrl = locationResolver.getServiceLocation(schema, portComponent);

                              if (isValidServiceUrl(wsdlURI) && ! locationResolver.alwaysResolve())
                                 addressUrl = wsdlURI;
                              else
                                 addressUrl = endpointUrl;

                              try
                              {
                                 portComponent.setServiceEndpointURL(new URL(endpointUrl));
                                 log.debug("Replace port location '" + wsdlURI + "' with '" + addressUrl + "'");
                                 address.setLocationURI(addressUrl);
                              }
                              catch (MalformedURLException e)
                              {
                                 log.error("Invalid service URL: " + endpointUrl);
                              }
                           }
                        }
                     }
                  }
               }

               if (wsdlPort == null)
                  throw new IllegalArgumentException("Cannot find port with name '" + pcWsdlPortName + "' in wsdl document");
            }
         }
      }
   }

   /**
    * An entry in the map of wsdl files that are referenced from webservices.xml
    * <p/>
    * This implementation maintains a flag for the wsdl definition that indicates
    * whether the address locations have already been replaced.
    */
   class WSDLMapEntry
   {
      Definition wsdlDefinition;
      boolean locationReplaced;

      public WSDLMapEntry(Definition wsdlDefinition)
      {
         this.wsdlDefinition = wsdlDefinition;
      }
   }
}