/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you 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.axis2.jaxws.util;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.wsdl.Binding;
import javax.wsdl.BindingFault;
import javax.wsdl.BindingInput;
import javax.wsdl.BindingOperation;
import javax.wsdl.BindingOutput;
import javax.wsdl.WSDLElement;
import javax.wsdl.extensions.ExtensibilityElement;
import javax.wsdl.extensions.UnknownExtensibilityElement;
import javax.wsdl.extensions.soap.SOAPBinding;
import javax.wsdl.extensions.soap12.SOAP12Binding;
import javax.xml.namespace.QName;

import org.apache.axis2.jaxws.ExceptionFactory;
import org.apache.axis2.jaxws.common.config.WSDLValidatorElement;
import org.apache.axis2.jaxws.common.config.WSDLValidatorElement.State;
import org.apache.axis2.jaxws.description.EndpointDescription;
import org.apache.axis2.jaxws.description.EndpointDescriptionWSDL;
import org.apache.axis2.jaxws.i18n.Messages;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
 * This utility class stores methods that can be used to fetch extension elements
 * from wsdl and will be used from RespectBindingConfigurator on Client and Server.
 */
public class WSDLExtensionUtils {
    private static final Log log = LogFactory.getLog(WSDLExtensionUtils.class);
    /**
     * This method will search for all wsdl extensibility elements marked as required=true in wsdl:bindings
     * As per the wsdl 2.2 specification section 2.5 here is how a wsdl:binding is defined:
     * <wsdl:definitions .... >
     *      <wsdl:binding name="nmtoken" type="qname"> *
     *       <-- extensibility element (1) --> *
     *       <wsdl:operation name="nmtoken"> *
     *          <-- extensibility element (2) --> *
     *          <wsdl:input name="nmtoken"? > ?
     *              <-- extensibility element (3) --> 
     *          </wsdl:input>
     *          <wsdl:output name="nmtoken"? > ?
     *              <-- extensibility element (4) --> *
     *          </wsdl:output>
     *          <wsdl:fault name="nmtoken"> *
     *              <-- extensibility element (5) --> *
     *          </wsdl:fault>
     *       </wsdl:operation>
     *   </wsdl:binding>
     * </wsdl:definitions>
     * we will look for wsdl extensions in binding root, wsdl:operation, wsdl:input, wsdl:output and wsdl:fault.
     * If the extensibility element is defines outside of these sections it will not be picked up by this method.
     * 
     * @param wsdlBinding - WSDLBinding Object read from WSDL Definition.
     * @param set - Set that will be filled with list of required=true extension elements.
     * @return
     */
    public static void search(WSDLElement element, Set<WSDLValidatorElement> set, List<QName> unusedExtensions) {
        if(log.isDebugEnabled()){
            log.debug("Start Searching for WSDLExtensions");
        }
        if(element == null){
            return;
        }
        //This search method uses a simple BFS technique to search for Extension elements in WSDLBindings.
        //I will Queue all available WSDLElements starting in wsdl:binding and traverse them looking for 
        //extensions. Queue will be empty when I have processed everything.
        //NOTE:Binding, Operation, OperationInput, OperationOutput and OperationFault are all WSDLElements.
        LinkedList<WSDLElement> queue = new LinkedList<WSDLElement>();
        queue.offer(element);
        
        while(!queue.isEmpty()){
            WSDLElement wsdlElement = queue.remove();
            //WSDLElement in Queue could be wsdl Binding, BindingOperations, Input, Output or Fault
            //Find Extensibility Elements in wsdlElement.
            processWSDLElement(wsdlElement, set, unusedExtensions);
            //check if we are dealing with wsdlBinding;
            //store all BindingOpeations from wsdlBindings
            if(wsdlElement instanceof Binding){
                //lets get all operations and add to queue
                //TODO: WSDLDef API's don't use generics, hence we use Iterator below and type cast.
                List operations = ((Binding)wsdlElement).getBindingOperations();
                Iterator iter = operations.iterator();
                while(iter.hasNext()){
                    BindingOperation op =(BindingOperation) iter.next();
                    queue.offer(op);
                }
            }
            //check if we are dealing with Bindingoperations
            //Store all input, output and faults.
            if(wsdlElement instanceof BindingOperation){
                BindingInput bi = ((BindingOperation)wsdlElement).getBindingInput();
                queue.offer(bi);
                BindingOutput bo = ((BindingOperation)wsdlElement).getBindingOutput();
                queue.offer(bo);
                Map map = ((BindingOperation)wsdlElement).getBindingFaults();
                Collection c = map.values();
                Iterator iter = c.iterator();
                while(iter.hasNext()){
                    Object o = iter.next();
                    if(o instanceof BindingFault){
                        BindingFault bf = (BindingFault)o;
                        queue.offer(bf);
                    }
                }
            }          
        }
        if(log.isDebugEnabled()){
            log.debug("End Searching for WSDLExtensions");
        }
    }
    
    private static void processWSDLElement(WSDLElement wsdlElement, Set<WSDLValidatorElement> set, List<QName> unusedExtensions){
        if(log.isDebugEnabled()){
            log.debug("Start processWSDLElement");
        }
        List list = wsdlElement.getExtensibilityElements();
        if (list == null || list.size() == 0) {
            return;
        }
        Iterator iter = list.iterator();
        while (iter.hasNext()) {
            ExtensibilityElement e = (ExtensibilityElement) iter.next();
            //TODO in JAX-WS 2.1 Respect Binding implementation we are ignoring
            //SOAPBinding, review the reason behind this.
            if (e instanceof SOAPBinding || e instanceof SOAP12Binding)
                continue;

            if (e instanceof UnknownExtensibilityElement) {              
                UnknownExtensibilityElement ue = (UnknownExtensibilityElement) e;
                String reqd = ue.getElement().getAttribute("required");
                //check if extension element is required.
                //one can set extension as required two different ways in wsdl
                //lets check both ways here
                boolean wsdl_required = e.getRequired() != null && e.getRequired();
                boolean wsdl_attribute = reqd!=null && reqd.equalsIgnoreCase("true");
                
                if (wsdl_attribute || wsdl_required) {
                    if (log.isDebugEnabled()) {
                        log.debug("Found a required element: " + e.getElementType());
                    }
                    WSDLValidatorElement element = new WSDLValidatorElement();
                    element.setExtensionElement(e);
                    element.setState(State.NOT_RECOGNIZED);
                    set.add(element);
                }
                else {
                    if (log.isDebugEnabled()) {
                        log.debug("Found a NOT required element: " + e.getElementType());
                    }
                    unusedExtensions.add(e.getElementType());
                }
            }
        }
        if(log.isDebugEnabled()){
            log.debug("Exit processWSDLElement");
        }
    }
    
    public static void processExtensions(EndpointDescription endpointDescription){
        if(endpointDescription == null){
            throw ExceptionFactory.makeWebServiceException(Messages.getMessage("RespectBindingConfiguratorErr1"));
        }

        EndpointDescriptionWSDL edw = (EndpointDescriptionWSDL) endpointDescription;
        if(endpointDescription == null){
            throw ExceptionFactory.makeWebServiceException(Messages.getMessage("RespectBindingConfiguratorErr2"));
        }
        javax.wsdl.Binding wsdlBinding = edw.getWSDLBinding();
        Set<WSDLValidatorElement> set = endpointDescription.getRequiredBindings();
        if(set.size()>0){
            //we have already looked for wsdl extension once, no need to 
            //find them again. WSDL is shared for all serviceDesc artifacts.
            return;
        }
        List<QName> unusedExtensions = new ArrayList<QName>();
        
        WSDLExtensionUtils.search(wsdlBinding, set, unusedExtensions);
        if (log.isDebugEnabled()) {
            log.debug("The following extensibility elements were found, but were not required.");
            for (int n = 0; n < unusedExtensions.size(); ++n)
                log.debug("[" + (n + 1) + "] - " + unusedExtensions.get(n));
        }           
        
    }
}
