/*
 * Copyright 2001-2004 The Apache Software Foundation.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.axis.providers;

import java.util.Hashtable;

import javax.xml.namespace.QName;

import org.apache.axis.AxisFault;
import org.apache.axis.Constants;
import org.apache.axis.MessageContext;
import org.apache.axis.AxisEngine;
import org.apache.axis.components.logger.LogFactory;
import org.apache.axis.description.ServiceDesc;
import org.apache.axis.description.JavaServiceDesc;
import org.apache.axis.encoding.TypeMapping;
import org.apache.axis.handlers.BasicHandler;
import org.apache.axis.handlers.soap.SOAPService;
import org.apache.axis.utils.Messages;
import org.apache.axis.wsdl.fromJava.Emitter;
import org.apache.commons.logging.Log;
import org.w3c.dom.Document;

/**
 * This class has one way of keeping track of the
 * operations declared for a particular service
 * provider.  I'm not exactly married to this though.
 */
public abstract class BasicProvider extends BasicHandler {
    
    public static final String OPTION_WSDL_PORTTYPE = "wsdlPortType";
    public static final String OPTION_WSDL_SERVICEELEMENT = "wsdlServiceElement";
    public static final String OPTION_WSDL_SERVICEPORT = "wsdlServicePort";
    public static final String OPTION_WSDL_TARGETNAMESPACE = "wsdlTargetNamespace";
    public static final String OPTION_WSDL_INPUTSCHEMA = "wsdlInputSchema";
    public static final String OPTION_WSDL_SOAPACTION_MODE = "wsdlSoapActionMode";
    public static final String OPTION_EXTRACLASSES = "extraClasses";

    protected static Log log =
            LogFactory.getLog(BasicProvider.class.getName());

    // The enterprise category is for stuff that an enterprise product might
    // want to track, but in a simple environment (like the AXIS build) would
    // be nothing more than a nuisance.
    protected static Log entLog =
            LogFactory.getLog(Constants.ENTERPRISE_LOG_CATEGORY);
    
    /**
     * This method returns a ServiceDesc that contains the correct 
     * implimentation class. 
     */ 
    public abstract void initServiceDesc(SOAPService service,
                                         MessageContext msgContext)
            throws AxisFault;
    
    public void addOperation(String name, QName qname) {
        Hashtable operations = (Hashtable)getOption("Operations");
        if (operations == null) {
            operations = new Hashtable();
            setOption("Operations", operations);
        }
        operations.put(qname, name);
    }
    
    public String getOperationName(QName qname) {
        Hashtable operations = (Hashtable)getOption("Operations");
        if (operations == null) return null;
        return (String)operations.get(qname);
    }
    
    public QName[] getOperationQNames() {
        Hashtable operations = (Hashtable)getOption("Operations");
        if (operations == null) return null;
        Object[] keys = operations.keySet().toArray();
        QName[] qnames = new QName[keys.length];
        System.arraycopy(keys,0,qnames,0,keys.length);
        return qnames;
    }
    
    public String[] getOperationNames() {
        Hashtable operations = (Hashtable)getOption("Operations");
        if (operations == null) return null;
        Object[] values = operations.values().toArray();
        String[] names = new String[values.length];
        System.arraycopy(values,0,names,0,values.length);
        return names;
    }
    
    /**
     * Generate the WSDL for this service.
     *
     * Put in the "WSDL" property of the message context
     * as a org.w3c.dom.Document
     */
    public void generateWSDL(MessageContext msgContext) throws AxisFault {
        if (log.isDebugEnabled())
            log.debug("Enter: BasicProvider::generateWSDL (" + this +")");

        /* Find the service we're invoking so we can grab it's options */
        /***************************************************************/
        SOAPService service = msgContext.getService();
        
        ServiceDesc serviceDesc = service.getInitializedServiceDesc(msgContext);

        // Calculate the appropriate namespaces for the WSDL we're going
        // to put out.
        //
        // If we've been explicitly told which namespaces to use, respect
        // that.  If not:
        //
        // The "interface namespace" should be either:
        // 1) The namespace of the ServiceDesc
        // 2) The transport URL (if there's no ServiceDesc ns)

        try {
            // Location URL is whatever is explicitly set in the MC
            String locationUrl = msgContext.getStrProp(MessageContext.WSDLGEN_SERV_LOC_URL);

            if (locationUrl == null) {
                // If nothing, try what's explicitly set in the ServiceDesc
                locationUrl = serviceDesc.getEndpointURL();
            }

            if (locationUrl == null) {
                // If nothing, use the actual transport URL
                locationUrl = msgContext.getStrProp(MessageContext.TRANS_URL);
            }

            // Interface namespace is whatever is explicitly set
            String interfaceNamespace = msgContext.getStrProp(MessageContext.WSDLGEN_INTFNAMESPACE);

            if (interfaceNamespace == null) {
                // If nothing, use the default namespace of the ServiceDesc
                interfaceNamespace = serviceDesc.getDefaultNamespace();
            }

            if (interfaceNamespace == null) {
                // If nothing still, use the location URL determined above
                interfaceNamespace = locationUrl;
            }

            //Do we want to do this?
            //
            //          if (locationUrl == null) {
            //              locationUrl = url;
            //          } else {
            //              try {
            //                  URL urlURL = new URL(url);
            //                  URL locationURL = new URL(locationUrl);
            //                  URL urlTemp = new URL(urlURL.getProtocol(),
            //                          locationURL.getHost(),
            //                          locationURL.getPort(),
            //                          urlURL.getFile());
            //                  interfaceNamespace += urlURL.getFile();
            //                  locationUrl = urlTemp.toString();
            //              } catch (Exception e) {
            //                  locationUrl = url;
            //                  interfaceNamespace = url;
            //              }
            //          }

            Emitter emitter = new Emitter();

            // This seems like a good idea, but in fact isn't because the
            // emitter will figure out a reasonable name (<classname>Service)
            // for the WSDL service element name.  We provide the 'alias'
            // setting to explicitly set this name. See bug 13262 for more info.
            //emitter.setServiceElementName(serviceDesc.getName());

            // service alias may be provided if exact naming is required,
            // otherwise Axis will name it according to the implementing class name
            String alias = (String) service.getOption("alias");
            if (alias != null)
                emitter.setServiceElementName(alias);

            // Set style/use
            emitter.setStyle(serviceDesc.getStyle());
            emitter.setUse(serviceDesc.getUse());

            if (serviceDesc instanceof JavaServiceDesc) {
                emitter.setClsSmart(((JavaServiceDesc)serviceDesc).getImplClass(),
                                    locationUrl);
            }

            // If a wsdl target namespace was provided, use the targetNamespace.
            // Otherwise use the interfaceNamespace constructed above.
            String targetNamespace = (String) service.getOption(OPTION_WSDL_TARGETNAMESPACE);
            if (targetNamespace == null || targetNamespace.length() == 0) {
                targetNamespace = interfaceNamespace;
            }
            emitter.setIntfNamespace(targetNamespace);

            emitter.setLocationUrl(locationUrl);
            emitter.setServiceDesc(serviceDesc);
            emitter.setTypeMappingRegistry(msgContext.getTypeMappingRegistry());

            String wsdlPortType = (String) service.getOption(OPTION_WSDL_PORTTYPE);
            String wsdlServiceElement = (String) service.getOption(OPTION_WSDL_SERVICEELEMENT);
            String wsdlServicePort = (String) service.getOption(OPTION_WSDL_SERVICEPORT);
            String wsdlInputSchema = (String) service.getOption(OPTION_WSDL_INPUTSCHEMA);
            String wsdlSoapActinMode = (String) service.getOption(OPTION_WSDL_SOAPACTION_MODE);
            String extraClasses = (String) service.getOption(OPTION_EXTRACLASSES);

            if (wsdlPortType != null && wsdlPortType.length() > 0) {
                emitter.setPortTypeName(wsdlPortType);
            }
            if (wsdlServiceElement != null && wsdlServiceElement.length() > 0) {
                emitter.setServiceElementName(wsdlServiceElement);
            }
            if (wsdlServicePort != null && wsdlServicePort.length() > 0) {
                emitter.setServicePortName(wsdlServicePort);
            }
            if (wsdlInputSchema != null && wsdlInputSchema.length() > 0) {
                emitter.setInputSchema(wsdlInputSchema);
            }
            if (wsdlSoapActinMode != null && wsdlSoapActinMode.length() > 0) {
                emitter.setSoapAction(wsdlSoapActinMode);
            }

            if (extraClasses != null && extraClasses.length() > 0) {
                emitter.setExtraClasses(extraClasses);
            }

            if (msgContext.isPropertyTrue(AxisEngine.PROP_EMIT_ALL_TYPES)) {
                emitter.setEmitAllTypes(true);
            }

            Document doc = emitter.emit(Emitter.MODE_ALL);

            msgContext.setProperty("WSDL", doc);
        } catch (NoClassDefFoundError e) {
            entLog.info(Messages.getMessage("toAxisFault00"), e);
            throw new AxisFault(e.toString(), e);
        } catch (Exception e) {
            entLog.info(Messages.getMessage("toAxisFault00"), e);
            throw AxisFault.makeFault(e);
        }

        if (log.isDebugEnabled())
            log.debug("Exit: BasicProvider::generateWSDL (" + this +")");
    }
}
