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