View Javadoc

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