/*
 * 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.handler;

import org.apache.axiom.om.OMAbstractFactory;
import org.apache.axiom.om.OMFactory;
import org.apache.axiom.soap.RolePlayer;
import org.apache.axiom.soap.SOAPEnvelope;
import org.apache.axiom.soap.SOAPFactory;
import org.apache.axiom.soap.SOAPHeader;
import org.apache.axiom.soap.SOAPHeaderBlock;
import org.apache.axis2.AxisFault;
import org.apache.axis2.Constants;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.context.ConfigurationContextFactory;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.description.AxisService;
import org.apache.axis2.engine.AxisConfiguration;
import org.apache.axis2.jaxws.description.DescriptionFactory;
import org.apache.axis2.jaxws.description.EndpointDescription;
import org.apache.axis2.jaxws.description.ServiceDescription;
import org.apache.axis2.jaxws.description.xml.handler.HandlerChainType;
import org.apache.axis2.jaxws.description.xml.handler.HandlerChainsType;
import org.apache.axis2.jaxws.description.xml.handler.HandlerType;

import javax.jws.HandlerChain;
import javax.jws.WebService;
import javax.xml.namespace.QName;
import javax.xml.ws.handler.Handler;
import javax.xml.ws.handler.PortInfo;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import junit.framework.TestCase;

/**
 * Test Role (SOAP 1.2 terminology) or Actor (SOAP 1.1 terminology) mustUndertand processing.
 */
public class RoleBasedMustUndertandTests extends TestCase {
    private SOAPFactory soap11Factory;
    private SOAPFactory soap12Factory;
    private OMFactory omFactory;
    
    private String namespace = "http://RoleBasedMustUnderstandTest/good/namespace";
    private String soap11Namespace = "http://schemas.xmlsoap.org/soap/envelope/";
    private String header_ultimateReceiver = "header_ultimateReceiver";
    private String header_ultimateReceiver_processed = "header_ultmiateReceiver_processed";
    private String header_rolePlayed = "header_rolePlayed";
    private String header_roleNotPlayed = "header_roleNotPlayed";
    private String header_bindingAdded = "header_bindingAdded";
    private String header_SoapNext = "header_SoapNext";
    
    private String rolePlayed1 = "http://Role/Played/role1";
    private String roleNotPlayed = "http://Role/Not/Played/role1";
    private String roleBindingAdded = "http://Role/Added/By/Binding";
    private String roleNoHeaders = "http://Role/Played/No/Headers";
    private String roleHandlerAdded = "http://org/apache/axis2/jaxws/handler/AddedRole";
    private String roleHandlerAdded2 = "http://org/apache/axis2/jaxws/handler/AddedRole2";
    private String roleSoap11Next = "http://schemas.xmlsoap.org/soap/actor/next";

    public RoleBasedMustUndertandTests(String testName) {
        super(testName);
        soap11Factory = OMAbstractFactory.getSOAP11Factory();
        soap12Factory = OMAbstractFactory.getSOAP12Factory();
        omFactory = OMAbstractFactory.getOMFactory();
    }
    
    /**
     * Validate that handler information can be cached on the ServiceDescritpion on the server side. 
     */
    public void testCachingOnServer() {
        ServiceDescription serviceDesc = DescriptionFactory.createServiceDescription(RoleBasedMUServiceImpl.class);
        HandlerResolverImpl handlerResolver1 = new HandlerResolverImpl(serviceDesc);
        HandlerResolverImpl handlerResolver2 = new HandlerResolverImpl(serviceDesc);

        EndpointDescription epDesc = serviceDesc.getEndpointDescriptions()[0];
        PortInfo portInfo = epDesc.getPortInfo();
        
        List<String> roles1 = handlerResolver1.getRoles(portInfo);
        List<String> roles2 = handlerResolver2.getRoles(portInfo);
        assertNotNull(roles1);
        assertNotNull(roles2);
        assertTrue(roles1 == roles2);
    }
    
    
    /**
     * Validate that handler information can NOT be cached on the client.  That is because the client
     * can specify handler information per instance of a service delegate.  Those service
     * delegates could share a ServiceDescription, but since each service delegate could specify
     * unique handler information, we can't use common handler information stored on the
     * ServiceDescription 
     */
    public void testCachingOnClient() {
        ServiceDescription serviceDesc = DescriptionFactory.createServiceDescription(RoleBasedMUServiceImpl.class);
        HandlerResolverImpl handlerResolver1 = new HandlerResolverImpl(serviceDesc, "sd1");
        HandlerResolverImpl handlerResolver2 = new HandlerResolverImpl(serviceDesc, "sd2");

        EndpointDescription epDesc = serviceDesc.getEndpointDescriptions()[0];
        PortInfo portInfo = epDesc.getPortInfo();
        
        List<String> roles1 = handlerResolver1.getRoles(portInfo);
        List<String> roles2 = handlerResolver2.getRoles(portInfo);
        assertNotNull(roles1);
        assertNotNull(roles2);
        assertTrue(roles1 != roles2);
        
    }
    
    /**
     * The JAXWS Spec, section 10, says roles can be set on the SOAP binding.  Verify that a 
     * mustUnderstand header which is not processed for a role added by the SOAPBinding causes
     * a Not Understood fault 
     */
    public void testRoleAddedToSoapBinding() {
        RolePlayer rolePlayer = new UltimateDestinationRoles();
        
        MessageContext mscCtx11 = createMessageContext(soap11Factory);
        // Note that since this role is UltimateReceiver, all unprocessed headers will be set
        // on the message context
        setUnprocessedHeaders(mscCtx11, rolePlayer);
        
        // Indicate we understand (e.g. via handlers) the headers for Ultimate Receiver and the
        // role we are acting in.  We don't indicate we understand the header that the SOAPBinding
        // added, so that should cause an exception
        List<QName> understoodHeaders = new ArrayList<QName>();
        understoodHeaders.add(new QName(namespace, header_ultimateReceiver));
        understoodHeaders.add(new QName(namespace, header_rolePlayed));
        
        // Create a list of additional roles that acted in (e.g. one played by a JAXWS
        // handler)
        List<String> headerRolesPlayed = bindingAddedRole();

        try {
            HandlerUtils.checkMustUnderstand(mscCtx11, understoodHeaders, headerRolesPlayed);
            fail("Should have gotten NotUnderstood fault for header for role added by binding: " + header_bindingAdded);
        } catch (AxisFault af) {
            // Expected path; should get exception for ultimate receiver not processed.
            String checkFault = af.toString();
            if (!checkFault.contains("Must Understand check failed")
                || !checkFault.contains(header_bindingAdded)) {
                fail("Did not get expected NotUnderstood AxisFault.  Unexpected fault " + af);
            }
        } catch (Exception e) {
            fail("Caught unexpected exception  + e");
        }
    }
    
    /**
     * Test where there are no previous non-understood headers on the MessageContext and there's an 
     * additional Role added by a JAXWS handler
     */
    public void testOnlySoapBindingRole() {
        MessageContext mscCtx11 = createMessageContext(soap11Factory);
        // Note that we are not putting any non-understood headers on the message context
        // Create a list of additional roles (e.g. one played by a JAXWS
        // handler
        List<String> headerRolesPlayed = bindingAddedRole();

        try {
            HandlerUtils.checkMustUnderstand(mscCtx11, null, headerRolesPlayed);
            fail("Should have gotten NotUnderstood fault for header for role added by binding: " + header_bindingAdded);
        } catch (AxisFault af) {
            // Expected path; should get exception for ultimate receiver not processed.
            String checkFault = af.toString();
            if (!checkFault.contains("Must Understand check failed")
                || !checkFault.contains(header_bindingAdded)) {
                fail("Did not get expected NotUnderstood AxisFault.  Unexpected fault " + af);
            }
        } catch (Exception e) {
            fail("Caught unexpected exception  + e");
        }
    }
    
    /**
     * Test that adding a role for headers that does not exist does not cause any fault
     */
    public void testNoHeadersForSoapBindingRole() {
        MessageContext mscCtx11 = createMessageContext(soap11Factory);

        List<String> headerRolesPlayed = noHeadersRole();

        try {
            HandlerUtils.checkMustUnderstand(mscCtx11, null, headerRolesPlayed);
        } catch (AxisFault af) {
            fail("Unexpected AxisFault "+ af);
        } catch (Exception e) {
            fail("Caught unexpected exception  + e");
        }
        
    }
    /**
     * A mustUnderstand header for a role not being acted in should NOT cause a notUnderstood
     * fault. 
     */
    public void testRoleNotActingIn() {
        RolePlayer rolePlayer = new UltimateDestinationRoles();
        
        MessageContext mscCtx11 = createMessageContext(soap11Factory);
        // Note that since this role is UltimateReceiver, all unprocessed headers will be set
        // on the message context
        setUnprocessedHeaders(mscCtx11, rolePlayer);
        
        // Indicate we understand (e.g. via handlers) the headers for Ultimate Receiver and the
        // role we are acting in.  This leaves the mustUnderstand header for the role we are not 
        // acting in, which should not cause an exception.
        List<QName> understoodHeaders = new ArrayList<QName>();
        understoodHeaders.add(new QName(namespace, header_ultimateReceiver));
        understoodHeaders.add(new QName(namespace, header_rolePlayed));
        try {
            HandlerUtils.checkMustUnderstand(mscCtx11, understoodHeaders, null);
        } catch (AxisFault e) {
            fail("Should not have caught an AxisFault " + e);
        }
    }
    
    /**
     * Test mustUnderstand processing for Ultimate Receiver with an understood header list
     * passed to the mustUnderstand checking
     */
    public void testUltimateReceiverRoles() {
        
        RolePlayer rolePlayer = new UltimateDestinationRoles();
        MessageContext mscCtx11 = createMessageContext(soap11Factory);
        setUnprocessedHeaders(mscCtx11, rolePlayer);
        // Indicate we understand (e.g. via handlers) all the headers.
        List<QName> understoodHeaders = new ArrayList<QName>();
        understoodHeaders.add(new QName(namespace, header_ultimateReceiver));
        understoodHeaders.add(new QName(namespace, header_rolePlayed));
        try {
            HandlerUtils.checkMustUnderstand(mscCtx11, understoodHeaders, null);
        } catch (AxisFault e) {
            fail("Should not have caught an AxisFault " + e);
        }
    }

    /**
     * Verify that an Ultimate Receiver with no roles only yields the header with no role specified
     * as unprocessed and that passing a list of understood headers including just that one causes
     * the mustUnderstand checks to pass.
     */
    public void testUltimateReceiver() {
        RolePlayer rolePlayer = new UltimateDestinationNoRoles();
        MessageContext msgCtx = createMessageContext(soap11Factory);
        // Indicate the must understand header without an explicit role will be understood.
        // The other headers, with explicit roles, should not cause an error.
        setUnprocessedHeaders(msgCtx, rolePlayer);
        List<QName> understoodHeaders = new ArrayList<QName>();
        understoodHeaders.add(new QName(namespace, header_ultimateReceiver));
        try {
            HandlerUtils.checkMustUnderstand(msgCtx, understoodHeaders, null);
        } catch (AxisFault e) {
            fail("Should not have caught and AxisFault " + e);
        }
    }
    /**
     * Test mustUnderstand processing for Ultimate Receiver without additional understood headers
     * being passed to the checker.  This should cause a notUnderstood fault
     */
    public void testUltimateReceiverNoRoles() {
        RolePlayer rolePlayer = new UltimateDestinationNoRoles();

        MessageContext messageContext11 = createMessageContext(soap11Factory);
        setUnprocessedHeaders(messageContext11, rolePlayer);
        try {
            HandlerUtils.checkMustUnderstand(messageContext11, null, null);
            fail("Should have gotten MustUnderstand Header Not Understood fault");
        } catch (AxisFault af) {
            // Expected path; should get exception for ultimate receiver not processed.
            String checkFault = af.toString();
            if (!checkFault.contains("Must Understand check failed")
                || !checkFault.contains(header_ultimateReceiver)) {
                fail("Did not get expected NotUnderstood AxisFault.  Unexpected fault " + af);
            }
        } catch (Exception e) {
            fail("Caught unexpected exception  + e");
        }
        
        MessageContext messageContext12 = createMessageContext(soap12Factory);
        setUnprocessedHeaders(messageContext12, rolePlayer);
        try {
            HandlerUtils.checkMustUnderstand(messageContext12, null, null);
            fail("Should have gotten MustUnderstand Header Not Understood fault");
        } catch (AxisFault af) {
            // Expected path; should get exception for ultimate receiver not processed.
            if (!af.toString().contains("Must Understand check failed")) {
                fail("Did not get expected NotUnderstood AxisFault.  Unexpected fault " + af);
            }
        } catch (Exception e) {
            fail("Caught unexpected exception  + e");
        }
    }
    
    /**
     * Test a RolePlayer that is not an ultimate receiver and does not act in any roles
     * corresponding to headers.  There should be no faults cause by the mustUnderstand headers
     */
    public void testNotUltimateReceiverNoRoles() {
        RolePlayer rolePlayer = new NotUltimateDestinationNoRoles();
        
        MessageContext msgCtx11 = createMessageContext(soap11Factory);
        setUnprocessedHeaders(msgCtx11, rolePlayer);
        try {
            HandlerUtils.checkMustUnderstand(msgCtx11, null, null);
        } catch (AxisFault e) {
            fail("Should not have caught and AxisFault");
        }
        
        MessageContext msgCtx12 = createMessageContext(soap12Factory);
        setUnprocessedHeaders(msgCtx12, rolePlayer);
        try {
            HandlerUtils.checkMustUnderstand(msgCtx12, null, null);
        } catch (AxisFault e) {
            fail("Should not have caught and AxisFault");
        }
    }
    
    /**
     * Test that the HandlerResolverImpl returns the correct roles defined in the handler config
     * file <soap-role> elements.
     */
    public void testHandlerResolverGetRoles() {
        ServiceDescription serviceDesc = DescriptionFactory.createServiceDescription(RoleBasedMUServiceImpl.class);
        HandlerResolverImpl handlerResolver = new HandlerResolverImpl(serviceDesc);
        
        EndpointDescription epDesc = serviceDesc.getEndpointDescriptions()[0];
        
        // Make sure the role information is specified in the handler config file
        HandlerChainsType epHandlerChains = epDesc.getHandlerChain();
        assertNotNull(epHandlerChains);
        List<HandlerChainType> epHandlerChain = epHandlerChains.getHandlerChain();
        assertEquals(1, epHandlerChain.size());
        List<HandlerType> epHandler = epHandlerChain.get(0).getHandler();
        assertEquals(1, epHandler.size());
        HandlerType handlerType = epHandler.get(0);
        List<org.apache.axis2.jaxws.description.xml.handler.String> soapRoles = 
            handlerType.getSoapRole();
        assertNotNull(soapRoles);
        assertEquals(1, soapRoles.size());
        String addedRole = soapRoles.get(0).getValue();
        assertEquals(roleHandlerAdded, addedRole);
        
        // Now verify the role information in the handler config file correctly affects the
        // roles played returned from the Resolver
        PortInfo portInfo = epDesc.getPortInfo();

        List<String> handlerRoles = handlerResolver.getRoles(portInfo);
        assertNotNull(handlerRoles);
        assertEquals(1, handlerRoles.size());
        assertEquals(roleHandlerAdded, handlerRoles.get(0));
        
        List<Handler> handlerChain = handlerResolver.getHandlerChain(portInfo);
        assertNotNull(handlerChain);
        assertEquals(1, handlerChain.size());
    }
    
    /**
     * Test that multiple <soap-role> elements in the handler config file work correctly. 
     */
    public void testHandlerResolverGerRoles2() {
        ServiceDescription serviceDesc = DescriptionFactory.createServiceDescription(RoleBasedMUServiceImpl2.class);
        HandlerResolverImpl handlerResolver = new HandlerResolverImpl(serviceDesc);
        EndpointDescription epDesc = serviceDesc.getEndpointDescriptions()[0];
        
        // Make sure the role information is specified in the handler config file
        HandlerChainsType epHandlerChains = epDesc.getHandlerChain();
        assertNotNull(epHandlerChains);
        List<HandlerChainType> epHandlerChain = epHandlerChains.getHandlerChain();
        assertEquals(1, epHandlerChain.size());
        List<HandlerType> epHandler = epHandlerChain.get(0).getHandler();
        assertEquals(1, epHandler.size());
        HandlerType handlerType = epHandler.get(0);
        List<org.apache.axis2.jaxws.description.xml.handler.String> soapRoles = 
            handlerType.getSoapRole();
        assertNotNull(soapRoles);
        assertEquals(2, soapRoles.size());
        ArrayList<String> checkRoles = new ArrayList<String>();
        checkRoles.add(soapRoles.get(0).getValue());
        checkRoles.add(soapRoles.get(1).getValue());
        assertTrue(checkRoles.contains(roleHandlerAdded));
        assertTrue(checkRoles.contains(roleHandlerAdded2));

        // Now verify the role information in the handler config file correctly affects the
        // roles played returned from the Resolver
        PortInfo portInfo = epDesc.getPortInfo();

        List<String> handlerRoles = handlerResolver.getRoles(portInfo);
        assertNotNull(handlerRoles);
        assertEquals(2, handlerRoles.size());
        ArrayList<String> checkResolverRoles = new ArrayList<String>();
        checkResolverRoles.add((String) handlerRoles.get(0));
        checkResolverRoles.add((String) handlerRoles.get(1));
        assertTrue(checkResolverRoles.contains(roleHandlerAdded));
        assertTrue(checkResolverRoles.contains(roleHandlerAdded2));
        
        List<Handler> handlerChain = handlerResolver.getHandlerChain(portInfo);
        assertNotNull(handlerChain);
        assertEquals(1, handlerChain.size());

    
    }
    
    /**
     * Test that a RolePlayer with no roles doesn't cause any problems in the mustUnderstand
     * checking.
     */
    public void testHandlerResolverEmptyRolesPlayed() {
        
        RolePlayer rolePlayer = new UltimateDestinationRoles();
        MessageContext msgCtx11 = createMessageContext(soap11Factory);
        setUnprocessedHeaders(msgCtx11, rolePlayer);
        // Indicate we understand (e.g. via handlers) all the headers.
        List<QName> understoodHeaders = new ArrayList<QName>();
        understoodHeaders.add(new QName(namespace, header_ultimateReceiver));
        understoodHeaders.add(new QName(namespace, header_rolePlayed));

        List<String> handlerRolePlayer = new ArrayList<String>();
        try {
            HandlerUtils.checkMustUnderstand(msgCtx11, understoodHeaders, handlerRolePlayer);
        } catch (AxisFault e) {
            fail("Should not have caught and AxisFault");
        }
    }
    
    // =============================================================================================
    // Utility methods
    // =============================================================================================

    private MessageContext createMessageContext(SOAPFactory soapFactory) {
        MessageContext messageContext = null;
        AxisService as1 = new AxisService("Service1");
        ConfigurationContext cc = null;
        try {
            cc = ConfigurationContextFactory.createEmptyConfigurationContext();
            AxisConfiguration ac = cc.getAxisConfiguration();
            ac.addService(as1);
            messageContext = cc.createMessageContext();
            messageContext.setAxisService(as1);

            SOAPEnvelope se = soapFactory.createSOAPEnvelope();

            SOAPHeader sh = soapFactory.createSOAPHeader(se);

            SOAPHeaderBlock shb1 = sh.addHeaderBlock(header_ultimateReceiver, 
                                                     omFactory.createOMNamespace(namespace, header_ultimateReceiver));
            // Since no role was set on the shb1, default is ultimate receiver
            shb1.setMustUnderstand(true);
            
            SOAPHeaderBlock shb2 = sh.addHeaderBlock(header_rolePlayed, 
                                                     omFactory.createOMNamespace(namespace, header_rolePlayed));
            shb2.setRole(rolePlayed1);
            shb2.setMustUnderstand(true);
            
            SOAPHeaderBlock shb3 = sh.addHeaderBlock(header_roleNotPlayed, 
                                                     omFactory.createOMNamespace(namespace, header_roleNotPlayed));
            shb3.setRole(roleNotPlayed);
            shb3.setMustUnderstand(true);
            
            SOAPHeaderBlock shb4 = sh.addHeaderBlock(header_bindingAdded,
                                                     omFactory.createOMNamespace(namespace, header_bindingAdded));
            shb4.setRole(roleBindingAdded);
            shb4.setMustUnderstand(true);
            
            // This header is destined for the ulmiate receiver, but it is already processed
            // so it shouldn't cause mustUnderstand fault
            SOAPHeaderBlock shb5 = sh.addHeaderBlock(header_ultimateReceiver_processed,
                                                     omFactory.createOMNamespace(namespace, header_ultimateReceiver_processed));
            // Since no role was set on the shb1, default is ultimate receiver
            shb5.setMustUnderstand(true);
            shb5.setProcessed();
            
            // Header targeted for SOAP11 role of Next, not set to MustUnderstand
            SOAPHeaderBlock shb6 = sh.addHeaderBlock(header_SoapNext,
                                                     omFactory.createOMNamespace(soap11Namespace, header_SoapNext));
            shb6.setRole(roleSoap11Next);

            messageContext.setEnvelope(se);
        } catch (AxisFault e) {
            fail("Caught unexpected exception creating message context" + e);
        }
        return messageContext;
    }
    
    private void setUnprocessedHeaders(MessageContext messageContext, RolePlayer rolePlayer) {
        SOAPEnvelope envelope = messageContext.getEnvelope();
        Iterator headerBlocks = envelope.getHeader().getHeadersToProcess(rolePlayer);
        List<QName> unprocessedHeaders = new ArrayList<QName>();
        while (headerBlocks.hasNext()) {
            SOAPHeaderBlock headerBlock = (SOAPHeaderBlock) headerBlocks.next();
            QName headerName = headerBlock.getQName();
            // if this header block has been processed or mustUnderstand isn't
            // turned on then its cool
            if (headerBlock.isProcessed() || !headerBlock.getMustUnderstand()) {
                continue;
            } else {
                unprocessedHeaders.add(headerName);
            }
        }
        if(unprocessedHeaders !=null && unprocessedHeaders.size()>0){
            messageContext.setProperty(Constants.UNPROCESSED_HEADER_QNAMES, unprocessedHeaders);           
        }       
    }
    
    private List<String> bindingAddedRole() {
        List<String> roles = new ArrayList<String>();
        roles.add(roleBindingAdded);
        return roles;
    }
    
    private List<String> noHeadersRole() {
        List<String> roles = new ArrayList<String>();
        roles.add(roleNoHeaders);
        return roles;
    }
    
    
    // =============================================================================================
    // Inner Test Classes
    // =============================================================================================
    
    class UltimateDestinationNoRoles implements RolePlayer {
        public List getRoles() {
            return null;
        }
        public boolean isUltimateDestination() {
            return true;
        }
    }

    class NotUltimateDestinationNoRoles implements RolePlayer {
        public List getRoles() {
            return null;
        }
        public boolean isUltimateDestination() {
            return false;
        }
    }
    
    class UltimateDestinationRoles implements RolePlayer {
        List<String> roles = new ArrayList<String>();
        public UltimateDestinationRoles() {
            roles.add(rolePlayed1);
        }
        public List getRoles() {
            return roles;
        }
        public boolean isUltimateDestination() {
            return true;
        }
    }
    
    @WebService
    @HandlerChain(file="RoleBasedMustUnderstandTests.xml")
    class RoleBasedMUServiceImpl {
        
    }

    @WebService
    @HandlerChain(file="RoleBasedMustUnderstandTests2.xml")
    class RoleBasedMUServiceImpl2 {
        
    }
}
