View Javadoc

1   /*
2    * $Id: AbstractXPathExpressionEvaluator.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  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.expression.ExpressionRuntimeException;
19  import org.mule.api.lifecycle.Disposable;
20  import org.mule.api.lifecycle.Initialisable;
21  import org.mule.api.lifecycle.InitialisationException;
22  import org.mule.api.registry.RegistrationException;
23  import org.mule.config.i18n.CoreMessages;
24  import org.mule.context.notification.MuleContextNotification;
25  import org.mule.module.xml.i18n.XmlMessages;
26  import org.mule.module.xml.util.NamespaceManager;
27  
28  import java.util.ArrayList;
29  import java.util.Iterator;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.WeakHashMap;
33  
34  import org.dom4j.Document;
35  import org.jaxen.JaxenException;
36  import org.jaxen.XPath;
37  import org.mule.transformer.types.DataTypeFactory;
38  
39  /**
40   * Provides a base class for XPath property extractors. The XPath engine used is jaxen (http://jaxen.org) which supports
41   * XPath queries on other object models such as JavaBeans as well as Xml
42   */
43  public abstract class AbstractXPathExpressionEvaluator implements ExpressionEvaluator, Initialisable, Disposable, MuleContextAware
44  {
45      private Map<String, XPath> cache = new WeakHashMap<String, XPath>(8);
46  
47      private MuleContext muleContext;
48      private NamespaceManager namespaceManager;
49  
50      public void setMuleContext(MuleContext context)
51      {
52          this.muleContext = context;
53  
54      }
55  
56      public void initialise() throws InitialisationException
57      {
58          try
59          {
60              /*
61                  Workaround for standalone mode, when registry bootstrap order may be non-deterministic and lead
62                  to failures on startup.
63  
64                  initialise() can't do any lookups as it will have spring create and init beans for things like
65                  global endpoints, interfering with current lifecycle and leading to failure.
66                  TODO AP/RM this will be solved by the @Inject annotation or phase, as discussed
67               */
68              this.muleContext.registerListener(new MuleContextNotificationListener<MuleContextNotification>(){
69  
70                  public void onNotification(MuleContextNotification notification)
71                  {
72                      // CONTEXT_INITIALIZED fires too soon, before registry is inited, thus using this one
73                      if (MuleContextNotification.CONTEXT_STARTING == notification.getAction())
74                      {
75                          try
76                          {
77                              namespaceManager = muleContext.getRegistry().lookupObject(NamespaceManager.class);
78                          }
79                          catch (RegistrationException e)
80                          {
81                              throw new RuntimeException(e);
82                          }
83                      }
84                  }
85              });
86          }
87          catch (Throwable t)
88          {
89              throw new InitialisationException(t, this);
90          }
91      }
92  
93      public void inject() throws Exception
94      {
95          try
96          {
97              namespaceManager = muleContext.getRegistry().lookupObject(NamespaceManager.class);
98          }
99          catch (RegistrationException e)
100         {
101             throw new ExpressionRuntimeException(CoreMessages.failedToLoad("NamespaceManager"), e);
102         }
103     }
104 
105     /** {@inheritDoc} */
106     public Object evaluate(String expression, MuleMessage message)
107     {
108         try
109         {
110             Object payload = message.getPayload();
111             //we need to convert to a Dom if its an XML string
112             if(payload instanceof String)
113             {
114                 payload = message.getPayload(DataTypeFactory.create(Document.class));
115             }
116 
117             XPath xpath = getXPath(expression, payload);
118             if(namespaceManager!=null)
119             {
120                 addNamespaces(namespaceManager, xpath);
121             }
122 
123             List<?> result = xpath.selectNodes(payload);
124             result = extractResultsFromNodes(result);
125             if(result.size()==1)
126             {
127                 return result.get(0);
128             }
129             else if(result.size()==0)
130             {
131                 return null;
132             }
133             else
134             {
135                 return result;
136             }
137         }
138         catch (Exception e)
139         {
140             throw new MuleRuntimeException(XmlMessages.failedToProcessXPath(expression), e);
141         }
142     }
143 
144     protected void addNamespaces(NamespaceManager manager, XPath xpath)
145     {
146         for (Iterator<?> iterator = manager.getNamespaces().entrySet().iterator(); iterator.hasNext();)
147         {
148             Map.Entry<?, ?> entry = (Map.Entry<?, ?>)iterator.next();
149             try
150             {
151                 xpath.addNamespace(entry.getKey().toString(), entry.getValue().toString());
152             }
153             catch (JaxenException e)
154             {
155                 throw new ExpressionRuntimeException(XmlMessages.failedToRegisterNamespace(entry.getKey().toString(), entry.getValue().toString()));
156             }
157         }
158     }
159 
160     /** {@inheritDoc} */
161     public final void setName(String name)
162     {
163         throw new UnsupportedOperationException("setName");
164     }
165 
166     protected XPath getXPath(String expression, Object object) throws JaxenException
167     {
168         XPath xpath = cache.get(expression + getClass().getName());
169         if(xpath==null)
170         {
171             xpath = createXPath(expression, object);
172             cache.put(expression + getClass().getName(), xpath);
173         }
174         return xpath;
175     }
176 
177     protected abstract XPath createXPath(String expression, Object object) throws JaxenException;
178 
179     protected List<?> extractResultsFromNodes(List<?> results)
180     {
181         if (results == null)
182         {
183             return null;
184         }
185         List<Object> newResults = new ArrayList<Object>(results.size());
186         for (Object o : results)
187         {
188             newResults.add(extractResultFromNode(o));
189         }
190         return newResults;
191     }
192 
193     /**
194      * A lifecycle method where implementor should free up any resources. If an
195      * exception is thrown it should just be logged and processing should continue.
196      * This method should not throw Runtime exceptions.
197      */
198     public void dispose()
199     {
200         cache.clear();
201     }
202 
203     public NamespaceManager getNamespaceManager()
204     {
205         return namespaceManager;
206     }
207 
208     public void setNamespaceManager(NamespaceManager namespaceManager)
209     {
210         this.namespaceManager = namespaceManager;
211     }
212 
213     public MuleContext getMuleContext()
214     {
215         return muleContext;
216     }
217 
218     protected abstract Object extractResultFromNode(Object result);
219 }