View Javadoc

1   /*
2    * $Id: MonitoredObjectStoreWrapper.java 22665 2011-08-15 06:33:46Z 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  
11  package org.mule.util.store;
12  
13  import org.mule.api.DefaultMuleException;
14  import org.mule.api.MuleContext;
15  import org.mule.api.MuleException;
16  import org.mule.api.config.MuleProperties;
17  import org.mule.api.context.MuleContextAware;
18  import org.mule.api.lifecycle.Disposable;
19  import org.mule.api.lifecycle.Initialisable;
20  import org.mule.api.lifecycle.InitialisationException;
21  import org.mule.api.store.ListableObjectStore;
22  import org.mule.api.store.ObjectStoreException;
23  import org.mule.config.i18n.CoreMessages;
24  import org.mule.util.UUID;
25  import org.mule.util.concurrent.DaemonThreadFactory;
26  
27  import java.io.Serializable;
28  import java.util.Comparator;
29  import java.util.List;
30  import java.util.PriorityQueue;
31  import java.util.concurrent.ScheduledThreadPoolExecutor;
32  import java.util.concurrent.TimeUnit;
33  
34  import org.apache.commons.logging.Log;
35  import org.apache.commons.logging.LogFactory;
36  
37  /**
38   * The MonitoredObjectStoreWrapper wraps an ObjectStore which does not support direct
39   * expiry and adds this behavior
40   */
41  public class MonitoredObjectStoreWrapper<T extends Serializable>
42      implements ListableObjectStore<T>, Runnable, MuleContextAware, Initialisable, Disposable
43  {
44      protected MuleContext context;
45      protected ScheduledThreadPoolExecutor scheduler;
46      ListableObjectStore<StoredObject<T>> baseStore;
47      private static Log logger = LogFactory.getLog(MonitoredObjectStoreWrapper.class);
48  
49      /**
50       * the maximum number of entries that this store keeps around. Specify
51       * <em>-1</em> if the store is supposed to be "unbounded".
52       */
53      protected int maxEntries = 4000;
54  
55      /**
56       * The time-to-live for each message ID, specified in milliseconds, or
57       * <em>-1</em> for entries that should never expire. <b>DO NOT</b> combine this
58       * with an unbounded store!
59       */
60      protected int entryTTL = -1;
61  
62      /**
63       * The interval for periodic bounded size enforcement and entry expiration,
64       * specified in milliseconds. Arbitrary positive values between 1 millisecond and
65       * several hours or days are possible, but should be chosen carefully according
66       * to the expected message rate to prevent out of memory conditions.
67       */
68      protected int expirationInterval = 1000;
69  
70      /**
71       * A name for this store, can be used for logging and identification purposes.
72       */
73      protected String name = null;
74  
75      public MonitoredObjectStoreWrapper(ListableObjectStore<StoredObject<T>> baseStore)
76      {
77          this.baseStore = baseStore;
78      }
79  
80      public MonitoredObjectStoreWrapper(ListableObjectStore<StoredObject<T>> baseStore,
81                                         int maxEntries,
82                                         int entryTTL,
83                                         int expirationInterval)
84      {
85          this.baseStore = baseStore;
86          this.maxEntries = maxEntries;
87          this.entryTTL = entryTTL;
88          this.expirationInterval = expirationInterval;
89      }
90  
91      @Override
92      public boolean contains(Serializable key) throws ObjectStoreException
93      {
94          return getStore().contains(key);
95      }
96  
97      @Override
98      public void store(Serializable key, T value) throws ObjectStoreException
99      {
100         Long time = Long.valueOf(System.nanoTime());
101         getStore().store(key, new StoredObject<T>(value, time, key));
102     }
103 
104     @Override
105     public T retrieve(Serializable key) throws ObjectStoreException
106     {
107         return getStore().retrieve(key).getItem();
108     }
109 
110     @Override
111     public T remove(Serializable key) throws ObjectStoreException
112     {
113         StoredObject<T> object = getStore().remove(key);
114         if (object == null)
115         {
116             return null;
117         }
118         else
119         {
120             return object.getItem();
121         }
122     }
123 
124     @Override
125     public boolean isPersistent()
126     {
127         return getStore().isPersistent();
128     }
129 
130     @Override
131     public void open() throws ObjectStoreException
132     {
133         getStore().open();
134     }
135 
136     @Override
137     public void close() throws ObjectStoreException
138     {
139         getStore().close();
140     }
141 
142     @Override
143     public List<Serializable> allKeys() throws ObjectStoreException
144     {
145         return getStore().allKeys();
146     }
147 
148     private ListableObjectStore<StoredObject<T>> getStore()
149     {
150         if (baseStore == null)
151         {
152             baseStore = context.getRegistry().lookupObject(
153                 MuleProperties.OBJECT_STORE_DEFAULT_PERSISTENT_NAME);
154         }
155         return baseStore;
156     }
157 
158     @Override
159     public void setMuleContext(MuleContext context)
160     {
161         this.context = context;
162     }
163 
164     @Override
165     public void run()
166     {
167         if (context.isPrimaryPollingInstance())
168         {
169             expire();
170         }
171     }
172 
173     public void expire()
174     {
175         try
176         {
177             final long now = System.nanoTime();
178             List<Serializable> keys = allKeys();
179             int excess = (allKeys().size() - maxEntries);
180             if (maxEntries > 0 && excess > 0)
181             {
182                 PriorityQueue<StoredObject<T>> q = new PriorityQueue<StoredObject<T>>(excess,
183                     new Comparator<StoredObject<T>>()
184                     {
185 
186                         @Override
187                         public int compare(StoredObject<T> paramT1, StoredObject<T> paramT2)
188                         {
189                             return paramT2.timestamp.compareTo(paramT1.timestamp);
190                         }
191                     });
192                 long youngest = Long.MAX_VALUE;
193                 for (Serializable key : keys)
194                 {
195                     StoredObject<T> obj = getStore().retrieve(key);
196                     //TODO extract the entryTTL>0 outside of loop
197                     if (entryTTL>0 && TimeUnit.NANOSECONDS.toMillis(now - obj.getTimestamp()) >= entryTTL)
198                     {
199                         remove(key);
200                         excess--;
201                         if (excess > 0 && q.size() > excess)
202                         {
203                             q.poll();
204                             youngest = q.peek().timestamp;
205                         }
206                     }
207                     else
208                     {
209                         if (excess > 0 && (q.size() < excess || obj.timestamp < youngest))
210                         {
211                             q.offer(obj);
212                             youngest = q.peek().timestamp;
213                         }
214                         if (excess > 0 && q.size() > excess)
215                         {
216                             q.poll();
217                             youngest = q.peek().timestamp;
218                         }
219 
220                     }
221                 }
222                 for (int i = 0; i < excess; i++)
223                 {
224                     Serializable key = q.poll().key;
225                     remove(key);
226                 }
227             }
228             else
229             {
230                 if(entryTTL>0)
231                 {
232                     for (Serializable key : keys)
233                     {
234                         StoredObject<T> obj = getStore().retrieve(key);
235                         if (TimeUnit.NANOSECONDS.toMillis(now - obj.getTimestamp()) >= entryTTL)
236                         {
237                             remove(key);
238                         }
239                     }
240                 }
241             }
242         }
243         catch (Exception e)
244         {
245             logger.warn("Running expirty on " + baseStore + " threw " + e + ":" + e.getMessage());
246         }
247     }
248 
249     @Override
250     public void dispose()
251     {
252         if (scheduler != null)
253         {
254             scheduler.shutdown();
255         }
256     }
257 
258     @Override
259     public void initialise() throws InitialisationException
260     {
261         if (name == null)
262         {
263             name = UUID.getUUID();
264         }
265 
266         if (expirationInterval <= 0)
267         {
268             throw new IllegalArgumentException(CoreMessages.propertyHasInvalidValue("expirationInterval",
269                 new Integer(expirationInterval)).toString());
270         }
271 
272         if (scheduler == null)
273         {
274             this.scheduler = new ScheduledThreadPoolExecutor(1);
275             scheduler.setThreadFactory(new DaemonThreadFactory(name + "-Monitor", this.getClass()
276                 .getClassLoader()));
277             scheduler.scheduleWithFixedDelay(this, 0, expirationInterval, TimeUnit.MILLISECONDS);
278         }
279     }
280 
281     protected static class StoredObject<T> implements Serializable, DeserializationPostInitialisable
282     {
283         private static final long serialVersionUID = 8656763235928199259L;
284         final private T item;
285         final private Long timestamp;
286         final private Serializable key;
287 
288         public StoredObject(T item, Long timestamp, Serializable key)
289         {
290             super();
291             this.item = item;
292             this.timestamp = timestamp;
293             this.key = key;
294         }
295 
296         public T getItem()
297         {
298             return item;
299         }
300 
301         public Long getTimestamp()
302         {
303             return timestamp;
304         }
305 
306         public Serializable getKey()
307         {
308             return key;
309         }
310 
311         /**
312          * Invoked after deserialization. This is called when the marker interface
313          * {@link org.mule.util.store.DeserializationPostInitialisable} is used. This will get invoked after the
314          * object has been deserialized passing in the current MuleContext when using either
315          * {@link org.mule.transformer.wire.SerializationWireFormat},
316          * {@link org.mule.transformer.wire.SerializedMuleMessageWireFormat} or the
317          * {@link org.mule.transformer.simple.ByteArrayToSerializable} transformer.
318          *
319          * @param muleContext the current muleContext instance
320          * @throws org.mule.api.MuleException if there is an error initializing
321          */
322         @SuppressWarnings({"unused", "unchecked"})
323         private void initAfterDeserialisation(MuleContext muleContext) throws MuleException
324         {
325             if (item instanceof DeserializationPostInitialisable)
326             {
327                 try
328                 {
329                     DeserializationPostInitialisable.Implementation.init(item, muleContext);
330                 }
331                 catch (Exception e)
332                 {
333                     throw new DefaultMuleException(e);
334                 }
335             }
336         }
337     }
338 
339 }