View Javadoc

1   /*
2    * $Id: MessagePropertiesContext.java 22407 2011-07-13 23:51:21Z mike.schilling $
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  package org.mule;
11  
12  import org.mule.api.MuleEvent;
13  import org.mule.api.MuleSession;
14  import org.mule.api.transport.PropertyScope;
15  import org.mule.util.CaseInsensitiveHashMap;
16  import org.mule.util.MapUtils;
17  import org.mule.util.ObjectUtils;
18  
19  import java.io.IOException;
20  import java.io.Serializable;
21  import java.util.Collections;
22  import java.util.HashMap;
23  import java.util.HashSet;
24  import java.util.Map;
25  import java.util.Set;
26  import java.util.TreeMap;
27  import java.util.TreeSet;
28  
29  import org.apache.commons.logging.Log;
30  import org.apache.commons.logging.LogFactory;
31  
32  /**
33   * This object maintains a scoped map of properties.  This means that certain properties will only be visible under some
34   * scopes. The scopes supported by Mule are:
35   * <ol>
36   * <li> {@link org.mule.api.transport.PropertyScope#INBOUND} Contains properties that were on the message when it was
37   * received by Mule.  This scope is read-only.</li>
38   * <li>{@link org.mule.api.transport.PropertyScope#INVOCATION} Any properties set on the invocation scope will be
39   * available to the current service but will not be attached to any outbound messages.</li>
40   * <li>{@link org.mule.api.transport.PropertyScope#OUTBOUND} Any properties set in this scope will be attached to any
41   * outbound messages resulting from this message.  This is the default scope.</li>
42   * <li>{@link org.mule.api.transport.PropertyScope#SESSION} Any properties set on this scope will be added to the session.
43   * Note that this is a convenience scope in that you cannot directly access session properties from this scope.  Session
44   * properties can be accessed from the {@link MuleEvent}</li>
45   * </ol>
46   */
47  public class MessagePropertiesContext implements Serializable
48  {
49      private static final long serialVersionUID = -5230693402768953742L;
50      private static final PropertyScope DEFAULT_SCOPE = PropertyScope.OUTBOUND;
51  
52      private static Log logger = LogFactory.getLog(MessagePropertiesContext.class);
53  
54      /**
55       * Map of maps containing the scoped properties, each scope has its own Map.
56       */
57      protected Map<PropertyScope, Map<String, Object>> scopedMap;
58  
59      /**
60       * The union of all property names from all scopes.
61       */
62      protected Set<String> keySet;
63  
64  
65      @SuppressWarnings("unchecked")
66      public MessagePropertiesContext()
67      {
68          keySet = new TreeSet<String>();
69          scopedMap = new TreeMap<PropertyScope, Map<String, Object>>(new PropertyScope.ScopeComparator());
70  
71          scopedMap.put(PropertyScope.INVOCATION, new CaseInsensitiveHashMap/*<String, Object>*/(6));
72          scopedMap.put(PropertyScope.INBOUND, new CaseInsensitiveHashMap/*<String, Object>*/(6));
73          scopedMap.put(PropertyScope.OUTBOUND, new CaseInsensitiveHashMap/*<String, Object>*/(6));
74      }
75  
76      protected Map<String, Object> getScopedProperties(PropertyScope scope)
77      {
78          Map<String, Object> map = scopedMap.get(scope);
79          if (map == null)
80          {
81              throw new IllegalArgumentException("Scope not registered: " + scope);
82          }
83          return map;
84      }
85  
86      public PropertyScope getDefaultScope()
87      {
88          return DEFAULT_SCOPE;
89      }
90  
91      protected void addInboundProperties(Map<String, Object> properties)
92      {
93          if (properties != null)
94          {
95              Map<String, Object> props = new HashMap<String, Object>(properties.size());
96              for (String key : properties.keySet())
97              {
98                  props.put(key, properties.get(key));
99              }
100             getScopedProperties(PropertyScope.INBOUND).putAll(props);
101             keySet.addAll(props.keySet());
102         }
103     }
104 
105     /**
106      *
107      * @deprecated use the overloaded version with an explicit lookup scope. This method will
108      * now use only the outbound scope.
109      */
110     @Deprecated
111     public Object getProperty(String key)
112     {
113         return getProperty(key, PropertyScope.OUTBOUND);
114     }
115 
116     @SuppressWarnings("unchecked")
117     public <T> T getProperty(String key, PropertyScope scope)
118     {
119         if (scope == null)
120         {
121             scope = PropertyScope.OUTBOUND;
122         }
123 
124         T value = null;
125         if (PropertyScope.SESSION.equals(scope))
126         {
127             if (RequestContext.getEvent() != null)
128             {
129                 value = (T) RequestContext.getEvent().getSession().getProperty(key);
130             }
131         }
132         else
133         {
134             value = (T) scopedMap.get(scope).get(key);
135         }
136         return value;
137     }
138 
139     /**
140      * Removes all properties from all scopes except for SESSION and INBOUND (which is read-only).
141      * You may explicitly clear the session properties by calling clearProperties(PropertyScope.SESSION)
142      */
143     public void clearProperties()
144     {
145         Map<String, Object> props = getScopedProperties(PropertyScope.INVOCATION);
146         keySet.removeAll(props.keySet());
147         props.clear();
148         props = getScopedProperties(PropertyScope.OUTBOUND);
149         keySet.removeAll(props.keySet());
150         props.clear();
151     }
152 
153     public void clearProperties(PropertyScope scope)
154     {
155         if (scope == null)
156         {
157             clearProperties();
158             return;
159         }
160 
161         //checkScopeForWriteAccess(scope);
162         if (PropertyScope.SESSION.equals(scope))
163         {
164             if (RequestContext.getEvent() != null)
165             {
166                 MuleSession session = RequestContext.getEvent().getSession();
167                 for (Object key : session.getPropertyNamesAsSet())
168                 {
169                     session.removeProperty(key);
170                 }
171             }
172         }
173         else
174         {
175             Map<String, Object> props = getScopedProperties(scope);
176             keySet.removeAll(props.keySet());
177             props.clear();
178         }
179     }
180 
181     /**
182      * Removes a property from all scopes except for SESSION and INBOUND (which is read-only).
183      * You may explicitly remove a session property by calling removeProperty(key, PropertyScope.SESSION)
184      *
185      * @param key the property key to remove
186      * @return the removed property value or null if the property did not exist
187      */
188     public Object removeProperty(String key)
189     {
190         Object value = getScopedProperties(PropertyScope.OUTBOUND).remove(key);
191         Object inv = getScopedProperties(PropertyScope.INVOCATION).remove(key);
192 
193         keySet.remove(key);
194 
195         if (value == null)
196         {
197             value = inv;
198         }
199 
200         return value;
201     }
202 
203     /**
204      * Removes a property from the specified property scope.
205      *
206      * @param key the property key to remove
207      * @return the removed property value or null if the property did not exist
208      */
209     public Object removeProperty(String key, PropertyScope scope)
210     {
211         if (scope == null)
212         {
213             return removeProperty(key);
214         }
215 
216         Object value = null;
217         if (PropertyScope.SESSION.equals(scope))
218         {
219             if (RequestContext.getEvent() != null)
220             {
221                 value = RequestContext.getEvent().getSession().removeProperty(key);
222             }
223         }
224         else
225         {
226             value = getScopedProperties(scope).remove(key);
227         }
228 
229         // Only remove the property from the keySet if it does not exist in any other scope besides this one.
230         if (getProperty(key, PropertyScope.OUTBOUND) == null
231                 && getProperty(key, PropertyScope.INVOCATION) == null
232                 && getProperty(key, PropertyScope.INBOUND) == null)
233         {
234             keySet.remove(key);
235         }
236 
237         return value;
238     }
239 
240     /**
241      * Set a property on the message
242      *
243      * @param key   the key on which to associate the value
244      * @param value the property value
245      * @deprecated use {@link #setProperty(String, Object, org.mule.api.transport.PropertyScope)}
246      */
247     @Deprecated
248     public void setProperty(String key, Object value)
249     {
250         getScopedProperties(DEFAULT_SCOPE).put(key, value);
251         keySet.add(key);
252     }
253 
254     /**
255      * Set a property on the message
256      *
257      * @param key   the key on which to associate the value
258      * @param value the property value
259      * @param scope the scope to se the property on
260      * @see org.mule.api.transport.PropertyScope
261      */
262     public void setProperty(String key, Object value, PropertyScope scope)
263     {
264         //checkScopeForWriteAccess(scope);
265         if (PropertyScope.SESSION.equals(scope))
266         {
267             if (RequestContext.getEvent() != null)
268             {
269                 RequestContext.getEvent().getSession().setProperty(key, value);
270             }
271             else
272             {
273                 logger.warn(String.format("Detected an attempt to set a session property, " +
274                                           "but MuleEvent isn't available in this thread. Key/value: %s=%s %s",
275                                           key, value, Thread.currentThread()));
276             }
277         }
278         else
279         {
280             getScopedProperties(scope).put(key, value);
281             keySet.add(key);
282         }
283     }
284 
285     /**
286      * @deprecated use {@link #getPropertyNames(org.mule.api.transport.PropertyScope)}
287      */
288     @Deprecated
289     public Set<String> getPropertyNames()
290     {
291         Set<String> allProps = new HashSet<String>();
292         allProps.addAll(keySet);
293         if (RequestContext.getEvent() != null)
294         {
295             allProps.addAll(RequestContext.getEvent().getSession().getPropertyNamesAsSet());
296         }
297         return allProps;
298     }
299 
300     /**
301      * @return all property keys on this message for the given scope
302      */
303     public Set<String> getPropertyNames(PropertyScope scope)
304     {
305         if (PropertyScope.SESSION.equals(scope))
306         {
307             if (RequestContext.getEvent() != null)
308             {
309                 return RequestContext.getEvent().getSession().getPropertyNamesAsSet();
310             }
311             else
312             {
313                 return Collections.emptySet();
314             }
315         }
316         else
317         {
318             return Collections.unmodifiableSet(getScopedProperties(scope).keySet());
319         }
320     }
321 
322     protected void checkScopeForWriteAccess(PropertyScope scope)
323     {
324         if (scope == null || PropertyScope.INBOUND.equals(scope))
325         {
326             throw new IllegalArgumentException("Scope is invalid for writing properties: " + scope);
327         }
328     }
329 
330     public Object getProperty(String key, Object defaultValue)
331     {
332         Object value = getProperty(key);
333         if (value == null)
334         {
335             value = defaultValue;
336         }
337         return value;
338     }
339 
340     public byte getByteProperty(String name, byte defaultValue)
341     {
342         return ObjectUtils.getByte(getProperty(name), defaultValue);
343     }
344 
345     public short getShortProperty(String name, short defaultValue)
346     {
347         return ObjectUtils.getShort(getProperty(name), defaultValue);
348     }
349 
350     public int getIntProperty(String name, int defaultValue)
351     {
352         return ObjectUtils.getInt(getProperty(name), defaultValue);
353     }
354 
355     public long getLongProperty(String name, long defaultValue)
356     {
357         return ObjectUtils.getLong(getProperty(name), defaultValue);
358     }
359 
360     public float getFloatProperty(String name, float defaultValue)
361     {
362         return ObjectUtils.getFloat(getProperty(name), defaultValue);
363     }
364 
365     public double getDoubleProperty(String name, double defaultValue)
366     {
367         return ObjectUtils.getDouble(getProperty(name), defaultValue);
368     }
369 
370     public boolean getBooleanProperty(String name, boolean defaultValue)
371     {
372         return ObjectUtils.getBoolean(getProperty(name), defaultValue);
373     }
374 
375     @Deprecated
376     public String getStringProperty(String name, String defaultValue)
377     {
378         return getStringProperty(name, PropertyScope.OUTBOUND, defaultValue);
379     }
380 
381     public String getStringProperty(String name, PropertyScope scope, String defaultValue)
382     {
383         return ObjectUtils.getString(getProperty(name, scope), defaultValue);
384     }
385 
386     @Override
387     public String toString()
388     {
389         StringBuffer buf = new StringBuffer(128);
390         buf.append("Properties{");
391         for (Map.Entry<PropertyScope, Map<String, Object>> entry : scopedMap.entrySet())
392         {
393             buf.append(entry.getKey()).append(":");
394             buf.append(MapUtils.toString(entry.getValue(), false));
395             buf.append(", ");
396         }
397         buf.append("}");
398         return buf.toString();
399     }
400 
401     /**
402      * Check for properties that can't be serialized
403      */
404     private void writeObject(java.io.ObjectOutputStream out) throws IOException
405     {
406         for (Map.Entry<PropertyScope, Map<String, Object>> context : scopedMap.entrySet())
407         {
408             for (Map.Entry<String, Object> entry : context.getValue().entrySet())
409             {
410                 Object value = entry.getValue();
411                 if (value != null && !(value instanceof Serializable))
412                 {
413                     String message = String.format("Unable to serialize the %s message property %s, which is of type %s ",
414                                                    context.getKey(), entry.getKey(), value);
415                     logger.error(message);
416                     throw new IOException(message);
417                 }
418             }
419         }
420         out.defaultWriteObject();
421     }
422 }