(no commit message)
[utils] / support / general / src / main / java / org / wamblee / cache / CachedObject.java
1 /*
2  * Copyright 2005-2010 the original author or authors.
3  * 
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  * 
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  * 
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package org.wamblee.cache;
17
18 import java.io.Serializable;
19 import java.util.logging.Level;
20 import java.util.logging.Logger;
21
22 /**
23  * Represents a cached object identified by the key it has in a certain
24  * {@link Cache}. The object is either retrieved from the cache if the cache has
25  * it, or a call back is invoked to get the object (and put it in the cache).
26  * 
27  * @author Erik Brakkee
28  * 
29  */
30 public class CachedObject<KeyType extends Serializable, ValueType extends Serializable> {
31     private static final Logger LOGGER = Logger.getLogger(CachedObject.class
32         .getName());
33
34     /**
35      * Cache to use.
36      */
37     private Cache<KeyType, ValueType> cache;
38
39     /**
40      * Key of the object in the cache.
41      */
42     private KeyType objectKey;
43
44     /**
45      * Last known value.
46      */
47     private ValueType value;
48
49     /**
50      * Are we now computing the value or not?
51      */
52     private boolean computing;
53
54     /**
55      * Computation used to obtain the object if it is not found in the cache.
56      */
57     private Computation<KeyType, ValueType> computation;
58
59     /**
60      * Constructs the cached object.
61      * 
62      * @param aCache
63      *            Cache to use.
64      * @param aObjectKey
65      *            Key of the object in the cache.
66      * @param aComputation
67      *            Computation to get the object in case the object is not in the
68      *            cache.
69      */
70     public CachedObject(Cache<KeyType, ValueType> aCache, KeyType aObjectKey,
71         Computation<KeyType, ValueType> aComputation) {
72         cache = aCache;
73         objectKey = aObjectKey;
74         computation = aComputation;
75     }
76
77     /**
78      * Gets the object. Since the object is cached, different calls to this
79      * method may return different objects.
80      * 
81      * If the object is expired from the cache it is recomputed using the
82      * callback. In case the callback throws an exception the last known value
83      * is used. In case an exception is thrown, the problem is also logged. In
84      * case a recomputation is already being done by another thread, the last
85      * known value is immediately returned.
86      * 
87      * @return Object.
88      */
89     public ValueType get() {
90         synchronized (this) {
91             if (computing) {
92                 // always return old value while computing.
93                 return value;
94             }
95
96             ValueType cachedValue = cache.get(objectKey);
97             if (cachedValue == null) {
98                 // expired
99                 computing = true;
100             } else {
101                 // no change.
102                 return value;
103             }
104         }
105         try {
106
107             // we only get here if we are computing
108             // do the computation without holding the lock.
109             LOGGER.fine("Refreshing cache for '" + objectKey + "'");
110             ValueType object = computation.getObject(objectKey);
111             cache.put(objectKey, object);
112
113             synchronized (this) {
114                 value = object;
115             }
116         } catch (Exception e) {
117             LOGGER.log(Level.INFO,
118                 "Recomputation of cached item failed for key '" + objectKey +
119                     "'", e);
120         } finally {
121             synchronized (this) {
122                 computing = false;
123             }
124         }
125         synchronized (this) {
126             return value;
127         }
128     }
129
130     /**
131      * Invalidates the cache for the object so that it is recomputed the next
132      * time it is requested.
133      */
134     public void invalidate() {
135         cache.remove(objectKey);
136     }
137
138     /**
139      * Gets the cache.
140      * 
141      * @return Cache.
142      */
143     public Cache getCache() {
144         return cache;
145     }
146
147     /**
148      * Callback invoked to compute an object if it was not found in the cache.
149      * 
150      * @param <T>
151      *            Type of the object
152      */
153     public static interface Computation<Key extends Serializable, Value extends Serializable> {
154         /**
155          * Gets the object. Called when the object is not in the cache. In case
156          * computation fails, an exception should be thrown to ensure that the
157          * last known value will be used.
158          * 
159          * @param aObjectKey
160          *            Id of the object in the cache.
161          * 
162          * @return Object, must be non-null.
163          */
164         Value getObject(Key aObjectKey) throws Exception;
165     }
166 }