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