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.config.i18n.MessageFactory;
22  import org.mule.module.xml.util.NamespaceManager;
23  import org.mule.util.ClassUtils;
24  
25  import java.text.MessageFormat;
26  import java.util.Arrays;
27  import java.util.Collections;
28  import java.util.HashMap;
29  import java.util.Iterator;
30  import java.util.Map;
31  
32  import javax.xml.namespace.NamespaceContext;
33  import javax.xml.xpath.XPath;
34  import javax.xml.xpath.XPathConstants;
35  import javax.xml.xpath.XPathFactory;
36  
37  import org.apache.commons.logging.Log;
38  import org.apache.commons.logging.LogFactory;
39  import org.w3c.dom.Node;
40  
41  /**
42   */
43  public class XPathFilter extends AbstractJaxpFilter 
44      implements Filter, Initialisable, MuleContextAware
45  {
46  
47      protected transient Log logger = LogFactory.getLog(getClass());
48  
49      private String pattern;
50      private String expectedValue;
51      private XPath xpath;
52      private Map<String, String> prefixToNamespaceMap = null;
53  
54      private NamespaceManager namespaceManager;
55  
56      private MuleContext muleContext;
57  
58      public XPathFilter()
59      {
60      }
61  
62      public XPathFilter(String pattern)
63      {
64          this.pattern = pattern;
65      }
66  
67      public XPathFilter(String pattern, String expectedValue)
68      {
69          this.pattern = pattern;
70          this.expectedValue = expectedValue;
71      }
72  
73      public void setMuleContext(MuleContext context)
74      {
75          this.muleContext = context;
76      }
77  
78      public void initialise() throws InitialisationException
79      {
80          super.initialise();
81          
82          if (getXpath() == null)
83          {
84              setXpath(XPathFactory.newInstance().newXPath());
85          }
86  
87  
88          if (pattern == null)
89          {
90              throw new InitialisationException(
91                  MessageFactory.createStaticMessage("A pattern must be supplied to the " +
92                                                     ClassUtils.getSimpleName(getClass())),
93                  this);
94          }
95  
96          if (muleContext != null)
97          {
98              try
99              {
100                 namespaceManager = muleContext.getRegistry().lookupObject(NamespaceManager.class);
101             }
102             catch (RegistrationException e)
103             {
104                 throw new ExpressionRuntimeException(CoreMessages.failedToLoad("NamespaceManager"), e);
105             }
106 
107             if (namespaceManager != null)
108             {
109                 if (prefixToNamespaceMap == null)
110                 {
111                     prefixToNamespaceMap = new HashMap(namespaceManager.getNamespaces());
112                 }
113                 else
114                 {
115                     prefixToNamespaceMap.putAll(namespaceManager.getNamespaces());
116                 }
117             }
118         }
119         
120         final Map<String, String> prefixToNamespaceMap = this.prefixToNamespaceMap;
121         if (prefixToNamespaceMap != null)
122         {
123             getXpath().setNamespaceContext(new NamespaceContext()
124             {
125                 public String getNamespaceURI(String prefix)
126                 {
127                     return prefixToNamespaceMap.get(prefix);
128                 }
129 
130                 public String getPrefix(String namespaceURI)
131                 {
132 
133                     for (Map.Entry<String, String> entry : prefixToNamespaceMap.entrySet())
134                     {
135                         if (namespaceURI.equals(entry.getValue()))
136                         {
137                             return entry.getKey();
138                         }
139                     }
140 
141                     return null;
142                 }
143 
144                 public Iterator getPrefixes(String namespaceURI)
145                 {
146                     String prefix = getPrefix(namespaceURI);
147                     if (prefix == null)
148                     {
149                         return Collections.emptyList().iterator();
150                     }
151                     else
152                     {
153                         return Arrays.asList(prefix).iterator();
154                     }
155                 }
156             });
157         }
158 
159         if (logger.isInfoEnabled())
160         {
161             logger.info("XPath implementation: " + getXpath());
162             logger.info("DocumentBuilderFactory implementation: " + getDocumentBuilderFactory());
163         }
164     }
165 
166     public boolean accept(MuleMessage message)
167     {
168         Object payload = message.getPayload();
169         if (payload == null)
170         {
171             if (logger.isWarnEnabled())
172             {
173                 logger.warn("Applying " + ClassUtils.getSimpleName(getClass()) + " to null object.");
174             }
175             return false;
176         }
177         if (pattern == null)
178         {
179             if (logger.isWarnEnabled())
180             {
181                 logger.warn("Expression for " + ClassUtils.getSimpleName(getClass()) + " is not set.");
182             }
183             return false;
184         }
185         if (expectedValue == null)
186         {
187             // Handle the special case where the expected value really is null.
188             if (pattern.endsWith("= null") || pattern.endsWith("=null"))
189             {
190                 expectedValue = "null";
191                 pattern = pattern.substring(0, pattern.lastIndexOf("="));
192             }
193             else
194             {
195                 if (logger.isInfoEnabled())
196                 {
197                     logger.info("''expectedValue'' attribute for " + ClassUtils.getSimpleName(getClass()) +
198                                 " is not set, using 'true' by default");
199                 }
200                 expectedValue = Boolean.TRUE.toString();
201             }
202         }
203 
204         Node node;
205         try
206         {
207             node = toDOMNode(payload);
208         }
209         catch (Exception e)
210         {
211             if (logger.isWarnEnabled())
212             {
213                 logger.warn(ClassUtils.getSimpleName(getClass()) + " filter rejected message because of an error while parsing XML: "
214                             + e.getMessage(), e);
215             }
216             return false;
217         }
218 
219         message.setPayload(node);
220 
221         return accept(node);
222     }
223 
224     protected boolean accept(Node node)
225     {
226         Object xpathResult;
227         boolean accept = false;
228 
229         try
230         {
231             xpathResult = getXpath().evaluate(pattern, node, XPathConstants.STRING);
232         }
233         catch (Exception e)
234         {
235             if (logger.isWarnEnabled())
236             {
237                 logger.warn(
238                         ClassUtils.getSimpleName(getClass()) + " filter rejected message because of an error while evaluating the expression: "
239                         + e.getMessage(), e);
240             }
241             return false;
242         }
243 
244         if (logger.isDebugEnabled())
245         {
246             logger.debug(MessageFormat.format("{0} Expression result = ''{1}'' -  Expected value = ''{2}''",
247                                               ClassUtils.getSimpleName(getClass()), xpathResult, expectedValue));
248         }
249 
250         // Compare the XPath result with the expected result.
251         if (xpathResult != null && !"".equals(xpathResult))
252         {
253             accept = xpathResult.toString().equals(expectedValue);
254         }
255         else
256         {
257             // A null result was actually expected.
258             if ("null".equals(expectedValue))
259             {
260                 accept = true;
261             }
262             // A null result was not expected, something probably went wrong.
263             else
264             {
265                 if (logger.isDebugEnabled())
266                 {
267                     logger.debug(MessageFormat.format("{0} expression evaluates to null: {1}",
268                                                       ClassUtils.getSimpleName(getClass()), pattern));
269                 }
270             }
271         }
272 
273         if (logger.isDebugEnabled())
274         {
275             logger.debug(MessageFormat.format("{0} accept object  : {1}", ClassUtils.getSimpleName(getClass()), accept));
276         }
277 
278         return accept;
279     }
280 
281     /**
282      * @return XPath expression
283      */
284     public String getPattern()
285     {
286         return pattern;
287     }
288 
289     /**
290      * @param pattern The XPath expression
291      */
292     public void setPattern(String pattern)
293     {
294         this.pattern = pattern;
295     }
296 
297     /**
298      * @return The expected result value of the XPath expression
299      */
300     public String getExpectedValue()
301     {
302         return expectedValue;
303     }
304 
305     /**
306      * Sets the expected result value of the XPath expression
307      * 
308      * @param expectedValue The expected value.
309      */
310     public void setExpectedValue(String expectedValue)
311     {
312         this.expectedValue = expectedValue;
313     }
314 
315     /**
316      * The xpath object to use to evaluate the expression.
317      * 
318      * @return The xpath object to use to evaluate the expression.
319      */
320     public XPath getXpath()
321     {
322         return xpath;
323     }
324 
325     /**
326      * The xpath object to use to evaluate the expression.
327      * 
328      * @param xpath The xpath object to use to evaluate the expression.
329      */
330     public void setXpath(XPath xpath)
331     {
332         this.xpath = xpath;
333     }
334 
335 
336     /**
337      * The prefix-to-namespace map for the namespace context to be applied to the
338      * XPath evaluation.
339      * 
340      * @return The prefix-to-namespace map for the namespace context to be applied to
341      *         the XPath evaluation.
342      */
343     public Map<String, String> getNamespaces()
344     {
345         return prefixToNamespaceMap;
346     }
347 
348     /**
349      * The prefix-to-namespace map for the namespace context to be applied to the
350      * XPath evaluation.
351      * 
352      * @param prefixToNamespaceMap The prefix-to-namespace map for the namespace
353      *            context to be applied to the XPath evaluation.
354      */
355     public void setNamespaces(Map<String, String> prefixToNamespaceMap)
356     {
357         this.prefixToNamespaceMap = prefixToNamespaceMap;
358     }
359     
360     public boolean equals(Object obj)
361     {
362         if (this == obj) return true;
363         if (obj == null || getClass() != obj.getClass()) return false;
364 
365         final XPathFilter other = (XPathFilter) obj;
366         return equal(expectedValue, other.expectedValue)
367             && equal(prefixToNamespaceMap, other.prefixToNamespaceMap)
368             && equal(pattern, other.pattern);
369     }
370 
371     public int hashCode()
372     {
373         return hash(new Object[]{this.getClass(), expectedValue, prefixToNamespaceMap, pattern});
374     }
375 }