View Javadoc

1   /*
2    * $Id: FunctionExpressionEvaluator.java 18005 2010-07-09 16:47:06Z aperepel $
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      public static Object getPropertyWithScope(String expression, MuleMessage msg)
40      {
41          return getPropertyWithScope(expression, msg, Object.class);
42      }
43  
44      /**
45       * Handler scope-aware expressions like "#[header:INBOUND:foo]
46       * @param expression the header name to evaluate.  this can be prefixed with a message scope such as INBOUND, OUTBOUND
47       * or INVOCATION scope. If no scope is defined the default scope is OUTBOUND
48       * 
49       * @param msg the message to evaluate on
50       * @param type the expected return type for this evaluation
51       * @return  an object of type 'type' corresponding to the message header requested or null if the header was not on
52       * the message in the specified scope
53       */
54      @SuppressWarnings("unchecked")
55      public static <T> T getPropertyWithScope(String expression, MuleMessage msg, Class<T> type)
56      {
57          PropertyScope defaultScope = getScope(expression);
58          if (defaultScope != null)
59          {
60              // cut-off leading scope and separator
61              expression = expression.substring(defaultScope.getScopeName().length() + 1);
62          }
63          else
64          {
65              // default
66              defaultScope = PropertyScope.OUTBOUND;
67          }
68  
69          if (expression.contains(ALL_ARGUMENT))
70          {
71              WildcardFilter filter = new WildcardFilter(expression);
72              if (Map.class.isAssignableFrom(type))
73              {
74                  Map<String, Object> props = new HashMap<String, Object>();
75                  for (String name : msg.getPropertyNames(defaultScope))
76                  {
77                      if (filter.accept(name))
78                      {
79                          props.put(name, msg.getProperty(name, defaultScope));
80                      }
81                  }
82                  return (T) returnMap(props, defaultScope);
83              }
84              else if (List.class.isAssignableFrom(type))
85              {
86                  List<Object> values = new ArrayList<Object>();
87                  for (String name : msg.getPropertyNames(defaultScope))
88                  {
89                      if (filter.accept(name))
90                      {
91                          values.add(msg.getProperty(name, defaultScope));
92                      }
93                  }
94                  return (T) returnList(values, defaultScope);
95              }
96              else
97              {
98                  //TODO i18n
99                  throw new IllegalArgumentException("Type specified is not a collection type but '" + ALL_ARGUMENT + "' was specified for all properties. Type is: " + type);
100             }
101         }
102         else if (Map.class.isAssignableFrom(type))
103         {
104             String[] names = expression.split(DELIM);
105             Map<String, Object> props = new HashMap<String, Object>();
106             for (String name : names)
107             {
108                 boolean required = true;
109                 name = name.trim();
110                 PropertyScope scope = getScope(name);
111                 if (scope != null)
112                 {
113                     // cut-off leading scope and separator
114                     name = name.substring(scope.getScopeName().length() + 1);
115                 }
116                 else
117                 {
118                     scope = defaultScope;
119                 }
120                 if (name.endsWith(OPTIONAL_ARGUMENT))
121                 {
122                     name = name.substring(0, name.length() - OPTIONAL_ARGUMENT.length());
123                     required = false;
124                 }
125                 Object value = msg.getProperty(name, scope);
126                 if (value == null && required)
127                 {
128                     throw new RequiredValueException(CoreMessages.expressionEvaluatorReturnedNull("headers", scope.getScopeName() + ":" + name));
129                 }
130                 else if (value != null)
131                 {
132                     props.put(name, value);
133                 }
134             }
135             return (T) returnMap(props, defaultScope);
136         }
137         else if (List.class.isAssignableFrom(type))
138         {
139             String[] names = expression.split(DELIM);
140             List<Object> values = new ArrayList<Object>();
141             for (String name : names)
142             {
143                 boolean required = true;
144                 name = name.trim();
145                 PropertyScope scope = getScope(name);
146                 if (scope != null)
147                 {
148                     // cut-off leading scope and separator
149                     name = name.substring(scope.getScopeName().length() + 1);
150                 }
151                 else
152                 {
153                     scope = defaultScope;
154                 }
155                 if (name.endsWith(OPTIONAL_ARGUMENT))
156                 {
157                     name = name.substring(0, name.length() - OPTIONAL_ARGUMENT.length());
158                     required = false;
159                 }
160                 name = name.trim();
161                 Object value = msg.getProperty(name, scope);
162                 if (value == null && required)
163                 {
164                     throw new RequiredValueException(CoreMessages.expressionEvaluatorReturnedNull("headers-list", scope.getScopeName() + ":" + name));
165                 }
166                 else if (value != null)
167                 {
168                     values.add(value);
169                 }
170             }
171             return (T) returnList(values, defaultScope);
172         }
173         else
174         {
175             boolean required = true;
176             if (expression.endsWith(OPTIONAL_ARGUMENT))
177             {
178                 expression = expression.substring(0, expression.length() - OPTIONAL_ARGUMENT.length());
179                 required = false;
180             }
181             Object result = msg.getProperty(expression.trim(), defaultScope);
182             if (result == null && required)
183             {
184                 throw new RequiredValueException(CoreMessages.expressionEvaluatorReturnedNull("header", defaultScope.getScopeName() + ":" + expression));
185 
186             }
187             return (T) result;
188         }
189     }
190 
191     private static Map<String, Object> returnMap(Map<String, Object> props, PropertyScope scope)
192     {
193         Map<String, Object> p = (props.size() == 0 ? Collections.<String, Object>emptyMap() : props);
194         if (scope.equals(PropertyScope.INBOUND))
195         {
196             p = Collections.unmodifiableMap(p);
197         }
198         return p;
199     }
200 
201     private static List<Object> returnList(List<Object> values, PropertyScope scope)
202     {
203         List<Object> l = (values.size() == 0 ? Collections.emptyList() : values);
204         if (scope.equals(PropertyScope.INBOUND))
205         {
206             l = Collections.unmodifiableList(l);
207         }
208         return l;
209     }
210 
211     private static PropertyScope getScope(String expression)
212     {
213         // see if scope has been specified explicitly
214         final String[] tokens = expression.split(":", 2); // note we split only once, not on every separator
215         PropertyScope scope;
216         if (tokens.length == 2)
217         {
218             final String candidate = tokens[0];
219             scope = PropertyScope.get(candidate.toLowerCase());
220             if (scope == null)
221             {
222                 throw new IllegalArgumentException(String.format("'%s' is not a valid property scope.", candidate));
223             }
224 
225             return scope;
226         }
227         return null;
228     }
229 }