View Javadoc

1   /*
2    * $Id: AbstractXPathExpressionEvaluator.java 23014 2011-09-22 18:04:21Z evangelinamrm $
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             List<?> result;
118 
119             /*  XPath context state is not thread safe so synchronization must be enforced when adding a new namespace and
120                 on evaluation when the context is read.
121              */
122             XPath xpath = getXPath(expression, payload);
123             synchronized (xpath)
124             {
125                 result = xpath.selectNodes(payload);
126             }
127 
128             result = extractResultsFromNodes(result);
129             if(result.size()==1)
130             {
131                 return result.get(0);
132             }
133             else if(result.size()==0)
134             {
135                 return null;
136             }
137             else
138             {
139                 return result;
140             }
141         }
142         catch (Exception e)
143         {
144             throw new MuleRuntimeException(XmlMessages.failedToProcessXPath(expression), e);
145         }
146     }
147 
148     protected void addNamespaces(NamespaceManager manager, XPath xpath)
149     {
150         for (Iterator<?> iterator = manager.getNamespaces().entrySet().iterator(); iterator.hasNext();)
151         {
152             Map.Entry<?, ?> entry = (Map.Entry<?, ?>)iterator.next();
153             try
154             {
155                 xpath.addNamespace(entry.getKey().toString(), entry.getValue().toString());
156             }
157             catch (JaxenException e)
158             {
159                 throw new ExpressionRuntimeException(XmlMessages.failedToRegisterNamespace(entry.getKey().toString(), entry.getValue().toString()));
160             }
161         }
162     }
163 
164     /** {@inheritDoc} */
165     public final void setName(String name)
166     {
167         throw new UnsupportedOperationException("setName");
168     }
169 
170     /*
171         The cache is not thread safe, more than one instance of the same xpath can be created, it wouldn't be an
172         issue in this case since one will eventually be selected for GC
173         A ReadWriteLock could be used to guard the cache but it would add unnecessary complexity
174      */
175     protected XPath getXPath(String expression, Object object) throws JaxenException
176     {
177         XPath xpath = cache.get(expression + getClass().getName());
178         if(xpath==null)
179         {
180             xpath = createXPath(expression, object);
181             synchronized (xpath)
182             {
183                 if(namespaceManager!=null)
184                 {
185                     addNamespaces(namespaceManager, xpath);
186                 }
187             }
188             cache.put(expression + getClass().getName(), xpath);
189         }
190         return xpath;
191     }
192 
193     protected abstract XPath createXPath(String expression, Object object) throws JaxenException;
194 
195     protected List<?> extractResultsFromNodes(List<?> results)
196     {
197         if (results == null)
198         {
199             return null;
200         }
201         List<Object> newResults = new ArrayList<Object>(results.size());
202         for (Object o : results)
203         {
204             newResults.add(extractResultFromNode(o));
205         }
206         return newResults;
207     }
208 
209     /**
210      * A lifecycle method where implementor should free up any resources. If an
211      * exception is thrown it should just be logged and processing should continue.
212      * This method should not throw Runtime exceptions.
213      */
214     public void dispose()
215     {
216         cache.clear();
217     }
218 
219     public NamespaceManager getNamespaceManager()
220     {
221         return namespaceManager;
222     }
223 
224     public void setNamespaceManager(NamespaceManager namespaceManager)
225     {
226         this.namespaceManager = namespaceManager;
227     }
228 
229     public MuleContext getMuleContext()
230     {
231         return muleContext;
232     }
233 
234     protected abstract Object extractResultFromNode(Object result);
235 }