View Javadoc

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