View Javadoc
1   /*
2    * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
3    * The software in this package is published under the terms of the CPAL v1.0
4    * license, a copy of which has been included with this distribution in the
5    * LICENSE.txt file.
6    */
7   package org.mule.module.xml.filters;
8   
9   import org.mule.api.MuleMessage;
10  import org.mule.api.lifecycle.Initialisable;
11  import org.mule.api.lifecycle.InitialisationException;
12  import org.mule.api.routing.filter.Filter;
13  import org.mule.config.i18n.CoreMessages;
14  import org.mule.module.xml.transformer.DelayedResult;
15  import org.mule.module.xml.util.XMLUtils;
16  import org.mule.util.IOUtils;
17  import org.mule.util.StringUtils;
18  
19  import java.io.IOException;
20  import java.io.InputStream;
21  import java.util.Map;
22  
23  import javax.xml.stream.XMLInputFactory;
24  import javax.xml.transform.Result;
25  import javax.xml.transform.Source;
26  import javax.xml.transform.dom.DOMResult;
27  import javax.xml.transform.stream.StreamSource;
28  import javax.xml.validation.Schema;
29  import javax.xml.validation.SchemaFactory;
30  import javax.xml.validation.Validator;
31  
32  import org.apache.commons.logging.Log;
33  import org.apache.commons.logging.LogFactory;
34  import org.w3c.dom.ls.LSResourceResolver;
35  import org.xml.sax.ErrorHandler;
36  import org.xml.sax.SAXException;
37  
38  /**
39   * Filter for schema validation.
40   * 
41   * @author Ryan Heaton
42   */
43  public class SchemaValidationFilter extends AbstractJaxpFilter implements Filter, Initialisable
44  {
45      public static final String DEFAULT_SCHEMA_LANGUAGE = "http://www.w3.org/2001/XMLSchema";
46  
47      protected transient Log logger = LogFactory.getLog(getClass());
48      private String schemaLocations;
49      private String schemaLanguage = DEFAULT_SCHEMA_LANGUAGE;
50      private Schema schemaObject;
51      private ErrorHandler errorHandler;
52      private Map<String, Boolean> validatorFeatures;
53      private Map<String, Object> validatorProperties;
54      private LSResourceResolver resourceResolver;
55      private boolean useStaxSource = false;
56      private boolean returnResult = true;
57      private XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
58  
59      /**
60       * Accepts the message if schema validation passes.
61       * 
62       * @param message The message.
63       * @return Whether the message passes schema validation.
64       */
65      public boolean accept(MuleMessage message)
66      {
67          Source source;
68          try
69          {
70              source = loadSource(message);
71          }
72          catch (Exception e)
73          {
74              if (e instanceof RuntimeException)
75              {
76                  throw (RuntimeException) e;
77              }
78              
79              if (logger.isInfoEnabled())
80              {
81                  logger.info("SchemaValidationFilter rejected a message because there was a problem interpreting the payload as XML.", e);
82              }
83              return false;
84          }
85  
86          if (source == null)
87          {
88              if (logger.isInfoEnabled())
89              {
90                  logger.info("SchemaValidationFilter rejected a message because the XML source was null.");
91              }
92              return false;
93          }
94  
95          
96          DOMResult result = null;
97          
98          try
99          {
100             if (returnResult) 
101             {
102                 result = new DOMResult();
103                 createValidator().validate(source, result);
104             }
105             else 
106             {
107                 createValidator().validate(source);
108             }
109         }
110         catch (SAXException e)
111         {
112             if (logger.isDebugEnabled())
113             {
114                 logger.debug(
115                     "SchemaValidationFilter rejected a message because it apparently failed to validate against the schema.",
116                     e);
117             }
118             return false;
119         }
120         catch (IOException e)
121         {
122             if (logger.isInfoEnabled())
123             {
124                 logger.info(
125                     "SchemaValidationFilter rejected a message because there was a problem reading the XML.",
126                     e);
127             }
128             return false;
129         }
130         finally 
131         {
132             if (result != null && result.getNode() != null)
133             {
134                 message.setPayload(result.getNode());
135             }
136         }
137         
138         if (logger.isDebugEnabled())
139         {
140             logger.debug("SchemaValidationFilter accepted the message.");
141         }
142 
143         return true;
144     }
145 
146     /**
147      * Get a delayed result.
148      * 
149      * @param source The source.
150      * @return The result.
151      */
152     protected Object getDelayedResult(final Source source)
153     {
154         return new DelayedResult()
155         {
156             private String systemId;
157 
158             public void write(Result result) throws Exception
159             {
160                 createValidator().validate(source, result);
161             }
162 
163             public String getSystemId()
164             {
165                 return systemId;
166             }
167 
168             public void setSystemId(String systemId)
169             {
170                 this.systemId = systemId;
171             }
172         };
173     }
174 
175     /**
176      * Load the source from the specified object.
177      * 
178      * @param msg Encompassing message
179      * @return The source
180      */
181     protected Source loadSource(MuleMessage msg) throws Exception
182     {
183         Object payload = msg.getPayload();
184         if (returnResult)
185         {
186             // Validation requires that a DOM goes in for a DOM to go out
187             payload = toDOMNode(payload);
188         }
189         return XMLUtils.toXmlSource(getXMLInputFactory(), isUseStaxSource(), payload);
190     }
191 
192     public void initialise() throws InitialisationException
193     {
194         super.initialise();
195         
196         if (getSchemaObject() == null)
197         {
198             if (schemaLocations == null)
199             {
200                 throw new InitialisationException(CoreMessages.objectIsNull("schemaLocations"), this);
201             }
202 
203             String[] split = StringUtils.splitAndTrim(schemaLocations, ",");
204             Source[] schemas = new Source[split.length];
205             for (int i = 0; i < split.length; i++)
206             {
207                 String loc = split[i];
208                 InputStream schemaStream;
209                 try
210                 {
211                     schemaStream = loadSchemaStream(loc);
212                 }
213                 catch (IOException e)
214                 {
215                     throw new InitialisationException(e, this);
216                 }
217     
218                 if (schemaStream == null)
219                 {
220                     throw new InitialisationException(CoreMessages.failedToLoad(loc), this);
221                 }
222                 
223                 schemas[i] = new StreamSource(schemaStream);
224             }
225             
226             SchemaFactory schemaFactory = SchemaFactory.newInstance(getSchemaLanguage());
227 
228             if (logger.isInfoEnabled())
229             {
230                 logger.info("Schema factory implementation: " + schemaFactory);
231             }
232 
233             if (this.errorHandler != null)
234             {
235                 schemaFactory.setErrorHandler(this.errorHandler);
236             }
237 
238             if (this.resourceResolver != null)
239             {
240                 schemaFactory.setResourceResolver(this.resourceResolver);
241             }
242 
243             Schema schema;
244             try
245             {
246                 schema = schemaFactory.newSchema(schemas);
247             }
248             catch (SAXException e)
249             {
250                 throw new InitialisationException(e, this);
251             }
252 
253             setSchemaObject(schema);
254         }
255 
256         if (getSchemaObject() == null)
257         {
258             throw new InitialisationException(CoreMessages.objectIsNull("schemaObject"), this);
259         }
260     }
261 
262     protected InputStream loadSchemaStream(String schemaLocation) throws IOException
263     {
264         return IOUtils.getResourceAsStream(schemaLocation, getClass());
265     }
266 
267     /**
268      * Create a validator.
269      * 
270      * @return The validator.
271      */
272     public Validator createValidator() throws SAXException
273     {
274         Validator validator = getSchemaObject().newValidator();
275 
276         if (this.validatorFeatures != null)
277         {
278             for (Map.Entry<String, Boolean> feature : this.validatorFeatures.entrySet())
279             {
280                 validator.setFeature(feature.getKey(), feature.getValue());
281             }
282         }
283 
284         if (this.validatorProperties != null)
285         {
286             for (Map.Entry<String, Object> validatorProperty : this.validatorProperties.entrySet())
287             {
288                 validator.setProperty(validatorProperty.getKey(), validatorProperty.getValue());
289             }
290         }
291 
292         return validator;
293     }
294 
295     public String getSchemaLocations()
296     {
297         return schemaLocations;
298     }
299 
300     public void setSchemaLocations(String schemaLocations)
301     {
302         this.schemaLocations = schemaLocations;
303     }
304 
305     public String getSchemaLanguage()
306     {
307         return schemaLanguage;
308     }
309 
310     public void setSchemaLanguage(String schemaLanguage)
311     {
312         this.schemaLanguage = schemaLanguage;
313     }
314 
315     public Schema getSchemaObject()
316     {
317         return schemaObject;
318     }
319 
320     public void setSchemaObject(Schema schemaObject)
321     {
322         this.schemaObject = schemaObject;
323     }
324 
325     public ErrorHandler getErrorHandler()
326     {
327         return errorHandler;
328     }
329 
330     public void setErrorHandler(ErrorHandler errorHandler)
331     {
332         this.errorHandler = errorHandler;
333     }
334 
335     public LSResourceResolver getResourceResolver()
336     {
337         return resourceResolver;
338     }
339 
340     public void setResourceResolver(LSResourceResolver resourceResolver)
341     {
342         this.resourceResolver = resourceResolver;
343     }
344 
345     public Map<String, Boolean> getValidatorFeatures()
346     {
347         return validatorFeatures;
348     }
349 
350     public void setValidatorFeatures(Map<String, Boolean> validatorFeatures)
351     {
352         this.validatorFeatures = validatorFeatures;
353     }
354 
355     public Map<String, Object> getValidatorProperties()
356     {
357         return validatorProperties;
358     }
359 
360     public void setValidatorProperties(Map<String, Object> validatorProperties)
361     {
362         this.validatorProperties = validatorProperties;
363     }
364 
365     public XMLInputFactory getXMLInputFactory()
366     {
367         return xmlInputFactory;
368     }
369 
370     public void setXMLInputFactory(XMLInputFactory xmlInputFactory)
371     {
372         this.xmlInputFactory = xmlInputFactory;
373     }
374 
375     public boolean isUseStaxSource()
376     {
377         return useStaxSource;
378     }
379 
380     public void setUseStaxSource(boolean useStaxSource)
381     {
382         this.useStaxSource = useStaxSource;
383     }
384 
385     public boolean isReturnResult()
386     {
387         return returnResult;
388     }
389 
390     public void setReturnResult(boolean returnResult)
391     {
392         this.returnResult = returnResult;
393     }
394 }