View Javadoc

1   /*
2    * $Id: InMemoryObjectStore.java 19191 2010-08-25 21:05:23Z tcarlson $
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.util.store;
11  
12  import org.mule.api.store.ObjectAlreadyExistsException;
13  import org.mule.api.store.ObjectDoesNotExistException;
14  import org.mule.api.store.ObjectStoreException;
15  import org.mule.config.i18n.CoreMessages;
16  
17  import java.io.Serializable;
18  import java.util.Iterator;
19  import java.util.Map;
20  
21  import edu.emory.mathcs.backport.java.util.concurrent.ConcurrentSkipListMap;
22  import edu.emory.mathcs.backport.java.util.concurrent.TimeUnit;
23  
24  /**
25   * <code>InMemoryObjectStore</code> implements an optionally bounded
26   * in-memory store for message IDs with periodic expiry of old entries. The bounded size
27   * is a <i>soft</i> limit and only enforced periodically by the expiry process; this
28   * means that the store may temporarily exceed its maximum size between expiry runs, but
29   * will eventually shrink to its configured size.
30   */
31  public class InMemoryObjectStore<T extends Serializable> extends AbstractMonitoredObjectStore<T>
32  {
33      protected ConcurrentSkipListMap/*<Long, StoredObject>*/ store;
34  
35      public InMemoryObjectStore()
36      {
37          this.store = new ConcurrentSkipListMap();
38      }
39  
40      /**
41       * {@inheritDoc}
42       */
43      public boolean contains(Serializable key) throws ObjectStoreException
44      {
45          if (key == null)
46          {
47              throw new ObjectStoreException(CoreMessages.objectIsNull("id"));
48          }
49  
50          synchronized (store)
51          {
52              return store.values().contains(new StoredObject<T>(key, null));
53          }
54      }
55  
56      /**
57       * {@inheritDoc}
58       */
59      public void store(Serializable id, T value) throws ObjectStoreException
60      {
61          if (id == null)
62          {
63              throw new ObjectStoreException(CoreMessages.objectIsNull("id"));
64          }
65  
66          // this block is unfortunately necessary to counter a possible race condition
67          // between multiple nonatomic calls to containsObject/storeObject
68          StoredObject<T> obj = new StoredObject<T>(id, value);
69          synchronized (store)
70          {
71              if (store.values().contains(obj))
72              {
73                  throw new ObjectAlreadyExistsException();
74              }
75  
76              boolean written = false;
77              while (!written)
78              {
79                  Long key = Long.valueOf(System.nanoTime());
80                  written = (store.put(key, obj) == null);
81              }
82          }
83      }
84  
85      /**
86       * {@inheritDoc}
87       */
88      @SuppressWarnings("unchecked")
89      public T retrieve(Serializable key) throws ObjectStoreException
90      {
91          synchronized (store)
92          {
93              Map.Entry<?, ?> entry = findEntry(key);
94              if (entry != null)
95              {
96                  StoredObject object = (StoredObject) entry.getValue();
97                  return (T)object.getItem();
98              }
99          }
100         
101         throw new ObjectDoesNotExistException(CoreMessages.objectNotFound(key));
102     }
103 
104     @SuppressWarnings("unchecked")
105     private Map.Entry<?, ?> findEntry(Serializable key)
106     {
107         Iterator<?> entryIterator = store.entrySet().iterator();
108         while (entryIterator.hasNext())
109         {
110             Map.Entry<?, ?> entry = (Map.Entry<?, ?>) entryIterator.next();
111             
112             StoredObject object = (StoredObject) entry.getValue();
113             if (object.getId().equals(key))
114             {
115                 return entry;
116             }
117         }
118         return null;
119     }
120 
121     @SuppressWarnings("unchecked")
122     public T remove(Serializable key) throws ObjectStoreException
123     {
124         synchronized (store)
125         {
126             Map.Entry<?, ?> entry = findEntry(key);
127             if (entry != null)
128             {
129                 StoredObject removedObject = (StoredObject) store.remove(entry.getKey());
130                 return (T)removedObject.getItem();
131             }
132         }
133         
134         throw new ObjectDoesNotExistException(CoreMessages.objectNotFound(key));
135     }
136     
137     @Override
138     public void expire()
139     {
140         // this is not guaranteed to be precise, but we don't mind
141         int currentSize = store.size();
142         
143         // first trim to maxSize if necessary
144         currentSize = trimToMaxSize(currentSize);
145 
146         // expire further if entry TTLs are enabled
147         if ((entryTTL > 0) && (currentSize != 0))
148         {
149             final long now = System.nanoTime();
150             int expiredEntries = 0;
151             Map.Entry<?, ?> oldestEntry;
152 
153             purge:
154             while ((oldestEntry = store.firstEntry()) != null)
155             {
156                 Long oldestKey = (Long) oldestEntry.getKey();
157                 long oldestKeyValue = oldestKey.longValue();
158 
159                 if (TimeUnit.NANOSECONDS.toMillis(now - oldestKeyValue) >= entryTTL)
160                 {
161                     store.remove(oldestKey);
162                     expiredEntries++;
163                 }
164                 else
165                 {
166                     break purge;
167                 }
168             }
169 
170             if (logger.isDebugEnabled())
171             {
172                 logger.debug("Expired " + expiredEntries + " old entries");
173             }
174         }
175     }
176 
177     private int trimToMaxSize(int currentSize)
178     {
179         if (maxEntries < 0)
180         {
181             return currentSize;
182         }
183         
184         int excess = (currentSize - maxEntries);
185         if (excess > 0)
186         {
187             while (currentSize > maxEntries)
188             {
189                 store.pollFirstEntry();
190                 currentSize--;
191             }
192 
193             if (logger.isDebugEnabled())
194             {
195                 logger.debug("Expired " + excess + " excess entries");
196             }
197         }
198         return currentSize;
199     }
200 
201     @Override
202     public String toString()
203     {
204         return getClass().getSimpleName() + " " + store;
205     }
206 
207     /**
208      * Represents the object stored in the store. This class holds the Object itslef and its ID.
209      */
210     protected static class StoredObject<T>
211     {
212         private Serializable id;
213         private T item;
214 
215         public StoredObject(Serializable id, T item)
216         {
217             this.id = id;
218             this.item = item;
219         }
220 
221         public Serializable getId()
222         {
223             return id;
224         }
225 
226         public T getItem()
227         {
228             return item;
229         }
230 
231         @Override
232         @SuppressWarnings("unchecked")
233         public boolean equals(Object o)
234         {
235             if (this == o)
236             {
237                 return true;
238             }
239             if (o == null || getClass() != o.getClass())
240             {
241                 return false;
242             }
243 
244             StoredObject that = (StoredObject) o;
245 
246             if (!id.equals(that.id))
247             {
248                 return false;
249             }
250 
251             return true;
252         }
253 
254         @Override
255         public int hashCode()
256         {
257             return id.hashCode();
258         }
259 
260         @Override
261         public String toString()
262         {
263             final StringBuffer sb = new StringBuffer();
264             sb.append("StoredObject");
265             sb.append("{id='").append(id).append('\'');
266             sb.append(", item=").append(item);
267             sb.append('}');
268             return sb.toString();
269         }
270     }
271 }