View Javadoc

1   /*
2    * $Id: InMemoryObjectStore.java 12269 2008-07-10 04:19:03Z dfeist $
3    * --------------------------------------------------------------------------------------
4    * Copyright (c) MuleSource, Inc.  All rights reserved.  http://www.mulesource.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.util.store;
11  
12  import org.mule.config.i18n.CoreMessages;
13  import org.mule.util.monitor.ExpiryMonitor;
14  
15  import java.util.Map;
16  
17  import edu.emory.mathcs.backport.java.util.concurrent.helpers.Utils;
18  import edu.emory.mathcs.backport.java.util.concurrent.ConcurrentSkipListMap;
19  import edu.emory.mathcs.backport.java.util.concurrent.TimeUnit;
20  
21  /**
22   * <code>InMemoryObjectStore</code> implements an optionally bounded
23   * in-memory store for message IDs with periodic expiry of old entries. The bounded size
24   * is a <i>soft</i> limit and only enforced periodically by the expiry process; this
25   * means that the store may temporarily exceed its maximum size between expiry runs, but
26   * will eventually shrink to its configured size.DO
27   */
28  public class InMemoryObjectStore extends AbstractMonitoredObjectStore
29  {
30      protected ConcurrentSkipListMap store;
31  
32      public InMemoryObjectStore()
33      {
34          this.store = new ConcurrentSkipListMap();
35      }
36  
37      
38  
39      /**
40       * Check whether the given Object is already registered with this store.
41       *
42       * @param id the ID to check
43       * @return <code>true</code> if the ID is stored or <code>false</code> if it could
44       *         not be found
45       * @throws IllegalArgumentException if the given ID is <code>null</code>
46       * @throws Exception                if any implementation-specific error occured, e.g. when the store
47       *                                  is not available
48       */
49      public boolean containsObject(String id) throws Exception
50      {
51          if (id == null)
52          {
53              throw new IllegalArgumentException(CoreMessages.objectIsNull("id").toString());
54          }
55  
56          // this is a relaxed check so we don't need to synchronize on the store.
57          return store.values().contains(new StoredObject(id, null));
58      }
59  
60      /**
61       * Store the given Object.
62       *
63       * @param id the ID to store
64       * @return <code>true</code> if the ID was stored properly, or <code>false</code>
65       *         if it already existed
66       * @throws IllegalArgumentException if the given ID cannot be stored or is
67       *                                  <code>null</code>
68       * @throws Exception                if the store is not available or any other
69       *                                  implementation-specific error occured
70       */
71      public boolean storeObject(String id, Object item) throws Exception
72      {
73          if (id == null)
74          {
75              throw new IllegalArgumentException(CoreMessages.objectIsNull("id").toString());
76          }
77  
78          // this block is unfortunately necessary to counter a possible race condition
79          // between multiple nonatomic calls to containsId/storeId, which are
80          // only necessary because of the nonatomic calls to isMatch/process by
81          // DefaultInboundRouterCollection.route().
82          StoredObject obj = new StoredObject(id, item);
83          synchronized (store)
84          {
85              if (store.values().contains(obj))
86              {
87                  return false;
88              }
89  
90              boolean written = false;
91              while (!written)
92              {
93                  written = (store.putIfAbsent(new Long(Utils.nanoTime()), obj) == null);
94              }
95  
96              return true;
97          }
98      }
99  
100     /**
101      * Retrieve the given Object.
102      *
103      * @param id the ID to store
104      * @return the object instance associated with this id or null if there was no entry for the supplied id.
105      * @throws IllegalArgumentException if the given ID cannot be stored or is
106      *                                  <code>null</code>
107      * @throws Exception                if the store is not available or any other
108      *                                  implementation-specific error occured
109      */
110     public Object retrieveObject(String id) throws Exception
111     {
112         StoredObject obj = (StoredObject)store.get(id);
113         if(obj!=null)
114         {
115             return obj.getItem();
116         }
117         return null;
118     }
119 
120     public boolean removeObject(String id) throws Exception
121     {
122         StoredObject obj = (StoredObject)store.get(id);
123         if(obj!=null)
124         {
125             return store.remove(obj) !=null;
126         }
127         return true;
128     }
129 
130     public final void expire()
131     {
132         // this is not guaranteed to be precise, but we don't mind
133         int currentSize = store.size();
134 
135         // first trim to maxSize if necessary
136         int excess = (currentSize - maxEntries);
137         if (excess > 0)
138         {
139             while (currentSize > maxEntries)
140             {
141                 store.pollFirstEntry();
142                 currentSize--;
143             }
144 
145             if (logger.isDebugEnabled())
146             {
147                 logger.debug("Expired " + excess + " excess entries");
148             }
149         }
150 
151         // expire further if entry TTLs are enabled
152         if (entryTTL > 0 && currentSize != 0)
153         {
154             final long now = Utils.nanoTime();
155             int expiredEntries = 0;
156             Map.Entry oldestEntry;
157 
158             purge:
159             while ((oldestEntry = store.firstEntry()) != null)
160             {
161                 Long oldestKey = (Long) oldestEntry.getKey();
162                 long oldestKeyValue = oldestKey.longValue();
163 
164                 if (TimeUnit.NANOSECONDS.toMillis(now - oldestKeyValue) >= entryTTL)
165                 {
166                     store.remove(oldestKey);
167                     expiredEntries++;
168                 }
169                 else
170                 {
171                     break purge;
172                 }
173             }
174 
175             if (logger.isDebugEnabled())
176             {
177                 logger.debug("Expired " + expiredEntries + " old entries");
178             }
179         }
180     }
181 
182     /**
183      * Represents the object stored in the store. This class holds the Object itslef and its ID.
184      */
185     protected static class StoredObject
186     {
187         private String id;
188         private Object item;
189 
190         public StoredObject(String id, Object item)
191         {
192             this.id = id;
193             this.item = item;
194         }
195 
196         public String getId()
197         {
198             return id;
199         }
200 
201         public Object getItem()
202         {
203             return item;
204         }
205 
206         public boolean equals(Object o)
207         {
208             if (this == o)
209             {
210                 return true;
211             }
212             if (o == null || getClass() != o.getClass())
213             {
214                 return false;
215             }
216 
217             StoredObject that = (StoredObject) o;
218 
219             if (!id.equals(that.id))
220             {
221                 return false;
222             }
223 
224             return true;
225         }
226 
227         public int hashCode()
228         {
229             return id.hashCode();
230         }
231 
232 
233         public String toString()
234         {
235             final StringBuffer sb = new StringBuffer();
236             sb.append("StoredObject");
237             sb.append("{id='").append(id).append('\'');
238             sb.append(", item=").append(item);
239             sb.append('}');
240             return sb.toString();
241         }
242     }
243 
244 
245 
246 }