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