View Javadoc

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 }