View Javadoc

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