View Javadoc

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             // create nested exception to preserve all stack traces
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         // lets always instantiate new objects to avoid
144         // updating existing transaction logs
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         // lets return the old entry if we've done updates to the data
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             // TODO lets guess though this could be wrong
241             // as it does not take into account updates
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         // TODO
276         throw createUnsupportedException();
277     }
278 
279     public Set keySet() {
280         // TODO
281         throw createUnsupportedException();
282     }
283 
284     public Collection values() {
285         // TODO
286         throw createUnsupportedException();
287     }
288 
289     // Implementation methods
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         // lets maintain order so changes are made in the correct order
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