View Javadoc

1   /*
2    * $Id: JaxpXPathExpressionEvaluator.java 20321 2010-11-24 15:21:24Z dfeist $
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  package org.mule.module.xml.expression;
11  
12  import org.mule.api.MuleContext;
13  import org.mule.api.MuleMessage;
14  import org.mule.api.MuleRuntimeException;
15  import org.mule.api.context.MuleContextAware;
16  import org.mule.api.context.notification.MuleContextNotificationListener;
17  import org.mule.api.expression.ExpressionEvaluator;
18  import org.mule.api.lifecycle.Disposable;
19  import org.mule.api.lifecycle.Initialisable;
20  import org.mule.api.lifecycle.InitialisationException;
21  import org.mule.api.registry.RegistrationException;
22  import org.mule.context.notification.MuleContextNotification;
23  import org.mule.module.xml.i18n.XmlMessages;
24  import org.mule.module.xml.stax.MapNamespaceContext;
25  import org.mule.module.xml.util.NamespaceManager;
26  
27  import java.util.Map;
28  import java.util.WeakHashMap;
29  
30  import javax.xml.namespace.QName;
31  import javax.xml.xpath.XPath;
32  import javax.xml.xpath.XPathConstants;
33  import javax.xml.xpath.XPathExpression;
34  import javax.xml.xpath.XPathExpressionException;
35  import javax.xml.xpath.XPathFactory;
36  
37  import org.mule.transformer.types.DataTypeFactory;
38  import org.w3c.dom.Node;
39  
40  /**
41   * Uses JAXP XPath processing to evaluate xpath expressions against Xml fragments and documents
42   * <p/>
43   * Note that the Jaxp Expression evaluator differs from the Mule XPATH evaluator slightly since you cna set the JaxP
44   * return type as a prefix to the expression i.e.
45   * <code>
46   * xpath2:[node]/foo/bar
47   * </code>
48   * <p/>
49   * Where the type can either be boolean, string, number, node or nodeset.  iBeans will automatically convert numbers based on the
50   * return type as well as convert node to Document if required.
51   */
52  public class JaxpXPathExpressionEvaluator implements ExpressionEvaluator, Initialisable, Disposable, MuleContextAware
53  {
54  
55      private Map cache = new WeakHashMap(8);
56  
57      private MuleContext muleContext;
58      private NamespaceManager namespaceManager;
59      private QName returnType = XPathConstants.STRING;
60  
61      public JaxpXPathExpressionEvaluator()
62      {
63  
64      }
65  
66      public String getName()
67      {
68          return "xpath2";
69      }
70  
71      public void setMuleContext(MuleContext context)
72      {
73          this.muleContext = context;
74      }
75  
76      public void initialise() throws InitialisationException
77      {
78          try
79          {
80              /*
81                  Workaround for standalone mode, when registry bootstrap order may be non-deterministic and lead
82                  to failures on startup.
83  
84                  initialise() can't do any lookups as it will have spring create and init beans for things like
85                  global endpoints, interfering with current lifecycle and leading to failure.
86                  TODO AP/RM this will be solved by the @Inject annotation or phase, as discussed
87  
88                  RM*: Update: I'm hesistant to include support for @Inject since MUle isn't a DI container and
89                  having this annotation (and associated @Named) sends a confusing message
90               */
91              this.muleContext.registerListener(new MuleContextNotificationListener<MuleContextNotification>(){
92  
93                  public void onNotification(MuleContextNotification notification)
94                  {
95                      // CONTEXT_INITIALIZED fires too soon, before registry is inited, thus using this one
96                      if (MuleContextNotification.CONTEXT_STARTING == notification.getAction())
97                      {
98                          try
99                          {
100                             namespaceManager = muleContext.getRegistry().lookupObject(NamespaceManager.class);
101                         }
102                         catch (RegistrationException e)
103                         {
104                             throw new RuntimeException(e);
105                         }
106                     }
107                 }
108             });
109         }
110         catch (Throwable t)
111         {
112             throw new InitialisationException(t, this);
113         }
114     }
115 
116     /**
117      * {@inheritDoc}
118      */
119     public Object evaluate(String expression, MuleMessage message)
120     {
121         QName retType = returnType;
122         if (expression.startsWith("["))
123         {
124             int x = expression.indexOf("]");
125             if (x == -1)
126             {
127                 throw new IllegalArgumentException("Expression is malformed: " + expression);
128             }
129             String type = expression.substring(1, x);
130             expression = expression.substring(x + 1);
131             if (type.equalsIgnoreCase("boolean"))
132             {
133                 retType = XPathConstants.BOOLEAN;
134             }
135             else if (type.equalsIgnoreCase("string"))
136             {
137                 retType = XPathConstants.STRING;
138             }
139             else if (type.equalsIgnoreCase("node"))
140             {
141                 retType = XPathConstants.NODE;
142             }
143             else if (type.equalsIgnoreCase("nodeset"))
144             {
145                 retType = XPathConstants.NODESET;
146             }
147             else if (type.equalsIgnoreCase("number"))
148             {
149                 retType = XPathConstants.NUMBER;
150             }
151             else
152             {
153                 throw new IllegalArgumentException("Result type not recognised: " + type + ". Use either boolean, string, number, node or nodeset.");
154             }
155         }
156         try
157         {
158             Node payload = (Node) message.getPayload(DataTypeFactory.create(Node.class));
159 
160             XPathExpression xpath = getXPath(expression);
161 
162             return xpath.evaluate(payload, retType);
163         }
164         catch (Exception e)
165         {
166             throw new MuleRuntimeException(XmlMessages.failedToProcessXPath(expression), e);
167         }
168     }
169 
170     /**
171      * {@inheritDoc}
172      */
173     public final void setName(String name)
174     {
175         throw new UnsupportedOperationException("setName");
176     }
177 
178     protected XPathExpression getXPath(String expression) throws XPathExpressionException
179     {
180         XPathExpression xpath = (XPathExpression) cache.get(expression + getClass().getName());
181         if (xpath == null)
182         {
183             xpath = createXPath(expression);
184             cache.put(expression + getClass().getName(), xpath);
185         }
186         return xpath;
187     }
188 
189     protected XPathExpression createXPath(String expression) throws XPathExpressionException
190     {
191         XPath xp = XPathFactory.newInstance().newXPath();
192         if (getNamespaceManager() != null)
193         {
194             xp.setNamespaceContext(new MapNamespaceContext(getNamespaceManager().getNamespaces()));
195         }
196         return xp.compile(expression);
197     }
198 
199     /**
200      * A lifecycle method where implementor should free up any resources. If an
201      * exception is thrown it should just be logged and processing should continue.
202      * This method should not throw Runtime exceptions.
203      */
204     public void dispose()
205     {
206         cache.clear();
207     }
208 
209     public NamespaceManager getNamespaceManager()
210     {
211         return namespaceManager;
212     }
213 
214     public void setNamespaceManager(NamespaceManager namespaceManager)
215     {
216         this.namespaceManager = namespaceManager;
217     }
218 
219     public MuleContext getMuleContext()
220     {
221         return muleContext;
222     }
223 
224     public QName getReturnType()
225     {
226         return returnType;
227     }
228 
229     public void setReturnType(QName returnType)
230     {
231         this.returnType = returnType;
232     }
233 }
234