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

// $Id: WSDLUtilities.java,v 1.6.4.2 2005/03/23 11:35:02 tdiesler Exp $
package org.jboss.webservice.util;

// $Id: WSDLUtilities.java,v 1.6.4.2 2005/03/23 11:35:02 tdiesler Exp $

import org.jboss.logging.Logger;
import org.jboss.webservice.metadata.jaxrpcmapping.JavaWsdlMapping;
import org.jboss.webservice.metadata.jaxrpcmapping.ServiceEndpointInterfaceMapping;
import org.jboss.webservice.metadata.jaxrpcmapping.ServiceEndpointMethodMapping;

import javax.wsdl.Definition;
import javax.wsdl.Import;
import javax.wsdl.Operation;
import javax.wsdl.Port;
import javax.wsdl.PortType;
import javax.wsdl.Service;
import javax.wsdl.extensions.soap.SOAPAddress;
import javax.xml.rpc.ServiceException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * A utility class for common wsdl operations.
 *
 * @author Thomas.Diesler@jboss.org
 * @since 15-May-2004
 */
public final class WSDLUtilities
{
   // provide logging
   private static final Logger log = Logger.getLogger(WSDLUtilities.class);

   public static final String WSDL_NAMESPACE_URI = "http://schemas.xmlsoap.org/wsdl/".intern();
   public static final String XSD_NAMESPACE_URI = "http://www.w3.org/2001/XMLSchema".intern();

   // hide the constructor
   private WSDLUtilities()
   {
   }

   /**
    * Validate that the operations in the given SEI correspond to the wsdl operations
    */
   public static void endorseServiceEndpointInterface(Definition wsdlDefinition, Class seiClass, JavaWsdlMapping jaxrpcMapping)
           throws ServiceException
   {
      if (wsdlDefinition == null)
         throw new IllegalArgumentException("WSDL definition cannot be null");

      if (seiClass == null)
         throw new IllegalArgumentException("Service endpoint interface cannot be null");

      if (jaxrpcMapping == null)
         throw new IllegalArgumentException("JavaWsdlMapping cannot be null");

      if (java.rmi.Remote.class.isAssignableFrom(seiClass) == false)
         throw new IllegalArgumentException("The service endpoint interface does not implement java.rmi.Remote: " + seiClass.getName());

      // Get the list of method names in the SEI
      List seiMethodNames = new ArrayList();
      Method[] seiMethods = seiClass.getDeclaredMethods();
      for (int i = 0; i < seiMethods.length; i++)
      {
         Method method = seiMethods[i];
         seiMethodNames.add(method.getName());
      }

      // Map the wsdl-operation to the java-method-name
      Map semMap = new HashMap();
      ServiceEndpointInterfaceMapping[] seiMappings = jaxrpcMapping.getServiceEndpointInterfaceMappings();
      for (int i = 0; i < seiMappings.length; i++)
      {
         ServiceEndpointInterfaceMapping seiMapping = seiMappings[i];
         if (seiClass.getName().equals(seiMapping.getServiceEndpointInterface()))
         {
            ServiceEndpointMethodMapping[] semMappings = seiMapping.getServiceEndpointMethodMappings();
            for (int j = 0; j < semMappings.length; j++)
            {
               ServiceEndpointMethodMapping semMapping = semMappings[j];
               semMap.put(semMapping.getWsdlOperation(), semMapping.getJavaMethodName());
            }
         }
      }

      // Map the port type qname to the list of its operations
      Map portTypeOperationsMap = new HashMap();

      // init WSDL operation names
      Iterator itPortType = getPortTypesWithImport(wsdlDefinition).iterator();
      while (itPortType.hasNext())
      {
         List ptMethods = new ArrayList();
         PortType portType = (PortType)itPortType.next();
         Iterator itop = portType.getOperations().iterator();
         while (itop.hasNext())
         {
            String wsdlOp = ((Operation)itop.next()).getName();
            String javaOp = (String)semMap.get(wsdlOp);
            ptMethods.add(javaOp != null ? javaOp : wsdlOp);
         }
         portTypeOperationsMap.put(portType.getQName(), ptMethods);
      }

      boolean portTypeFound = false;

      Iterator it = portTypeOperationsMap.values().iterator();
      while (it.hasNext())
      {
         boolean seiOpInPortType = true;
         List opList = (List)it.next();
         for (int i = 0; i < seiMethods.length; i++)
         {
            Method seiMethod = seiMethods[i];
            seiOpInPortType &= opList.contains(seiMethod.getName());
         }

         // all sei methods were found in this wsdl port type
         if (seiOpInPortType == true)
         {
            portTypeFound = true;
            break;
         }
      }

      if (portTypeFound == false)
         throw new ServiceException("Cannot find SEI operations " + seiMethodNames + " in any wsdl port type, we have " + portTypeOperationsMap);
   }

   /**
    * Get the complete list of PortTypes including all the imports as well
    */
   private static List getPortTypesWithImport(Definition wsdl)
   {
      ArrayList pTypes = new ArrayList(wsdl.getPortTypes().values());
      Iterator it = wsdl.getImports().values().iterator();
      while (it.hasNext())
      {
         List imports = (List)it.next();
         for (int i = 0; i < imports.size(); i++)
         {
            Import imp = (Import)imports.get(i);
            Definition subdef = imp.getDefinition();
            pTypes.addAll(subdef.getPortTypes().values());
            pTypes.addAll(getPortTypesWithImport(subdef));
         }
      }
      return pTypes;
   }

   /**
    * Get the list of available service locations
    */
   public static String[] getServiceLocations(Definition wsdlDefinition)
   {
      ArrayList locations = new ArrayList();

      Service wsdlService = (Service)wsdlDefinition.getServices().values().iterator().next();

      Map wsdlPorts = wsdlService.getPorts();
      Iterator it = wsdlPorts.values().iterator();
      while (it.hasNext())
      {
         Port wsdlPort = (Port)it.next();
         Iterator itElements = wsdlPort.getExtensibilityElements().iterator();
         while (itElements.hasNext())
         {
            Object obj = itElements.next();
            if (obj instanceof SOAPAddress)
            {
               SOAPAddress address = (SOAPAddress)obj;
               String wsdlURI = address.getLocationURI();
               locations.add(wsdlURI);
            }
         }
      }

      String[] arr = new String[locations.size()];
      locations.toArray(arr);
      return arr;
   }
}