View Javadoc

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