1 /***
2 *
3 * Copyright 2004 Protique Ltd
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 **/
18 package org.codehaus.activespace.cache.impl;
19
20 import org.apache.commons.logging.Log;
21 import org.apache.commons.logging.LogFactory;
22 import org.codehaus.activespace.cache.TransactionException;
23
24 import javax.cache.Cache;
25 import javax.cache.CacheEntry;
26 import javax.cache.CacheException;
27 import java.util.Collection;
28 import java.util.IdentityHashMap;
29 import java.util.Iterator;
30 import java.util.Map;
31 import java.util.Set;
32
33 /***
34 * This class is only used by one thread at any point in time and
35 * provides a thread local view of a transactional cache which implements
36 * the <i>READ_COMMITTED</i> isolation level in that there are no dirty reads
37 * and only committed data is read but that the data can change, during a transaction
38 * as concurrent transactions in other processes and threads commit.
39 *
40 * @version $Revision: 1.9 $
41 */
42 public class ThreadCache extends CacheFacade {
43 private static final Log log = LogFactory.getLog(ThreadCache.class);
44
45 private Cache backingCache;
46 private Map localChanges;
47 private Map localRemoves;
48 private boolean clear;
49 private String name;
50 private String ID;
51 private TransactionException commitException;
52
53 public ThreadCache(Cache behindCache) {
54 init(behindCache, createMap(), createMap());
55 }
56
57 public ThreadCache(Cache behindCache, Map localChanges, Map localRemoves) {
58 init(behindCache, localChanges, localRemoves);
59 }
60
61 private void init(Cache behindCache, Map localChanges, Map localRemoves) {
62 if (behindCache == null) {
63 throw new IllegalArgumentException("A NULL cache is not allowed");
64 }
65 this.backingCache = behindCache;
66 this.localChanges = localChanges;
67 this.localRemoves = localRemoves;
68 }
69
70 public String toString() {
71 Map map = (clear) ? new IdentityHashMap() : new IdentityHashMap(backingCache);
72 for (Iterator iter = localRemoves.keySet().iterator(); iter.hasNext();) {
73 map.remove(iter.next());
74 }
75 for (Iterator iter = localChanges.entrySet().iterator(); iter.hasNext();) {
76 Entry entry = (Entry) iter.next();
77 Object key = entry.getKey();
78 VersionedValue change = (VersionedValue) entry.getValue();
79 map.put(key, change.getValue());
80 }
81 return map.toString();
82 }
83
84 /***
85 * Returns the unique ID of this cache
86 */
87 public String getID() {
88 return ID;
89 }
90
91 public void setID(String ID) {
92 this.ID = ID;
93 }
94
95 /***
96 * Returns whether or not this cache has been modified in the transaction
97 */
98 public boolean isModifiedInTransaction() {
99 return clear || !localChanges.isEmpty() || !localRemoves.isEmpty();
100 }
101
102 public TransactionalCacheCommand createTransactionCommand() {
103 if (!clear && localChanges.isEmpty() && localRemoves.isEmpty()) {
104 return null;
105 }
106 return new TransactionalCacheCommand(getName(), localChanges, localRemoves, clear, ID);
107 }
108
109 public String getName() {
110 return name;
111 }
112
113 public void setName(String name) {
114 this.name = name;
115 }
116
117
118 /***
119 * Throws any runtime exceptions which occurred on a background
120 * thread due to the current transaction commit.
121 */
122 public void handleCommitException() {
123 RuntimeException e = commitException;
124 commitException = null;
125 if (e != null) {
126
127 throw new TransactionException(e);
128 }
129 }
130
131 /***
132 * Passes in a runtime exception from the cache updating thread
133 * if an exception should be passed to the caller thread
134 */
135 public void onCommitException(TransactionException e) {
136 this.commitException = e;
137 }
138
139 /***
140 * Called by the transaction manager after a commit or rollback
141 */
142 public void resetLocalChanges() {
143
144
145 localChanges = createMap();
146 localRemoves = createMap();
147 clear = false;
148 }
149
150 public void clear() {
151 resetLocalChanges();
152 clear = true;
153 }
154
155 public boolean containsKey(Object key) {
156 if (clear) {
157 return localChangesContainsKey(key);
158 }
159 return localChangesContainsKey(key) || (super.containsKey(key) && !localRemoves.containsKey(key));
160 }
161
162 public boolean containsValue(Object value) {
163 if (clear) {
164 return localChangesContainsValue(value);
165 }
166 else {
167 if (localChangesContainsValue(value)) {
168 return true;
169 }
170 else {
171 for (Iterator iter = getDelegate().entrySet().iterator(); iter.hasNext();) {
172 Entry entry = (Entry) iter.next();
173 if (entry.getValue().equals(value) && !localRemoves.containsKey(entry.getKey())) {
174 return true;
175 }
176 }
177 return false;
178 }
179 }
180 }
181
182 public Object get(Object key) {
183 if (clear) {
184 return getLocalChangeValue(key);
185 }
186 else {
187 Object answer = getLocalChangeValue(key);
188 if (answer == null && !localRemoves.containsKey(key)) {
189 answer = super.get(key);
190 }
191 return answer;
192 }
193 }
194
195 public Map getAll(Collection keys) throws CacheException {
196 Map answer = createMap();
197 for (Iterator iter = keys.iterator(); iter.hasNext();) {
198 Object key = iter.next();
199 answer.put(key, get(key));
200 }
201 return answer;
202 }
203
204
205 public CacheEntry getCacheEntry(Object key) {
206 if (clear || localRemoves.containsKey(key)) {
207 return null;
208 }
209
210 return super.getCacheEntry(key);
211 }
212
213 public boolean isEmpty() {
214 if (clear) {
215 return localChanges.isEmpty();
216 }
217 else {
218 return localChanges.isEmpty() && (super.isEmpty() || super.size() == localRemoves.size());
219 }
220 }
221
222 public Object peek(Object key) {
223 if (clear) {
224 return getLocalChangeValue(key);
225 }
226 else {
227 Object answer = getLocalChangeValue(key);
228 if (answer == null && !localRemoves.containsKey(key)) {
229 answer = super.peek(key);
230 }
231 return answer;
232 }
233 }
234
235 public int size() {
236 if (clear) {
237 return localChanges.size();
238 }
239 else {
240
241
242 return localChanges.size() + super.size() - localRemoves.size();
243 }
244 }
245
246 public void putAll(Map map) {
247 for (Iterator iter = map.entrySet().iterator(); iter.hasNext();) {
248 Entry entry = (Entry) iter.next();
249 localChangesPut(entry.getKey(), entry.getValue());
250 }
251 }
252
253 public Object put(Object key, Object value) {
254 Object answer = getLocalChangeValue(key);
255 if (answer == null) {
256 answer = getDelegate().get(key);
257 }
258 localChangesPut(key, value);
259 return answer;
260 }
261
262
263 public Object remove(Object key) {
264 if (localRemoves.containsKey(key)) {
265 return null;
266 }
267 else {
268 Object version = getVersion(key);
269 localRemove(key, version);
270 return getDelegate().get(key);
271 }
272 }
273
274 public Set entrySet() {
275
276 throw createUnsupportedException();
277 }
278
279 public Set keySet() {
280
281 throw createUnsupportedException();
282 }
283
284 public Collection values() {
285
286 throw createUnsupportedException();
287 }
288
289
290
291 protected Cache getDelegate() {
292 return backingCache;
293 }
294
295 protected void localChangesPut(Object key, Object value) {
296 localChanges.put(key, createVersionedValue(key, value));
297 }
298
299 protected boolean localChangesContainsKey(Object key) {
300 return localChanges.containsKey(key);
301 }
302
303 protected boolean localChangesContainsValue(Object value) {
304 for (Iterator iter = localChanges.values().iterator(); iter.hasNext();) {
305 VersionedValue change = (VersionedValue) iter.next();
306 if (value.equals(change.getValue())) {
307 return true;
308 }
309 }
310 return false;
311 }
312
313 protected void localRemove(Object key, Object version) {
314 localRemoves.put(key, version);
315 }
316
317 protected Object getLocalChangeValue(Object key) {
318 VersionedValue change = (VersionedValue) localChanges.get(key);
319 return (change != null) ? change.getValue() : null;
320 }
321
322 protected VersionedValue createVersionedValue(Object key, Object value) {
323 Object version = getVersion(key);
324 return new VersionedValue(value, version);
325 }
326
327 protected Object getVersion(Object key) {
328 return JCacheHelper.getEntryVersion(this, key);
329 }
330
331 protected Map createMap() {
332
333 return new IdentityHashMap();
334 }
335
336 protected UnsupportedOperationException createUnsupportedException() {
337 return new UnsupportedOperationException("This operation is not yet supported for the TransactionalCache");
338 }
339 }
340