View Javadoc

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