001    /*
002     @license.text@ 
003     */
004    package biz.hammurapi.cache;
005    
006    import java.lang.ref.SoftReference;
007    import java.util.HashMap;
008    import java.util.HashSet;
009    import java.util.Iterator;
010    import java.util.Map;
011    import java.util.Set;
012    
013    import biz.hammurapi.config.ConfigurationException;
014    import biz.hammurapi.util.Acceptor;
015    
016    
017    /**
018     * Memory sensitive cache. Uses soft references to cache objects.
019     * Doesn't create helper threads - housekeepeng happens on every 'cleanupInterval' get() invocation.
020     * @author Pavel Vlasov
021     * @version $Revision: 1.3 $
022     */
023    public class PassiveMemoryCache extends AbstractProducer implements Cache {
024            private Map cache=new HashMap();
025            private Producer producer;
026            private Cache fallBack;
027            private int cleanupInterval;
028        private int cleanupCounter;
029            
030            private class CacheReference extends SoftReference {
031                    private long expires;
032                    private long time;
033    
034                    /**
035                     * @param referent
036                     * @param q
037                     * @param expires
038                     */
039                    public CacheReference(Object value, long time, long expires) {
040                            super(value);
041                            this.expires=expires;
042                            this.time=time;
043                    }               
044            }
045            
046            /**
047             * Used as entry if fallBack cache is used.
048             * @author Pavel Vlasov
049             * @version $Revision: 1.3 $
050             */
051            private class FallBackEntry {
052                    Object value;
053                    private long expires;
054                    private long time;
055                    private Object key;
056                    
057                    FallBackEntry(Object key, Object value, long time, long expires) {
058                            this.key=key;
059                            this.value=value;
060                            this.expires=expires;
061                            this.time=time;
062                    }
063                    
064                    protected void finalize() throws Throwable {
065                            if (fallBack.isActive() && (expires<=0 || System.currentTimeMillis()<expires)) {
066                                    fallBack.put(key, value, time, expires);
067                            }
068                            
069                            super.finalize();
070                    }               
071            }
072    
073            private void cleanup() {
074                    long now=System.currentTimeMillis();
075                    synchronized (cache) {
076                            Iterator it=cache.entrySet().iterator();
077                            while (it.hasNext()) {
078                                    Map.Entry entry=(Map.Entry) it.next();
079                                    CacheReference cacheReference = (CacheReference) entry.getValue();
080                                    if (cacheReference.get()==null || (cacheReference.expires>0 && cacheReference.expires<now)) {
081                                            it.remove();
082                                    }
083                            }
084                    }
085                    cleanupCounter=0;
086            }
087    
088        private void checkShutdown() {
089            if (shutDown) {
090                    throw new IllegalStateException("Shut down");
091            }
092        }
093            
094            /**
095             * 
096             */
097            public PassiveMemoryCache(Producer producer, Cache fallBack, int cleanupInterval) {
098                    super();
099                    
100                    this.producer=producer;
101                    if (producer!=null) {
102                            producer.addCache(this);
103                    }
104                    
105                    this.fallBack=fallBack;
106                    if (fallBack!=null) {
107                            addCache(fallBack);
108                    }
109                    
110                    this.cleanupInterval=cleanupInterval;
111            }
112            
113            public void put(Object key, Object value, long time, long expirationTime) {
114                    checkShutdown();
115                    synchronized (cache) {
116                            cache.put(key, new CacheReference(toValue(key, value, time, expirationTime), time, expirationTime));
117                    }
118            }
119    
120            /**
121             * @param key
122             * @param value
123             * @param expirationTime
124             * @return
125             */
126            private Object toValue(Object key, Object value, long time, long expirationTime) {
127                    return fallBack==null ? value : new FallBackEntry(key, value, time, expirationTime);
128            }
129            
130            private Object fromValue(Object object) {
131                    if (object instanceof FallBackEntry) {
132                            return ((FallBackEntry) object).value;
133                    }
134                    return object;
135            }
136            
137            
138    
139            public Entry get(Object key) {      
140                    checkShutdown();
141                    if (cleanupInterval>0 && cleanupCounter++ > cleanupInterval) {
142                        cleanup();
143                    }
144                    
145                    synchronized (cache) {
146                            CacheReference cr=(CacheReference) cache.get(key);
147                            long now = System.currentTimeMillis();
148                            boolean doFallBack=true;
149                            if (cr!=null) {
150                                    if ((cr.expires>0 && cr.expires<now) || cr.get()==null) {
151                                            cache.remove(key);              
152                                            doFallBack=false;
153                                    } else {
154                                            final Object o=fromValue(cr.get());
155                                            final long et=cr.expires;
156                                            final long time=cr.time;
157                                            return new Entry() {
158    
159                                                    public long getExpirationTime() {
160                                                            return et;
161                                                    }
162    
163                                                    public long getTime() {
164                                                            return time;
165                                                    }
166    
167                                                    public Object get() {
168                                                            return o;
169                                                    }
170                                                    
171                                            };
172                                    }
173                            } 
174                            
175                            if (doFallBack && fallBack!=null) {
176                                    Entry entry = fallBack.get(key);
177                                    if (entry!=null) {
178                                            cache.put(key, new CacheReference(toValue(key, entry.get(), entry.getTime(), entry.getExpirationTime()), entry.getTime(), entry.getExpirationTime()));
179                                            return entry;
180                                    }
181                            }
182                            
183                            if (key instanceof ProducingKey) {
184                                    Entry entry = ((ProducingKey) key).get();
185                                    cache.put(key, new CacheReference(toValue(key, entry.get(), entry.getTime(), entry.getExpirationTime()), entry.getTime(), entry.getExpirationTime()));
186                                    return entry;
187                            }
188                            
189                            if (producer!=null) {
190                                    Entry entry = producer.get(key);
191                                    cache.put(key, new CacheReference(toValue(key, entry.get(), entry.getTime(), entry.getExpirationTime()), entry.getTime(), entry.getExpirationTime()));
192                                    return entry;
193                            }
194                            
195                            return null;
196                    }
197            }
198            
199            public void clear() {
200                    synchronized (cache) {
201                            cache.clear();
202                            
203                            if (fallBack!=null) {
204                                    fallBack.clear();
205                            }
206                    }
207            }
208            
209            public void remove(Object key) {
210                    synchronized (cache) {
211                            cache.remove(key);
212                    }
213                    onRemove(key);
214            }
215    
216            public void remove(Acceptor acceptor) {
217                    synchronized (cache) {
218                            Iterator it=cache.keySet().iterator();
219                            while (it.hasNext()) {
220                                    Object key=it.next();
221                                    if (acceptor.accept(key)) {
222                                            it.remove();
223                                            onRemove(key);
224                                    }
225                            }
226                    }
227            }
228            
229            private boolean shutDown=false;
230            
231            public void stop() {
232                    if (!shutDown) {                        
233                            cache.clear();
234                            shutDown=true;
235                    }
236            }
237            
238            protected void finalize() throws Throwable {
239                    if (isActive()) {
240                            stop();
241                    }
242                    super.finalize();
243            }
244    
245            public Set keySet() {
246                    HashSet ret = new HashSet();
247                    synchronized (cache) {
248                            ret.addAll(cache.keySet());
249                    }
250                    
251                    if (producer!=null) {
252                            Set pkeys=producer.keySet();
253                            if (pkeys!=null) {
254                                    ret.addAll(pkeys);
255                            }
256                    }
257                    
258                    if (fallBack!=null) {
259                            Set fkeys=fallBack.keySet();
260                            if (fkeys!=null) {
261                                    ret.addAll(fkeys);
262                            }
263                    }
264                    
265                    return ret;
266            }
267    
268            /* (non-Javadoc)
269             * @see biz.hammurapi.cache.Cache#isActive()
270             */
271            public boolean isActive() {
272                    return !shutDown;
273            }
274    
275            public void start() throws ConfigurationException {
276                    // TODO Nothing
277                    
278            }
279    
280            public void setOwner(Object owner) {
281                    // TODO Nothing
282                    
283            }
284    }