/*
* JBoss, Home of Professional Open Source
* Copyright 2005, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.ws.tools;

// $Id: JavaToWSDL.java 10520 2009-08-11 17:56:42Z mageshbk@jboss.com $

import java.io.StringWriter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.jws.soap.SOAPBinding.ParameterStyle;
import javax.xml.rpc.encoding.TypeMapping;

import org.jboss.logging.Logger;
import org.jboss.ws.Constants;
import org.jboss.ws.WSException;
import org.jboss.ws.core.soap.Style;
import org.jboss.ws.metadata.jaxrpcmapping.JavaWsdlMapping;
import org.jboss.ws.metadata.umdm.UnifiedMetaData;
import org.jboss.ws.metadata.wsdl.WSDLDefinitions;
import org.jboss.ws.tools.Configuration.OperationConfig;
import org.jboss.ws.tools.metadata.ToolsUnifiedMetaDataBuilder;
import org.jboss.ws.tools.wsdl.WSDLWriter;
import org.jboss.wsf.common.DOMUtils;
import org.jboss.wsf.common.DOMWriter;
import org.w3c.dom.Element;

/**
 * Generates a WSDL for a service endpoint.
 *
 * <BR/> This is the main entry point for all Java To WSDL needs.
 * <BR/> Features that can be set are derived from org.jboss.ws.tools.WSToolsConstants
 * <p/>
 * Notable ones are:<br/>
 * @see org.jboss.ws.tools.WSToolsConstants.WSTOOLS_FEATURE_RESTRICT_TO_TARGET_NS
 * @see org.jboss.ws.tools.WSToolsConstants.WSTOOLS_FEATURE_INCLUDE_SCHEMA_IN_WSDL
 * @see org.jboss.ws.tools.WSToolsConstants.WSTOOLS_FEATURE_USE_ANNOTATIONS
 *
 * @author Thomas.Diesler@jboss.org
 * @author Anil.Saldhana@jboss.org
 * @since 24-Jul-2005
 */
public class JavaToWSDL
{
   // provide logging
   private static final Logger log = Logger.getLogger(JavaToWSDL.class);

   // The required wsdl namespace URI
   private String wsdlNamespace;
   // The target namespace
   private String targetNamespace;
   //The type namespace (it can be different from the target namespace)
   private String typeNamespace;
   // The service name
   private String serviceName;
   // The portType name
   private String portTypeName;

   private Style style;

   private ParameterStyle parameterStyle;

   // Features as represented by Constants
   private Map<String, Boolean> features = new HashMap<String, Boolean>();

   // A Map of package/namespace mapping that needs to be passed onto types generator
   private Map<String,String> packageNamespaceMap = new HashMap<String, String>();

   private TypeMapping typeMapping = null;

   private JavaWsdlMapping javaWsdlMapping = null;

   private UnifiedMetaData umd = null;

   private boolean qualifiedElements = false;

   private Map<String, List<OperationConfig>> operationMap = null;

   /** Contruct a java to wsdl generator for a given wsdl version.
    * <p/>
    * WSDL-1.1 namespace URI: http://schemas.xmlsoap.org/wsdl/<br/>
    * WSDL-2.0 namespace URI: http://www.w3.org/2003/11/wsdl
    *
    * @param namespace wsdl namespace URI
    */
   public JavaToWSDL(String namespace)
   {
      if (Constants.NS_WSDL11.equals(namespace) == false)
         throw new IllegalArgumentException("Unsupported wsdl version: " + namespace);

      this.wsdlNamespace = namespace;
   }

   /**
    * Add a feature to this subsystem
    * @see org.jboss.ws.tools.WSToolsConstants
    * @param name
    * @param value
    */
   public void addFeature(String name, boolean value)
   {
      features.put(name, new Boolean(value));
   }

   /**
    * Return a feature if set
    * @see org.jboss.ws.tools.WSToolsConstants
    * @param name
    * @return boolean value representing the feature, if not
    * @throws IllegalStateException  Feature unrecognized
    */
   public boolean getFeature(String name)
   {
      Boolean val = features.get(name);
      if (val == null)
         throw new WSException("Feature value not available: " + name);

      return val.booleanValue();
   }

   /** Get the immutable wsdl namespace URI
    */
   public String getWsdlNamespace()
   {
      return wsdlNamespace;
   }

   /** Get the wsdl target namespace
    */
   public String getTargetNamespace()
   {
      return targetNamespace;
   }

   /** Set the wsdl target namespace
    */
   public void setTargetNamespace(String targetNamespace)
   {
      this.targetNamespace = targetNamespace;
   }

   /** Get the type Namespace */
   public String getTypeNamespace()
   {
      return typeNamespace;
   }

   /** Set the Type Namespace */
   public void setTypeNamespace(String typeNamespace)
   {
      this.typeNamespace = typeNamespace;
   }

   /** Get the wsdl service name
    */
   public String getServiceName()
   {
      return serviceName;
   }

   /** Set the wsdl service name
    */
   public void setServiceName(String serviceName)
   {
      this.serviceName = serviceName;
   }

   /** Get the wsdl service endpoint name
    */
   public String getPortTypeName()
   {
      return portTypeName;
   }

   /** Set the wsdl service PortType Name
    */
   public void setPortTypeName(String endpointName)
   {
      this.portTypeName = endpointName;
   }

   /**
    * During the WSDL generation process, a typeMapping will be
    * created that maps xml types -> java types
    *
    * @return  typeMapping
    * @exception IllegalStateException If typeMapping has not been generated
    */
   public TypeMapping getTypeMapping()
   {
      if(typeMapping == null)
         throw new WSException("TypeMapping has not been generated");
      return typeMapping;
   }

   public Style getStyle()
   {
      return style;
   }

   public void setStyle(Style style)
   {
      this.style = style;
   }

   public ParameterStyle getParameterStyle()
   {
      return parameterStyle;
   }

   public void setParameterStyle(ParameterStyle parameterStyle)
   {
      this.parameterStyle = parameterStyle;
   }

   /**
    * Users can customize a java package->xml namespace map
    * that will be used in the Java to WSDL process.
    * <br/>The package representing the endpoint will always be mapped
    * to the target namespace, irrespective of an attempt to
    * customize that in the map. If you desire to change that, then
    * think about changing just the type namespace as the types are
    * generated using the typenamespace.
    *
    * @param map  The Map
    */
   public void setPackageNamespaceMap(Map<String,String> map)
   {
      //Lets convert the namespace->package map to package->namespace map
      Set<String> keys = map.keySet();
      Iterator<String> iter = keys.iterator();
      while (iter != null && iter.hasNext())
      {
         String pkg = iter.next();
         packageNamespaceMap.put(map.get(pkg), pkg);
      }
   }

   public void setOperationMap(Map<String, List<OperationConfig>> operationMap)
   {
      this.operationMap = operationMap;
   }

   /**
    * Clients of Tools can build a UnifiedMetaData externally
    * and pass it to the Java To WSDL subsystem [Optional]
    *
    * @param um
    */
   public void setUnifiedMetaData(UnifiedMetaData um)
   {
      this.umd = um;
   }

   public UnifiedMetaData getUnifiedMetaData()
   {
      return umd;
   }

   public void setUmd(UnifiedMetaData umd)
   {
      this.umd = umd;
   }

   public boolean isQualifiedElements()
   {
      return qualifiedElements;
   }

   public void setQualifiedElements(boolean qualifiedElements)
   {
      this.qualifiedElements = qualifiedElements;
   }

   public JavaWsdlMapping getJavaWsdlMapping()
   {
      return javaWsdlMapping;
   }

   /** Generate the common WSDL definition for a given endpoint
    */
   public WSDLDefinitions generate(Class endpoint)
   {
      if(log.isDebugEnabled()) log.debug("generate [endpoint=" + endpoint.getName() + ",tnsURI=" + targetNamespace + ",service=" + serviceName
            + ",portType=" + portTypeName + "]");

      if( umd == null)
      {
         umd = new ToolsUnifiedMetaDataBuilder(endpoint, targetNamespace,
              typeNamespace,  serviceName, style, parameterStyle, operationMap).getUnifiedMetaData();
      }

      if (typeNamespace != null)
         packageNamespaceMap.put(endpoint.getPackage().getName(), typeNamespace);

      WSDLDefinitions wsdlDefinitions = null;
      try
      {
         if (Constants.NS_WSDL11.equals(wsdlNamespace))
         {
            JavaToWSDL11 javaWSDL11 = new JavaToWSDL11();
            javaWSDL11.addFeatures(features);
            javaWSDL11.setPackageNamespaceMap(packageNamespaceMap);
            javaWSDL11.addFeatures(features);
            if( umd != null )
               javaWSDL11.setUnifiedMetaData(umd);
            javaWSDL11.setQualifiedElements(qualifiedElements);

            wsdlDefinitions = javaWSDL11.generate(endpoint);
            typeMapping = javaWSDL11.getTypeMapping();
            javaWsdlMapping = javaWSDL11.getJavaWsdlMapping();
         }
         if (wsdlDefinitions == null)
            throw new WSException("Cannot generate WSDL definitions");

         // Debug the generated wsdl
         StringWriter sw = new StringWriter();
         new WSDLWriter(wsdlDefinitions).write(sw, Constants.DEFAULT_XML_CHARSET);
         if(log.isDebugEnabled()) log.debug("Generated WSDL:\n" + sw.toString());

         // Debug the generated mapping file
         String jaxrpcMappingStr = null;
         if (javaWsdlMapping != null)
         {
            Element root = DOMUtils.parse(javaWsdlMapping.serialize());
            jaxrpcMappingStr = DOMWriter.printNode(root, true);
         }
         if(log.isDebugEnabled()) log.debug("Generated Mapping:\n" + jaxrpcMappingStr);
      }
      catch (RuntimeException rte)
      {
         throw rte;
      }
      catch (Exception e)
      {
         log.error("Cannot generate WSDL",e);
         throw new WSException("Cannot generate wsdl from: " + endpoint);
      }
      return wsdlDefinitions;
   }
}
