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 static org.mule.util.ClassUtils.equal;
10  import static org.mule.util.ClassUtils.hash;
11  
12  import org.mule.api.MuleContext;
13  import org.mule.api.MuleMessage;
14  import org.mule.api.context.MuleContextAware;
15  import org.mule.api.expression.ExpressionRuntimeException;
16  import org.mule.api.lifecycle.Initialisable;
17  import org.mule.api.lifecycle.InitialisationException;
18  import org.mule.api.registry.RegistrationException;
19  import org.mule.api.routing.filter.Filter;
20  import org.mule.config.i18n.CoreMessages;
21  import org.mule.module.xml.util.NamespaceManager;
22  import org.mule.module.xml.util.XMLUtils;
23  
24  import java.util.HashMap;
25  import java.util.Iterator;
26  import java.util.Map;
27  
28  import org.apache.commons.jxpath.AbstractFactory;
29  import org.apache.commons.jxpath.JXPathContext;
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  import org.dom4j.Document;
33  import org.dom4j.DocumentHelper;
34  import org.dom4j.XPath;
35  
36  /**
37   * <code>JXPathFilter</code> evaluates an XPath expression against a W3C Document,
38   * XML string, or Java bean and returns true if the result is as expected.
39   */
40  public class JXPathFilter implements Filter, MuleContextAware, Initialisable
41  {
42  
43      protected transient Log logger = LogFactory.getLog(getClass());
44  
45      private String pattern;
46      private String expectedValue;
47      private Map namespaces = null;
48      private Map contextProperties = null;
49      private AbstractFactory factory;
50      private boolean lenient = true;
51  
52      private MuleContext muleContext;
53      private NamespaceManager namespaceManager;
54  
55      public JXPathFilter()
56      {
57          super();
58      }
59  
60      public void setMuleContext(MuleContext context)
61      {
62          this.muleContext = context;
63      }
64  
65      public void initialise() throws InitialisationException
66      {
67          try
68          {
69              namespaceManager = muleContext.getRegistry().lookupObject(NamespaceManager.class);
70          }
71          catch (RegistrationException e)
72          {
73              throw new ExpressionRuntimeException(CoreMessages.failedToLoad("NamespaceManager"), e);
74          }
75  
76          if (namespaceManager != null)
77          {
78              if (namespaces == null)
79              {
80                  namespaces = new HashMap(namespaceManager.getNamespaces());
81              }
82              else
83              {
84                  namespaces.putAll(namespaceManager.getNamespaces());
85              }
86          }
87      }
88  
89      public JXPathFilter(String pattern)
90      {
91          this.pattern = pattern;
92      }
93  
94      public JXPathFilter(String pattern, String expectedValue)
95      {
96          this.pattern = pattern;
97          this.expectedValue = expectedValue;
98      }
99  
100     public boolean accept(MuleMessage obj)
101     {
102         if (obj.getPayload() instanceof byte[])
103         {
104             try
105             {
106                 return accept(obj.getPayloadAsString());
107             }
108             catch (Exception e)
109             {
110                 logger.warn("JxPath filter rejected message because it could not convert from byte[] to String: " + e.getMessage(), e);
111                 return false;
112             }
113         }
114         return accept(obj.getPayload());
115     }
116 
117     private boolean accept(Object obj)
118     {
119         if (obj == null)
120         {
121             logger.warn("Applying JXPathFilter to null object.");
122             return false;
123         }
124         if (pattern == null)
125         {
126             logger.warn("Expression for JXPathFilter is not set.");
127             return false;
128         }
129         if (expectedValue == null)
130         {
131             // Handle the special case where the expected value really is null.
132             if (pattern.endsWith("= null") || pattern.endsWith("=null"))
133             {
134                 expectedValue = "null";
135                 pattern = pattern.substring(0, pattern.lastIndexOf("="));
136             }
137             else
138             {
139                 if (logger.isInfoEnabled())
140                 {
141                     logger.info("Expected value for JXPathFilter is not set, using 'true' by default");
142                 }
143                 expectedValue = Boolean.TRUE.toString();
144             }
145         }
146 
147         Object xpathResult = null;
148         boolean accept = false;
149 
150         Document dom4jDoc;
151         try
152         {
153             dom4jDoc = XMLUtils.toDocument(obj, muleContext);
154         }
155         catch (Exception e)
156         {
157             logger.warn("JxPath filter rejected message because of an error while parsing XML: " + e.getMessage(), e);
158             return false;
159         }
160 
161         // Payload is XML
162         if (dom4jDoc != null)
163         {
164             if (namespaces == null)
165             {
166                 // no namespace defined, let's perform a direct evaluation
167                 xpathResult = dom4jDoc.valueOf(pattern);
168             }
169             else
170             {
171                 // create an xpath expression with namespaces and evaluate it
172                 XPath xpath = DocumentHelper.createXPath(pattern);
173                 xpath.setNamespaceURIs(namespaces);
174                 xpathResult = xpath.valueOf(dom4jDoc);
175             }
176         }
177         // Payload is a Java object
178         else
179         {
180             if (logger.isDebugEnabled())
181             {
182                 logger.debug("Passing object of type " + obj.getClass().getName() + " to JXPathContext");
183             }
184             JXPathContext context = JXPathContext.newContext(obj);
185             initialise(context);
186             xpathResult = context.getValue(pattern);
187         }
188 
189         if (logger.isDebugEnabled())
190         {
191             logger.debug("JXPathFilter Expression result = '" + xpathResult + "' -  Expected value = '"
192                     + expectedValue + "'");
193         }
194         // Compare the XPath result with the expected result.
195         if (xpathResult != null)
196         {
197             accept = xpathResult.toString().equals(expectedValue);
198         }
199         else
200         {
201             // A null result was actually expected.
202             if (expectedValue.equals("null"))
203             {
204                 accept = true;
205             }
206             // A null result was not expected, something probably went wrong.
207             else
208             {
209                 logger.warn("JXPathFilter expression evaluates to null: " + pattern);
210             }
211         }
212 
213         if (logger.isDebugEnabled())
214         {
215             logger.debug("JXPathFilter accept object  : " + accept);
216         }
217 
218         return accept;
219     }
220 
221     /**
222      * Initializes the JXPathContext based on any relevant properties set for the
223      * filter.
224      *
225      * @param context the JXPathContext to initialize
226      */
227     protected void initialise(JXPathContext context)
228     {
229         Map.Entry entry;
230         if (namespaces != null)
231         {
232             if (logger.isDebugEnabled())
233             {
234                 logger.debug("Initializing JXPathContext with namespaces: " + namespaces);
235             }
236 
237             for (Iterator iterator = namespaces.entrySet().iterator(); iterator.hasNext();)
238             {
239                 entry = (Map.Entry) iterator.next();
240                 context.registerNamespace(entry.getKey().toString(), entry.getValue().toString());
241             }
242         }
243 
244         if (contextProperties != null)
245         {
246             if (logger.isDebugEnabled())
247             {
248                 logger.debug("Initializing JXPathContext with properties: " + contextProperties);
249             }
250 
251             for (Iterator iterator = contextProperties.entrySet().iterator(); iterator.hasNext();)
252             {
253                 entry = (Map.Entry) iterator.next();
254                 context.setValue(entry.getKey().toString(), entry.getValue());
255             }
256         }
257 
258         if (factory != null)
259         {
260             context.setFactory(factory);
261         }
262 
263         context.setLenient(lenient);
264     }
265 
266     /**
267      * @return XPath expression
268      */
269     public String getPattern()
270     {
271         return pattern;
272     }
273 
274     /**
275      * @param pattern The XPath expression
276      */
277     public void setPattern(String pattern)
278     {
279         this.pattern = pattern;
280     }
281 
282     /**
283      * @return The expected result value of the XPath expression
284      */
285     public String getExpectedValue()
286     {
287         return expectedValue;
288     }
289 
290     /**
291      * Sets the expected result value of the XPath expression
292      */
293     public void setExpectedValue(String expectedValue)
294     {
295         this.expectedValue = expectedValue;
296     }
297 
298     /**
299      * @return The expected result value of the XPath expression
300      * @deprecated Use <code>getExpectedValue()</code>.
301      */
302     public String getValue()
303     {
304         return getExpectedValue();
305     }
306 
307     /**
308      * Sets the expected result value of the XPath expression
309      *
310      * @deprecated Use <code>setExpectedValue(String expectedValue)</code>.
311      */
312     public void setValue(String value)
313     {
314         setExpectedValue(value);
315     }
316 
317     public Map getNamespaces()
318     {
319         return namespaces;
320     }
321 
322     public void setNamespaces(Map namespaces)
323     {
324         this.namespaces = namespaces;
325     }
326 
327     public Map getContextProperties()
328     {
329         return contextProperties;
330     }
331 
332     public void setContextProperties(Map contextProperties)
333     {
334         this.contextProperties = contextProperties;
335     }
336 
337     public AbstractFactory getFactory()
338     {
339         return factory;
340     }
341 
342     public void setFactory(AbstractFactory factory)
343     {
344         this.factory = factory;
345     }
346 
347     public boolean isLenient()
348     {
349         return lenient;
350     }
351 
352     public void setLenient(boolean lenient)
353     {
354         this.lenient = lenient;
355     }
356     
357     public boolean equals(Object obj)
358     {
359         if (this == obj) return true;
360         if (obj == null || getClass() != obj.getClass()) return false;
361 
362         final JXPathFilter other = (JXPathFilter) obj;
363         return equal(expectedValue, other.expectedValue)
364             && equal(contextProperties, other.contextProperties)
365             && equal(namespaces, other.namespaces)
366             && equal(pattern, other.pattern)
367             && lenient == other.lenient;
368     }
369 
370     public int hashCode()
371     {
372         return hash(new Object[]{this.getClass(), expectedValue, contextProperties, namespaces, pattern, lenient});
373     }
374 }