View Javadoc

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