View Javadoc

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 }