/**
 * 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.cxf.ws.addressing.soap;


import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.namespace.QName;

import org.w3c.dom.Element;
import org.w3c.dom.NodeList;


import org.apache.cxf.binding.soap.SoapFault;
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.binding.soap.SoapVersion;
import org.apache.cxf.headers.Header;
import org.apache.cxf.message.Exchange;
import org.apache.cxf.message.ExchangeImpl;
import org.apache.cxf.message.Message;
import org.apache.cxf.message.MessageImpl;
import org.apache.cxf.ws.addressing.AddressingPropertiesImpl;
import org.apache.cxf.ws.addressing.AttributedURIType;
import org.apache.cxf.ws.addressing.ContextUtils;
import org.apache.cxf.ws.addressing.EndpointReferenceType;
import org.apache.cxf.ws.addressing.Names;
import org.apache.cxf.ws.addressing.RelatesToType;
import org.apache.cxf.ws.addressing.v200408.AttributedURI;
import org.apache.cxf.ws.addressing.v200408.Relationship;
import org.apache.cxf.wsdl.EndpointReferenceUtils;
import org.easymock.IArgumentMatcher;
import org.easymock.classextension.EasyMock;
import org.easymock.classextension.IMocksControl;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import static org.apache.cxf.message.Message.MIME_HEADERS;
import static org.apache.cxf.message.Message.REQUESTOR_ROLE;
import static org.apache.cxf.ws.addressing.JAXWSAConstants.CLIENT_ADDRESSING_PROPERTIES_INBOUND;
import static org.apache.cxf.ws.addressing.JAXWSAConstants.CLIENT_ADDRESSING_PROPERTIES_OUTBOUND;
import static org.apache.cxf.ws.addressing.JAXWSAConstants.SERVER_ADDRESSING_PROPERTIES_INBOUND;
import static org.apache.cxf.ws.addressing.JAXWSAConstants.SERVER_ADDRESSING_PROPERTIES_OUTBOUND;


public class MAPCodecTest extends Assert {

    private MAPCodec codec;
    private IMocksControl control;
    private QName[] expectedNames;
    private Class<?>[] expectedDeclaredTypes;
    private Object[] expectedValues;
    private int expectedIndex;
    private String expectedNamespaceURI;
    private Map<String, List<String>> mimeHeaders;
    private Exchange correlatedExchange;
    private boolean expectRelatesTo;

    @Before
    public void setUp() {
        codec = new MAPCodec();
        control = EasyMock.createNiceControl();
    }

    @After
    public void tearDown() {
        expectedNames = null;
        expectedDeclaredTypes = null;
        expectedValues = null;
        expectedIndex = 0;
        expectedNamespaceURI = null;
        mimeHeaders = null;
        correlatedExchange = null;
    }

    @Test
    public void testGetHeaders() throws Exception {
        Set<QName> headers = codec.getUnderstoodHeaders();
        assertTrue("expected From header", 
                   headers.contains(Names.WSA_FROM_QNAME));
        assertTrue("expected To header", 
                   headers.contains(Names.WSA_TO_QNAME));
        assertTrue("expected ReplyTo header", 
                   headers.contains(Names.WSA_REPLYTO_QNAME));
        assertTrue("expected FaultTo header", 
                   headers.contains(Names.WSA_FAULTTO_QNAME));
        assertTrue("expected Action header", 
                   headers.contains(Names.WSA_ACTION_QNAME));
        assertTrue("expected MessageID header", 
                   headers.contains(Names.WSA_MESSAGEID_QNAME));
    }

    @Test
    public void testRequestorOutbound() throws Exception {
        SoapMessage message = setUpMessage(true, true);
        codec.handleMessage(message);
        control.verify();
        verifyMessage(message, true, true, true);
    }
    
    @Test
    public void testRequestorOutboundPreExistingSOAPAction() throws Exception {
        SoapMessage message = setUpMessage(true, true, false, true);
        codec.handleMessage(message);
        verifyAction();
        control.verify();
        verifyMessage(message, true, true, true);
    }
    
    @Test
    public void testRequestorOutboundNonNative() throws Exception {
        String uri = VersionTransformer.Names200408.WSA_NAMESPACE_NAME;
        SoapMessage message = 
            setUpMessage(true, true, false, false, uri);
        codec.handleMessage(message);
        control.verify();
        verifyMessage(message, true, true, false);
    }
    
    @Test
    public void testResponderInbound() throws Exception {
        SoapMessage message = setUpMessage(false, false);
        codec.handleMessage(message);
        control.verify();
        verifyMessage(message, false, false, true);
    }

    @Test
    public void testResponderOutbound() throws Exception {
        SoapMessage message = setUpMessage(false, true);
        codec.handleMessage(message);
        control.verify();
        verifyMessage(message, false, true, true);
    }
    
    @Test
    public void testResponderInboundNonNative() throws Exception {
        String uri = VersionTransformer.Names200408.WSA_NAMESPACE_NAME;
        SoapMessage message = 
            setUpMessage(false, false, false, false, uri);
        codec.handleMessage(message);
        control.verify();
        verifyMessage(message, false, false, false);
    }
    
    @Test
    public void testResponderOutboundInvalidMAP() throws Exception {
        SoapMessage message = setUpMessage(false, true, true);
        try {
            codec.handleMessage(message);
            fail("expected SOAPFaultException on invalid MAP");
        } catch (SoapFault sfe) {
            assertEquals("unexpected fault string",
                         "Duplicate Message ID urn:uuid:12345", 
                         sfe.getMessage());
        }
        control.verify();
        verifyMessage(message, false, true, true);
    }
    
    @Test
    public void testResponderOutboundPreExistingSOAPAction() throws Exception {
        SoapMessage message = setUpMessage(false, true, false, true);
        codec.handleMessage(message);
        verifyAction();
        control.verify();
        verifyMessage(message, false, true, true);
    }

    @Test
    public void testResponderOutboundNonNative() throws Exception {
        String uri = VersionTransformer.Names200408.WSA_NAMESPACE_NAME;
        SoapMessage message = 
            setUpMessage(false, true, false, false, uri);
        codec.handleMessage(message);
        control.verify();
        verifyMessage(message, false, true, false);
    }
    
    @Test
    public void testRequestorInbound() throws Exception {
        SoapMessage message = setUpMessage(true, false);
        codec.handleMessage(message);
        control.verify();
        verifyMessage(message, true, false, true);
    }
    
    @Test
    public void testRequestorInboundNonNative() throws Exception {
        String uri = VersionTransformer.Names200408.WSA_NAMESPACE_NAME;
        SoapMessage message = 
            setUpMessage(true, false, false, false, uri);
        codec.handleMessage(message);
        control.verify();
        verifyMessage(message, true, false, false);
    }

    private SoapMessage setUpMessage(boolean requestor, 
                                     boolean outbound)
        throws Exception {
        return setUpMessage(requestor, outbound, false); 
    }

    private SoapMessage setUpMessage(boolean requestor, 
                                     boolean outbound,
                                     boolean invalidMAP) 
        throws Exception {
        return setUpMessage(requestor, 
                            outbound,
                            invalidMAP,
                            false);
    }

    private SoapMessage setUpMessage(boolean requestor, 
                                     boolean outbound,
                                     boolean invalidMAP,
                                     boolean preExistingSOAPAction) 
        throws Exception {
        return setUpMessage(requestor, 
                            outbound,
                            invalidMAP,
                            preExistingSOAPAction,
                            Names.WSA_NAMESPACE_NAME);
    }

    private SoapMessage setUpMessage(boolean requestor, 
                                     boolean outbound,
                                     boolean invalidMAP,
                                     boolean preExistingSOAPAction,
                                     String exposeAs) 
        throws Exception {
        SoapMessage message = new SoapMessage(new MessageImpl());
        setUpOutbound(message, outbound);
        expectRelatesTo = (requestor && !outbound) || (!requestor && outbound);
        message.put(REQUESTOR_ROLE, Boolean.valueOf(requestor));
        String mapProperty = getMAPProperty(requestor, outbound);
        AddressingPropertiesImpl maps = getMAPs(requestor, outbound, exposeAs);
        final Element header = control.createMock(Element.class);
        codec.setHeaderFactory(new MAPCodec.HeaderFactory() {
            public Element getHeader(SoapVersion version) {
                return header;
            }
        });
        List<Header> headers = message.getHeaders();
        JAXBContext jaxbContext = control.createMock(JAXBContext.class);
        ContextUtils.setJAXBContext(jaxbContext);
        VersionTransformer.Names200408.setJAXBContext(jaxbContext);
        if (outbound) {
            setUpEncode(requestor,
                        message,
                        header,
                        maps,
                        mapProperty,
                        invalidMAP,
                        preExistingSOAPAction);
        } else {
            setUpDecode(message, headers, maps, mapProperty, requestor);
        }
        control.replay();
        return message;
    }

    private void setUpEncode(boolean requestor,
                             SoapMessage message,
                             Element header,
                             AddressingPropertiesImpl maps,
                             String mapProperty,
                             boolean invalidMAP,
                             boolean preExistingSOAPAction) throws Exception { 
        message.put(mapProperty, maps);
        Marshaller marshaller = control.createMock(Marshaller.class);
        ContextUtils.getJAXBContext().createMarshaller();
        EasyMock.expectLastCall().andReturn(marshaller);
        marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
        EasyMock.expectLastCall();
        IArgumentMatcher matcher = new JAXBEltMatcher();
        int expectedMarshals = requestor 
                               ? expectedValues.length - 1
                               : expectedValues.length;
        for (int i = 0; i < expectedMarshals; i++) {
            EasyMock.reportMatcher(matcher);
            EasyMock.eq(header);
            marshaller.marshal(null, header);
            EasyMock.expectLastCall();
        }
        
        NodeList children = control.createMock(NodeList.class);
        header.getChildNodes();
        EasyMock.expectLastCall().andReturn(children);
        children.getLength();
        EasyMock.expectLastCall().andReturn(expectedMarshals).anyTimes();
        for (int i = 0; i < expectedMarshals; i++) {
            Element child = control.createMock(Element.class);
            children.item(i);
            EasyMock.expectLastCall().andReturn(child);
            /*
            child.setAttributeNS(EasyMock.eq("http://www.w3.org/2000/xmlns/"),
                                 EasyMock.eq("xmlns:wsa"),
                                 EasyMock.eq(maps.getNamespaceURI()));
            EasyMock.expectLastCall();
            */
            child.getNamespaceURI();
            EasyMock.expectLastCall().andReturn(expectedNames[i].getNamespaceURI());
            child.getLocalName();
            EasyMock.expectLastCall().andReturn(expectedNames[i].getLocalPart());
        }
        
        mimeHeaders = new HashMap<String, List<String>>();
        message.put(MIME_HEADERS, mimeHeaders);
        if (preExistingSOAPAction) {
            List<String> soapAction = new ArrayList<String>();
            soapAction.add("\"foobar\"");
            mimeHeaders.put("SOAPAction", soapAction);
        }
        if (invalidMAP) {
            message.put("org.apache.cxf.ws.addressing.map.fault.name",
                        Names.DUPLICATE_MESSAGE_ID_NAME);
            message.put("org.apache.cxf.ws.addressing.map.fault.reason",
                        "Duplicate Message ID urn:uuid:12345");
        }
    }
    
    private void setUpDecode(SoapMessage message, 
                             List<Header> headers,
                             AddressingPropertiesImpl maps,
                             String mapProperty,
                             boolean requestor) throws Exception {
        Unmarshaller unmarshaller = control.createMock(Unmarshaller.class);
        ContextUtils.getJAXBContext().createUnmarshaller();
        EasyMock.expectLastCall().andReturn(unmarshaller);
        String uri = maps.getNamespaceURI();
        boolean exposedAsNative = Names.WSA_NAMESPACE_NAME.equals(uri);
        boolean exposedAs200408 = 
            VersionTransformer.Names200408.WSA_NAMESPACE_NAME.equals(uri);
        assertTrue("unexpected namescape URI: " + uri, 
                   exposedAsNative || exposedAs200408);
        setUpHeaderDecode(headers,
                          uri,
                          Names.WSA_MESSAGEID_NAME,
                          exposedAsNative
                          ? AttributedURIType.class
                          : AttributedURI.class,
                          0,
                          unmarshaller);
        setUpHeaderDecode(headers,
                          uri,
                          Names.WSA_TO_NAME,
                          exposedAsNative
                          ? AttributedURIType.class
                          : AttributedURI.class,
                          1,
                          unmarshaller);
        setUpHeaderDecode(headers,
                          uri,
                          Names.WSA_REPLYTO_NAME,
                          exposedAsNative
                          ? EndpointReferenceType.class
                          : VersionTransformer.Names200408.EPR_TYPE,
                          2,
                          unmarshaller);
        setUpHeaderDecode(headers,
                          uri,
                          Names.WSA_FAULTTO_NAME,
                          exposedAsNative
                          ? EndpointReferenceType.class
                          : VersionTransformer.Names200408.EPR_TYPE,
                          3,
                          unmarshaller);
        setUpHeaderDecode(headers,
                          uri,
                          Names.WSA_RELATESTO_NAME,
                          exposedAsNative
                          ? RelatesToType.class
                          : Relationship.class,
                          4,
                          unmarshaller);
        setUpHeaderDecode(headers,
                          uri,
                          Names.WSA_ACTION_NAME,
                          exposedAsNative
                          ? AttributedURIType.class
                          : AttributedURI.class,
                          5,
                          unmarshaller);
    }

    private <T> void setUpHeaderDecode(List<Header> headers,
                                       String uri,
                                       String name,
                                       Class<T> clz,
                                       int index,
                                       Unmarshaller unmarshaller) 
        throws Exception { 
        Element headerElement = control.createMock(Element.class);
        headers.add(new Header(new QName(uri, name), headerElement));
        headerElement.getNamespaceURI();
        EasyMock.expectLastCall().andReturn(uri);
        headerElement.getLocalName();
        EasyMock.expectLastCall().andReturn(name);
        Object v = expectedValues[index];
        JAXBElement<?> jaxbElement = 
            new JAXBElement<T>(new QName(uri, name), clz, clz.cast(v));
        unmarshaller.unmarshal(headerElement, clz);
        EasyMock.expectLastCall().andReturn(jaxbElement);
    }
    
    private void setUpOutbound(Message message, boolean outbound) {
        Exchange exchange = new ExchangeImpl();
        exchange.setOutMessage(outbound ? message : new MessageImpl());
        message.setExchange(exchange);
    }

    private String getMAPProperty(boolean requestor, boolean outbound) { 
        return requestor
               ? outbound
                 ? CLIENT_ADDRESSING_PROPERTIES_OUTBOUND
                 : CLIENT_ADDRESSING_PROPERTIES_INBOUND
               : outbound
                 ? SERVER_ADDRESSING_PROPERTIES_OUTBOUND
                 : SERVER_ADDRESSING_PROPERTIES_INBOUND;
    }

    private AddressingPropertiesImpl getMAPs(boolean requestor,
                                             boolean outbound,
                                             String uri) {
        AddressingPropertiesImpl maps = new AddressingPropertiesImpl();
        boolean exposeAsNative = Names.WSA_NAMESPACE_NAME.equals(uri);
        boolean exposeAs200408 = 
            VersionTransformer.Names200408.WSA_NAMESPACE_NAME.equals(uri);
        AttributedURIType id = 
            ContextUtils.getAttributedURI("urn:uuid:12345");
        maps.setMessageID(id);
        AttributedURIType to =
            ContextUtils.getAttributedURI("foobar");
        EndpointReferenceType toEpr = EndpointReferenceUtils.getEndpointReference(to);
        maps.setTo(toEpr);
        EndpointReferenceType replyTo = new EndpointReferenceType();
        String anonymous = 
            exposeAsNative
            ? Names.WSA_ANONYMOUS_ADDRESS
            : VersionTransformer.Names200408.WSA_ANONYMOUS_ADDRESS;
        replyTo.setAddress(
            ContextUtils.getAttributedURI(anonymous));
        maps.setReplyTo(replyTo);
        EndpointReferenceType faultTo = new EndpointReferenceType();
        anonymous = 
            exposeAsNative
            ? Names.WSA_ANONYMOUS_ADDRESS
            : VersionTransformer.Names200408.WSA_ANONYMOUS_ADDRESS;
        faultTo.setAddress(
            ContextUtils.getAttributedURI(anonymous));
        maps.setFaultTo(faultTo);
        RelatesToType relatesTo = null;
        if (expectRelatesTo) {
            String correlationID = "urn:uuid:67890";
            relatesTo = new RelatesToType(); 
            relatesTo.setValue(correlationID);
            maps.setRelatesTo(relatesTo);
            correlatedExchange = new ExchangeImpl();
            codec.uncorrelatedExchanges.put(correlationID, correlatedExchange);
        }
        AttributedURIType action = 
            ContextUtils.getAttributedURI("http://foo/bar/SEI/opRequest");
        maps.setAction(action);
        maps.exposeAs(uri);
        expectedNamespaceURI = uri;

        expectedNames = 
            new QName[] {new QName(uri, Names.WSA_MESSAGEID_NAME), 
                         new QName(uri, Names.WSA_TO_NAME), 
                         new QName(uri, Names.WSA_REPLYTO_NAME),
                         new QName(uri, Names.WSA_FAULTTO_NAME),
                         new QName(uri, Names.WSA_RELATESTO_NAME),
                         new QName(uri, Names.WSA_ACTION_NAME)};
        if (exposeAsNative) {
            expectedValues = new Object[] {id, to, replyTo, faultTo, relatesTo, action};
            expectedDeclaredTypes = 
                new Class<?>[] {AttributedURIType.class,
                                AttributedURIType.class,
                                EndpointReferenceType.class,
                                EndpointReferenceType.class,
                                RelatesToType.class, 
                                AttributedURIType.class};
        } else if (exposeAs200408) {
            expectedValues = new Object[] {VersionTransformer.convert(id),
                                           VersionTransformer.convert(to),
                                           VersionTransformer.convert(replyTo),
                                           VersionTransformer.convert(faultTo),
                                           VersionTransformer.convert(relatesTo),
                                           VersionTransformer.convert(action)};
            if (!outbound) {
                // conversion from 2004/08 to 2005/08 anonymous address
                // occurs transparently in VersionTransformer
                VersionTransformer.Names200408.EPR_TYPE.cast(expectedValues[2]).
                    getAddress().setValue(Names.WSA_ANONYMOUS_ADDRESS);
                VersionTransformer.Names200408.EPR_TYPE.cast(expectedValues[3]).
                    getAddress().setValue(Names.WSA_ANONYMOUS_ADDRESS);    
            }
            expectedDeclaredTypes = 
                new Class<?>[] {AttributedURI.class,
                                AttributedURI.class,
                                VersionTransformer.Names200408.EPR_TYPE,
                                VersionTransformer.Names200408.EPR_TYPE,
                                Relationship.class, 
                                AttributedURI.class};
        } else {
            fail("unexpected namespace URI: " + uri);
        }
        return maps;
    }
    
    private final class JAXBEltMatcher implements IArgumentMatcher {
        public boolean matches(Object obj) {
            QName name = expectedNames[expectedIndex];
            Class<?> declaredType = expectedDeclaredTypes[expectedIndex];
            Object value = expectedValues[expectedIndex];
            boolean ret = false;
            expectedIndex++;
            if (expectedIndex == 5 && !expectRelatesTo) {
                return true;
            }
            if (obj instanceof JAXBElement) {
                JAXBElement other = (JAXBElement)obj;
                ret = name.equals(other.getName()) 
                      && declaredType.isAssignableFrom(other.getDeclaredType())
                      && compare(value, other.getValue());
            }
            return ret;
        }

        public void appendTo(StringBuffer buffer) {
            buffer.append("JAXBElements did not match[" + expectedIndex + "]");
        }
        
        private boolean compare(Object a, Object b) {
            boolean ret = false;
            if (a instanceof AttributedURI && b instanceof AttributedURI) {
                ret = ((AttributedURI)a).getValue().equals(((AttributedURI)b).getValue());
            } else if (a instanceof AttributedURIType && b instanceof AttributedURIType) {
                ret = ((AttributedURIType)a).getValue().equals(((AttributedURIType)b).getValue());
            } else if (a instanceof EndpointReferenceType && b instanceof EndpointReferenceType) {
                EndpointReferenceType aEPR = (EndpointReferenceType)a;
                EndpointReferenceType bEPR = (EndpointReferenceType)b;
                ret = aEPR.getAddress() != null
                      && bEPR.getAddress() != null
                      && aEPR.getAddress().getValue().equals(bEPR.getAddress().getValue());
            } else if (VersionTransformer.Names200408.EPR_TYPE.isInstance(a)
                       && VersionTransformer.Names200408.EPR_TYPE.isInstance(b)) {
                ret = VersionTransformer.Names200408.EPR_TYPE.cast(a).getAddress() != null
                      && VersionTransformer.Names200408.EPR_TYPE.cast(b).getAddress() != null
                      && VersionTransformer.Names200408.EPR_TYPE.cast(a).getAddress().getValue().equals(
                             VersionTransformer.Names200408.EPR_TYPE.cast(b).getAddress().getValue());
            } else if (a instanceof Relationship && b instanceof Relationship) {
                ret = ((Relationship)a).getValue().equals(((Relationship)b).getValue());
            } else if (a instanceof RelatesToType && b instanceof RelatesToType) {
                ret = ((RelatesToType)a).getValue().equals(((RelatesToType)b).getValue());
            } 
            return ret;
        }
    }

    private boolean verifyMAPs(Object obj) {
        if (obj instanceof AddressingPropertiesImpl) {
            AddressingPropertiesImpl other = (AddressingPropertiesImpl)obj;
            return compareExpected(other);
        } 
        return false;
    }    

    private boolean compareExpected(AddressingPropertiesImpl other) {
        boolean ret = false;
        String uri = other.getNamespaceURI();
        boolean exposedAsNative = Names.WSA_NAMESPACE_NAME.equals(uri);
        boolean exposedAs200408 = 
            VersionTransformer.Names200408.WSA_NAMESPACE_NAME.equals(uri);
        if (exposedAsNative || exposedAs200408) {
            String expectedMessageID = 
                exposedAsNative
                ? ((AttributedURIType)expectedValues[0]).getValue()
                : ((AttributedURI)expectedValues[0]).getValue();
            String expectedTo =
                exposedAsNative
                ? ((AttributedURIType)expectedValues[1]).getValue()
                : ((AttributedURI)expectedValues[1]).getValue();
            String expectedReplyTo = 
                exposedAsNative
                ? ((EndpointReferenceType)expectedValues[2]).getAddress().getValue()
                : (VersionTransformer.Names200408.EPR_TYPE.cast(expectedValues[2])).
                    getAddress().getValue();
            String expectedAction =                    
                exposedAsNative
                ? ((AttributedURIType)expectedValues[5]).getValue()
                : ((AttributedURI)expectedValues[5]).getValue();

            ret = expectedMessageID.equals(other.getMessageID().getValue())
                  && expectedTo.equals(other.getTo().getValue())
                  && expectedReplyTo.equals(
                        other.getReplyTo().getAddress().getValue())
                  && expectedAction.equals(other.getAction().getValue())
                  && expectedNamespaceURI.equals(other.getNamespaceURI());
            if (expectRelatesTo) {
                String expectedRelatesTo = 
                    exposedAsNative
                    ? ((RelatesToType)expectedValues[4]).getValue()
                    : ((Relationship)expectedValues[4]).getValue();
                ret = ret
                      && expectedRelatesTo.equals(other.getRelatesTo().getValue());
            }
        } 
        return ret;
    }
        
    private void verifyAction() {
        List<?> soapAction = (List<?>)mimeHeaders.get("SOAPAction");
        assertNotNull("expected propogated action", soapAction);
        assertEquals("expected single action", 1, soapAction.size());
        String expectedAction =
            "\"" + ((AttributedURIType)expectedValues[5]).getValue() + "\"";
        assertEquals("expected propogated action", 
                     expectedAction,
                     soapAction.get(0));
    }

    private void verifyMessage(SoapMessage message,
                               boolean requestor,
                               boolean outbound,
                               boolean exposedAsNative) {
        if (requestor) {
            if (outbound) {
                String id = expectedValues[0] instanceof AttributedURIType
                            ? ((AttributedURIType)expectedValues[0]).getValue()
                            : ((AttributedURI)expectedValues[0]).getValue();
                //assertTrue("expected correlationID : " + id + " in map: " + codec.uncorrelatedExchanges,
                //           codec.uncorrelatedExchanges.containsKey(id));
                assertSame("unexpected correlated exchange",
                           codec.uncorrelatedExchanges.get(id),
                           message.getExchange());
            } else {
                assertSame("unexpected correlated exchange",
                           correlatedExchange,
                           message.getExchange());
            }
        }
        if (outbound) {
            int expectedMarshals = requestor 
                                   ? expectedValues.length - 1
                                   : expectedValues.length;
            List<Header> headers = message.getHeaders();
            assertTrue("expected holders added to header list",
                       headers.size() >= expectedMarshals);
            for (int i = 0; i < expectedMarshals; i++) {
                assertTrue("expected " + expectedNames[i] + " added to headers",
                           message.hasHeader(expectedNames[i]));
            }
        }
        assertTrue("unexpected MAPs",
                   verifyMAPs(message.get(getMAPProperty(requestor, outbound))));
        
    }
}


