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.transformer.simple;
8   
9   import org.mule.DefaultMuleMessage;
10  import org.mule.api.MuleMessage;
11  import org.mule.api.transport.PropertyScope;
12  import org.mule.routing.filters.WildcardFilter;
13  import org.mule.transformer.AbstractMessageTransformer;
14  import org.mule.transformer.types.DataTypeFactory;
15  import org.mule.transport.NullPayload;
16  
17  import java.text.MessageFormat;
18  import java.util.ArrayList;
19  import java.util.Arrays;
20  import java.util.HashMap;
21  import java.util.HashSet;
22  import java.util.List;
23  import java.util.Map;
24  import java.util.Set;
25  
26  /**
27   * <p>
28   * A configurable message transformer that allows users to add, overwrite, rename and delete
29   * properties on the current message. Users can set a {@link List} of 'deleteProperties' regular 
30   * expressions to remove the matching properties from the message and can also set a {@link Map} 
31   * of 'addProperties' that will be added to the message and possibly overwrite existing properties
32   * with the same name.
33   * <p/> 
34   * <p>
35   * If {@link #overwrite} is set to <code>false</code>, and a property 
36   * exists on the message (even if the value is <code>null</code>, it will be left intact. The 
37   * transformer then acts as a more gentle 'enricher'. The default setting is <code>true</code>.
38   * </p>
39   */
40  public class MessagePropertiesTransformer extends AbstractMessageTransformer
41  {
42      private List<String> deleteProperties = null;
43      private Map<String, Object> addProperties = null;
44      /** the properties map containing rename mappings for message properties */
45      private Map<String, String> renameProperties;
46      private String getProperty;
47      private boolean overwrite = true;
48      // outbound is the default scope
49      private PropertyScope scope = PropertyScope.OUTBOUND;
50  
51      public MessagePropertiesTransformer()
52      {
53          registerSourceType(DataTypeFactory.OBJECT);
54          setReturnDataType(DataTypeFactory.OBJECT);
55      }
56  
57      @Override
58      public Object clone() throws CloneNotSupportedException
59      {
60          MessagePropertiesTransformer clone = (MessagePropertiesTransformer) super.clone();
61  
62          if (deleteProperties != null)
63          {
64              clone.setDeleteProperties(new ArrayList<String>(deleteProperties));
65          }
66  
67          if (addProperties != null)
68          {
69              clone.setAddProperties(new HashMap<String, Object>(addProperties));
70          }
71  
72          if (renameProperties != null)
73          {
74              clone.setRenameProperties(new HashMap<String, String>(renameProperties));
75          }
76          return clone;
77      }
78  
79      @Override
80      public Object transformMessage(MuleMessage message, String outputEncoding)
81      {
82          if (deleteProperties != null && deleteProperties.size() > 0)
83          {
84              deleteProperties(message);
85          }
86  
87          if (addProperties != null && addProperties.size() > 0)
88          {
89              addProperties(message);
90          }
91  
92          /* perform renaming transformation */
93          if (this.renameProperties != null && this.renameProperties.size() > 0)
94          {
95              renameProperties(message);
96          }
97          
98          if (getProperty != null)
99          {
100             Object prop = message.getProperty(getProperty, scope);
101             if (prop != null)
102             {
103                 message = new DefaultMuleMessage(prop, muleContext);
104             }
105             else
106             {
107                 message = new DefaultMuleMessage(NullPayload.getInstance(), muleContext);
108             }
109         }
110 
111         return message;
112     }
113 
114     protected void deleteProperties(MuleMessage message)
115     {
116         final Set<String> propertyNames = new HashSet<String>(message.getPropertyNames(scope));
117         
118         for (String expression : deleteProperties)
119         {
120             for (String key : propertyNames)
121             {
122                 if (key.matches(expression))
123                 {
124                     if (logger.isDebugEnabled())
125                     {
126                         logger.debug(String.format("Removing property: '%s' from scope: '%s'", key, scope.getScopeName()));
127                     }
128                     message.removeProperty(key, scope);
129                 }
130                 else
131                 {
132                     // fallback to the plain wildcard for simplicity
133                     WildcardFilter filter = new WildcardFilter(expression);
134                     if (filter.accept(key))
135                     {
136                         message.removeProperty(key, scope);
137                     }
138                 }
139             }
140         }
141     }
142 
143     protected void addProperties(MuleMessage message)
144     {
145         for (Map.Entry<String, Object> entry : addProperties.entrySet())
146         {
147             if (entry.getKey() == null)
148             {
149                 logger.error("Setting Null property keys is not supported, this entry is being ignored");
150             }
151             else
152             {
153                 final String key = entry.getKey();
154 
155                 Object value= entry.getValue();
156                 Object realValue = value;
157 
158                 //Enable expression support for property values
159                 if (muleContext.getExpressionManager().isExpression(value.toString()))
160                 {
161                     realValue = muleContext.getExpressionManager().evaluate(value.toString(), message);
162                 }
163 
164                 if (realValue != null)
165                 {
166                     if (message.getProperty(key, scope) != null)
167                     {
168                         if (overwrite)
169                         {
170                             logger.debug("Overwriting message property " + key);
171                             message.setProperty(key, realValue, scope);
172                         }
173                         else if(logger.isDebugEnabled())
174                         {
175                             logger.debug(MessageFormat.format(
176                                 "Message already contains the property and overwrite is false, skipping: key={0}, value={1}, scope={2}",
177                                 key, realValue, scope));
178                         }
179                     }
180                     //If value is null an exception will not be thrown if the key was marked as optional (with a '?'). If not
181                     //optional the expression evaluator will throw an exception
182                     else
183                     {
184                         message.setProperty(key, realValue, scope);
185                     }
186                 }
187                 else if (logger.isInfoEnabled())
188                 {
189                     logger.info(MessageFormat.format(
190                         "Property with key ''{0}'', not found on message using ''{1}''. Since the value was marked optional, nothing was set on the message for this property",
191                         key, value));
192                 }
193             }
194         }
195     }
196 
197     protected void renameProperties(MuleMessage message)
198     {
199         for (Map.Entry<String, String> entry : this.renameProperties.entrySet())
200         {
201             if (entry.getKey() == null)
202             {
203                 logger.error("Setting Null property keys is not supported, this entry is being ignored");
204             }
205             else
206             {
207                 final String key = entry.getKey();
208                 String value = entry.getValue();
209 
210                 if (value == null)
211                 {
212                     logger.error("Setting Null property values for renameProperties is not supported, this entry is being ignored");
213                 }
214                 else
215                 {
216                     //Enable expression support for property values
217                     if (muleContext.getExpressionManager().isValidExpression(value))
218                     {
219                         Object temp = muleContext.getExpressionManager().evaluate(value, message);
220                         if (temp != null)
221                         {
222                             value = temp.toString();
223                         }
224                     }
225 
226                     /* log transformation */
227                     if (logger.isDebugEnabled() && message.getProperty(key, scope) == null)
228                     {
229                         logger.debug(String.format("renaming message property '%s' to '%s'", key, value));
230                     }
231 
232                     renameInScope(key, value, scope, message);
233                 }
234             }
235         }
236     }
237 
238     protected void renameInScope(String oldKey, String newKey, PropertyScope propertyScope, MuleMessage message)
239     {
240         Object propValue = message.getProperty(oldKey, propertyScope);
241         message.removeProperty(oldKey, propertyScope);
242         message.setProperty(newKey, propValue, propertyScope);
243     }
244 
245     public List<String> getDeleteProperties()
246     {
247         return deleteProperties;
248     }
249 
250     /**
251      * @see #setDeleteProperties(String...)
252      */
253     public void setDeleteProperties(List<String> deleteProperties)
254     {
255         this.deleteProperties = deleteProperties;
256     }
257 
258     public void setDeleteProperties(String... deleteProperties)
259     {
260         this.deleteProperties = Arrays.asList(deleteProperties);
261     }
262 
263     public Map<String, Object> getAddProperties()
264     {
265         return addProperties;
266     }
267 
268     public void setAddProperties(Map<String, Object> addProperties)
269     {
270         this.addProperties = addProperties;
271     }
272 
273     /**
274      * @return the renameProperties
275      */
276     public Map<String, String> getRenameProperties()
277     {
278         return this.renameProperties;
279     }
280 
281     /**
282      * @param renameProperties the renameProperties to set
283      */
284     public void setRenameProperties(Map<String, String> renameProperties)
285     {
286         this.renameProperties = renameProperties;
287     }
288 
289     public String getGetProperty()
290     {
291         return getProperty;
292     }
293 
294     public void setGetProperty(String getProperty)
295     {
296         this.getProperty = getProperty;
297     }
298 
299     public boolean isOverwrite()
300     {
301         return overwrite;
302     }
303 
304     public void setOverwrite(final boolean overwrite)
305     {
306         this.overwrite = overwrite;
307     }
308 
309     public PropertyScope getScope()
310     {
311         return scope;
312     }
313 
314     public void setScope(PropertyScope scope)
315     {
316         this.scope = scope;
317     }
318     
319     /**
320      * For XML-based config
321      *
322      * @return The string value name for a {@link org.mule.api.transport.PropertyScope}
323      */
324     public String getScopeName()
325     {
326         return scope != null ? scope.getScopeName() : null;
327     }
328 
329     /**
330      * For XML-based config
331      * @param scopeName The string value name for a {@link org.mule.api.transport.PropertyScope}
332      */
333     public void setScopeName(String scopeName)
334     {
335         this.scope = PropertyScope.get(scopeName);
336     }
337 }