[CXF-5686] Fix problems of Providers replying with faults with schema validation on
Project: http://git-wip-us.apache.org/repos/asf/cxf/repo Commit: http://git-wip-us.apache.org/repos/asf/cxf/commit/03e877f6 Tree: http://git-wip-us.apache.org/repos/asf/cxf/tree/03e877f6 Diff: http://git-wip-us.apache.org/repos/asf/cxf/diff/03e877f6 Branch: refs/heads/2.7.x-fixes Commit: 03e877f6c2f81e1220cba66cf295de98db7ea5c5 Parents: 49de470 Author: Daniel Kulp <[email protected]> Authored: Tue Apr 15 15:14:17 2014 -0400 Committer: Daniel Kulp <[email protected]> Committed: Tue Apr 15 16:01:26 2014 -0400 ---------------------------------------------------------------------- .../interceptors/MessageModeOutInterceptor.java | 109 +++++++++++++++++-- .../ValidationClientServerTest.java | 34 +++++- .../schema_validation/ValidationServer.java | 72 +++++++++++- 3 files changed, 198 insertions(+), 17 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cxf/blob/03e877f6/rt/frontend/jaxws/src/main/java/org/apache/cxf/jaxws/interceptors/MessageModeOutInterceptor.java ---------------------------------------------------------------------- diff --git a/rt/frontend/jaxws/src/main/java/org/apache/cxf/jaxws/interceptors/MessageModeOutInterceptor.java b/rt/frontend/jaxws/src/main/java/org/apache/cxf/jaxws/interceptors/MessageModeOutInterceptor.java index 76f9ec3..dd82c9c 100644 --- a/rt/frontend/jaxws/src/main/java/org/apache/cxf/jaxws/interceptors/MessageModeOutInterceptor.java +++ b/rt/frontend/jaxws/src/main/java/org/apache/cxf/jaxws/interceptors/MessageModeOutInterceptor.java @@ -26,19 +26,24 @@ import java.util.List; import javax.activation.DataSource; import javax.xml.namespace.QName; +import javax.xml.soap.Detail; import javax.xml.soap.MessageFactory; import javax.xml.soap.SOAPException; +import javax.xml.soap.SOAPFault; import javax.xml.soap.SOAPHeader; import javax.xml.soap.SOAPMessage; import javax.xml.soap.SOAPPart; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; import javax.xml.transform.Source; +import javax.xml.transform.dom.DOMSource; +import javax.xml.validation.Schema; import org.w3c.dom.DocumentFragment; +import org.w3c.dom.Element; import org.w3c.dom.Node; - +import org.apache.cxf.annotations.SchemaValidation.SchemaValidationType; import org.apache.cxf.attachment.AttachmentDeserializer; import org.apache.cxf.binding.soap.Soap12; import org.apache.cxf.binding.soap.SoapFault; @@ -48,7 +53,9 @@ import org.apache.cxf.binding.soap.saaj.SAAJOutInterceptor; import org.apache.cxf.binding.soap.saaj.SAAJOutInterceptor.SAAJOutEndingInterceptor; import org.apache.cxf.binding.soap.saaj.SAAJStreamWriter; import org.apache.cxf.binding.soap.saaj.SAAJUtils; +import org.apache.cxf.helpers.DOMUtils; import org.apache.cxf.helpers.IOUtils; +import org.apache.cxf.helpers.ServiceUtils; import org.apache.cxf.interceptor.AbstractOutDatabindingInterceptor; import org.apache.cxf.interceptor.Fault; import org.apache.cxf.interceptor.StaxOutInterceptor; @@ -59,10 +66,12 @@ import org.apache.cxf.message.MessageImpl; import org.apache.cxf.message.MessageUtils; import org.apache.cxf.phase.AbstractPhaseInterceptor; import org.apache.cxf.phase.Phase; +import org.apache.cxf.service.model.BindingFaultInfo; import org.apache.cxf.service.model.BindingMessageInfo; import org.apache.cxf.service.model.BindingOperationInfo; import org.apache.cxf.staxutils.StaxUtils; import org.apache.cxf.staxutils.W3CDOMStreamWriter; +import org.apache.cxf.ws.addressing.EndpointReferenceUtils; public class MessageModeOutInterceptor extends AbstractPhaseInterceptor<Message> { MessageModeOutInterceptorInternal internal; @@ -147,11 +156,82 @@ public class MessageModeOutInterceptor extends AbstractPhaseInterceptor<Message> XMLStreamWriter writer = StaxUtils.createXMLStreamWriter(out); message.setContent(XMLStreamWriter.class, writer); } + } else if (ServiceUtils.isSchemaValidationEnabled(SchemaValidationType.OUT, message) + && Source.class.isAssignableFrom(type)) { + //if schema validation is on, we'll end up converting to a DOMSource anyway, + //let's convert and check for a fault + MessageContentsList list = (MessageContentsList)message.getContent(List.class); + Source ds = (Source)list.get(0); + if (!(ds instanceof DOMSource)) { + try { + ds = new DOMSource(StaxUtils.read(ds)); + } catch (XMLStreamException e) { + throw new Fault(e); + } + list.set(0, ds); + validatePossibleFault(message, bop, ((DOMSource)ds).getNode()); + } } } + private void validatePossibleFault(Message message, BindingOperationInfo bop, Node ds) { + Element el = DOMUtils.getFirstElement(ds); + if (!"Fault".equals(el.getLocalName())) { + return; + } + message.put(Message.RESPONSE_CODE, 500); + + el = DOMUtils.getFirstElement(el); + while (el != null && !"detail".equals(el.getLocalName())) { + el = DOMUtils.getNextElement(el); + } + if (el != null) { + Schema schema = EndpointReferenceUtils.getSchema(message.getExchange().getService() + .getServiceInfos().get(0), + message.getExchange().getBus()); + try { + validateFaultDetail(el, schema, bop); + } catch (Exception e) { + throw new Fault(e); + } + + //We validated what we can from a fault standpoint + message.put(Message.SCHEMA_VALIDATION_ENABLED, Boolean.FALSE); + } + } + private void validateFaultDetail(Element detail, Schema schema, BindingOperationInfo bop) throws Exception { + Element el = DOMUtils.getFirstElement(detail); + while (el != null) { + QName qn = DOMUtils.getElementQName(el); + for (BindingFaultInfo bfi : bop.getFaults()) { + if (bfi.getFaultInfo().getMessagePartByIndex(0).getConcreteName().equals(qn)) { + //Found a fault with the correct QName, we can validate it + schema.newValidator().validate(new DOMSource(el)); + } + } + el = DOMUtils.getNextElement(el); + } + } + private void validateFault(SoapMessage message, SOAPFault fault, BindingOperationInfo bop) { + if (ServiceUtils.isSchemaValidationEnabled(SchemaValidationType.OUT, message)) { + Schema schema = EndpointReferenceUtils.getSchema(message.getExchange().getService() + .getServiceInfos().get(0), + message.getExchange().getBus()); + Detail d = fault.getDetail(); + try { + validateFaultDetail(d, schema, bop); + } catch (Exception e) { + throw new SoapFault(e.getMessage(), e, message.getVersion().getReceiver()); + } + + //We validated what we can from a fault standpoint + message.put(Message.SCHEMA_VALIDATION_ENABLED, Boolean.FALSE); + } + } + + private void doSoap(Message message) { MessageContentsList list = (MessageContentsList)message.getContent(List.class); if (list == null || list.isEmpty()) { @@ -233,28 +313,32 @@ public class MessageModeOutInterceptor extends AbstractPhaseInterceptor<Message> message.getVersion().getSender()); } } - message.setContent(SOAPMessage.class, soapMessage); - - if (!message.containsKey(SAAJOutInterceptor.ORIGINAL_XML_WRITER)) { - XMLStreamWriter origWriter = message.getContent(XMLStreamWriter.class); - message.put(SAAJOutInterceptor.ORIGINAL_XML_WRITER, origWriter); - } - W3CDOMStreamWriter writer = new SAAJStreamWriter(soapMessage.getSOAPPart()); - // Replace stax writer with DomStreamWriter - message.setContent(XMLStreamWriter.class, writer); - message.setContent(SOAPMessage.class, soapMessage); BindingOperationInfo bop = message.getExchange().get(BindingOperationInfo.class); - DocumentFragment frag = soapMessage.getSOAPPart().createDocumentFragment(); try { Node body = SAAJUtils.getBody(soapMessage); Node nd = body.getFirstChild(); while (nd != null) { + if (nd instanceof SOAPFault) { + message.put(Message.RESPONSE_CODE, 500); + validateFault(message, (SOAPFault)nd, bop); + } body.removeChild(nd); frag.appendChild(nd); nd = SAAJUtils.getBody(soapMessage).getFirstChild(); } + + message.setContent(SOAPMessage.class, soapMessage); + + if (!message.containsKey(SAAJOutInterceptor.ORIGINAL_XML_WRITER)) { + XMLStreamWriter origWriter = message.getContent(XMLStreamWriter.class); + message.put(SAAJOutInterceptor.ORIGINAL_XML_WRITER, origWriter); + } + W3CDOMStreamWriter writer = new SAAJStreamWriter(soapMessage.getSOAPPart()); + // Replace stax writer with DomStreamWriter + message.setContent(XMLStreamWriter.class, writer); + message.setContent(SOAPMessage.class, soapMessage); int index = 0; @@ -281,6 +365,7 @@ public class MessageModeOutInterceptor extends AbstractPhaseInterceptor<Message> if (buffer == null) { message.put(AbstractOutDatabindingInterceptor.OUT_BUFFERING, Boolean.FALSE); } + } catch (Exception ex) { throw new Fault(ex); } http://git-wip-us.apache.org/repos/asf/cxf/blob/03e877f6/systests/uncategorized/src/test/java/org/apache/cxf/systest/schema_validation/ValidationClientServerTest.java ---------------------------------------------------------------------- diff --git a/systests/uncategorized/src/test/java/org/apache/cxf/systest/schema_validation/ValidationClientServerTest.java b/systests/uncategorized/src/test/java/org/apache/cxf/systest/schema_validation/ValidationClientServerTest.java index b04eda2..3dfb0a9 100644 --- a/systests/uncategorized/src/test/java/org/apache/cxf/systest/schema_validation/ValidationClientServerTest.java +++ b/systests/uncategorized/src/test/java/org/apache/cxf/systest/schema_validation/ValidationClientServerTest.java @@ -61,9 +61,37 @@ public class ValidationClientServerTest extends AbstractBusClientServerTestBase assertTrue("server did not launch correctly", launchServer(ValidationServer.class, true)); } - // TODO : Change this test so that we test the combinations of - // client and server with schema validation enabled/disabled... - // Only tests client side validation enabled/server side disabled. + @Test + public void testSchemaValidationProviderPayload() throws Exception { + doProviderTest("PProvider"); + } + @Test + public void testSchemaValidationProviderMessage() throws Exception { + doProviderTest("MProvider"); + } + private void doProviderTest(String postfix) throws Exception { + SchemaValidation validation = createService(Boolean.FALSE, postfix); + SomeRequest req = new SomeRequest(); + req.setId("9999999999"); + try { + validation.doSomething(req); + fail("Should have faulted"); + } catch (DoSomethingFault e) { + assertEquals("1234", e.getFaultInfo().getErrorCode()); + } + req.setId("8888888888"); + try { + validation.doSomething(req); + fail("Should have faulted"); + } catch (DoSomethingFault e) { + fail("Should not have happened"); + } catch (WebServiceException e) { + String expected = "Value '1' is not facet-valid"; + assertTrue(e.getMessage().indexOf(expected) != -1); + } + ((java.io.Closeable)validation).close(); + } + @Test public void testSchemaValidationServer() throws Exception { SchemaValidation validation = createService(Boolean.FALSE, "SoapPortValidate"); http://git-wip-us.apache.org/repos/asf/cxf/blob/03e877f6/systests/uncategorized/src/test/java/org/apache/cxf/systest/schema_validation/ValidationServer.java ---------------------------------------------------------------------- diff --git a/systests/uncategorized/src/test/java/org/apache/cxf/systest/schema_validation/ValidationServer.java b/systests/uncategorized/src/test/java/org/apache/cxf/systest/schema_validation/ValidationServer.java index 79f2939..5f2404d 100644 --- a/systests/uncategorized/src/test/java/org/apache/cxf/systest/schema_validation/ValidationServer.java +++ b/systests/uncategorized/src/test/java/org/apache/cxf/systest/schema_validation/ValidationServer.java @@ -20,18 +20,27 @@ package org.apache.cxf.systest.schema_validation; +import java.io.StringReader; import java.util.LinkedList; import java.util.List; import javax.jws.WebService; +import javax.xml.stream.XMLStreamException; import javax.xml.transform.Source; +import javax.xml.transform.stream.StreamSource; import javax.xml.ws.Endpoint; import javax.xml.ws.Provider; import javax.xml.ws.Service; import javax.xml.ws.ServiceMode; import javax.xml.ws.WebServiceProvider; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import org.apache.cxf.annotations.Logging; import org.apache.cxf.annotations.SchemaValidation; +import org.apache.cxf.helpers.DOMUtils; +import org.apache.cxf.staxutils.StaxUtils; import org.apache.cxf.testutil.common.AbstractBusTestServerBase; public class ValidationServer extends AbstractBusTestServerBase { @@ -67,22 +76,81 @@ public class ValidationServer extends AbstractBusTestServerBase { } + private static String getResponse(String v) { + if ("9999999999".equals(v)) { + return "<soap:Fault xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" + + "<faultcode>soap:Server</faultcode>" + + "<faultstring>Fault</faultstring>" + + "<detail>" + + "<SomeFault xmlns=\"http://apache.org/schema_validation/types\">" + + " <errorCode>1234</errorCode></SomeFault>" + + "</detail>" + + "</soap:Fault>"; + } else if ("8888888888".equals(v)) { + return "<soap:Fault xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" + + "<faultcode>soap:Server</faultcode>" + + "<faultstring>Fault</faultstring>" + + "<detail>" + + "<SomeFault xmlns=\"http://apache.org/schema_validation/types\">" + + " <errorCode>1</errorCode></SomeFault>" + + "</detail>" + + "</soap:Fault>"; + } + return ""; + } - @WebServiceProvider + @WebServiceProvider(wsdlLocation = "classpath:/wsdl/schema_validation.wsdl", + serviceName = "SchemaValidationService", + portName = "SoapPort", + targetNamespace = "http://apache.org/schema_validation") @ServiceMode(Service.Mode.PAYLOAD) @SchemaValidation + @Logging static class PayloadProvider implements Provider<Source> { @Override public Source invoke(Source request) { + try { + Document doc = StaxUtils.read(request); + String name = doc.getDocumentElement().getLocalName(); + if ("SomeRequest".equals(name)) { + String v = DOMUtils.getFirstElement(doc.getDocumentElement()).getTextContent(); + return new StreamSource(new StringReader(getResponse(v))); + } + } catch (XMLStreamException e) { + e.printStackTrace(); + } return null; } } - @WebServiceProvider + + @WebServiceProvider(wsdlLocation = "classpath:/wsdl/schema_validation.wsdl", + serviceName = "SchemaValidationService", + portName = "SoapPort", + targetNamespace = "http://apache.org/schema_validation") @ServiceMode(Service.Mode.MESSAGE) @SchemaValidation static class MessageProvider implements Provider<Source> { @Override public Source invoke(Source request) { + try { + Document doc = StaxUtils.read(request); + Element el = DOMUtils.getFirstElement(doc.getDocumentElement()); + while (!"Body".equals(el.getLocalName())) { + el = DOMUtils.getNextElement(el); + } + el = DOMUtils.getFirstElement(el); + String name = el.getLocalName(); + + if ("SomeRequest".equals(name)) { + String v = DOMUtils.getFirstElement(el).getTextContent(); + return new StreamSource( + new StringReader("<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" + + "<soap:Body>" + getResponse(v) + "</soap:Body></soap:Envelope>")); + } + } catch (XMLStreamException ex) { + ex.printStackTrace(); + } + return null; } }
