1
2
3
4
5
6
7
8
9
10
11 package org.mule.routing.inbound;
12
13 import org.mule.config.i18n.CoreMessages;
14 import org.mule.util.concurrent.DaemonThreadFactory;
15
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.ScheduledThreadPoolExecutor;
20 import edu.emory.mathcs.backport.java.util.concurrent.TimeUnit;
21 import edu.emory.mathcs.backport.java.util.concurrent.helpers.Utils;
22
23 import org.apache.commons.logging.Log;
24 import org.apache.commons.logging.LogFactory;
25
26
27
28
29
30
31
32
33 public class IdempotentInMemoryMessageIdStore implements IdempotentMessageIdStore
34 {
35 protected final Log logger = LogFactory.getLog(this.getClass());
36 protected final ConcurrentSkipListMap store;
37 protected final ScheduledThreadPoolExecutor scheduler;
38 protected final int maxEntries;
39 protected final int entryTTL;
40 protected final int expirationInterval;
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61 public IdempotentInMemoryMessageIdStore(String name, int maxEntries, int entryTTL, int expirationInterval)
62 {
63 super();
64 this.store = new ConcurrentSkipListMap();
65 this.maxEntries = (maxEntries >= 0 ? maxEntries : Integer.MAX_VALUE);
66 this.entryTTL = entryTTL;
67
68 if (expirationInterval <= 0)
69 {
70 throw new IllegalArgumentException(CoreMessages.propertyHasInvalidValue("expirationInterval",
71 new Integer(expirationInterval)).toString());
72 }
73
74 this.expirationInterval = expirationInterval;
75
76 this.scheduler = new ScheduledThreadPoolExecutor(1);
77 scheduler.setThreadFactory(new DaemonThreadFactory(name + "-IdempotentMessageIdStore"));
78 scheduler.scheduleWithFixedDelay(new Expirer(), this.expirationInterval, this.expirationInterval,
79 TimeUnit.SECONDS);
80 }
81
82 public boolean containsId(Object id) throws IllegalArgumentException, Exception
83 {
84 if (id == null)
85 {
86 throw new IllegalArgumentException(CoreMessages.objectIsNull("id").toString());
87 }
88
89
90 return store.values().contains(id);
91 }
92
93 public boolean storeId(Object id) throws IllegalArgumentException, Exception
94 {
95 if (id == null)
96 {
97 throw new IllegalArgumentException(CoreMessages.objectIsNull("id").toString());
98 }
99
100
101
102
103
104 synchronized (store)
105 {
106 if (store.values().contains(id))
107 {
108 return false;
109 }
110
111 boolean written = false;
112 while (!written)
113 {
114 written = (store.putIfAbsent(new Long(Utils.nanoTime()), id) == null);
115 }
116
117 return true;
118 }
119 }
120
121 protected void expire()
122 {
123
124 int currentSize = store.size();
125
126
127 int excess = (currentSize - maxEntries);
128 if (excess > 0)
129 {
130 while (currentSize > maxEntries)
131 {
132 store.pollFirstEntry();
133 currentSize--;
134 }
135
136 if (logger.isDebugEnabled())
137 {
138 logger.debug("Expired " + excess + " excess entries");
139 }
140 }
141
142
143 if (entryTTL > 0 && currentSize != 0)
144 {
145 final long now = Utils.nanoTime();
146 int expiredEntries = 0;
147 Map.Entry oldestEntry;
148
149 purge : while ((oldestEntry = store.firstEntry()) != null)
150 {
151 Long oldestKey = (Long) oldestEntry.getKey();
152 long oldestKeyValue = oldestKey.longValue();
153
154 if (TimeUnit.NANOSECONDS.toSeconds(now - oldestKeyValue) >= entryTTL)
155 {
156 store.remove(oldestKey);
157 expiredEntries++;
158 }
159 else
160 {
161 break purge;
162 }
163 }
164
165 if (logger.isDebugEnabled())
166 {
167 logger.debug("Expired " + expiredEntries + " old entries");
168 }
169 }
170 }
171
172 protected class Expirer implements Runnable
173 {
174 public void run()
175 {
176 try
177 {
178
179 IdempotentInMemoryMessageIdStore.this.expire();
180 }
181 catch (Exception ex)
182 {
183
184 logger.error(ex.getMessage(), ex);
185 }
186 }
187 }
188
189 }