View Javadoc

1   /*
2    * $Id: DefaultExpressionManager.java 19500 2010-09-09 17:29:17Z dzapata $
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.expression;
11  
12  import org.mule.api.MuleContext;
13  import org.mule.api.MuleMessage;
14  import org.mule.api.context.MuleContextAware;
15  import org.mule.api.expression.ExpressionEvaluator;
16  import org.mule.api.expression.ExpressionManager;
17  import org.mule.api.expression.ExpressionRuntimeException;
18  import org.mule.api.expression.InvalidExpressionException;
19  import org.mule.api.expression.RequiredValueException;
20  import org.mule.api.lifecycle.Disposable;
21  import org.mule.config.i18n.CoreMessages;
22  import org.mule.util.TemplateParser;
23  
24  import java.text.MessageFormat;
25  import java.util.Iterator;
26  
27  import edu.emory.mathcs.backport.java.util.concurrent.ConcurrentHashMap;
28  import edu.emory.mathcs.backport.java.util.concurrent.ConcurrentMap;
29  import edu.emory.mathcs.backport.java.util.concurrent.atomic.AtomicBoolean;
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  
33  /**
34   * Provides universal access for evaluating expressions embedded in Mule configurations, such  as Xml, Java,
35   * scripting and annotations.
36   * <p/>
37   * Users can register or unregister {@link ExpressionEvaluator} through this interface.
38   */
39  public class DefaultExpressionManager implements ExpressionManager, MuleContextAware
40  {
41  
42      /**
43       * logger used by this class
44       */
45      protected static transient final Log logger = LogFactory.getLog(DefaultExpressionManager.class);
46  
47      // default style parser
48      private TemplateParser parser = TemplateParser.createMuleStyleParser();
49  
50      private ConcurrentMap evaluators = new ConcurrentHashMap(8);
51  
52      private MuleContext muleContext;
53  
54      public void setMuleContext(MuleContext context)
55      {
56          this.muleContext = context;
57      }
58  
59      public void registerEvaluator(ExpressionEvaluator evaluator)
60      {
61          if (evaluator == null)
62          {
63              throw new IllegalArgumentException(CoreMessages.objectIsNull("evaluator").getMessage());
64          }
65  
66          final String name = evaluator.getName();
67          // TODO MULE-3809 Eliminate duplicate evaluators registration
68          if (logger.isDebugEnabled())
69          {
70              logger.debug("Evaluators already contain an object named '" + name + "'.  The previous object will be overwritten.");
71          }
72          evaluators.put(evaluator.getName(), evaluator);
73      }
74  
75      /**
76       * Checks whether an evaluator is registered with the manager
77       *
78       * @param name the name of the expression evaluator
79       * @return true if the evaluator is registered with the manager, false otherwise
80       */
81      public boolean isEvaluatorRegistered(String name)
82      {
83          return evaluators.containsKey(name);
84      }
85  
86      /**
87       * Removes the evaluator with the given name
88       *
89       * @param name the name of the evaluator to remove
90       */
91      public ExpressionEvaluator unregisterEvaluator(String name)
92      {
93          if (name == null)
94          {
95              return null;
96          }
97  
98          ExpressionEvaluator evaluator = (ExpressionEvaluator) evaluators.remove(name);
99          if (evaluator instanceof Disposable)
100         {
101             ((Disposable) evaluator).dispose();
102         }
103         return evaluator;
104     }
105 
106     /**
107      * Evaluates the given expression.  The expression should be a single expression definition with or without
108      * enclosing braces. i.e. "mule:serviceName" and "#[mule:serviceName]" are both valid. For situations where
109      * one or more expressions need to be parsed within a single text, the {@link org.mule.api.expression.ExpressionManager#parse(String,org.mule.api.MuleMessage,boolean)}
110      * method should be used since it will iterate through all expressions in a string.
111      *
112      * @param expression a single expression i.e. xpath://foo
113      * @param message    the current message to process.  The expression will evaluata on the message.
114      * @return the result of the evaluation. Expressions that return collection will return an empty collection, not null.
115      * @throws ExpressionRuntimeException if the expression is invalid, or a null is found for the expression and
116      *                                    'failIfNull is set to true.
117      */
118     public Object evaluate(String expression, MuleMessage message) throws ExpressionRuntimeException
119     {
120         return evaluate(expression, message, false);
121     }
122 
123     /**
124      * Evaluates the given expression.  The expression should be a single expression definition with or without
125      * enclosing braces. i.e. "mule:serviceName" and "#[mule:serviceName]" are both valid. For situations where
126      * one or more expressions need to be parsed within a single text, the {@link org.mule.api.expression.ExpressionManager#parse(String,org.mule.api.MuleMessage,boolean)}
127      * method should be used since it will iterate through all expressions in a string.
128      *
129      * @param expression a single expression i.e. xpath://foo
130      * @param message    the current message to process.  The expression will evaluata on the message.
131      * @param failIfNull determines if an exception should be thrown if expression could not be evaluated or returns
132      *                   null.
133      * @return the result of the evaluation.  Expressions that return collection will return an empty collection, not null.
134      * @throws ExpressionRuntimeException if the expression is invalid, or a null is found for the expression and
135      *                                    'failIfNull is set to true.
136      */
137     public Object evaluate(String expression, MuleMessage message, boolean failIfNull) throws ExpressionRuntimeException
138     {
139         String name;
140 
141         if (expression == null)
142         {
143             throw new IllegalArgumentException(CoreMessages.objectIsNull("expression").getMessage());
144         }
145         if (expression.startsWith(DEFAULT_EXPRESSION_PREFIX))
146         {
147             expression = expression.substring(2, expression.length() - 1);
148         }
149         int i = expression.indexOf(":");
150         if (i > -1)
151         {
152             name = expression.substring(0, i);
153             expression = expression.substring(i + DEFAULT_EXPRESSION_POSTFIX.length());
154         }
155         else
156         {
157             name = expression;
158             expression = null;
159         }
160         return evaluate(expression, name, message, failIfNull);
161     }
162 
163     /**
164      * Evaluates the given expression.  The expression should be a single expression definition with or without
165      * enclosing braces. i.e. "mule:serviceName" and "#[mule:serviceName]" are both valid. For situations where
166      * one or more expressions need to be parsed within a single text, the {@link org.mule.api.expression.ExpressionManager#parse(String,org.mule.api.MuleMessage,boolean)}
167      * method should be used since it will iterate through all expressions in a string.
168      *
169      * @param expression a single expression i.e. xpath://foo
170      * @param evaluator  the evaluator to use when executing the expression
171      * @param message    the current message to process.  The expression will evaluata on the message.
172      * @param failIfNull determines if an exception should be thrown if expression could not be evaluated or returns
173      *                   null or if an exception should be thrown if an empty collection is returned.
174      * @return the result of the evaluation. Expressions that return collection will return an empty collection, not null.
175      * @throws ExpressionRuntimeException if the expression is invalid, or a null is found for the expression and
176      *                                    'failIfNull is set to true.
177      */
178     public Object evaluate(String expression, String evaluator, MuleMessage message, boolean failIfNull) throws ExpressionRuntimeException
179     {
180         ExpressionEvaluator extractor = (ExpressionEvaluator) evaluators.get(evaluator);
181         if (extractor == null)
182         {
183             throw new IllegalArgumentException(CoreMessages.expressionEvaluatorNotRegistered(evaluator).getMessage());
184         }
185         Object result = extractor.evaluate(expression, message);
186         //TODO Handle empty collections || (result instanceof Collection && ((Collection)result).size()==0)
187         if (failIfNull && (result == null))
188         {
189             throw new RequiredValueException(CoreMessages.expressionEvaluatorReturnedNull(evaluator, expression));
190         }
191         if (logger.isDebugEnabled())
192         {
193             logger.debug(MessageFormat.format("Result of expression: {0}:{1} is: {2}", evaluator, expression, result));
194         }
195         return result;
196     }
197 
198 
199     /**
200      * Evaluates expressions in a given string. This method will iterate through each expression and evaluate it. If
201      * a user needs to evaluate a single expression they can use {@link org.mule.api.expression.ExpressionManager#evaluate(String,org.mule.api.MuleMessage,boolean)}.
202      *
203      * @param expression a single expression i.e. xpath://foo
204      * @param message    the current message to process.  The expression will evaluata on the message.
205      * @return the result of the evaluation. Expressions that return collection will return an empty collection, not null.
206      * @throws org.mule.api.expression.ExpressionRuntimeException
207      *          if the expression is invalid, or a null is found for the expression and
208      *          'failIfNull is set to true.
209      */
210     public String parse(String expression, MuleMessage message) throws ExpressionRuntimeException
211     {
212         return parse(expression, message, false);
213     }
214 
215     /**
216      * Evaluates expressions in a given string. This method will iterate through each expression and evaluate it. If
217      * a user needs to evaluate a single expression they can use {@link org.mule.api.expression.ExpressionManager#evaluate(String,org.mule.api.MuleMessage,boolean)}.
218      *
219      * @param expression a single expression i.e. xpath://foo
220      * @param message    the current message to process.  The expression will evaluata on the message.
221      * @param failIfNull determines if an exception should be thrown if expression could not be evaluated or returns null.
222      * @return the result of the evaluation. Expressions that return collection will return an empty collection, not null.
223      * @throws ExpressionRuntimeException if the expression is invalid, or a null is found for the expression and
224      *                                    'failIfNull is set to true.
225      */
226     public String parse(final String expression, final MuleMessage message, final boolean failIfNull) throws ExpressionRuntimeException
227     {
228         return parser.parse(new TemplateParser.TemplateCallback()
229         {
230             public Object match(String token)
231             {
232                 return evaluate(token, message, failIfNull);
233             }
234         }, expression);
235     }
236 
237     /**
238      * Clears all registered evaluators from the manager.
239      */
240     public synchronized void clearEvaluators()
241     {
242         for (Iterator iterator = evaluators.values().iterator(); iterator.hasNext();)
243         {
244             ExpressionEvaluator evaluator = (ExpressionEvaluator) iterator.next();
245             if (evaluator instanceof Disposable)
246             {
247                 ((Disposable) evaluator).dispose();
248             }
249         }
250         evaluators.clear();
251     }
252 
253     public boolean isExpression(String string)
254     {
255         return (string.contains(DEFAULT_EXPRESSION_PREFIX));
256     }
257 
258     /**
259      * Determines if the expression is valid or not.  This method will validate a single expression or
260      * expressions embedded in a string.  the expression must be well formed i.e. #[bean:user]
261      *
262      * @param expression the expression to validate
263      * @return true if the expression evaluator is recognised
264      */
265     public boolean isValidExpression(String expression)
266     {
267         try
268         {
269             validateExpression(expression);
270             return true;
271         }
272         catch (InvalidExpressionException e)
273         {
274             logger.warn(e.getMessage());
275             return false;
276         }
277     }
278 
279     public void validateExpression(String expression) throws InvalidExpressionException
280     {
281         if (!muleContext.getConfiguration().isValidateExpressions())
282         {
283             if (logger.isDebugEnabled()) {
284                 logger.debug("Validate expressions is turned off, no checking done for: " + expression);
285             }
286             return;
287         }
288         try
289         {
290             parser.validate(expression);
291         }
292         catch (IllegalArgumentException e)
293         {
294             throw new InvalidExpressionException(expression, e.getMessage());
295         }
296 
297         final AtomicBoolean valid = new AtomicBoolean(true);
298         final AtomicBoolean match = new AtomicBoolean(false);
299         final StringBuffer message = new StringBuffer();
300         parser.parse(new TemplateParser.TemplateCallback()
301         {
302             public Object match(String token)
303             {
304                 match.set(true);
305                 if (token.indexOf(":") == -1)
306                 {
307                     if (valid.get())
308                     {
309                         valid.compareAndSet(true, false);
310                     }
311                     message.append(token).append(" is invalid\n");
312                 }
313                 return null;
314             }
315         }, expression);
316 
317         if (message.length() > 0)
318         {
319             throw new InvalidExpressionException(expression, message.toString());
320         }
321         else if(!match.get())
322         {
323             throw new InvalidExpressionException(expression, "Expression string is not an expression.  Use isExpression(String) to validate first");
324         }
325     }
326 }