This commit is contained in:
Arjen Poutsma
2007-11-02 11:48:21 +00:00
parent 7841ed6410
commit 82bf6eeca3
5 changed files with 507 additions and 31 deletions

View File

@@ -24,7 +24,6 @@ import org.springframework.beans.factory.InitializingBean;
import org.springframework.oxm.Marshaller;
import org.springframework.oxm.Unmarshaller;
import org.springframework.util.Assert;
import org.springframework.validation.Validator;
import org.springframework.ws.WebServiceMessage;
import org.springframework.ws.context.MessageContext;
import org.springframework.ws.support.MarshallingUtils;
@@ -51,8 +50,6 @@ public abstract class AbstractMarshallingPayloadEndpoint implements MessageEndpo
private Unmarshaller unmarshaller;
private Validator[] validators;
/**
* Creates a new <code>AbstractMarshallingPayloadEndpoint</code>. The {@link Marshaller} and {@link Unmarshaller}
* must be injected using properties.
@@ -122,30 +119,6 @@ public abstract class AbstractMarshallingPayloadEndpoint implements MessageEndpo
this.unmarshaller = unmarshaller;
}
/**
* Set the primary {@link Validator} for this endpoint. The {@link Validator} is must support the specified command
* class. If there are one or more existing validators set already when this method is called, only the specified
* validator will be kept. Use {@link #setValidators(Validator[])} to set multiple validators.
*/
public final void setValidator(Validator validator) {
this.validators = new Validator[]{validator};
}
/** Return the primary Validator for this controller. */
public final Validator getValidator() {
return (this.validators != null && this.validators.length > 0 ? this.validators[0] : null);
}
/** Set the Validators for this controller. The Validator must support the specified command class. */
public final void setValidators(Validator[] validators) {
this.validators = validators;
}
/** Return the Validators for this controller. */
public final Validator[] getValidators() {
return validators;
}
public final void afterPropertiesSet() throws Exception {
Assert.notNull(getMarshaller(), "marshaller is required");
Assert.notNull(getUnmarshaller(), "unmarshaller is required");
@@ -155,10 +128,13 @@ public abstract class AbstractMarshallingPayloadEndpoint implements MessageEndpo
public final void invoke(MessageContext messageContext) throws Exception {
WebServiceMessage request = messageContext.getRequest();
Object requestObject = unmarshalRequest(request);
Object responseObject = invokeInternal(requestObject);
if (responseObject != null) {
WebServiceMessage response = messageContext.getResponse();
marshalResponse(responseObject, response);
if (onUnmarshalRequest(messageContext, requestObject)) {
Object responseObject = invokeInternal(requestObject);
if (responseObject != null) {
WebServiceMessage response = messageContext.getResponse();
marshalResponse(responseObject, response);
onMarshalResponse(messageContext, requestObject, responseObject);
}
}
}
@@ -170,6 +146,20 @@ public abstract class AbstractMarshallingPayloadEndpoint implements MessageEndpo
return requestObject;
}
/**
* Callback for post-processing in terms of unmarshalling. Called on each message request, after standard
* unmarshalling.
* <p/>
* Default implementation returns <code>true</code>.
*
* @param messageContext the message context
* @param requestObject the object unmarshalled from the {@link MessageContext#getRequest() request}
* @return <code>true</code> to continue and call {@link #invokeInternal(Object)}; <code>false</code> otherwise
*/
protected boolean onUnmarshalRequest(MessageContext messageContext, Object requestObject) throws Exception {
return true;
}
private void marshalResponse(Object responseObject, WebServiceMessage response) throws IOException {
if (logger.isDebugEnabled()) {
logger.debug("Marshalling [" + responseObject + "] to response payload");
@@ -177,6 +167,19 @@ public abstract class AbstractMarshallingPayloadEndpoint implements MessageEndpo
MarshallingUtils.marshal(getMarshaller(), responseObject, response);
}
/**
* Callback for post-processing in terms of marshalling. Called on each message request, after standard marshalling
* of the response. Only invoked when {@link #invokeInternal(Object)} returns an object.
* <p/>
* Default implementation is empty.
*
* @param messageContext the message context
* @param requestObject the object unmarshalled from the {@link MessageContext#getRequest() request}
* @param responseObject the object marshalled to the {@link MessageContext#getResponse()} request}
*/
protected void onMarshalResponse(MessageContext messageContext, Object requestObject, Object responseObject) {
}
/**
* Template method that gets called after the marshaller and unmarshaller have been set.
* <p/>

View File

@@ -0,0 +1,99 @@
/*
* 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.server.endpoint;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import org.springframework.ws.context.MessageContext;
/**
* Extension of the {@link AbstractMarshallingPayloadEndpoint} which validates the request payload with {@link
* Validator}(s). The desired validators can be set using properties, and <strong>must</strong> {@link
* Validator#supports(Class) support} the request object.
*
* @author Arjen Poutsma
* @since 1.0.2
*/
public abstract class AbstractValidatingMarshallingPayloadEndpoint extends AbstractMarshallingPayloadEndpoint {
/** Default request object name used for validating request objects. */
public static final String DEFAULT_REQUEST_NAME = "request";
private String requestName = DEFAULT_REQUEST_NAME;
private Validator[] validators;
/** Return the name of the request object for validation error codes. */
public final String getRequestName() {
return requestName;
}
/** Set the name of the request object user for validation errors. */
public final void setRequestName(String requestName) {
this.requestName = requestName;
}
/** Return the primary Validator for this controller. */
public final Validator getValidator() {
return (this.validators != null && this.validators.length > 0 ? this.validators[0] : null);
}
/**
* Set the primary {@link Validator} for this endpoint. The {@link Validator} is must support the unmarshalled
* class. If there are one or more existing validators set already when this method is called, only the specified
* validator will be kept. Use {@link #setValidators(Validator[])} to set multiple validators.
*/
public final void setValidator(Validator validator) {
this.validators = new Validator[]{validator};
}
/** Return the Validators for this controller. */
public final Validator[] getValidators() {
return validators;
}
/** Set the Validators for this controller. The Validator must support the specified command class. */
public final void setValidators(Validator[] validators) {
this.validators = validators;
}
protected final boolean onUnmarshalRequest(MessageContext messageContext, Object requestObject) throws Exception {
if (validators != null) {
Errors errors = new BeanPropertyBindingResult(requestObject, getRequestName());
for (int i = 0; i < validators.length; i++) {
ValidationUtils.invokeValidator(validators[i], requestObject, errors);
}
if (errors.hasErrors()) {
return onValidationErrors(messageContext, requestObject, errors);
}
}
return true;
}
/**
* Callback for post-processing validation errors. Called when validator(s) have been specified, and validation
* fails.
*
* @param messageContext the message context
* @param requestObject the object unmarshalled from the {@link MessageContext#getRequest() request}
* @param errors validation errors holder
* @return <code>true</code> to continue and call {@link #invokeInternal(Object)}; <code>false</code> otherwise
*/
protected abstract boolean onValidationErrors(MessageContext messageContext, Object requestObject, Errors errors);
}

View File

@@ -0,0 +1,181 @@
/*
* 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.Iterator;
import java.util.Locale;
import javax.xml.namespace.QName;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.validation.Errors;
import org.springframework.validation.ObjectError;
import org.springframework.validation.Validator;
import org.springframework.ws.context.MessageContext;
import org.springframework.ws.server.endpoint.AbstractValidatingMarshallingPayloadEndpoint;
import org.springframework.ws.soap.SoapBody;
import org.springframework.ws.soap.SoapFault;
import org.springframework.ws.soap.SoapFaultDetail;
import org.springframework.ws.soap.SoapFaultDetailElement;
import org.springframework.ws.soap.SoapMessage;
import org.springframework.xml.namespace.QNameUtils;
/**
* Extension of the {@link AbstractValidatingMarshallingPayloadEndpoint} which validates the request payload with {@link
* Validator}(s), and creates a SOAP Fault whenever the request message cannot be validated. The desired validators can
* be set using properties, and <strong>must</strong> {@link Validator#supports(Class) support} the request object.
* <p/>
* The contents of the SOAP Fault can be specified by setting the {@link #setAddValidationErrorDetail(boolean)
* addValidationErrorDetail}, {@link #setFaultStringOrReason(String) faultStringOrReason}, or {@link
* #setDetailElementName(QName) detailElementName} properties.
*
* @author Arjen Poutsma
* @since 1.0.2
*/
public abstract class AbstractFaultCreatingValidatingMarshallingPayloadEndpoint
extends AbstractValidatingMarshallingPayloadEndpoint implements MessageSourceAware {
/**
* Default SOAP Fault Detail name used when a global validation error occur on the request.
*
* @see #setDetailElementName(javax.xml.namespace.QName)
*/
public static final QName DEFAULT_DETAIL_ELEMENT_NAME =
QNameUtils.createQName("http://springframework.org/spring-ws", "ValidationError", "spring-ws");
/**
* Default SOAP Fault string used when a validation errors occur on the request.
*
* @see #setFaultStringOrReason(String)
*/
public static final String DEFAULT_FAULTSTRING_OR_REASON = "Validation error";
private boolean addValidationErrorDetail = true;
private QName detailElementName = DEFAULT_DETAIL_ELEMENT_NAME;
private String faultStringOrReason = DEFAULT_FAULTSTRING_OR_REASON;
private Locale faultStringOrReasonLocale = Locale.ENGLISH;
private MessageSource messageSource;
/**
* Returns whether a SOAP Fault detail element should be created when a validation error occurs. This detail element
* will contain the exact validation errors. It is only added when the underlying message is a
* <code>SoapMessage</code>. Defaults to <code>true</code>.
*
* @see org.springframework.ws.soap.SoapFault#addFaultDetail()
*/
public boolean getAddValidationErrorDetail() {
return addValidationErrorDetail;
}
/**
* Indicates whether a SOAP Fault detail element should be created when a validation error occurs. This detail
* element will contain the exact validation errors. It is only added when the underlying message is a
* <code>SoapMessage</code>. Defaults to <code>true</code>.
*
* @see org.springframework.ws.soap.SoapFault#addFaultDetail()
*/
public void setAddValidationErrorDetail(boolean addValidationErrorDetail) {
this.addValidationErrorDetail = addValidationErrorDetail;
}
/** Returns the fault detail element name when validation errors occur on the request. */
public QName getDetailElementName() {
return detailElementName;
}
/**
* Sets the fault detail element name when validation errors occur on the request. Defaults to
* <code>DEFAULT_DETAIL_ELEMENT_NAME</code>.
*
* @see #DEFAULT_DETAIL_ELEMENT_NAME
*/
public void setDetailElementName(QName detailElementName) {
this.detailElementName = detailElementName;
}
/** Sets the SOAP <code>faultstring</code> or <code>Reason</code> used when validation errors occur on the request. */
public String getFaultStringOrReason() {
return faultStringOrReason;
}
/**
* Sets the SOAP <code>faultstring</code> or <code>Reason</code> used when validation errors occur on the request.
* It is only added when the underlying message is a <code>SoapMessage</code>. Defaults to
* <code>DEFAULT_FAULTSTRING_OR_REASON</code>.
*
* @see #DEFAULT_FAULTSTRING_OR_REASON
*/
public void setFaultStringOrReason(String faultStringOrReason) {
this.faultStringOrReason = faultStringOrReason;
}
/** Returns the locale for SOAP fault reason and validation message resolution. */
public Locale getFaultLocale() {
return faultStringOrReasonLocale;
}
/**
* Sets the locale for SOAP fault reason and validation messages. It is only added when the underlying message is a
* <code>SoapMessage</code>. Defaults to English.
*
* @see java.util.Locale#ENGLISH
*/
public void setFaultStringOrReasonLocale(Locale faultStringOrReasonLocale) {
this.faultStringOrReasonLocale = faultStringOrReasonLocale;
}
public final void setMessageSource(MessageSource messageSource) {
this.messageSource = messageSource;
}
/**
* This implementation logs all errors, returns <code>false</code>, and creates a {@link
* SoapBody#addClientOrSenderFault(String,Locale) client or sender} {@link SoapFault}, adding a {@link
* SoapFaultDetail} with all errors if the <code>addValidationErrorDetail</code> property is <code>true</code>.
*
* @param messageContext the message context
* @param errors the validation errors
* @return <code>true</code> to continue processing the request, <code>false</code> (the default) otherwise
* @see Errors#getAllErrors()
*/
protected final boolean onValidationErrors(MessageContext messageContext, Object requestObject, Errors errors) {
for (Iterator iterator = errors.getAllErrors().iterator(); iterator.hasNext();) {
ObjectError objectError = (ObjectError) iterator.next();
String msg = messageSource.getMessage(objectError, getFaultLocale());
logger.warn("Validation error on request object[" + requestObject + "]: " + msg);
}
if (messageContext.getResponse() instanceof SoapMessage) {
SoapMessage response = (SoapMessage) messageContext.getResponse();
SoapBody body = response.getSoapBody();
SoapFault fault = body.addClientOrSenderFault(getFaultStringOrReason(), getFaultLocale());
if (getAddValidationErrorDetail()) {
SoapFaultDetail detail = fault.addFaultDetail();
for (Iterator iterator = errors.getAllErrors().iterator(); iterator.hasNext();) {
ObjectError objectError = (ObjectError) iterator.next();
String msg = messageSource.getMessage(objectError, getFaultLocale());
SoapFaultDetailElement detailElement = detail.addFaultDetailElement(getDetailElementName());
detailElement.addText(msg);
}
}
}
return false;
}
}

View File

@@ -0,0 +1,191 @@
/*
* Copyright ${YEAR} 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.io.IOException;
import java.util.Iterator;
import javax.xml.namespace.QName;
import javax.xml.soap.Detail;
import javax.xml.soap.DetailEntry;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.SOAPFault;
import javax.xml.soap.SOAPMessage;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import junit.framework.TestCase;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.oxm.Marshaller;
import org.springframework.oxm.Unmarshaller;
import org.springframework.oxm.XmlMappingException;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import org.springframework.ws.context.DefaultMessageContext;
import org.springframework.ws.context.MessageContext;
import org.springframework.ws.soap.saaj.SaajSoapMessage;
import org.springframework.ws.soap.saaj.SaajSoapMessageFactory;
public class FaultCreatingValidatingMarshallingPayloadEndpointTest extends TestCase {
private MessageContext messageContext;
private ResourceBundleMessageSource messageSource;
protected void setUp() throws Exception {
this.messageSource = new ResourceBundleMessageSource();
this.messageSource.setBasename("org.springframework.ws.soap.server.endpoint.messages");
MessageFactory messageFactory = MessageFactory.newInstance();
SOAPMessage request = messageFactory.createMessage();
request.getSOAPBody().addBodyElement(new QName("http://www.springframework.org/spring-ws", "request"));
messageContext =
new DefaultMessageContext(new SaajSoapMessage(request), new SaajSoapMessageFactory(messageFactory));
}
public void testValidationIncorrect() throws Exception {
Person p = new Person("", -1);
PersonMarshaller marshaller = new PersonMarshaller(p);
AbstractFaultCreatingValidatingMarshallingPayloadEndpoint endpoint =
new AbstractFaultCreatingValidatingMarshallingPayloadEndpoint() {
protected Object invokeInternal(Object requestObject) throws Exception {
fail("No expected");
return null;
}
};
endpoint.setValidator(new PersonValidator());
endpoint.setMessageSource(messageSource);
endpoint.setMarshaller(marshaller);
endpoint.setUnmarshaller(marshaller);
endpoint.invoke(messageContext);
SOAPMessage response = ((SaajSoapMessage) messageContext.getResponse()).getSaajMessage();
assertTrue("Response has no fault", response.getSOAPBody().hasFault());
SOAPFault fault = response.getSOAPBody().getFault();
assertEquals("Invalid fault code", new QName("http://schemas.xmlsoap.org/soap/envelope/", "Client"),
fault.getFaultCodeAsQName());
assertEquals("Invalid fault string", endpoint.getFaultStringOrReason(), fault.getFaultString());
Detail detail = fault.getDetail();
assertNotNull("No detail", detail);
Iterator iterator = detail.getDetailEntries();
assertTrue("No detail entry", iterator.hasNext());
DetailEntry detailEntry = (DetailEntry) iterator.next();
assertEquals("Invalid detail entry name", new QName("http://springframework.org/spring-ws", "ValidationError"),
detailEntry.getElementQName());
assertEquals("Invalid detail entry text", "Name is required", detailEntry.getTextContent());
assertTrue("No detail entry", iterator.hasNext());
detailEntry = (DetailEntry) iterator.next();
assertEquals("Invalid detail entry name", new QName("http://springframework.org/spring-ws", "ValidationError"),
detailEntry.getElementQName());
assertEquals("Invalid detail entry text", "Age Cannot be negative", detailEntry.getTextContent());
assertFalse("Too many detail entries", iterator.hasNext());
}
public void testValidationCorrect() throws Exception {
Person p = new Person("John", 42);
PersonMarshaller marshaller = new PersonMarshaller(p);
AbstractFaultCreatingValidatingMarshallingPayloadEndpoint endpoint =
new AbstractFaultCreatingValidatingMarshallingPayloadEndpoint() {
protected Object invokeInternal(Object requestObject) throws Exception {
return null;
}
};
endpoint.setValidator(new PersonValidator());
endpoint.setMessageSource(messageSource);
endpoint.setMarshaller(marshaller);
endpoint.setUnmarshaller(marshaller);
endpoint.invoke(messageContext);
SOAPMessage response = ((SaajSoapMessage) messageContext.getResponse()).getSaajMessage();
assertFalse("Response has fault", response.getSOAPBody().hasFault());
}
private static class PersonValidator implements Validator {
public boolean supports(Class clazz) {
return Person.class.equals(clazz);
}
public void validate(Object obj, Errors e) {
ValidationUtils.rejectIfEmpty(e, "name", "name.empty");
Person p = (Person) obj;
if (p.getAge() < 0) {
e.rejectValue("age", "age.negativevalue");
}
else if (p.getAge() > 110) {
e.rejectValue("age", "too.darn.old");
}
}
}
private static class Person {
private String name;
private int age;
private Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String toString() {
return "Person{" + name + "," + age + "}";
}
}
private static class PersonMarshaller implements Unmarshaller, Marshaller {
private final Person person;
private PersonMarshaller(Person person) {
this.person = person;
}
public Object unmarshal(Source source) throws XmlMappingException, IOException {
return person;
}
public boolean supports(Class clazz) {
return Person.class.equals(clazz);
}
public void marshal(Object graph, Result result) throws XmlMappingException, IOException {
}
}
}

View File

@@ -0,0 +1,2 @@
name.empty=Name is required
age.negativevalue=Age Cannot be negative