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