View Javadoc

1   /*
2    * $Id: DefaultExpressionManager.java 21939 2011-05-18 13:32:09Z aperepel $
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.ExpressionEnricher;
16  import org.mule.api.expression.ExpressionEvaluator;
17  import org.mule.api.expression.ExpressionManager;
18  import org.mule.api.expression.ExpressionRuntimeException;
19  import org.mule.api.expression.InvalidExpressionException;
20  import org.mule.api.expression.RequiredValueException;
21  import org.mule.api.lifecycle.Disposable;
22  import org.mule.config.i18n.CoreMessages;
23  import org.mule.util.TemplateParser;
24  
25  import java.text.MessageFormat;
26  import java.util.Iterator;
27  import java.util.concurrent.ConcurrentHashMap;
28  import java.util.concurrent.ConcurrentMap;
29  import java.util.concurrent.atomic.AtomicBoolean;
30  
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  
34  /**
35   * Provides universal access for evaluating expressions embedded in Mule configurations, such  as Xml, Java,
36   * scripting and annotations.
37   * <p/>
38   * Users can register or unregister {@link ExpressionEvaluator} through this interface.
39   */
40  public class DefaultExpressionManager implements ExpressionManager, MuleContextAware
41  {
42  
43      /**
44       * logger used by this class
45       */
46      protected static transient final Log logger = LogFactory.getLog(DefaultExpressionManager.class);
47  
48      // default style parser
49      private TemplateParser parser = TemplateParser.createMuleStyleParser();
50  
51      private ConcurrentMap evaluators = new ConcurrentHashMap(8);
52      private ConcurrentMap enrichers = new ConcurrentHashMap(8);
53  
54      private MuleContext muleContext;
55  
56      public void setMuleContext(MuleContext context)
57      {
58          this.muleContext = context;
59      }
60  
61      public void registerEvaluator(ExpressionEvaluator evaluator)
62      {
63          if (evaluator == null)
64          {
65              throw new IllegalArgumentException(CoreMessages.objectIsNull("evaluator").getMessage());
66          }
67  
68          final String name = evaluator.getName();
69          // TODO MULE-3809 Eliminate duplicate evaluators registration
70          if (logger.isDebugEnabled())
71          {
72              logger.debug("Evaluators already contain an object named '" + name + "'.  The previous object will be overwritten.");
73          }
74          evaluators.put(evaluator.getName(), evaluator);
75      }
76  
77      public void registerEnricher(ExpressionEnricher enricher)
78      {
79          if (enricher == null)
80          {
81              throw new IllegalArgumentException(CoreMessages.objectIsNull("enricher").getMessage());
82          }
83  
84          final String name = enricher.getName();
85          // TODO MULE-3809 Eliminate duplicate evaluators registration
86          if (logger.isDebugEnabled())
87          {
88              logger.debug("Enrichers already contain an object named '" + name + "'.  The previous object will be overwritten.");
89          }
90          enrichers.put(enricher.getName(), enricher);
91      }
92  
93      /**
94       * Checks whether an evaluator is registered with the manager
95       *
96       * @param name the name of the expression evaluator
97       * @return true if the evaluator is registered with the manager, false otherwise
98       */
99      public boolean isEvaluatorRegistered(String name)
100     {
101         return evaluators.containsKey(name);
102     }
103     
104     /**
105      * Checks whether an enricher is registered with the manager
106      *
107      * @param name the name of the expression enricher
108      * @return true if the enricher is registered with the manager, false otherwise
109      */
110     public boolean isEnricherRegistered(String name)
111     {
112         return enrichers.containsKey(name);
113     }
114     
115     /**
116      * Removes the evaluator with the given name
117      *
118      * @param name the name of the evaluator to remove
119      */
120     public ExpressionEvaluator unregisterEvaluator(String name)
121     {
122         if (name == null)
123         {
124             return null;
125         }
126 
127         ExpressionEvaluator evaluator = (ExpressionEvaluator) evaluators.remove(name);
128         if (evaluator instanceof Disposable)
129         {
130             ((Disposable) evaluator).dispose();
131         }
132         return evaluator;
133     }
134     
135     /**
136      * Removes the evaluator with the given name
137      *
138      * @param name the name of the evaluator to remove
139      */
140     public ExpressionEnricher unregisterEnricher(String name)
141     {
142         if (name == null)
143         {
144             return null;
145         }
146 
147         ExpressionEnricher enricher = (ExpressionEnricher) enrichers.remove(name);
148         if (enricher instanceof Disposable)
149         {
150             ((Disposable) enricher).dispose();
151         }
152         return enricher;
153     }
154 
155     /**
156      * Evaluates the given expression.  The expression should be a single expression definition with or without
157      * enclosing braces. i.e. "mule:serviceName" and "#[mule:serviceName]" are both valid. For situations where
158      * 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)}
159      * method should be used since it will iterate through all expressions in a string.
160      *
161      * @param expression a single expression i.e. xpath://foo
162      * @param message    the current message to process.  The expression will evaluata on the message.
163      * @return the result of the evaluation. Expressions that return collection will return an empty collection, not null.
164      * @throws ExpressionRuntimeException if the expression is invalid, or a null is found for the expression and
165      *                                    'failIfNull is set to true.
166      */
167     public Object evaluate(String expression, MuleMessage message) throws ExpressionRuntimeException
168     {
169         return evaluate(expression, message, false);
170     }
171 
172     /**
173      * Evaluates the given expression.  The expression should be a single expression definition with or without
174      * enclosing braces. i.e. "mule:serviceName" and "#[mule:serviceName]" are both valid. For situations where
175      * 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)}
176      * method should be used since it will iterate through all expressions in a string.
177      *
178      * @param expression a single expression i.e. xpath://foo
179      * @param message    the current message to process.  The expression will evaluata on the message.
180      * @param failIfNull determines if an exception should be thrown if expression could not be evaluated or returns
181      *                   null.
182      * @return the result of the evaluation.  Expressions that return collection will return an empty collection, not null.
183      * @throws ExpressionRuntimeException if the expression is invalid, or a null is found for the expression and
184      *                                    'failIfNull is set to true.
185      */
186     public Object evaluate(String expression, MuleMessage message, boolean failIfNull) throws ExpressionRuntimeException
187     {
188         String name;
189 
190         if (expression == null)
191         {
192             throw new IllegalArgumentException(CoreMessages.objectIsNull("expression").getMessage());
193         }
194         if (expression.startsWith(DEFAULT_EXPRESSION_PREFIX))
195         {
196             expression = expression.substring(2, expression.length() - 1);
197         }
198         int i = expression.indexOf(":");
199         if (i > -1)
200         {
201             name = expression.substring(0, i);
202             expression = expression.substring(i + DEFAULT_EXPRESSION_POSTFIX.length());
203         }
204         else
205         {
206             name = expression;
207             expression = null;
208         }
209         return evaluate(expression, name, message, failIfNull);
210     }
211 
212 
213     public void enrich(String expression, MuleMessage message, Object object)
214         throws ExpressionRuntimeException
215     {
216         String enricherName;
217 
218         if (expression == null)
219         {
220             throw new IllegalArgumentException(CoreMessages.objectIsNull("expression").getMessage());
221         }
222         if (expression.startsWith(DEFAULT_EXPRESSION_PREFIX))
223         {
224             expression = expression.substring(2, expression.length() - 1);
225         }
226         int i = expression.indexOf(":");
227         if (i > -1)
228         {
229             enricherName = expression.substring(0, i);
230             expression = expression.substring(i + DEFAULT_EXPRESSION_POSTFIX.length());
231         }
232         else
233         {
234             enricherName = expression;
235             expression = null;
236         }
237         enrich(expression, enricherName, message, object);
238     }
239 
240     public void enrich(String expression, String enricherName, MuleMessage message, Object object)
241     {
242         ExpressionEnricher enricher = (ExpressionEnricher) enrichers.get(enricherName);
243         if (enricher == null)
244         {
245             throw new IllegalArgumentException(CoreMessages.expressionEnricherNotRegistered(enricherName)
246                 .getMessage());
247         }
248         enricher.enrich(expression, message, object);
249     }
250 
251     /**
252      * Evaluates the given expression.  The expression should be a single expression definition with or without
253      * enclosing braces. i.e. "mule:serviceName" and "#[mule:serviceName]" are both valid. For situations where
254      * 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)}
255      * method should be used since it will iterate through all expressions in a string.
256      *
257      * @param expression a single expression i.e. xpath://foo
258      * @param evaluator  the evaluator to use when executing the expression
259      * @param message    the current message to process.  The expression will evaluata on the message.
260      * @param failIfNull determines if an exception should be thrown if expression could not be evaluated or returns
261      *                   null or if an exception should be thrown if an empty collection is returned.
262      * @return the result of the evaluation. Expressions that return collection will return an empty collection, not null.
263      * @throws ExpressionRuntimeException if the expression is invalid, or a null is found for the expression and
264      *                                    'failIfNull is set to true.
265      */
266     public Object evaluate(String expression, String evaluator, MuleMessage message, boolean failIfNull) throws ExpressionRuntimeException
267     {
268         ExpressionEvaluator extractor = (ExpressionEvaluator) evaluators.get(evaluator);
269         if (extractor == null)
270         {
271             throw new IllegalArgumentException(CoreMessages.expressionEvaluatorNotRegistered(evaluator).getMessage());
272         }
273         Object result = extractor.evaluate(expression, message);
274         //TODO Handle empty collections || (result instanceof Collection && ((Collection)result).size()==0)
275         if (failIfNull && (result == null))
276         {
277             throw new RequiredValueException(CoreMessages.expressionEvaluatorReturnedNull(evaluator, expression));
278         }
279         if (logger.isDebugEnabled())
280         {
281             logger.debug(MessageFormat.format("Result of expression: {0}:{1} is: {2}", evaluator, expression, result));
282         }
283         return result;
284     }
285 
286     public boolean evaluateBoolean(String expression, String evaluator, MuleMessage message)
287         throws ExpressionRuntimeException
288     {
289         return evaluateBoolean(expression, evaluator, message, false, false);
290     }
291 
292     public boolean evaluateBoolean(String expression, MuleMessage message) throws ExpressionRuntimeException
293     {
294         return evaluateBoolean(expression, message, false, false);
295     }
296 
297     public boolean evaluateBoolean(String expression,
298                                    String evaluator,
299                                    MuleMessage message,
300                                    boolean nullReturnsTrue,
301                                    boolean nonBooleanReturnsTrue) throws ExpressionRuntimeException
302     {
303         try
304         {
305             return resolveBoolean(evaluate(expression, evaluator, message, false), nullReturnsTrue,
306                 nonBooleanReturnsTrue, expression);
307         }
308         catch (RequiredValueException e)
309         {
310             return nullReturnsTrue;
311         }
312     }
313 
314     public boolean evaluateBoolean(String expression,
315                                    MuleMessage message,
316                                    boolean nullReturnsTrue,
317                                    boolean nonBooleanReturnsTrue) throws ExpressionRuntimeException
318     {
319         try
320         {
321             return resolveBoolean(evaluate(expression, message, false), nullReturnsTrue,
322                 nonBooleanReturnsTrue, expression);
323         }
324         catch (RequiredValueException e)
325         {
326             return nullReturnsTrue;
327         }
328     }
329 
330     protected boolean resolveBoolean(Object result,
331                                      boolean nullReturnsTrue,
332                                      boolean nonBooleanReturnsTrue,
333                                      String expression)
334     {
335         if (result == null)
336         {
337             return nullReturnsTrue;
338         }
339         else if (result instanceof Boolean)
340         {
341             return (Boolean) result;
342         }
343         else if (result instanceof String)
344         {
345             if (result.toString().toLowerCase().equalsIgnoreCase("false"))
346             {
347                 return false;
348             }
349             else if (result.toString().toLowerCase().equalsIgnoreCase("true"))
350             {
351                 return true;
352             }
353             else
354             {
355                 return nonBooleanReturnsTrue;
356             }
357         }
358         else
359         {
360             logger.warn("Expression: " + expression + ", returned an non-boolean result. Returning: "
361                         + nonBooleanReturnsTrue);
362             return nonBooleanReturnsTrue;
363         }
364     }
365 
366     /**
367      * Evaluates expressions in a given string. This method will iterate through each expression and evaluate it. If
368      * a user needs to evaluate a single expression they can use {@link org.mule.api.expression.ExpressionManager#evaluate(String,org.mule.api.MuleMessage,boolean)}.
369      *
370      * @param expression a single expression i.e. xpath://foo
371      * @param message    the current message to process.  The expression will evaluata on the message.
372      * @return the result of the evaluation. Expressions that return collection will return an empty collection, not null.
373      * @throws org.mule.api.expression.ExpressionRuntimeException
374      *          if the expression is invalid, or a null is found for the expression and
375      *          'failIfNull is set to true.
376      */
377     public String parse(String expression, MuleMessage message) throws ExpressionRuntimeException
378     {
379         return parse(expression, message, false);
380     }
381 
382     /**
383      * Evaluates expressions in a given string. This method will iterate through each expression and evaluate it. If
384      * a user needs to evaluate a single expression they can use {@link org.mule.api.expression.ExpressionManager#evaluate(String,org.mule.api.MuleMessage,boolean)}.
385      *
386      * @param expression a single expression i.e. xpath://foo
387      * @param message    the current message to process.  The expression will evaluata on the message.
388      * @param failIfNull determines if an exception should be thrown if expression could not be evaluated or returns null.
389      * @return the result of the evaluation. Expressions that return collection will return an empty collection, not null.
390      * @throws ExpressionRuntimeException if the expression is invalid, or a null is found for the expression and
391      *                                    'failIfNull is set to true.
392      */
393     public String parse(final String expression, final MuleMessage message, final boolean failIfNull) throws ExpressionRuntimeException
394     {
395         return parser.parse(new TemplateParser.TemplateCallback()
396         {
397             public Object match(String token)
398             {
399                 Object result = evaluate(token, message, failIfNull);
400                 if (result instanceof MuleMessage)
401                 {
402                     return ((MuleMessage) result).getPayload();
403                 }
404                 else
405                 {
406                     return result;
407                 }
408             }
409         }, expression);
410     }
411 
412     /**
413      * Clears all registered evaluators from the manager.
414      */
415     public synchronized void clearEvaluators()
416     {
417         for (Iterator iterator = evaluators.values().iterator(); iterator.hasNext();)
418         {
419             ExpressionEvaluator evaluator = (ExpressionEvaluator) iterator.next();
420             if (evaluator instanceof Disposable)
421             {
422                 ((Disposable) evaluator).dispose();
423             }
424         }
425         evaluators.clear();
426     }
427     
428     public void clearEnrichers()
429     {
430         for (Iterator iterator = enrichers.values().iterator(); iterator.hasNext();)
431         {
432             ExpressionEnricher enricher = (ExpressionEnricher) iterator.next();
433             if (enricher instanceof Disposable)
434             {
435                 ((Disposable) enricher).dispose();
436             }
437         }
438         enrichers.clear();
439     }
440 
441     public boolean isExpression(String string)
442     {
443         return (string.contains(DEFAULT_EXPRESSION_PREFIX));
444     }
445 
446     /**
447      * Determines if the expression is valid or not.  This method will validate a single expression or
448      * expressions embedded in a string.  the expression must be well formed i.e. #[bean:user]
449      *
450      * @param expression the expression to validate
451      * @return true if the expression evaluator is recognised
452      */
453     public boolean isValidExpression(String expression)
454     {
455         try
456         {
457             validateExpression(expression);
458             return true;
459         }
460         catch (InvalidExpressionException e)
461         {
462             logger.warn(e.getMessage());
463             return false;
464         }
465     }
466 
467     public void validateExpression(String expression) throws InvalidExpressionException
468     {
469         if (!muleContext.getConfiguration().isValidateExpressions())
470         {
471             if (logger.isDebugEnabled()) {
472                 logger.debug("Validate expressions is turned off, no checking done for: " + expression);
473             }
474             return;
475         }
476         try
477         {
478             parser.validate(expression);
479         }
480         catch (IllegalArgumentException e)
481         {
482             throw new InvalidExpressionException(expression, e.getMessage());
483         }
484 
485         final AtomicBoolean valid = new AtomicBoolean(true);
486         final AtomicBoolean match = new AtomicBoolean(false);
487         final StringBuffer message = new StringBuffer();
488         parser.parse(new TemplateParser.TemplateCallback()
489         {
490             public Object match(String token)
491             {
492                 match.set(true);
493                 if (token.indexOf(":") == -1)
494                 {
495                     if (valid.get())
496                     {
497                         valid.compareAndSet(true, false);
498                     }
499                     message.append(token).append(" is invalid\n");
500                 }
501                 return null;
502             }
503         }, expression);
504 
505         if (message.length() > 0)
506         {
507             throw new InvalidExpressionException(expression, message.toString());
508         }
509         else if(!match.get())
510         {
511             throw new InvalidExpressionException(expression, "Expression string is not an expression.  Use isExpression(String) to validate first");
512         }
513     }
514 
515 }