View Javadoc

1   /*
2    * Copyright 2005-2010 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.springframework.beans.factory.InitializingBean;
23  import org.springframework.core.io.Resource;
24  import org.springframework.util.Assert;
25  import org.springframework.util.ObjectUtils;
26  import org.springframework.util.StringUtils;
27  import org.springframework.ws.WebServiceMessage;
28  import org.springframework.ws.client.WebServiceClientException;
29  import org.springframework.ws.client.WebServiceIOException;
30  import org.springframework.ws.context.MessageContext;
31  import org.springframework.xml.transform.TransformerObjectSupport;
32  import org.springframework.xml.validation.XmlValidator;
33  import org.springframework.xml.validation.XmlValidatorFactory;
34  import org.springframework.xml.xsd.XsdSchema;
35  import org.springframework.xml.xsd.XsdSchemaCollection;
36  
37  import org.xml.sax.SAXParseException;
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 (Resource schema : schemas) {
101             Assert.notNull(schema, "schema must not be null");
102             Assert.isTrue(schema.exists(), "schema \"" + schema + "\" 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 (Resource schema : schemas) {
145                 Assert.isTrue(schema.exists(), "schema [" + schema + "] 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 (SAXParseException error : errors) {
199             logger.error("XML validation error on request: " + error.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 (SAXParseException error : errors) {
250             logger.warn("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) 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 }