View Javadoc

1   /*
2    * $Id: ExpressionUtils.java 20813 2010-12-21 11:37:48Z dirk.olmes $
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  
11  package org.mule.expression;
12  
13  import org.mule.api.MuleMessage;
14  import org.mule.api.expression.RequiredValueException;
15  import org.mule.api.transport.PropertyScope;
16  import org.mule.config.i18n.CoreMessages;
17  import org.mule.routing.filters.WildcardFilter;
18  
19  import java.util.ArrayList;
20  import java.util.Collections;
21  import java.util.HashMap;
22  import java.util.List;
23  import java.util.Map;
24  
25  import static org.mule.expression.ExpressionConstants.ALL_ARGUMENT;
26  import static org.mule.expression.ExpressionConstants.DELIM;
27  import static org.mule.expression.ExpressionConstants.OPTIONAL_ARGUMENT;
28  
29  /**
30   * Used by the different header expression evaluators to read message properties, honuouring scope and return type
31   */
32  public final class ExpressionUtils
33  {
34      private ExpressionUtils()
35      {
36          // don't instantiate
37      }
38  
39      /**
40       * Gets a property or map/list of properties specific by an expression supporting multiple return types as well as all and optional modifiers
41       *
42       * Handles scope-aware expressions like "#[header:INBOUND:foo]
43       * @param expression the header name to evaluate.  this can be prefixed with a message scope such as INBOUND, OUTBOUND
44       * or INVOCATION scope. If no scope is defined the default scope is OUTBOUND
45       *
46       * @param msg the message to evaluate on
47       */
48      public static Object getPropertyWithScope(String expression, MuleMessage msg)
49      {
50          return getPropertyWithScope(expression, msg, Object.class);
51      }
52  
53      /**
54       * Gets a property or map/list of properties specific by an expression supporting multiple return types as well as all and optional modifiers
55       *
56       * Handles scope-aware expressions like "#[header:INBOUND:foo]
57       * @param expression the header name to evaluate.  this can be prefixed with a message scope such as INBOUND, OUTBOUND
58       * or INVOCATION scope. If no scope is defined the default scope is OUTBOUND
59       *
60       * @param msg the message to evaluate on
61       * @param type the expected return type for this evaluation
62       * @return  an object of type 'type' corresponding to the message header requested or null if the header was not on
63       * the message in the specified scope
64       */
65      public static <T> T getPropertyWithScope(String expression, MuleMessage msg, Class<T> type)
66      {
67          return getPropertyInternal(expression, PropertyScope.OUTBOUND, true, msg, type);
68      }
69  
70      /**
71       * Gets a property or map/list of properties specified by an expression supporting
72       * multiple return types as well as all and optional modifiers.
73       */
74      public static Object getProperty(String expression, PropertyScope scope, MuleMessage msg)
75      {
76          return getProperty(expression, scope, msg, Object.class);
77      }
78  
79      /**
80       * Gets a property or map/list of properties specific by an expression supporting multiple return types as well as all and optional modifiers
81       *
82       * @param msg the message to evaluate on
83       * @param type the expected return type for this evaluation
84       * @return  an object of type 'type' corresponding to the message header requested or null if the header was not on
85       * the message in the specified scope
86       */
87      public static <T> T getProperty(String expression, PropertyScope scope,  MuleMessage msg, Class<T> type)
88      {
89          return getPropertyInternal(expression, scope, false, msg, type);
90      }
91  
92      /**
93       * Obtains a property or map/list of properties from a message using an expression that specifies which property or properties to evaluate.
94       * This method can be used  default scope
95       * @param expression the expression used to evaluator the message
96       * @param scope the scope to be used when obtaining a property.  This is the default if parseScopes is true.
97       * @param parseScope should scope we parsed from expression string.  When true the scope acts as a default.
98       * @param msg the message to be evaluated
99       * @param type return type expected
100      * @return property or list/map of evaluated property values
101      */
102     @SuppressWarnings("unchecked")
103     protected static <T> T getPropertyInternal(String expression, PropertyScope scope, boolean parseScope, MuleMessage msg, Class<T> type)
104     {
105         if (parseScope)
106         {
107             PropertyScope tempScope = getScope(expression);
108             if (tempScope != null)
109             {
110                 // cut-off leading scope and separator
111                 expression = expression.substring(tempScope.getScopeName().length() + 1);
112                 scope = tempScope;
113             }
114         }
115 
116         if (expression.contains(ALL_ARGUMENT))
117         {
118             WildcardFilter filter = new WildcardFilter(expression);
119             if (Map.class.isAssignableFrom(type))
120             {
121                 Map<String, Object> props = new HashMap<String, Object>();
122                 for (String name : msg.getPropertyNames(scope))
123                 {
124                     if (filter.accept(name))
125                     {
126                         props.put(name, msg.getProperty(name, scope));
127                     }
128                 }
129                 return (T) returnMap(props, scope);
130             }
131             else if (List.class.isAssignableFrom(type))
132             {
133                 List<Object> values = new ArrayList<Object>();
134                 for (String name : msg.getPropertyNames(scope))
135                 {
136                     if (filter.accept(name))
137                     {
138                         values.add(msg.getProperty(name, scope));
139                     }
140                 }
141                 return (T) returnList(values, scope);
142             }
143             else
144             {
145                 //TODO i18n
146                 throw new IllegalArgumentException("Type specified is not a collection type but '" + ALL_ARGUMENT + "' was specified for all properties. Type is: " + type);
147             }
148         }
149         else if (Map.class.isAssignableFrom(type))
150         {
151             String[] names = expression.split(DELIM);
152             Map<String, Object> props = new HashMap<String, Object>();
153             for (String name : names)
154             {
155                 boolean required = true;
156                 name = name.trim();
157                 PropertyScope entryScope = scope;
158                 if (parseScope)
159                 {
160                     entryScope = getScope(name);
161                     if (entryScope != null)
162                     {
163                         // cut-off leading scope and separator
164                         name = name.substring(entryScope.getScopeName().length() + 1);
165                     }
166                     else
167                     {
168                         entryScope = scope;
169                     }
170                 }
171                 if (name.endsWith(OPTIONAL_ARGUMENT))
172                 {
173                     name = name.substring(0, name.length() - OPTIONAL_ARGUMENT.length());
174                     required = false;
175                 }
176                 Object value = msg.getProperty(name, entryScope);
177                 if (value == null && required)
178                 {
179                     throw new RequiredValueException(CoreMessages.expressionEvaluatorReturnedNull("headers", entryScope.getScopeName() + ":" + name));
180                 }
181                 else if (value != null)
182                 {
183                     props.put(name, value);
184                 }
185             }
186             return (T) returnMap(props, scope);
187         }
188         else if (List.class.isAssignableFrom(type))
189         {
190             String[] names = expression.split(DELIM);
191             List<Object> values = new ArrayList<Object>();
192             for (String name : names)
193             {
194                 boolean required = true;
195                 name = name.trim();
196                 PropertyScope itemScope = scope;
197                 if (parseScope)
198                 {
199                     itemScope = getScope(name);
200                     if (itemScope != null)
201                     {
202                         // cut-off leading scope and separator
203                         name = name.substring(itemScope.getScopeName().length() + 1);
204                     }
205                     else
206                     {
207                         itemScope = scope;
208                     }
209                 }
210                 if (name.endsWith(OPTIONAL_ARGUMENT))
211                 {
212                     name = name.substring(0, name.length() - OPTIONAL_ARGUMENT.length());
213                     required = false;
214                 }
215                 name = name.trim();
216                 Object value = msg.getProperty(name, itemScope);
217                 if (value == null && required)
218                 {
219                     throw new RequiredValueException(CoreMessages.expressionEvaluatorReturnedNull("headers-list", itemScope.getScopeName() + ":" + name));
220                 }
221                 else if (value != null)
222                 {
223                     values.add(value);
224                 }
225             }
226             return (T) returnList(values, scope);
227         }
228         else
229         {
230             boolean required = true;
231             if (expression.endsWith(OPTIONAL_ARGUMENT))
232             {
233                 expression = expression.substring(0, expression.length() - OPTIONAL_ARGUMENT.length());
234                 required = false;
235             }
236             Object result = msg.getProperty(expression.trim(), scope);
237             if (result == null && required)
238             {
239                 throw new RequiredValueException(CoreMessages.expressionEvaluatorReturnedNull("header", scope.getScopeName() + ":" + expression));
240 
241             }
242             return (T) result;
243         }
244     }
245 
246     private static Map<String, Object> returnMap(Map<String, Object> props, PropertyScope scope)
247     {
248         Map<String, Object> p = (props.size() == 0 ? Collections.<String, Object>emptyMap() : props);
249         if (scope.equals(PropertyScope.INBOUND))
250         {
251             p = Collections.unmodifiableMap(p);
252         }
253         return p;
254     }
255 
256     private static List<Object> returnList(List<Object> values, PropertyScope scope)
257     {
258         List<Object> l = (values.size() == 0 ? Collections.emptyList() : values);
259         if (scope.equals(PropertyScope.INBOUND))
260         {
261             l = Collections.unmodifiableList(l);
262         }
263         return l;
264     }
265 
266     protected static PropertyScope getScope(String expression)
267     {
268         // see if scope has been specified explicitly
269         final String[] tokens = expression.split(":", 2); // note we split only once, not on every separator
270         PropertyScope scope;
271         if (tokens.length == 2)
272         {
273             final String candidate = tokens[0];
274             scope = PropertyScope.get(candidate.toLowerCase());
275             if (scope == null)
276             {
277                 throw new IllegalArgumentException(String.format("'%s' is not a valid property scope.", candidate));
278             }
279 
280             return scope;
281         }
282         return null;
283     }
284 }