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