1 /*
2 * Copyright 2005-2011 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.server.endpoint.interceptor;
18
19 import java.io.IOException;
20 import javax.xml.transform.Source;
21 import javax.xml.transform.TransformerException;
22
23 import org.springframework.beans.factory.InitializingBean;
24 import org.springframework.core.io.Resource;
25 import org.springframework.util.Assert;
26 import org.springframework.util.ObjectUtils;
27 import org.springframework.util.StringUtils;
28 import org.springframework.ws.WebServiceMessage;
29 import org.springframework.ws.context.MessageContext;
30 import org.springframework.ws.server.EndpointInterceptor;
31 import org.springframework.ws.soap.SoapFault;
32 import org.springframework.ws.soap.SoapMessage;
33 import org.springframework.xml.transform.TransformerObjectSupport;
34 import org.springframework.xml.validation.ValidationErrorHandler;
35 import org.springframework.xml.validation.XmlValidator;
36 import org.springframework.xml.validation.XmlValidatorFactory;
37 import org.springframework.xml.xsd.XsdSchema;
38 import org.springframework.xml.xsd.XsdSchemaCollection;
39
40 import org.xml.sax.SAXException;
41 import org.xml.sax.SAXParseException;
42
43 /**
44 * Abstract base class for <code>EndpointInterceptor</code> implementations that validate part of the message using a
45 * schema. The exact message part is determined by the <code>getValidationRequestSource</code> and
46 * <code>getValidationResponseSource</code> template methods.
47 * <p/>
48 * By default, only the request message is validated, but this behaviour can be changed using the
49 * <code>validateRequest</code> and <code>validateResponse</code> properties.
50 *
51 * @author Arjen Poutsma
52 * @see #getValidationRequestSource(org.springframework.ws.WebServiceMessage)
53 * @see #getValidationResponseSource(org.springframework.ws.WebServiceMessage)
54 * @since 1.0.0
55 */
56 public abstract class AbstractValidatingInterceptor extends TransformerObjectSupport
57 implements EndpointInterceptor, InitializingBean {
58
59 private String schemaLanguage = XmlValidatorFactory.SCHEMA_W3C_XML;
60
61 private Resource[] schemas;
62
63 private boolean validateRequest = true;
64
65 private boolean validateResponse = false;
66
67 private XmlValidator validator;
68
69 private ValidationErrorHandler errorHandler;
70
71 public String getSchemaLanguage() {
72 return schemaLanguage;
73 }
74
75 /**
76 * Sets the schema language. Default is the W3C XML Schema: <code>http://www.w3.org/2001/XMLSchema"</code>.
77 *
78 * @see org.springframework.xml.validation.XmlValidatorFactory#SCHEMA_W3C_XML
79 * @see org.springframework.xml.validation.XmlValidatorFactory#SCHEMA_RELAX_NG
80 */
81 public void setSchemaLanguage(String schemaLanguage) {
82 this.schemaLanguage = schemaLanguage;
83 }
84
85 /** Returns the schema resources to use for validation. */
86 public Resource[] getSchemas() {
87 return schemas;
88 }
89
90 /**
91 * Sets the schema resource to use for validation. Setting this property, {@link
92 * #setXsdSchemaCollection(XsdSchemaCollection) xsdSchemaCollection}, {@link #setSchema(Resource) schema}, or {@link
93 * #setSchemas(Resource[]) schemas} is required.
94 */
95 public void setSchema(Resource schema) {
96 setSchemas(new Resource[]{schema});
97 }
98
99 /**
100 * Sets the schema resources to use for validation. Setting this property, {@link
101 * #setXsdSchemaCollection(XsdSchemaCollection) xsdSchemaCollection}, {@link #setSchema(Resource) schema}, or {@link
102 * #setSchemas(Resource[]) schemas} is required.
103 */
104 public void setSchemas(Resource[] schemas) {
105 Assert.notEmpty(schemas, "schemas must not be empty or null");
106 for (Resource schema : schemas) {
107 Assert.notNull(schema, "schema must not be null");
108 Assert.isTrue(schema.exists(), "schema \"" + schema + "\" does not exit");
109 }
110 this.schemas = schemas;
111 }
112
113 /**
114 * Sets the {@link XsdSchema} to use for validation. Setting this property, {@link
115 * #setXsdSchemaCollection(XsdSchemaCollection) xsdSchemaCollection}, {@link #setSchema(Resource) schema}, or {@link
116 * #setSchemas(Resource[]) schemas} is required.
117 *
118 * @param schema the xsd schema to use
119 * @throws IOException in case of I/O errors
120 */
121 public void setXsdSchema(XsdSchema schema) throws IOException {
122 this.validator = schema.createValidator();
123 }
124
125 /**
126 * Sets the {@link XsdSchemaCollection} to use for validation. Setting this property, {@link
127 * #setXsdSchema(XsdSchema) xsdSchema}, {@link #setSchema(Resource) schema}, or {@link #setSchemas(Resource[])
128 * schemas} is required.
129 *
130 * @param schemaCollection the xsd schema collection to use
131 * @throws IOException in case of I/O errors
132 */
133 public void setXsdSchemaCollection(XsdSchemaCollection schemaCollection) throws IOException {
134 this.validator = schemaCollection.createValidator();
135 }
136
137 /**
138 * Sets the error handler to use for validation. If not set, a default error handler will be used.
139 *
140 * @param errorHandler the error handler.
141 */
142 public void setErrorHandler(ValidationErrorHandler errorHandler) {
143 this.errorHandler = errorHandler;
144 }
145
146 /** Indicates whether the request should be validated against the schema. Default is <code>true</code>. */
147 public void setValidateRequest(boolean validateRequest) {
148 this.validateRequest = validateRequest;
149 }
150
151 /** Indicates whether the response should be validated against the schema. Default is <code>false</code>. */
152 public void setValidateResponse(boolean validateResponse) {
153 this.validateResponse = validateResponse;
154 }
155
156 public void afterPropertiesSet() throws Exception {
157 if (validator == null && !ObjectUtils.isEmpty(schemas)) {
158 Assert.hasLength(schemaLanguage, "schemaLanguage is required");
159 for (Resource schema : schemas) {
160 Assert.isTrue(schema.exists(), "schema [" + schema + "] does not exist");
161 }
162 if (logger.isInfoEnabled()) {
163 logger.info("Validating using " + StringUtils.arrayToCommaDelimitedString(schemas));
164 }
165 validator = XmlValidatorFactory.createValidator(schemas, schemaLanguage);
166 }
167 Assert.notNull(validator, "Setting 'schema', 'schemas', 'xsdSchema', or 'xsdSchemaCollection' is required");
168 }
169
170 /**
171 * Validates the request message in the given message context. Validation only occurs if
172 * <code>validateRequest</code> is set to <code>true</code>, which is the default.
173 * <p/>
174 * Returns <code>true</code> if the request is valid, or <code>false</code> if it isn't. Additionally, when the
175 * request message is a {@link SoapMessage}, a {@link SoapFault} is added as response.
176 *
177 * @param messageContext the message context
178 * @return <code>true</code> if the message is valid; <code>false</code> otherwise
179 * @see #setValidateRequest(boolean)
180 */
181 public boolean handleRequest(MessageContext messageContext, Object endpoint)
182 throws IOException, SAXException, TransformerException {
183 if (validateRequest) {
184 Source requestSource = getValidationRequestSource(messageContext.getRequest());
185 if (requestSource != null) {
186 SAXParseException[] errors = validator.validate(requestSource, errorHandler);
187 if (!ObjectUtils.isEmpty(errors)) {
188 return handleRequestValidationErrors(messageContext, errors);
189 }
190 else if (logger.isDebugEnabled()) {
191 logger.debug("Request message validated");
192 }
193 }
194 }
195 return true;
196 }
197
198 /**
199 * Template method that is called when the request message contains validation errors. Default implementation logs
200 * all errors, and returns <code>false</code>, i.e. do not process the request.
201 *
202 * @param messageContext the message context
203 * @param errors the validation errors
204 * @return <code>true</code> to continue processing the request, <code>false</code> (the default) otherwise
205 */
206 protected boolean handleRequestValidationErrors(MessageContext messageContext, SAXParseException[] errors)
207 throws TransformerException {
208 for (SAXParseException error : errors) {
209 logger.warn("XML validation error on request: " + error.getMessage());
210 }
211 return false;
212 }
213
214 /**
215 * Validates the response message in the given message context. Validation only occurs if
216 * <code>validateResponse</code> is set to <code>true</code>, which is <strong>not</strong> the default.
217 * <p/>
218 * Returns <code>true</code> if the request is valid, or <code>false</code> if it isn't.
219 *
220 * @param messageContext the message context.
221 * @return <code>true</code> if the response is valid; <code>false</code> otherwise
222 * @see #setValidateResponse(boolean)
223 */
224 public boolean handleResponse(MessageContext messageContext, Object endpoint) throws IOException, SAXException {
225 if (validateResponse) {
226 Source responseSource = getValidationResponseSource(messageContext.getResponse());
227 if (responseSource != null) {
228 SAXParseException[] errors = validator.validate(responseSource, errorHandler);
229 if (!ObjectUtils.isEmpty(errors)) {
230 return handleResponseValidationErrors(messageContext, errors);
231 }
232 else if (logger.isDebugEnabled()) {
233 logger.debug("Response message validated");
234 }
235 }
236 }
237 return true;
238 }
239
240 /**
241 * Template method that is called when the response message contains validation errors. Default implementation logs
242 * all errors, and returns <code>false</code>, i.e. do not cot continue to process the response interceptor chain.
243 *
244 * @param messageContext the message context
245 * @param errors the validation errors
246 * @return <code>true</code> to continue the response interceptor chain, <code>false</code> (the default) otherwise
247 */
248 protected boolean handleResponseValidationErrors(MessageContext messageContext, SAXParseException[] errors) {
249 for (SAXParseException error : errors) {
250 logger.error("XML validation error on response: " + error.getMessage());
251 }
252 return false;
253 }
254
255 /** Does nothing by default. Faults are not validated. */
256 public boolean handleFault(MessageContext messageContext, Object endpoint) throws Exception {
257 return true;
258 }
259
260 /** Does nothing by default.*/
261 public void afterCompletion(MessageContext messageContext, Object endpoint, Exception ex) {
262 }
263
264 /**
265 * Abstract template method that returns the part of the request message that is to be validated.
266 *
267 * @param request the request message
268 * @return the part of the message that is to validated, or <code>null</code> not to validate anything
269 */
270 protected abstract Source getValidationRequestSource(WebServiceMessage request);
271
272 /**
273 * Abstract template method that returns the part of the response message that is to be validated.
274 *
275 * @param response the response message
276 * @return the part of the message that is to validated, or <code>null</code> not to validate anything
277 */
278 protected abstract Source getValidationResponseSource(WebServiceMessage response);
279 }