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