Skip to content

Commit 57e4944

Browse files
committed
SWS-391
1 parent 3afdcc0 commit 57e4944

File tree

13 files changed

+734
-4
lines changed

13 files changed

+734
-4
lines changed

core/src/main/java/org/springframework/ws/WebServiceException.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616

1717
package org.springframework.ws;
1818

19-
import java.io.Serializable;
20-
2119
import org.springframework.core.NestedRuntimeException;
2220

2321
/**
@@ -26,7 +24,7 @@
2624
* @author Arjen Poutsma
2725
* @since 1.0.0
2826
*/
29-
public abstract class WebServiceException extends NestedRuntimeException implements Serializable {
27+
public abstract class WebServiceException extends NestedRuntimeException {
3028

3129
/**
3230
* Create a new instance of the <code>WebServiceException</code> class.
Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
/*
2+
* Copyright 2008 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.ws.client.support.interceptor;
18+
19+
import java.io.IOException;
20+
import javax.xml.transform.Source;
21+
22+
import org.xml.sax.SAXParseException;
23+
24+
import org.springframework.beans.factory.InitializingBean;
25+
import org.springframework.core.io.Resource;
26+
import org.springframework.util.Assert;
27+
import org.springframework.util.ObjectUtils;
28+
import org.springframework.util.StringUtils;
29+
import org.springframework.ws.WebServiceMessage;
30+
import org.springframework.ws.client.WebServiceClientException;
31+
import org.springframework.ws.client.WebServiceIOException;
32+
import org.springframework.ws.context.MessageContext;
33+
import org.springframework.xml.transform.TransformerObjectSupport;
34+
import org.springframework.xml.validation.XmlValidator;
35+
import org.springframework.xml.validation.XmlValidatorFactory;
36+
import org.springframework.xml.xsd.XsdSchema;
37+
import org.springframework.xml.xsd.XsdSchemaCollection;
38+
39+
/**
40+
* Abstract base class for {@link ClientInterceptor} implementations that validate part of the message using a schema.
41+
* The exact message part is determined by the {@link #getValidationRequestSource(WebServiceMessage)} and {@link
42+
* #getValidationResponseSource(WebServiceMessage)} template methods.
43+
* <p/>
44+
* By default, only the request message is validated, but this behaviour can be changed using the
45+
* <code>validateRequest</code> and <code>validateResponse</code> properties.
46+
*
47+
* @author Arjen Poutsma
48+
* @see #getValidationRequestSource(WebServiceMessage)
49+
* @see #getValidationResponseSource(WebServiceMessage)
50+
* @since 1.5.4
51+
*/
52+
public abstract class AbstractValidatingInterceptor extends TransformerObjectSupport
53+
implements ClientInterceptor, InitializingBean {
54+
55+
private String schemaLanguage = XmlValidatorFactory.SCHEMA_W3C_XML;
56+
57+
private Resource[] schemas;
58+
59+
private boolean validateRequest = true;
60+
61+
private boolean validateResponse = false;
62+
63+
private XmlValidator validator;
64+
65+
public String getSchemaLanguage() {
66+
return schemaLanguage;
67+
}
68+
69+
/**
70+
* Sets the schema language. Default is the W3C XML Schema: <code>http://www.w3.org/2001/XMLSchema"</code>.
71+
*
72+
* @see XmlValidatorFactory#SCHEMA_W3C_XML
73+
* @see XmlValidatorFactory#SCHEMA_RELAX_NG
74+
*/
75+
public void setSchemaLanguage(String schemaLanguage) {
76+
this.schemaLanguage = schemaLanguage;
77+
}
78+
79+
/** Returns the schema resources to use for validation. */
80+
public Resource[] getSchemas() {
81+
return schemas;
82+
}
83+
84+
/**
85+
* Sets the schema resource to use for validation. Setting this property, {@link
86+
* #setXsdSchemaCollection(XsdSchemaCollection) xsdSchemaCollection}, {@link #setSchema(Resource) schema}, or {@link
87+
* #setSchemas(Resource[]) schemas} is required.
88+
*/
89+
public void setSchema(Resource schema) {
90+
setSchemas(new Resource[]{schema});
91+
}
92+
93+
/**
94+
* Sets the schema resources to use for validation. Setting this property, {@link
95+
* #setXsdSchemaCollection(XsdSchemaCollection) xsdSchemaCollection}, {@link #setSchema(Resource) schema}, or {@link
96+
* #setSchemas(Resource[]) schemas} is required.
97+
*/
98+
public void setSchemas(Resource[] schemas) {
99+
Assert.notEmpty(schemas, "schemas must not be empty or null");
100+
for (int i = 0; i < schemas.length; i++) {
101+
Assert.notNull(schemas[i], "schema must not be null");
102+
Assert.isTrue(schemas[i].exists(), "schema \"" + schemas[i] + "\" does not exit");
103+
}
104+
this.schemas = schemas;
105+
}
106+
107+
/**
108+
* Sets the {@link XsdSchema} to use for validation. Setting this property, {@link
109+
* #setXsdSchemaCollection(XsdSchemaCollection) xsdSchemaCollection}, {@link #setSchema(Resource) schema}, or {@link
110+
* #setSchemas(Resource[]) schemas} is required.
111+
*
112+
* @param schema the xsd schema to use
113+
* @throws java.io.IOException in case of I/O errors
114+
*/
115+
public void setXsdSchema(XsdSchema schema) throws IOException {
116+
this.validator = schema.createValidator();
117+
}
118+
119+
/**
120+
* Sets the {@link XsdSchemaCollection} to use for validation. Setting this property, {@link
121+
* #setXsdSchema(XsdSchema) xsdSchema}, {@link #setSchema(Resource) schema}, or {@link #setSchemas(Resource[])
122+
* schemas} is required.
123+
*
124+
* @param schemaCollection the xsd schema collection to use
125+
* @throws java.io.IOException in case of I/O errors
126+
*/
127+
public void setXsdSchemaCollection(XsdSchemaCollection schemaCollection) throws IOException {
128+
this.validator = schemaCollection.createValidator();
129+
}
130+
131+
/** Indicates whether the request should be validated against the schema. Default is <code>true</code>. */
132+
public void setValidateRequest(boolean validateRequest) {
133+
this.validateRequest = validateRequest;
134+
}
135+
136+
/** Indicates whether the response should be validated against the schema. Default is <code>false</code>. */
137+
public void setValidateResponse(boolean validateResponse) {
138+
this.validateResponse = validateResponse;
139+
}
140+
141+
public void afterPropertiesSet() throws Exception {
142+
if (validator == null && !ObjectUtils.isEmpty(schemas)) {
143+
Assert.hasLength(schemaLanguage, "schemaLanguage is required");
144+
for (int i = 0; i < schemas.length; i++) {
145+
Assert.isTrue(schemas[i].exists(), "schema [" + schemas[i] + "] does not exist");
146+
}
147+
if (logger.isInfoEnabled()) {
148+
logger.info("Validating using " + StringUtils.arrayToCommaDelimitedString(schemas));
149+
}
150+
validator = XmlValidatorFactory.createValidator(schemas, schemaLanguage);
151+
}
152+
Assert.notNull(validator, "Setting 'schema', 'schemas', 'xsdSchema', or 'xsdSchemaCollection' is required");
153+
}
154+
155+
/**
156+
* Validates the request message in the given message context. Validation only occurs if {@link
157+
* #setValidateRequest(boolean) validateRequest} is set to <code>true</code>, which is the default.
158+
* <p/>
159+
* Returns <code>true</code> if the request is valid, or <code>false</code> if it isn't.
160+
*
161+
* @param messageContext the message context
162+
* @return <code>true</code> if the message is valid; <code>false</code> otherwise
163+
* @see #setValidateRequest(boolean)
164+
*/
165+
public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException {
166+
if (validateRequest) {
167+
Source requestSource = getValidationRequestSource(messageContext.getRequest());
168+
if (requestSource != null) {
169+
SAXParseException[] errors;
170+
try {
171+
errors = validator.validate(requestSource);
172+
}
173+
catch (IOException e) {
174+
throw new WebServiceIOException("Could not validate response: " + e.getMessage(), e);
175+
}
176+
if (!ObjectUtils.isEmpty(errors)) {
177+
return handleRequestValidationErrors(messageContext, errors);
178+
}
179+
else if (logger.isDebugEnabled()) {
180+
logger.debug("Request message validated");
181+
}
182+
}
183+
}
184+
return true;
185+
}
186+
187+
/**
188+
* Template method that is called when the request message contains validation errors.
189+
* <p/>
190+
* Default implementation logs all errors, and throws a {@link WebServiceValidationException}. Subclasses can
191+
* override this method to customize this behavior.
192+
*
193+
* @param messageContext the message context
194+
* @param errors the validation errors
195+
* @return <code>true</code> to continue processing the request, <code>false</code> otherwise
196+
*/
197+
protected boolean handleRequestValidationErrors(MessageContext messageContext, SAXParseException[] errors) {
198+
for (int i = 0; i < errors.length; i++) {
199+
logger.error("XML validation error on request: " + errors[i].getMessage());
200+
}
201+
throw new WebServiceValidationException(errors);
202+
}
203+
204+
/**
205+
* Validates the response message in the given message context. Validation only occurs if {@link
206+
* #setValidateResponse(boolean) validateResponse} is set to <code>true</code>, which is <strong>not</strong> the
207+
* default.
208+
* <p/>
209+
* Returns <code>true</code> if the request is valid, or <code>false</code> if it isn't.
210+
*
211+
* @param messageContext the message context.
212+
* @return <code>true</code> if the response is valid; <code>false</code> otherwise
213+
* @see #setValidateResponse(boolean)
214+
*/
215+
public boolean handleResponse(MessageContext messageContext) throws WebServiceClientException {
216+
if (validateResponse) {
217+
Source responseSource = getValidationResponseSource(messageContext.getResponse());
218+
if (responseSource != null) {
219+
SAXParseException[] errors;
220+
try {
221+
errors = validator.validate(responseSource);
222+
}
223+
catch (IOException e) {
224+
throw new WebServiceIOException("Could not validate response: " + e.getMessage(), e);
225+
}
226+
if (!ObjectUtils.isEmpty(errors)) {
227+
return handleResponseValidationErrors(messageContext, errors);
228+
}
229+
else if (logger.isDebugEnabled()) {
230+
logger.debug("Response message validated");
231+
}
232+
}
233+
}
234+
return true;
235+
}
236+
237+
/**
238+
* Template method that is called when the response message contains validation errors.
239+
* <p/>
240+
* Default implementation logs all errors, and returns <code>false</code>, i.e. do not cot continue to process the
241+
* respone interceptor chain.
242+
*
243+
* @param messageContext the message context
244+
* @param errors the validation errors
245+
* @return <code>true</code> to continue the reponse interceptor chain, <code>false</code> (the default) otherwise
246+
*/
247+
protected boolean handleResponseValidationErrors(MessageContext messageContext, SAXParseException[] errors)
248+
throws WebServiceValidationException {
249+
for (int i = 0; i < errors.length; i++) {
250+
logger.warn("XML validation error on response: " + errors[i].getMessage());
251+
}
252+
return false;
253+
}
254+
255+
/** Does nothing by default. Faults are not validated. */
256+
public boolean handleFault(MessageContext messageContext) throws WebServiceClientException {
257+
return true;
258+
}
259+
260+
/**
261+
* Abstract template method that returns the part of the request message that is to be validated.
262+
*
263+
* @param request the request message
264+
* @return the part of the message that is to validated, or <code>null</code> not to validate anything
265+
*/
266+
protected abstract Source getValidationRequestSource(WebServiceMessage request);
267+
268+
/**
269+
* Abstract template method that returns the part of the response message that is to be validated.
270+
*
271+
* @param response the response message
272+
* @return the part of the message that is to validated, or <code>null</code> not to validate anything
273+
*/
274+
protected abstract Source getValidationResponseSource(WebServiceMessage response);
275+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright 2006 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.ws.client.support.interceptor;
18+
19+
import javax.xml.transform.Source;
20+
21+
import org.springframework.ws.WebServiceMessage;
22+
23+
/**
24+
* Client-side interceptor that validates the contents of <code>WebServiceMessage</code>s using a schema. Allows for
25+
* both W3C XML and RELAX NG schemas.
26+
* <p/>
27+
* When the payload is invalid, this interceptor stops processing of the interceptor chain.
28+
* <p/>
29+
* The schema to validate against is set with the <code>schema</code> property or <code>schemas</code> property. By
30+
* default, only the request message is validated, but this behaviour can be changed using the
31+
* <code>validateRequest</code> and <code>validateResponse</code> properties. Responses that contains faults are not
32+
* validated.
33+
*
34+
* @author Stefan Schmidt
35+
* @author Arjen Poutsma
36+
* @see #setSchema(org.springframework.core.io.Resource)
37+
* @see #setSchemas(org.springframework.core.io.Resource[])
38+
* @see #setValidateRequest(boolean)
39+
* @see #setValidateResponse(boolean)
40+
* @since 1.5.4
41+
*/
42+
public class PayloadValidatingInterceptor extends AbstractValidatingInterceptor {
43+
44+
/**
45+
* Returns the part of the request message that is to be validated. Default
46+
*
47+
* @param request the request message
48+
* @return the part of the message that is to validated, or <code>null</code> not to validate anything
49+
*/
50+
protected Source getValidationRequestSource(WebServiceMessage request) {
51+
return request.getPayloadSource();
52+
}
53+
54+
/**
55+
* Returns the part of the response message that is to be validated.
56+
*
57+
* @param response the response message
58+
* @return the part of the message that is to validated, or <code>null</code> not to validate anything
59+
*/
60+
protected Source getValidationResponseSource(WebServiceMessage response) {
61+
return response.getPayloadSource();
62+
}
63+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package org.springframework.ws.client.support.interceptor;
2+
3+
import org.xml.sax.SAXParseException;
4+
5+
import org.springframework.ws.client.WebServiceClientException;
6+
7+
/**
8+
* Exception thrown whenever a validation error occurs on the client-side.
9+
*
10+
* @author Stefan Schmidt
11+
* @author Arjen Poutsma
12+
* @since 1.5.4
13+
*/
14+
public class WebServiceValidationException extends WebServiceClientException {
15+
16+
private SAXParseException[] validationErrors;
17+
18+
/**
19+
* Create a new instance of the <code>WebServiceValidationException</code> class.
20+
*
21+
* @param msg the detail message
22+
*/
23+
public WebServiceValidationException(SAXParseException[] validationErrors) {
24+
super(createMessage(validationErrors));
25+
this.validationErrors = validationErrors;
26+
}
27+
28+
private static String createMessage(SAXParseException[] validationErrors) {
29+
StringBuffer buffer = new StringBuffer("XML validation error on response: ");
30+
31+
for (int i = 0; i < validationErrors.length; i++) {
32+
buffer.append(validationErrors[i].getMessage());
33+
}
34+
return buffer.toString();
35+
}
36+
37+
/** Returns the validation errors. */
38+
public SAXParseException[] getValidationErrors() {
39+
return validationErrors;
40+
}
41+
}

0 commit comments

Comments
 (0)