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.transformer;
8   
9   import org.mule.api.MuleContext;
10  import org.mule.api.expression.ExpressionRuntimeException;
11  import org.mule.api.lifecycle.InitialisationException;
12  import org.mule.api.registry.RegistrationException;
13  import org.mule.api.transformer.TransformerException;
14  import org.mule.config.i18n.CoreMessages;
15  import org.mule.module.xml.util.NamespaceManager;
16  import org.mule.transformer.AbstractTransformer;
17  import org.mule.util.StringUtils;
18  
19  import java.util.ArrayList;
20  import java.util.HashMap;
21  import java.util.List;
22  import java.util.Map;
23  
24  import org.apache.commons.jxpath.JXPathContext;
25  import org.dom4j.Document;
26  import org.dom4j.DocumentHelper;
27  import org.dom4j.Node;
28  import org.dom4j.XPath;
29  
30  /**
31   * The JXPathExtractor is a simple transformer that evaluates an xpath expression
32   * against the given bean and that returns the result. <p/> By default, a single
33   * result will be returned. If multiple values are expected, set the
34   * {@link #singleResult} property to <code>false</code>. In this case a
35   * {@link List} of values will be returned. Note the property is currently ignored
36   * for non-String/XML payloads.
37   */
38  public class JXPathExtractor extends AbstractTransformer
39  {
40      public static final String OUTPUT_TYPE_NODE = "NODE";
41  
42      public static final String OUTPUT_TYPE_XML = "XML";
43  
44      public static final String OUTPUT_TYPE_VALUE = "VALUE";
45  
46      private volatile String expression;
47      
48      private volatile String outputType;
49      
50      private volatile Map namespaces;
51  
52      private volatile boolean singleResult = true;
53  
54      private NamespaceManager namespaceManager;
55  
56      public void setMuleContext(MuleContext context)
57      {
58          this.muleContext = context;
59          try
60          {
61              namespaceManager = muleContext.getRegistry().lookupObject(NamespaceManager.class);
62          }
63          catch (RegistrationException e)
64          {
65              throw new ExpressionRuntimeException(CoreMessages.failedToLoad("NamespaceManager"), e);
66          }
67      }
68  
69      /**
70       * Template method where deriving classes can do any initialisation after the
71       * properties have been set on this transformer
72       *
73       * @throws org.mule.api.lifecycle.InitialisationException
74       *
75       */
76      @Override
77      public void initialise() throws InitialisationException
78      {
79          super.initialise();
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      /**
94       * Evaluate the expression in the context of the given object and returns the
95       * result. If the given object is a string, it assumes it is an valid xml and
96       * parses it before evaluating the xpath expression.
97       */
98      public Object doTransform(Object src, String encoding) throws TransformerException
99      {
100         try
101         {
102             Object result = null;
103             if (src instanceof String)
104             {
105                 Document doc = DocumentHelper.parseText((String) src);
106 
107                 XPath xpath = doc.createXPath(expression);
108                 if (namespaces != null)
109                 {
110                     xpath.setNamespaceURIs(namespaces);
111                 }
112                 
113                 // This is the way we always did it before, so keep doing it that way
114                 // as xpath.evaluate() will return non-string results (like Doubles)
115                 // for some scenarios.
116                 if (outputType == null && singleResult)
117                 {
118                     return xpath.valueOf(doc);
119                 }
120                 
121                 // TODO handle non-list cases, see
122                 //http://www.dom4j.org/apidocs/org/dom4j/XPath.html#evaluate(java.lang.Object)
123                 Object obj = xpath.evaluate(doc);
124                 if (obj instanceof List)
125                 {
126                     for (int i = 0; i < ((List) obj).size(); i++)
127                     {
128                         final Node node = (Node) ((List) obj).get(i);
129                         result = add(result, node);
130                         
131                         if (singleResult)
132                         {
133                             break;
134                         }
135                     }
136                 }
137                 else
138                 {
139                     result = add(result, obj);
140                 }
141 
142             }
143             else
144             {
145                 JXPathContext context = JXPathContext.newContext(src);
146                 result = context.getValue(expression);
147             }
148             return result;
149         }
150         catch (Exception e)
151         {
152             throw new TransformerException(this, e);
153         }
154 
155     }
156     
157     private Object add(Object result, Object value)
158     {
159         Object formattedResult = getResult(value);
160         if (singleResult)
161         {
162             return formattedResult;
163         }
164         else
165         {
166             if (result == null)
167             {
168                 result = new ArrayList();
169             }
170             
171             ((List) result).add(formattedResult);
172         }
173         return result;
174     }
175 
176     private Object getResult(Object value)
177     {
178         Object result = null;
179         if (StringUtils.contains(OUTPUT_TYPE_VALUE, outputType) || outputType == null)
180         {
181             if (value instanceof Node)
182             {
183                 result = ((Node) value).getText();
184             }
185             else
186             {
187                 // this maintains backward compat with previous 2.1.x versions. 
188                 result = value.toString();
189             }
190         }
191         else if (StringUtils.contains(OUTPUT_TYPE_XML, outputType))
192         {
193             if (value instanceof Node)
194             {
195                 result = ((Node) value).asXML();
196             }
197             else 
198             {
199                 throw new IllegalStateException("XPath expression output must be a Node to output as XML. Expression type was: " + value.getClass());
200             }
201         }
202         else if (StringUtils.contains(OUTPUT_TYPE_NODE, outputType))
203         {
204             result = value;
205         }
206         return result;
207     }
208 
209     /**
210      * @return Returns the expression.
211      */
212     public String getExpression()
213     {
214         return expression;
215     }
216 
217     /**
218      * @param expression The expression to set.
219      */
220     public void setExpression(String expression)
221     {
222         this.expression = expression;
223     }
224 
225     /**
226      * Should a single value be returned.
227      * 
228      * @return value
229      */
230     public boolean isSingleResult()
231     {
232         return singleResult;
233     }
234 
235     /**
236      * If multiple results are expected from the {@link #expression} evaluation, set
237      * this to false.
238      * 
239      * @param singleResult flag
240      */
241     public void setSingleResult(boolean singleResult)
242     {
243         this.singleResult = singleResult;
244     }
245 
246     public String getOutputType()
247     {
248         return outputType;
249     }
250 
251     public void setOutputType(String outputEncoding)
252     {
253         this.outputType = outputEncoding;
254     }
255 
256     public Map getNamespaces()
257     {
258         return namespaces;
259     }
260 
261     public void setNamespaces(Map namespaceURIs)
262     {
263         this.namespaces = namespaceURIs;
264     }
265     
266 }