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