View Javadoc

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