View Javadoc
1   /**
2    *    Copyright 2009-2015 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.apache.ibatis.cache.decorators;
17  
18  import java.util.concurrent.ConcurrentHashMap;
19  import java.util.concurrent.TimeUnit;
20  import java.util.concurrent.locks.Lock;
21  import java.util.concurrent.locks.ReadWriteLock;
22  import java.util.concurrent.locks.ReentrantLock;
23  
24  import org.apache.ibatis.cache.Cache;
25  import org.apache.ibatis.cache.CacheException;
26  
27  /**
28   * Simple blocking decorator 
29   * 
30   * Simple and inefficient version of EhCache's BlockingCache decorator.
31   * It sets a lock over a cache key when the element is not found in cache.
32   * This way, other threads will wait until this element is filled instead of hitting the database.
33   * 
34   * @author Eduardo Macarron
35   *
36   */
37  public class BlockingCache implements Cache {
38  
39    private long timeout;
40    private final Cache delegate;
41    private final ConcurrentHashMap<Object, ReentrantLock> locks;
42  
43    public BlockingCache(Cache delegate) {
44      this.delegate = delegate;
45      this.locks = new ConcurrentHashMap<Object, ReentrantLock>();
46    }
47  
48    @Override
49    public String getId() {
50      return delegate.getId();
51    }
52  
53    @Override
54    public int getSize() {
55      return delegate.getSize();
56    }
57  
58    @Override
59    public void putObject(Object key, Object value) {
60      try {
61        delegate.putObject(key, value);
62      } finally {
63        releaseLock(key);
64      }
65    }
66  
67    @Override
68    public Object getObject(Object key) {
69      acquireLock(key);
70      Object value = delegate.getObject(key);
71      if (value != null) {
72        releaseLock(key);
73      }        
74      return value;
75    }
76  
77    @Override
78    public Object removeObject(Object key) {
79      // despite of its name, this method is called only to release locks
80      releaseLock(key);
81      return null;
82    }
83  
84    @Override
85    public void clear() {
86      delegate.clear();
87    }
88  
89    @Override
90    public ReadWriteLock getReadWriteLock() {
91      return null;
92    }
93    
94    private ReentrantLock getLockForKey(Object key) {
95      ReentrantLock lock = new ReentrantLock();
96      ReentrantLock previous = locks.putIfAbsent(key, lock);
97      return previous == null ? lock : previous;
98    }
99    
100   private void acquireLock(Object key) {
101     Lock lock = getLockForKey(key);
102     if (timeout > 0) {
103       try {
104         boolean acquired = lock.tryLock(timeout, TimeUnit.MILLISECONDS);
105         if (!acquired) {
106           throw new CacheException("Couldn't get a lock in " + timeout + " for the key " +  key + " at the cache " + delegate.getId());  
107         }
108       } catch (InterruptedException e) {
109         throw new CacheException("Got interrupted while trying to acquire lock for key " + key, e);
110       }
111     } else {
112       lock.lock();
113     }
114   }
115   
116   private void releaseLock(Object key) {
117     ReentrantLock lock = locks.get(key);
118     if (lock.isHeldByCurrentThread()) {
119       lock.unlock();
120     }
121   }
122 
123   public long getTimeout() {
124     return timeout;
125   }
126 
127   public void setTimeout(long timeout) {
128     this.timeout = timeout;
129   }  
130 }