/**
* 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.jaxws.handler.soap;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.MimeHeaders;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPBodyElement;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPHeaderElement;
import javax.xml.soap.SOAPMessage;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.ws.Binding;
import javax.xml.ws.handler.Handler;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.apache.cxf.binding.soap.Soap11;
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.binding.soap.SoapVersion;
import org.apache.cxf.binding.soap.SoapVersionFactory;
import org.apache.cxf.bus.managers.PhaseManagerImpl;
import org.apache.cxf.headers.Header;
import org.apache.cxf.helpers.DOMUtils;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.interceptor.InterceptorChain;
import org.apache.cxf.io.CachedOutputStream;
import org.apache.cxf.jaxws.handler.AbstractProtocolHandlerInterceptor;
import org.apache.cxf.jaxws.handler.HandlerChainInvoker;
import org.apache.cxf.message.Exchange;
import org.apache.cxf.message.MessageImpl;
import org.apache.cxf.phase.Phase;
import org.apache.cxf.phase.PhaseInterceptorChain;
import org.apache.cxf.staxutils.PartialXMLStreamReader;
import org.apache.cxf.staxutils.StaxUtils;
import org.easymock.IMocksControl;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import static org.easymock.EasyMock.createNiceControl;
import static org.easymock.EasyMock.expect;
public class SOAPHandlerInterceptorTest extends Assert {
@Before
public void setUp() {
}
@After
public void tearDown() {
}
// SAAJ tree is created from DOMXMLStreamWriter. Any changes to SOAPMessage should be streamed back to
// outputStream
@Test
public void testChangeSOAPBodyOutBound() throws Exception {
@SuppressWarnings("rawtypes")
List<Handler> list = new ArrayList<>();
list.add(new SOAPHandler<SOAPMessageContext>() {
public boolean handleMessage(SOAPMessageContext smc) {
Boolean outboundProperty = (Boolean)smc.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
if (outboundProperty.booleanValue()) {
try {
smc.setMessage(prepareSOAPMessage("resources/greetMeRpcLitRespChanged.xml"));
} catch (Exception e) {
throw new Fault(e);
}
}
return true;
}
public boolean handleFault(SOAPMessageContext smc) {
return true;
}
public Set<QName> getHeaders() {
return null;
}
public void close(MessageContext messageContext) {
}
});
HandlerChainInvoker invoker = new HandlerChainInvoker(list);
IMocksControl control = createNiceControl();
Binding binding = control.createMock(Binding.class);
expect(binding.getHandlerChain()).andReturn(list).anyTimes();
Exchange exchange = control.createMock(Exchange.class);
expect(exchange.get(HandlerChainInvoker.class)).andReturn(invoker).anyTimes();
SoapMessage message = new SoapMessage(new MessageImpl());
message.setExchange(exchange);
// This is to set direction to outbound
expect(exchange.getOutMessage()).andReturn(message).anyTimes();
CachedStream originalEmptyOs = new CachedStream();
XMLStreamWriter writer = StaxUtils.createXMLStreamWriter(originalEmptyOs);
message.setContent(XMLStreamWriter.class, writer);
InterceptorChain chain = new PhaseInterceptorChain((new PhaseManagerImpl()).getOutPhases());
//Interceptors after SOAPHandlerInterceptor DOMXMLStreamWriter to write
chain.add(new AbstractProtocolHandlerInterceptor<SoapMessage>(binding, Phase.MARSHAL) {
public void handleMessage(SoapMessage message) throws Fault {
try {
XMLStreamWriter writer = message.getContent(XMLStreamWriter.class);
SoapVersion soapVersion = Soap11.getInstance();
writer.setPrefix("soap", soapVersion.getNamespace());
writer.writeStartElement("soap",
soapVersion.getEnvelope().getLocalPart(),
soapVersion.getNamespace());
writer.writeNamespace("soap", soapVersion.getNamespace());
writer.writeEndElement();
writer.flush();
} catch (Exception e) {
// do nothing
}
}
});
chain.add(new SOAPHandlerInterceptor(binding));
message.setInterceptorChain(chain);
control.replay();
chain.doIntercept(message);
control.verify();
writer.flush();
// Verify SOAPMessage
SOAPMessage resultedMessage = message.getContent(SOAPMessage.class);
assertNotNull(resultedMessage);
SOAPBody bodyNew = resultedMessage.getSOAPBody();
Iterator<?> itNew = bodyNew.getChildElements(new QName("http://apache.org/hello_world_rpclit",
"sendReceiveDataResponse"));
SOAPBodyElement bodyElementNew = (SOAPBodyElement)itNew.next();
Iterator<?> outIt = bodyElementNew
.getChildElements(new QName("http://apache.org/hello_world_rpclit/types", "out"));
Element outElement = (SOAPElement)outIt.next();
assertNotNull(outElement);
Element elem3Element =
DOMUtils.findAllElementsByTagNameNS(outElement,
"http://apache.org/hello_world_rpclit/types",
"elem3").get(0);
assertEquals("100", elem3Element.getTextContent());
}
@Test
public void testChangeSOAPHeaderInBound() throws Exception {
@SuppressWarnings("rawtypes")
List<Handler> list = new ArrayList<>();
list.add(new SOAPHandler<SOAPMessageContext>() {
public boolean handleMessage(SOAPMessageContext smc) {
try {
Boolean outboundProperty = (Boolean)smc.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
if (!outboundProperty.booleanValue()) {
// change mustUnderstand to false
SOAPMessage message = smc.getMessage();
SOAPHeader soapHeader = message.getSOAPHeader();
Element headerElementNew = (Element)soapHeader.getFirstChild();
SoapVersion soapVersion = Soap11.getInstance();
Attr attr =
headerElementNew.getOwnerDocument().createAttributeNS(soapVersion.getNamespace(),
"SOAP-ENV:mustUnderstand");
attr.setValue("false");
headerElementNew.setAttributeNodeNS(attr);
}
} catch (Exception e) {
throw new Fault(e);
}
return true;
}
public boolean handleFault(SOAPMessageContext smc) {
return true;
}
public Set<QName> getHeaders() {
return null;
}
public void close(MessageContext messageContext) {
}
});
HandlerChainInvoker invoker = new HandlerChainInvoker(list);
IMocksControl control = createNiceControl();
Binding binding = control.createMock(Binding.class);
expect(binding.getHandlerChain()).andReturn(list).anyTimes();
Exchange exchange = control.createMock(Exchange.class);
expect(exchange.get(HandlerChainInvoker.class)).andReturn(invoker).anyTimes();
// This is to set direction to inbound
expect(exchange.getOutMessage()).andReturn(null);
SoapMessage message = new SoapMessage(new MessageImpl());
message.setExchange(exchange);
XMLStreamReader reader = preparemXMLStreamReader("resources/greetMeRpcLitReq.xml");
message.setContent(XMLStreamReader.class, reader);
Object[] headerInfo = prepareSOAPHeader();
message.setContent(Node.class, headerInfo[0]);
Node node = ((Element) headerInfo[1]).getFirstChild();
message.getHeaders().add(new Header(new QName(node.getNamespaceURI(), node.getLocalName()), node));
control.replay();
SOAPHandlerInterceptor li = new SOAPHandlerInterceptor(binding);
li.handleMessage(message);
control.verify();
// Verify SOAPMessage header
SOAPMessage soapMessageNew = message.getContent(SOAPMessage.class);
Element headerElementNew = DOMUtils.getFirstElement(soapMessageNew.getSOAPHeader());
SoapVersion soapVersion = Soap11.getInstance();
assertEquals("false", headerElementNew.getAttributeNS(soapVersion.getNamespace(), "mustUnderstand"));
// Verify XMLStreamReader
XMLStreamReader xmlReader = message.getContent(XMLStreamReader.class);
QName qn = xmlReader.getName();
assertEquals("sendReceiveData", qn.getLocalPart());
// Verify Header Element
Iterator<Header> iter = message.getHeaders().iterator();
Element requiredHeader = null;
while (iter.hasNext()) {
Header localHdr = iter.next();
if (localHdr.getObject() instanceof Element) {
Element elem = (Element) localHdr.getObject();
if (elem.getNamespaceURI().equals("http://apache.org/hello_world_rpclit/types")
&& elem.getLocalName().equals("header1")) {
requiredHeader = (Element) localHdr.getObject();
break;
}
}
}
assertNotNull("Should have found header1", requiredHeader);
assertEquals("false", requiredHeader.getAttributeNS(soapVersion.getNamespace(), "mustUnderstand"));
}
@Test
public void testChangeSOAPHeaderOutBound() throws Exception {
@SuppressWarnings("rawtypes")
List<Handler> list = new ArrayList<>();
list.add(new SOAPHandler<SOAPMessageContext>() {
public boolean handleMessage(SOAPMessageContext smc) {
try {
Boolean outboundProperty = (Boolean)smc.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
if (outboundProperty.booleanValue()) {
// change mustUnderstand to false
SOAPMessage message = smc.getMessage();
SOAPHeader soapHeader = message.getSOAPHeader();
Iterator<?> it = soapHeader.getChildElements(new QName(
"http://apache.org/hello_world_rpclit/types", "header1"));
SOAPHeaderElement headerElementNew = (SOAPHeaderElement)it.next();
SoapVersion soapVersion = Soap11.getInstance();
Attr attr =
headerElementNew.getOwnerDocument().createAttributeNS(soapVersion.getNamespace(),
"SOAP-ENV:mustUnderstand");
attr.setValue("false");
headerElementNew.setAttributeNodeNS(attr);
}
} catch (Exception e) {
throw new Fault(e);
}
return true;
}
public boolean handleFault(SOAPMessageContext smc) {
return true;
}
public Set<QName> getHeaders() {
return null;
}
public void close(MessageContext messageContext) {
}
});
HandlerChainInvoker invoker = new HandlerChainInvoker(list);
IMocksControl control = createNiceControl();
Binding binding = control.createMock(Binding.class);
expect(binding.getHandlerChain()).andReturn(list).anyTimes();
Exchange exchange = control.createMock(Exchange.class);
expect(exchange.get(HandlerChainInvoker.class)).andReturn(invoker).anyTimes();
SoapMessage message = new SoapMessage(new MessageImpl());
message.setExchange(exchange);
// This is to set direction to outbound
expect(exchange.getOutMessage()).andReturn(message).anyTimes();
CachedStream originalEmptyOs = new CachedStream();
message.setContent(OutputStream.class, originalEmptyOs);
InterceptorChain chain = new PhaseInterceptorChain((new PhaseManagerImpl()).getOutPhases());
//Interceptors after SOAPHandlerInterceptor DOMXMLStreamWriter to write
chain.add(new AbstractProtocolHandlerInterceptor<SoapMessage>(binding, Phase.MARSHAL) {
public void handleMessage(SoapMessage message) throws Fault {
try {
XMLStreamWriter writer = message.getContent(XMLStreamWriter.class);
SoapVersion soapVersion = Soap11.getInstance();
writer.setPrefix("soap", soapVersion.getNamespace());
writer.writeStartElement("soap",
soapVersion.getEnvelope().getLocalPart(),
soapVersion.getNamespace());
writer.writeNamespace("soap", soapVersion.getNamespace());
Object[] headerInfo = prepareSOAPHeader();
StaxUtils.writeElement((Element) headerInfo[1], writer, true, false);
writer.writeEndElement();
writer.flush();
} catch (Exception e) {
// do nothing
}
}
});
chain.add(new SOAPHandlerInterceptor(binding));
message.setInterceptorChain(chain);
control.replay();
chain.doIntercept(message);
control.verify();
// Verify SOAPMessage header
SOAPMessage soapMessageNew = message.getContent(SOAPMessage.class);
SOAPHeader soapHeader = soapMessageNew.getSOAPHeader();
Iterator<?> itNew = soapHeader
.getChildElements(new QName("http://apache.org/hello_world_rpclit/types",
"header1"));
SOAPHeaderElement headerElementNew = (SOAPHeaderElement)itNew.next();
SoapVersion soapVersion = Soap11.getInstance();
assertEquals("false", headerElementNew.getAttributeNS(soapVersion.getNamespace(), "mustUnderstand"));
originalEmptyOs.close();
}
@Test
public void testGetSOAPMessageInBound() throws Exception {
@SuppressWarnings("rawtypes")
List<Handler> list = new ArrayList<>();
list.add(new SOAPHandler<SOAPMessageContext>() {
public boolean handleMessage(SOAPMessageContext smc) {
try {
smc.getMessage();
} catch (Exception e) {
throw new Fault(e);
}
return true;
}
public boolean handleFault(SOAPMessageContext smc) {
return true;
}
public Set<QName> getHeaders() {
return null;
}
public void close(MessageContext messageContext) {
}
});
HandlerChainInvoker invoker = new HandlerChainInvoker(list);
IMocksControl control = createNiceControl();
Binding binding = control.createMock(Binding.class);
Exchange exchange = control.createMock(Exchange.class);
expect(binding.getHandlerChain()).andReturn(list).anyTimes();
expect(exchange.get(HandlerChainInvoker.class)).andReturn(invoker).anyTimes();
// This is to set direction to inbound
expect(exchange.getOutMessage()).andReturn(null);
SoapMessage message = new SoapMessage(new MessageImpl());
message.setExchange(exchange);
XMLStreamReader reader = preparemXMLStreamReader("resources/greetMeRpcLitReq.xml");
message.setContent(XMLStreamReader.class, reader);
control.replay();
SOAPHandlerInterceptor li = new SOAPHandlerInterceptor(binding);
li.handleMessage(message);
control.verify();
// Verify SOAPMessage
SOAPMessage soapMessageNew = message.getContent(SOAPMessage.class);
SOAPBody bodyNew = soapMessageNew.getSOAPBody();
Iterator<?> itNew = bodyNew.getChildElements();
SOAPBodyElement bodyElementNew = (SOAPBodyElement)itNew.next();
assertEquals("sendReceiveData", bodyElementNew.getLocalName());
// Verify the XMLStreamReader
XMLStreamReader xmlReader = message.getContent(XMLStreamReader.class);
QName qn = xmlReader.getName();
assertEquals("sendReceiveData", qn.getLocalPart());
}
@Test
public void testGetUnderstoodHeadersReturnsNull() {
@SuppressWarnings("rawtypes")
List<Handler> list = new ArrayList<>();
list.add(new SOAPHandler<SOAPMessageContext>() {
public boolean handleMessage(SOAPMessageContext smc) {
return true;
}
public boolean handleFault(SOAPMessageContext smc) {
return true;
}
public Set<QName> getHeaders() {
return null;
}
public void close(MessageContext messageContext) {
}
});
HandlerChainInvoker invoker = new HandlerChainInvoker(list);
IMocksControl control = createNiceControl();
Binding binding = control.createMock(Binding.class);
expect(binding.getHandlerChain()).andReturn(list).anyTimes();
SoapMessage message = control.createMock(SoapMessage.class);
Exchange exchange = control.createMock(Exchange.class);
expect(message.getExchange()).andReturn(exchange).anyTimes();
expect(message.keySet()).andReturn(new HashSet<>());
expect(exchange.get(HandlerChainInvoker.class)).andReturn(invoker);
control.replay();
SOAPHandlerInterceptor li = new SOAPHandlerInterceptor(binding);
Set<QName> understood = li.getUnderstoodHeaders();
assertNotNull(understood);
assertTrue(understood.isEmpty());
}
private XMLStreamReader preparemXMLStreamReader(String resouceName) throws Exception {
InputStream is = this.getClass().getResourceAsStream(resouceName);
XMLStreamReader xmlReader = XMLInputFactory.newInstance().createXMLStreamReader(is);
// skip until soap body
if (xmlReader.nextTag() == XMLStreamConstants.START_ELEMENT) {
String ns = xmlReader.getNamespaceURI();
SoapVersion soapVersion = SoapVersionFactory.getInstance().getSoapVersion(ns);
// message.setVersion(soapVersion);
QName qn = xmlReader.getName();
while (!qn.equals(soapVersion.getBody()) && !qn.equals(soapVersion.getHeader())) {
while (xmlReader.nextTag() != XMLStreamConstants.START_ELEMENT) {
// nothing to do
}
qn = xmlReader.getName();
}
if (qn.equals(soapVersion.getHeader())) {
XMLStreamReader filteredReader = new PartialXMLStreamReader(xmlReader, soapVersion.getBody());
StaxUtils.read(filteredReader);
}
// advance just past body.
xmlReader.next();
while (xmlReader.isWhiteSpace()) {
xmlReader.next();
}
}
return xmlReader;
}
private Object[] prepareSOAPHeader() throws Exception {
Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
SoapVersion soapVersion = Soap11.getInstance();
Element envElement = doc.createElementNS(soapVersion.getEnvelope().getNamespaceURI(),
soapVersion.getEnvelope().getLocalPart());
Element headerElement = doc.createElementNS(soapVersion.getNamespace(),
soapVersion.getHeader().getLocalPart());
Element bodyElement = doc.createElementNS(soapVersion.getBody().getNamespaceURI(),
soapVersion.getBody().getLocalPart());
Element childElement = doc.createElementNS("http://apache.org/hello_world_rpclit/types",
"ns2:header1");
Attr attr =
childElement.getOwnerDocument().createAttributeNS(soapVersion.getNamespace(),
"SOAP-ENV:mustUnderstand");
attr.setValue("true");
childElement.setAttributeNodeNS(attr);
headerElement.appendChild(childElement);
envElement.appendChild(headerElement);
envElement.appendChild(bodyElement);
doc.appendChild(envElement);
return new Object[] {doc, headerElement};
}
private SOAPMessage prepareSOAPMessage(String resouceName) throws Exception {
InputStream is = this.getClass().getResourceAsStream(resouceName);
SOAPMessage soapMessage = null;
MessageFactory factory = MessageFactory.newInstance();
MimeHeaders mhs = null;
soapMessage = factory.createMessage(mhs, is);
return soapMessage;
}
private class CachedStream extends CachedOutputStream {
protected void doFlush() throws IOException {
currentStream.flush();
}
protected void doClose() throws IOException {
}
protected void onWrite() throws IOException {
}
}
}