From 12b4ac0fb6dc97b4eabdf8e40078b61d79887137 Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Sat, 19 May 2007 01:46:19 +0000 Subject: [PATCH] Implemented #SWS-117: Allow SoapFaultMappingExceptionResolver to use strategy to obtain SoapFaultDefinition --- core-tiger/pom.xml | 11 + .../SoapFaultAnnotationExceptionResolver.java | 51 ++++ .../server/endpoint/annotation/FaultCode.java | 81 ++++++ .../server/endpoint/annotation/SoapFault.java | 55 +++++ ...pFaultAnnotationExceptionResolverTest.java | 230 ++++++++++++++++++ .../AbstractEndpointExceptionResolver.java | 2 +- ...tSoapFaultDefinitionExceptionResolver.java | 111 +++++++++ .../server/endpoint/SoapFaultDefinition.java | 16 +- .../endpoint/SoapFaultDefinitionEditor.java | 25 +- .../SoapFaultMappingExceptionResolver.java | 68 +----- src/changes/changes.xml | 6 +- 11 files changed, 571 insertions(+), 85 deletions(-) create mode 100644 core-tiger/src/main/java/org/springframework/ws/soap/server/endpoint/SoapFaultAnnotationExceptionResolver.java create mode 100644 core-tiger/src/main/java/org/springframework/ws/soap/server/endpoint/annotation/FaultCode.java create mode 100644 core-tiger/src/main/java/org/springframework/ws/soap/server/endpoint/annotation/SoapFault.java create mode 100644 core-tiger/src/test/java/org/springframework/ws/soap/server/endpoint/SoapFaultAnnotationExceptionResolverTest.java create mode 100644 core/src/main/java/org/springframework/ws/soap/server/endpoint/AbstractSoapFaultDefinitionExceptionResolver.java diff --git a/core-tiger/pom.xml b/core-tiger/pom.xml index 1ca513cf..76156e6e 100644 --- a/core-tiger/pom.xml +++ b/core-tiger/pom.xml @@ -46,6 +46,17 @@ javax.activation activation + + javax.xml.soap + saaj-api + test + + + + com.sun.xml.messaging.saaj + saaj-impl + test + org.easymock diff --git a/core-tiger/src/main/java/org/springframework/ws/soap/server/endpoint/SoapFaultAnnotationExceptionResolver.java b/core-tiger/src/main/java/org/springframework/ws/soap/server/endpoint/SoapFaultAnnotationExceptionResolver.java new file mode 100644 index 00000000..27151a88 --- /dev/null +++ b/core-tiger/src/main/java/org/springframework/ws/soap/server/endpoint/SoapFaultAnnotationExceptionResolver.java @@ -0,0 +1,51 @@ +/* + * Copyright 2007 the original author or authors. + * + * Licensed 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.springframework.ws.soap.server.endpoint; + +import javax.xml.namespace.QName; + +import org.springframework.util.StringUtils; +import org.springframework.ws.soap.server.endpoint.annotation.FaultCode; +import org.springframework.ws.soap.server.endpoint.annotation.SoapFault; + +/** + * Implementation of the {@link org.springframework.ws.server.EndpointExceptionResolver} interface that uses the {@link + * SoapFault} annotation to map exceptions to SOAP Faults. + * + * @author Arjen Poutsma + */ +public class SoapFaultAnnotationExceptionResolver extends AbstractSoapFaultDefinitionExceptionResolver { + + protected final SoapFaultDefinition getFaultDefinition(Object endpoint, Exception ex) { + SoapFault faultAnnotation = ex.getClass().getAnnotation(SoapFault.class); + if (faultAnnotation != null) { + SoapFaultDefinition definition = new SoapFaultDefinition(); + if (faultAnnotation.faultCode() != FaultCode.CUSTOM) { + definition.setFaultCode(faultAnnotation.faultCode().value()); + } + else if (StringUtils.hasLength(faultAnnotation.customFaultCode())) { + definition.setFaultCode(QName.valueOf(faultAnnotation.customFaultCode())); + } + definition.setFaultStringOrReason(faultAnnotation.faultStringOrReason()); + definition.setLocale(StringUtils.parseLocaleString(faultAnnotation.locale())); + return definition; + } + else { + return null; + } + } +} diff --git a/core-tiger/src/main/java/org/springframework/ws/soap/server/endpoint/annotation/FaultCode.java b/core-tiger/src/main/java/org/springframework/ws/soap/server/endpoint/annotation/FaultCode.java new file mode 100644 index 00000000..da72f290 --- /dev/null +++ b/core-tiger/src/main/java/org/springframework/ws/soap/server/endpoint/annotation/FaultCode.java @@ -0,0 +1,81 @@ +/* +* Copyright 2007 the original author or authors. +* +* Licensed 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.springframework.ws.soap.server.endpoint.annotation; + +import javax.xml.namespace.QName; + +import org.springframework.ws.soap.SoapBody; +import org.springframework.ws.soap.soap11.Soap11Body; + +/** + * Enumeration that represents the standard SOAP Fault codes for use with the JDK 1.5+ {@link SoapFault} annotation. + * + * @author Arjen Poutsma + */ +public enum FaultCode { + + /** + * Constant used to indicate that a fault must be created with a custom fault code. When this value is used, the + * customFaultCode string property must be used on {@link SoapFault}. + *

+ * Note that custom Fault Codes are only supported on SOAP 1.1. + * + * @see SoapFault#customFaultCode() + * @see Soap11Body#addFault(javax.xml.namespace.QName,String,java.util.Locale) + */ + CUSTOM(new QName("CUSTOM")), + + /** + * Constant used to indicate that a Client fault must be created. + * + * @see SoapBody#addClientOrSenderFault(String,java.util.Locale) + */ + CLIENT(new QName("CLIENT")), + + /** + * Constant QName used to indicate that a Receiver fault must be created. + * + * @see SoapBody#addServerOrReceiverFault(String,java.util.Locale) + */ + RECEIVER(new QName("RECEIVER")), + + /** + * Constant QName used to indicate that a Sender fault must be created. + * + * @see SoapBody#addServerOrReceiverFault(String,java.util.Locale) + */ + SENDER(new QName("SENDER")), + + /** + * Constant QName used to indicate that a Server fault must be created. + * + * @see SoapBody#addClientOrSenderFault(String,java.util.Locale) + */ + SERVER(new QName("SERVER")); + + private final QName value; + + private FaultCode(QName value) { + this.value = value; + } + + public QName value() { + return value; + } + + +} diff --git a/core-tiger/src/main/java/org/springframework/ws/soap/server/endpoint/annotation/SoapFault.java b/core-tiger/src/main/java/org/springframework/ws/soap/server/endpoint/annotation/SoapFault.java new file mode 100644 index 00000000..3c29f250 --- /dev/null +++ b/core-tiger/src/main/java/org/springframework/ws/soap/server/endpoint/annotation/SoapFault.java @@ -0,0 +1,55 @@ +/* + * Copyright 2007 the original author or authors. + * + * Licensed 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.springframework.ws.soap.server.endpoint.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks an exception class with the fault elements that should be returned whenever this exception is thrown. + * + * @author Arjen Poutsma + * @see org.springframework.ws.soap.server.endpoint.SoapFaultAnnotationExceptionResolver + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +public @interface SoapFault { + + /** The fault code. */ + FaultCode faultCode(); + + /** + * The custom fault code, to be used if {@link #faultCode()} is set to {@link FaultCode#CUSTOM}. + *

+ * Note that custom Fault Codes are only supported on SOAP 1.1. + */ + String customFaultCode() default ""; + + /** The fault string or reason text. By default, it is set to the exception message. */ + String faultStringOrReason() default ""; + + /** The fault string locale. By default, it is English. */ + String locale() default "en"; + + +} diff --git a/core-tiger/src/test/java/org/springframework/ws/soap/server/endpoint/SoapFaultAnnotationExceptionResolverTest.java b/core-tiger/src/test/java/org/springframework/ws/soap/server/endpoint/SoapFaultAnnotationExceptionResolverTest.java new file mode 100644 index 00000000..1be969ed --- /dev/null +++ b/core-tiger/src/test/java/org/springframework/ws/soap/server/endpoint/SoapFaultAnnotationExceptionResolverTest.java @@ -0,0 +1,230 @@ +/* + * Copyright 2007 the original author or authors. + * + * Licensed 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.springframework.ws.soap.server.endpoint; + +import java.util.Locale; +import javax.xml.namespace.QName; +import javax.xml.soap.MessageFactory; +import javax.xml.soap.SOAPConstants; +import javax.xml.soap.SOAPMessage; + +import junit.framework.TestCase; +import org.springframework.ws.context.DefaultMessageContext; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.soap.SoapMessage; +import org.springframework.ws.soap.SoapMessageFactory; +import org.springframework.ws.soap.SoapVersion; +import org.springframework.ws.soap.saaj.SaajSoapMessage; +import org.springframework.ws.soap.saaj.SaajSoapMessageFactory; +import org.springframework.ws.soap.server.endpoint.annotation.FaultCode; +import org.springframework.ws.soap.server.endpoint.annotation.SoapFault; +import org.springframework.ws.soap.soap11.Soap11Fault; +import org.springframework.ws.soap.soap12.Soap12Fault; + +public class SoapFaultAnnotationExceptionResolverTest extends TestCase { + + private SoapFaultAnnotationExceptionResolver resolver; + + protected void setUp() throws Exception { + resolver = new SoapFaultAnnotationExceptionResolver(); + } + + public void testResolveExceptionClientSoap11() throws Exception { + MessageFactory saajFactory = MessageFactory.newInstance(SOAPConstants.SOAP_1_1_PROTOCOL); + SoapMessageFactory factory = new SaajSoapMessageFactory(saajFactory); + MessageContext context = new DefaultMessageContext(factory); + + boolean result = resolver.resolveException(context, null, new MyClientException()); + assertTrue("resolveException returns false", result); + assertTrue("Context has no response", context.hasResponse()); + SoapMessage response = (SoapMessage) context.getResponse(); + assertTrue("Resonse has no fault", response.getSoapBody().hasFault()); + Soap11Fault fault = (Soap11Fault) response.getSoapBody().getFault(); + assertEquals("Invalid fault code on fault", SoapVersion.SOAP_11.getClientOrSenderFaultName(), + fault.getFaultCode()); + assertEquals("Invalid fault string on fault", "Client error", fault.getFaultStringOrReason()); + assertNull("Detail on fault", fault.getFaultDetail()); + } + + public void testResolveExceptionSenderSoap12() throws Exception { + MessageFactory saajFactory = MessageFactory.newInstance(SOAPConstants.SOAP_1_2_PROTOCOL); + SoapMessageFactory factory = new SaajSoapMessageFactory(saajFactory); + MessageContext context = new DefaultMessageContext(factory); + + boolean result = resolver.resolveException(context, null, new MySenderException()); + assertTrue("resolveException returns false", result); + assertTrue("Context has no response", context.hasResponse()); + SoapMessage response = (SoapMessage) context.getResponse(); + assertTrue("Resonse has no fault", response.getSoapBody().hasFault()); + Soap12Fault fault = (Soap12Fault) response.getSoapBody().getFault(); + assertEquals("Invalid fault code on fault", SoapVersion.SOAP_12.getClientOrSenderFaultName(), + fault.getFaultCode()); + assertEquals("Invalid fault string on fault", "Sender error", fault.getFaultReasonText(Locale.ENGLISH)); + assertNull("Detail on fault", fault.getFaultDetail()); + } + + public void testResolveExceptionServerSoap11() throws Exception { + MessageFactory saajFactory = MessageFactory.newInstance(SOAPConstants.SOAP_1_1_PROTOCOL); + SoapMessageFactory factory = new SaajSoapMessageFactory(saajFactory); + MessageContext context = new DefaultMessageContext(factory); + + boolean result = resolver.resolveException(context, null, new MyServerException()); + assertTrue("resolveException returns false", result); + assertTrue("Context has no response", context.hasResponse()); + SoapMessage response = (SoapMessage) context.getResponse(); + assertTrue("Resonse has no fault", response.getSoapBody().hasFault()); + Soap11Fault fault = (Soap11Fault) response.getSoapBody().getFault(); + assertEquals("Invalid fault code on fault", SoapVersion.SOAP_11.getServerOrReceiverFaultName(), + fault.getFaultCode()); + assertEquals("Invalid fault string on fault", "Server error", fault.getFaultStringOrReason()); + assertNull("Detail on fault", fault.getFaultDetail()); + } + + public void testResolveExceptionReceiverSoap12() throws Exception { + MessageFactory saajFactory = MessageFactory.newInstance(SOAPConstants.SOAP_1_2_PROTOCOL); + SOAPMessage message = saajFactory.createMessage(); + SoapMessageFactory factory = new SaajSoapMessageFactory(saajFactory); + MessageContext context = new DefaultMessageContext(new SaajSoapMessage(message), factory); + + boolean result = resolver.resolveException(context, null, new MyReceiverException()); + assertTrue("resolveException returns false", result); + assertTrue("Context has no response", context.hasResponse()); + SoapMessage response = (SoapMessage) context.getResponse(); + assertTrue("Resonse has no fault", response.getSoapBody().hasFault()); + Soap12Fault fault = (Soap12Fault) response.getSoapBody().getFault(); + assertEquals("Invalid fault code on fault", SoapVersion.SOAP_12.getServerOrReceiverFaultName(), + fault.getFaultCode()); + assertEquals("Invalid fault string on fault", "Receiver error", fault.getFaultReasonText(Locale.ENGLISH)); + assertNull("Detail on fault", fault.getFaultDetail()); + } + + public void testResolveExceptionDefault() throws Exception { + SoapFaultDefinition defaultFault = new SoapFaultDefinition(); + defaultFault.setFaultCode(SoapFaultDefinition.CLIENT); + defaultFault.setFaultStringOrReason("faultstring"); + resolver.setDefaultFault(defaultFault); + MessageFactory saajFactory = MessageFactory.newInstance(SOAPConstants.SOAP_1_1_PROTOCOL); + SoapMessageFactory factory = new SaajSoapMessageFactory(saajFactory); + MessageContext context = new DefaultMessageContext(factory); + + boolean result = resolver.resolveException(context, null, new NonAnnotatedException()); + assertTrue("resolveException returns false", result); + assertTrue("Context has no response", context.hasResponse()); + SoapMessage response = (SoapMessage) context.getResponse(); + assertTrue("Resonse has no fault", response.getSoapBody().hasFault()); + Soap11Fault fault = (Soap11Fault) response.getSoapBody().getFault(); + assertEquals("Invalid fault code on fault", SoapVersion.SOAP_11.getClientOrSenderFaultName(), + fault.getFaultCode()); + assertEquals("Invalid fault string on fault", "faultstring", fault.getFaultStringOrReason()); + assertNull("Detail on fault", fault.getFaultDetail()); + } + + public void testResolveExceptionCustom() throws Exception { + MessageFactory saajFactory = MessageFactory.newInstance(SOAPConstants.SOAP_1_1_PROTOCOL); + SoapMessageFactory factory = new SaajSoapMessageFactory(saajFactory); + MessageContext context = new DefaultMessageContext(factory); + + boolean result = resolver.resolveException(context, null, new MyCustomException()); + assertTrue("resolveException returns false", result); + assertTrue("Context has no response", context.hasResponse()); + SoapMessage response = (SoapMessage) context.getResponse(); + assertTrue("Resonse has no fault", response.getSoapBody().hasFault()); + Soap11Fault fault = (Soap11Fault) response.getSoapBody().getFault(); + assertEquals("Invalid fault code on fault", new QName("http://springframework.org/spring-ws", "Fault"), + fault.getFaultCode()); + assertEquals("Invalid fault string on fault", "MyCustomException thrown", fault.getFaultStringOrReason()); + assertEquals("Invalid fault locale on fault", new Locale("nl"), fault.getFaultStringLocale()); + } + + public void testResolveExceptionInheritance() throws Exception { + MessageFactory saajFactory = MessageFactory.newInstance(SOAPConstants.SOAP_1_1_PROTOCOL); + SoapMessageFactory factory = new SaajSoapMessageFactory(saajFactory); + MessageContext context = new DefaultMessageContext(factory); + + boolean result = resolver.resolveException(context, null, new MySubClientException()); + assertTrue("resolveException returns false", result); + assertTrue("Context has no response", context.hasResponse()); + SoapMessage response = (SoapMessage) context.getResponse(); + assertTrue("Resonse has no fault", response.getSoapBody().hasFault()); + Soap11Fault fault = (Soap11Fault) response.getSoapBody().getFault(); + assertEquals("Invalid fault code on fault", SoapVersion.SOAP_11.getClientOrSenderFaultName(), + fault.getFaultCode()); + assertEquals("Invalid fault string on fault", "Client error", fault.getFaultStringOrReason()); + assertNull("Detail on fault", fault.getFaultDetail()); + } + + public void testResolveExceptionExceptionMessage() throws Exception { + MessageFactory saajFactory = MessageFactory.newInstance(SOAPConstants.SOAP_1_1_PROTOCOL); + SoapMessageFactory factory = new SaajSoapMessageFactory(saajFactory); + MessageContext context = new DefaultMessageContext(factory); + + boolean result = resolver.resolveException(context, null, new NoStringOrReasonException("Exception message")); + assertTrue("resolveException returns false", result); + assertTrue("Context has no response", context.hasResponse()); + SoapMessage response = (SoapMessage) context.getResponse(); + assertTrue("Resonse has no fault", response.getSoapBody().hasFault()); + Soap11Fault fault = (Soap11Fault) response.getSoapBody().getFault(); + assertEquals("Invalid fault code on fault", SoapVersion.SOAP_11.getClientOrSenderFaultName(), + fault.getFaultCode()); + assertEquals("Invalid fault string on fault", "Exception message", fault.getFaultStringOrReason()); + assertNull("Detail on fault", fault.getFaultDetail()); + } + + @SoapFault(faultCode = FaultCode.CLIENT, faultStringOrReason = "Client error") + public class MyClientException extends Exception { + + } + + public class MySubClientException extends MyClientException { + + } + + @SoapFault(faultCode = FaultCode.CLIENT) + public class NoStringOrReasonException extends Exception { + + public NoStringOrReasonException(String message) { + super(message); + } + } + + @SoapFault(faultCode = FaultCode.SENDER, faultStringOrReason = "Sender error") + public class MySenderException extends Exception { + + } + + @SoapFault(faultCode = FaultCode.SERVER, faultStringOrReason = "Server error") + public class MyServerException extends Exception { + + } + + @SoapFault(faultCode = FaultCode.RECEIVER, faultStringOrReason = "Receiver error") + public class MyReceiverException extends Exception { + + } + + @SoapFault(faultCode = FaultCode.CUSTOM, customFaultCode = "{http://springframework.org/spring-ws}Fault", + faultStringOrReason = "MyCustomException thrown", locale = "nl") + public class MyCustomException extends Exception { + + } + + public class NonAnnotatedException extends Exception { + + } + + +} \ No newline at end of file diff --git a/core/src/main/java/org/springframework/ws/server/endpoint/AbstractEndpointExceptionResolver.java b/core/src/main/java/org/springframework/ws/server/endpoint/AbstractEndpointExceptionResolver.java index 4c7c95ef..f1909904 100644 --- a/core/src/main/java/org/springframework/ws/server/endpoint/AbstractEndpointExceptionResolver.java +++ b/core/src/main/java/org/springframework/ws/server/endpoint/AbstractEndpointExceptionResolver.java @@ -64,7 +64,7 @@ public abstract class AbstractEndpointExceptionResolver implements EndpointExcep * Template method for resolving exceptions. Gets called after resolveException. * * @param messageContext current message context - * @param endpoint the executed endpoint, or null if none chosen at the time of the exception + * @param endpoint the executed endpoint, or null if none chosen at the time of the exception * @param ex the exception that got thrown during endpoint execution * @return true if resolved; false otherwise * @see #resolveException(org.springframework.ws.context.MessageContext,Object,Exception) diff --git a/core/src/main/java/org/springframework/ws/soap/server/endpoint/AbstractSoapFaultDefinitionExceptionResolver.java b/core/src/main/java/org/springframework/ws/soap/server/endpoint/AbstractSoapFaultDefinitionExceptionResolver.java new file mode 100644 index 00000000..1f821a07 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/server/endpoint/AbstractSoapFaultDefinitionExceptionResolver.java @@ -0,0 +1,111 @@ +/* + * Copyright 2007 the original author or authors. + * + * Licensed 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.springframework.ws.soap.server.endpoint; + +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.server.EndpointExceptionResolver; +import org.springframework.ws.server.endpoint.AbstractEndpointExceptionResolver; +import org.springframework.ws.soap.SoapBody; +import org.springframework.ws.soap.SoapFault; +import org.springframework.ws.soap.SoapMessage; +import org.springframework.ws.soap.soap11.Soap11Body; + +/** + * Abstract base class for SOAP-based {@link EndpointExceptionResolver} implementations that depend on {@link + * SoapFaultDefinition}. Provides a default endpoint property, and a template method that provides the definition for a + * given exception. + * + * @author Arjen Poutsma + * @see #setDefaultFault(SoapFaultDefinition) + * @see #getFaultDefinition(Object,Exception) + */ +public abstract class AbstractSoapFaultDefinitionExceptionResolver extends AbstractEndpointExceptionResolver { + + private SoapFaultDefinition defaultFault; + + /** Set the default fault. This fault will be returned if no specific mapping was found. */ + public void setDefaultFault(SoapFaultDefinition defaultFault) { + this.defaultFault = defaultFault; + } + + /** + * Template method that returns the {@link SoapFaultDefinition} for the given exception. + * + * @param endpoint the executed endpoint, or null if none chosen at the time of the exception + * @param ex the exception to be handled + * @return the definition mapped to the exception, or null if none is found. + */ + protected abstract SoapFaultDefinition getFaultDefinition(Object endpoint, Exception ex); + + protected final boolean resolveExceptionInternal(MessageContext messageContext, Object endpoint, Exception ex) { + Assert.isTrue(messageContext.getResponse() instanceof SoapMessage, + "AbstractSoapFaultDefinitionExceptionResolver requires a SoapMessage"); + + SoapFaultDefinition definition = getFaultDefinition(endpoint, ex); + if (definition == null) { + definition = defaultFault; + } + if (definition == null) { + return false; + } + if (!StringUtils.hasLength(definition.getFaultStringOrReason())) { + definition.setFaultStringOrReason(ex.getMessage()); + } + SoapBody soapBody = ((SoapMessage) messageContext.getResponse()).getSoapBody(); + SoapFault fault = null; + + if (SoapFaultDefinition.SERVER.equals(definition.getFaultCode()) || + SoapFaultDefinition.RECEIVER.equals(definition.getFaultCode())) { + fault = soapBody.addServerOrReceiverFault(definition.getFaultStringOrReason(), definition.getLocale()); + } + else if (SoapFaultDefinition.CLIENT.equals(definition.getFaultCode()) || + SoapFaultDefinition.SENDER.equals(definition.getFaultCode())) { + fault = soapBody.addClientOrSenderFault(definition.getFaultStringOrReason(), definition.getLocale()); + } + else { + // custom code, only supported for SOAP 1.1 + if (soapBody instanceof Soap11Body) { + Soap11Body soap11Body = (Soap11Body) soapBody; + fault = soap11Body.addFault(definition.getFaultCode(), definition.getFaultStringOrReason(), + definition.getLocale()); + } + else { + logger.warn("SOAP 1.2 does not allow custom FaultCodes, only SENDER or RECEIVER."); + } + } + if (fault != null) { + customizeFault(endpoint, ex, fault); + } + return true; + } + + /** + * Customize the {@link SoapFault} created by this resolver. Called for each created fault + *

+ * The default implementation is empty. Can be overridden in subclasses to customize the properties of the fault, + * such as adding details, etc. + * + * @param endpoint the executed endpoint, or null if none chosen at the time of the exception + * @param ex the exception to be handled + * @param fault the created fault + */ + protected void customizeFault(Object endpoint, Exception ex, SoapFault fault) { + } + +} diff --git a/core/src/main/java/org/springframework/ws/soap/server/endpoint/SoapFaultDefinition.java b/core/src/main/java/org/springframework/ws/soap/server/endpoint/SoapFaultDefinition.java index eee5a3c2..12732908 100644 --- a/core/src/main/java/org/springframework/ws/soap/server/endpoint/SoapFaultDefinition.java +++ b/core/src/main/java/org/springframework/ws/soap/server/endpoint/SoapFaultDefinition.java @@ -63,30 +63,22 @@ public class SoapFaultDefinition { private Locale locale = Locale.ENGLISH; - /** - * Returns the fault code. - */ + /** Returns the fault code. */ public QName getFaultCode() { return faultCode; } - /** - * Sets the fault code. - */ + /** Sets the fault code. */ public void setFaultCode(QName faultCode) { this.faultCode = faultCode; } - /** - * Returns the fault string or reason text. - */ + /** Returns the fault string or reason text. By default, it is set to the exception message. */ public String getFaultStringOrReason() { return faultStringOrReason; } - /** - * Sets the fault string or reason text. - */ + /** Sets the fault string or reason text. By default, it is set to the exception message. */ public void setFaultStringOrReason(String faultStringOrReason) { this.faultStringOrReason = faultStringOrReason; } diff --git a/core/src/main/java/org/springframework/ws/soap/server/endpoint/SoapFaultDefinitionEditor.java b/core/src/main/java/org/springframework/ws/soap/server/endpoint/SoapFaultDefinitionEditor.java index fe916e05..932a506a 100644 --- a/core/src/main/java/org/springframework/ws/soap/server/endpoint/SoapFaultDefinitionEditor.java +++ b/core/src/main/java/org/springframework/ws/soap/server/endpoint/SoapFaultDefinitionEditor.java @@ -30,11 +30,12 @@ import org.springframework.xml.namespace.QNameEditor; * faultCode,faultString,locale * * where faultCode is the string representation of a QName, faultStringOrReason - * is the fault string, and locale is the optional string representations for the - * faultStringOrReasonlanguage. By default, the language is set to English. + * is the optional fault string, and locale is the optional string representations for the + * faultStringOrReasonlanguage. By default, the language is set to English, and the fault string set to the + * exception message. *

* Instead of supplying a custom fault code, you can use the constants SERVER or RECEIVER - * indicate a Server/Receiver fault, or or CLIENT or SENDER + * indicate a Server/Receiver fault, or CLIENT or SENDER * toClient/Sender fault respectivaly. *

* For example: @@ -54,7 +55,9 @@ import org.springframework.xml.namespace.QNameEditor; * @see javax.xml.namespace.QName#toString() * @see org.springframework.xml.namespace.QNameEditor * @see SoapFaultDefinition#RECEIVER + * @see SoapFaultDefinition#SERVER * @see SoapFaultDefinition#SENDER + * @see SoapFaultDefinition#CLIENT * @see org.springframework.ws.soap.SoapFault#getFaultCode() */ public class SoapFaultDefinitionEditor extends PropertyEditorSupport { @@ -71,19 +74,21 @@ public class SoapFaultDefinitionEditor extends PropertyEditorSupport { } else { String[] tokens = StringUtils.commaDelimitedListToStringArray(text); - if (tokens.length < 2) { + if (tokens.length < FAULT_STRING_INDEX) { throw new IllegalArgumentException("Invalid amount of comma delimited values in [" + text + - "]: SoapFaultDefinitionEditor requires at least 2"); + "]: SoapFaultDefinitionEditor requires at least 1"); } SoapFaultDefinition definition = new SoapFaultDefinition(); QNameEditor qNameEditor = new QNameEditor(); qNameEditor.setAsText(tokens[FAULT_CODE_INDEX].trim()); definition.setFaultCode((QName) qNameEditor.getValue()); - definition.setFaultStringOrReason(tokens[FAULT_STRING_INDEX].trim()); - if (tokens.length > 2) { - LocaleEditor localeEditor = new LocaleEditor(); - localeEditor.setAsText(tokens[FAULT_STRING_LOCALE_INDEX].trim()); - definition.setLocale((Locale) localeEditor.getValue()); + if (tokens.length > 1) { + definition.setFaultStringOrReason(tokens[FAULT_STRING_INDEX].trim()); + if (tokens.length > 2) { + LocaleEditor localeEditor = new LocaleEditor(); + localeEditor.setAsText(tokens[FAULT_STRING_LOCALE_INDEX].trim()); + definition.setLocale((Locale) localeEditor.getValue()); + } } setValue(definition); } diff --git a/core/src/main/java/org/springframework/ws/soap/server/endpoint/SoapFaultMappingExceptionResolver.java b/core/src/main/java/org/springframework/ws/soap/server/endpoint/SoapFaultMappingExceptionResolver.java index 1264ce18..f827cf68 100644 --- a/core/src/main/java/org/springframework/ws/soap/server/endpoint/SoapFaultMappingExceptionResolver.java +++ b/core/src/main/java/org/springframework/ws/soap/server/endpoint/SoapFaultMappingExceptionResolver.java @@ -19,12 +19,7 @@ package org.springframework.ws.soap.server.endpoint; import java.util.Enumeration; import java.util.Properties; -import org.springframework.util.Assert; -import org.springframework.ws.context.MessageContext; -import org.springframework.ws.server.endpoint.AbstractEndpointExceptionResolver; -import org.springframework.ws.soap.SoapBody; -import org.springframework.ws.soap.SoapMessage; -import org.springframework.ws.soap.soap11.Soap11Body; +import org.springframework.util.CollectionUtils; /** * Exception resolver that allows for mapping exception class names to SOAP Faults. The mappings are set using the @@ -32,12 +27,10 @@ import org.springframework.ws.soap.soap11.Soap11Body; * * @author Arjen Poutsma */ -public class SoapFaultMappingExceptionResolver extends AbstractEndpointExceptionResolver { +public class SoapFaultMappingExceptionResolver extends AbstractSoapFaultDefinitionExceptionResolver { private Properties exceptionMappings; - private SoapFaultDefinition defaultFault; - /** * Set the mappings between exception class names and SOAP Faults. The exception class name can be a substring, with * no wildcard support at present. @@ -45,60 +38,19 @@ public class SoapFaultMappingExceptionResolver extends AbstractEndpointException * The values of the given properties object should use the format described in * SoapFaultDefinitionEditor. *

- * Follows the same matching algorithm as RuleBasedTransactionAttribute and - * RollbackRuleAttribute. + * Follows the same matching algorithm as SimpleMappingExceptionResolver. * * @param mappings exception patterns (can also be fully qualified class names) as keys, fault definition texts as * values * @see SoapFaultDefinitionEditor * @see org.springframework.web.servlet.handler.SimpleMappingExceptionResolver - * @see org.springframework.transaction.interceptor.RuleBasedTransactionAttribute - * @see org.springframework.transaction.interceptor.RollbackRuleAttribute */ public void setExceptionMappings(Properties mappings) { exceptionMappings = mappings; } - /** - * Set the default fault. This fault will be returned if no specific mapping was found. - */ - public void setDefaultFault(SoapFaultDefinition defaultFault) { - this.defaultFault = defaultFault; - } - - protected boolean resolveExceptionInternal(MessageContext messageContext, Object endpoint, Exception ex) { - Assert.isTrue(messageContext.getResponse() instanceof SoapMessage, - "SimpleSoapExceptionResolver requires a SoapMessage"); - - SoapFaultDefinition definition = getFaultDefinition(ex); - if (definition == null) { - return false; - } - SoapMessage soapResponse = (SoapMessage) messageContext.getResponse(); - SoapBody soapBody = soapResponse.getSoapBody(); - - if (SoapFaultDefinition.SERVER.equals(definition.getFaultCode()) || - SoapFaultDefinition.RECEIVER.equals(definition.getFaultCode())) { - soapBody.addServerOrReceiverFault(definition.getFaultStringOrReason(), definition.getLocale()); - } - else if (SoapFaultDefinition.CLIENT.equals(definition.getFaultCode()) || - SoapFaultDefinition.SENDER.equals(definition.getFaultCode())) { - soapBody.addClientOrSenderFault(definition.getFaultStringOrReason(), definition.getLocale()); - } - else { - // custom code, only supported for SOAP 1.1 - if (soapBody instanceof Soap11Body) { - Soap11Body soap11Body = (Soap11Body) soapBody; - soap11Body.addFault(definition.getFaultCode(), definition.getFaultStringOrReason(), - definition.getLocale()); - } - } - return true; - } - - private SoapFaultDefinition getFaultDefinition(Exception ex) { - SoapFaultDefinition definition = null; - if (exceptionMappings != null) { + protected SoapFaultDefinition getFaultDefinition(Object endpoint, Exception ex) { + if (!CollectionUtils.isEmpty(exceptionMappings)) { String definitionText = null; int deepest = Integer.MAX_VALUE; for (Enumeration names = exceptionMappings.propertyNames(); names.hasMoreElements();) { @@ -112,14 +64,10 @@ public class SoapFaultMappingExceptionResolver extends AbstractEndpointException if (definitionText != null) { SoapFaultDefinitionEditor editor = new SoapFaultDefinitionEditor(); editor.setAsText(definitionText); - definition = (SoapFaultDefinition) editor.getValue(); + return (SoapFaultDefinition) editor.getValue(); } } - if (definition != null || defaultFault == null) { - return definition; - } - definition = defaultFault; - return definition; + return null; } /** @@ -130,7 +78,7 @@ public class SoapFaultMappingExceptionResolver extends AbstractEndpointException * * @see org.springframework.web.servlet.handler.SimpleMappingExceptionResolver */ - public int getDepth(String exceptionMapping, Exception ex) { + protected int getDepth(String exceptionMapping, Exception ex) { return getDepth(exceptionMapping, ex.getClass(), 0); } diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 4002a8bf..4f62a055 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -6,8 +6,10 @@ - Pulled handleFault() from SoapEndpointInterceptor up to - EndpointInterceptor + Allow SoapFaultMappingExceptionResolver to use strategy to + obtain SoapFaultDefinition + + Pulled handleFault() from SoapEndpointInterceptor up to EndpointInterceptor Remove IOExceptions from WebServiceTemplate. A runtime exception hierarchy has been created in org.springframework.ws.client.