Description
When our response text contains escape characters then those will be encoded twice.
For example when I return "hello > world". Cxf will encode this as "hello &> world"
hello &> world
Below a test with all the interceptors used in our setup on production:
package be.vlaanderen.omgeving.rest.controller.parameter.v1; import org.apache.cxf.Bus; import org.apache.cxf.BusFactory; import org.apache.cxf.binding.Binding; import org.apache.cxf.binding.BindingFactory; import org.apache.cxf.binding.BindingFactoryManager; import org.apache.cxf.binding.soap.SoapMessage; import org.apache.cxf.binding.soap.saaj.SAAJOutInterceptor; import org.apache.cxf.endpoint.Endpoint; import org.apache.cxf.endpoint.EndpointException; import org.apache.cxf.endpoint.EndpointImpl; import org.apache.cxf.jaxb.JAXBDataBinding; import org.apache.cxf.message.Exchange; import org.apache.cxf.message.ExchangeImpl; import org.apache.cxf.message.MessageImpl; import org.apache.cxf.phase.PhaseInterceptorChain; import org.apache.cxf.service.Service; import org.apache.cxf.service.model.BindingOperationInfo; import org.apache.cxf.service.model.EndpointInfo; import org.apache.cxf.staxutils.StaxUtils; import org.apache.cxf.wsdl.interceptors.BareOutInterceptor; import org.apache.cxf.wsdl.interceptors.DocLiteralInInterceptor; import org.apache.cxf.wsdl11.WSDLServiceFactory; import org.junit.Test; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.namespace.QName; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import javax.xml.stream.XMLStreamWriter; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.util.List; import java.util.TreeSet; import static java.util.Collections.emptyList; import static org.apache.cxf.interceptor.AbstractOutDatabindingInterceptor.OUT_BUFFERING; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; public class CxfIsUnstable { CxfFacade cxf = new CxfFacade(); @Test public void givenAResponseWithAmpersans_whenMarshallingToXml_theAmpersandAreEncoded_onlyOnce() throws Exception { cxf.setupExampleService(); String request = cxf.demarshallRequest("<request>hello & world</request>"); assertThat(request).isEqualTo("hello & world"); String responseXml = cxf.marshallResponse("hello & world"); assertThat(responseXml).isEqualTo("<response>hello & world<</response>"); } static class CxfFacade { private final SAAJOutInterceptor saajOutInterceptor = new SAAJOutInterceptor(); private final BareOutInterceptor outInterceptor = new BareOutInterceptor(); private final DocLiteralInInterceptor inInterceptor = new DocLiteralInInterceptor(); private SoapMessage message; private List<Object> messageContents; public String demarshallRequest(String input) throws XMLStreamException { message.setContent(XMLStreamReader.class, XMLInputFactory.newInstance().createXMLStreamReader(new ByteArrayInputStream(input.getBytes()))); StaxUtils.skipToStartOfElement(message.getContent(XMLStreamReader.class)); inInterceptor.handleMessage(message); assertThat(message.getContent(Exception.class)).isNull(); messageContents = message.getContent(List.class); String requestContent = (String) messageContents.get(0); saajOutInterceptor.handleMessage(message); return requestContent; } public String marshallResponse(String responseBody) throws Exception { messageContents.set(0, responseBody); ByteArrayOutputStream boas = new ByteArrayOutputStream(); XMLOutputFactory xmlOutputFactory = XMLOutputFactory.newInstance(); XMLStreamWriter output = xmlOutputFactory.createXMLStreamWriter(boas); message.setContent(XMLStreamWriter.class, output); message.put(OUT_BUFFERING, "true"); outInterceptor.handleMessage(message); output.flush(); boas.flush(); return boas.toString(); } public void setupExampleService() throws JAXBException, EndpointException { Bus bus = BusFactory.newInstance().createBus(); BindingFactoryManager bfm = bus.getExtension(BindingFactoryManager.class); BindingFactory bf = mock(BindingFactory.class); Binding binding = mock(Binding.class); given(bf.createBinding(null)).willReturn(binding); given(binding.getInFaultInterceptors()).willReturn(emptyList()); given(binding.getOutFaultInterceptors()).willReturn(emptyList()); bfm.registerBindingFactory("http://schemas.xmlsoap.org/wsdl/soap/", bf); String ns = "http://webservice.example-V1.vlaanderen.be"; WSDLServiceFactory factory = new WSDLServiceFactory(bus, "classpath:/files/ExampleService.wsdl"); Service service = factory.create(); service.setDataBinding(new JAXBDataBinding(JAXBContext.newInstance())); EndpointInfo endpointInfo = service.getEndpointInfo(new QName(ns, "exampleServicePort")); EndpointImpl endpoint = new EndpointImpl(bus, service, endpointInfo); BindingOperationInfo operation = endpointInfo.getBinding().getOperation(new QName(ns, "example")); operation.getOperationInfo().getInput().getMessagePartByIndex(0).setTypeClass(String.class); message = new SoapMessage(new MessageImpl()); Exchange exchange = new ExchangeImpl(); message.setExchange(exchange); message.setInterceptorChain(new PhaseInterceptorChain(new TreeSet<>())); exchange.put(Service.class, service); exchange.put(Endpoint.class, endpoint); exchange.put(Binding.class, endpoint.getBinding()); } } }