1 /***
2 * Copyright 2003-2010 Terracotta, Inc.
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 net.sf.ehcache.hibernate.ccs;
17
18 import java.io.Serializable;
19
20 import java.util.Comparator;
21 import java.util.UUID;
22 import java.util.concurrent.atomic.AtomicLong;
23 import java.util.concurrent.locks.ReentrantReadWriteLock;
24 import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
25 import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
26
27 import org.hibernate.cache.CacheException;
28 import org.hibernate.cache.access.SoftLock;
29
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
32
33 /***
34 * Ehcache specific read/write cache concurrency strategy.
35 * <p>
36 * This is the Ehcache specific equivalent to Hibernate's ReadWriteCache. This implementation uses a more robust soft-lock system (less
37 * prone to accidental collisions).
38 *
39 * @author Chris Dennis
40 */
41 @Deprecated
42 public class EhcacheReadWriteCache extends AbstractEhcacheConcurrencyStrategy {
43
44 private static final Logger LOG = LoggerFactory.getLogger(EhcacheReadWriteCache.class);
45
46 private final UUID uuid = UUID.randomUUID();
47 private final AtomicLong nextLockId = new AtomicLong();
48 private final ReadLock coarseReadLock;
49 private final WriteLock coarseWriteLock;
50 {
51 ReentrantReadWriteLock coarseLock = new ReentrantReadWriteLock();
52 coarseReadLock = coarseLock.readLock();
53 coarseWriteLock = coarseLock.writeLock();
54 }
55
56 /***
57 * {@inheritDoc}
58 */
59 public Object get(Object key, long txTimestamp) throws CacheException {
60 readLockIfCoarse(key);
61 try {
62 Lockable item = (Lockable) cache.get(key);
63
64 boolean readable = item != null && item.isReadable(txTimestamp);
65 if (readable) {
66 return item.getValue();
67 } else {
68 return null;
69 }
70 } finally {
71 readUnlockIfCoarse(key);
72 }
73 }
74
75 /***
76 * {@inheritDoc}
77 */
78 public boolean put(Object key, Object value, long txTimestamp, Object version, Comparator versionComparator, boolean minimalPut)
79 throws CacheException {
80
81 writeLock(key);
82 try {
83 Lockable item = (Lockable) cache.get(key);
84 boolean writeable = item == null || item.isWriteable(txTimestamp, version, versionComparator);
85 if (writeable) {
86 cache.put(key, new Item(value, version, cache.nextTimestamp()));
87 return true;
88 } else {
89 return false;
90 }
91 } finally {
92 writeUnlock(key);
93 }
94 }
95
96 /***
97 * Soft-locks the associated mapping prior to updating/inserting a new value.
98 */
99 public SoftLock lock(Object key, Object version) throws CacheException {
100 writeLock(key);
101 try {
102 Lockable item = (Lockable) cache.get(key);
103 long timeout = cache.nextTimestamp() + cache.getTimeout();
104 final Lock lock = (item == null) ? new Lock(timeout, uuid, nextLockId(), version) : item.lock(timeout, uuid, nextLockId());
105 cache.update(key, lock);
106 return lock;
107 } finally {
108 writeUnlock(key);
109 }
110 }
111
112 /***
113 * Soft-unlocks the associated mapping.
114 */
115 public void release(Object key, SoftLock lock) throws CacheException {
116 writeLock(key);
117 try {
118 Lockable item = (Lockable) cache.get(key);
119
120 if ((item != null) && item.isUnlockable(lock)) {
121 decrementLock(key, (Lock) item);
122 } else {
123 handleLockExpiry(key);
124 }
125 } finally {
126 writeUnlock(key);
127 }
128 }
129
130 /***
131 * {@inheritDoc}
132 */
133 public boolean afterUpdate(Object key, Object value, Object version, SoftLock softlock) throws CacheException {
134 writeLock(key);
135 try {
136 Lockable item = (Lockable) cache.get(key);
137
138 if (item != null && item.isUnlockable(softlock)) {
139 Lock lock = (Lock) item;
140 if (lock.wasLockedConcurrently()) {
141 decrementLock(key, lock);
142 return false;
143 } else {
144 cache.update(key, new Item(value, version, cache.nextTimestamp()));
145 return true;
146 }
147 } else {
148 handleLockExpiry(key);
149 return false;
150 }
151 } finally {
152 writeUnlock(key);
153 }
154 }
155
156 /***
157 * {@inheritDoc}
158 */
159 public boolean afterInsert(Object key, Object value, Object version) throws CacheException {
160 writeLock(key);
161 try {
162 Lockable item = (Lockable) cache.get(key);
163 if (item == null) {
164 cache.update(key, new Item(value, version, cache.nextTimestamp()));
165 return true;
166 } else {
167 return false;
168 }
169 } finally {
170 writeUnlock(key);
171 }
172 }
173
174 /***
175 * A No-Op, since we are an asynchronous cache concurrency strategy.
176 */
177 public void evict(Object key) throws CacheException {
178
179 }
180
181 /***
182 * A No-Op, since we are an asynchronous cache concurrency strategy.
183 */
184 public boolean update(Object key, Object value, Object currentVersion, Object previousVersion) throws CacheException {
185 return false;
186 }
187
188 /***
189 * A No-Op, since we are an asynchronous cache concurrency strategy.
190 */
191 public boolean insert(Object key, Object value, Object currentVersion) throws CacheException {
192 return false;
193 }
194
195 private long nextLockId() {
196 return nextLockId.getAndIncrement();
197 }
198
199 private void decrementLock(Object key, Lock lock) {
200 lock.unlock(cache.nextTimestamp());
201 cache.update(key, lock);
202 }
203
204 private void handleLockExpiry(Object key) {
205 long ts = cache.nextTimestamp() + cache.getTimeout();
206
207 Lock lock = new Lock(ts, uuid, nextLockId.getAndIncrement(), null);
208 lock.unlock(ts);
209 cache.update(key, lock);
210 }
211
212 private void writeLock(Object key) {
213 if (cache.canLockEntries()) {
214 cache.lock(key);
215 } else {
216 coarseWriteLock.lock();
217 }
218 }
219
220 private void writeUnlock(Object key) {
221 if (cache.canLockEntries()) {
222 cache.unlock(key);
223 } else {
224 coarseWriteLock.unlock();
225 }
226 }
227
228 private void readLockIfCoarse(Object key) {
229 if (!cache.canLockEntries()) {
230 coarseReadLock.lock();
231 }
232 }
233
234 private void readUnlockIfCoarse(Object key) {
235 if (!cache.canLockEntries()) {
236 coarseReadLock.unlock();
237 }
238 }
239
240 /***
241 * Interface type implemented by all wrapper objects in the cache.
242 */
243 private static interface Lockable {
244
245 public boolean isReadable(long txTimestamp);
246
247 public boolean isWriteable(long txTimestamp, Object version, Comparator versionComparator);
248
249 public Object getValue();
250
251 public boolean isUnlockable(SoftLock lock);
252
253 public Lock lock(long timeout, UUID uuid, long lockId);
254 }
255
256 /***
257 * Wrapper type representing unlocked items.
258 */
259 private static final class Item implements Serializable, Lockable {
260
261 private static final long serialVersionUID = 1L;
262 private final Object value;
263 private final Object version;
264 private final long timestamp;
265
266 private Item(Object value, Object version, long timestamp) {
267 this.value = value;
268 this.version = version;
269 this.timestamp = timestamp;
270 }
271
272 public boolean isReadable(long txTimestamp) {
273 return txTimestamp > timestamp;
274 }
275
276 public boolean isWriteable(long txTimestamp, Object newVersion, Comparator versionComparator) {
277 return version != null && versionComparator.compare(version, newVersion) < 0;
278 }
279
280 public Object getValue() {
281 return value;
282 }
283
284 public boolean isUnlockable(SoftLock lock) {
285 return false;
286 }
287
288 public Lock lock(long timeout, UUID uuid, long lockId) {
289 return new Lock(timeout, uuid, lockId, version);
290 }
291 }
292
293 /***
294 * Wrapper type representing locked items.
295 */
296 private static final class Lock implements Serializable, Lockable, SoftLock {
297
298 private static final long serialVersionUID = 2L;
299
300 private final UUID sourceUuid;
301 private final long lockId;
302 private final Object version;
303
304 private long timeout;
305 private boolean concurrent;
306 private int multiplicity;
307 private long unlockTimestamp;
308
309 Lock(long timeout, UUID sourceUuid, long lockId, Object version) {
310 this.timeout = timeout;
311 this.lockId = lockId;
312 this.version = version;
313 this.sourceUuid = sourceUuid;
314 }
315
316 public boolean isReadable(long txTimestamp) {
317 return false;
318 }
319
320 public boolean isWriteable(long txTimestamp, Object newVersion, Comparator versionComparator) {
321 if (txTimestamp > timeout) {
322
323 return true;
324 }
325 if (multiplicity > 0) {
326
327 return false;
328 }
329 return version == null ? txTimestamp > unlockTimestamp : versionComparator.compare(version, newVersion) < 0;
330 }
331
332 public Object getValue() {
333 return null;
334 }
335
336 public boolean isUnlockable(SoftLock lock) {
337 return equals(lock);
338 }
339
340 @Override
341 public boolean equals(Object o) {
342 if (o == this) {
343 return true;
344 } else if (o instanceof Lock) {
345 return (lockId == ((Lock) o).lockId) && sourceUuid.equals(((Lock) o).sourceUuid);
346 } else {
347 return false;
348 }
349 }
350
351 @Override
352 public int hashCode() {
353 int hash = (sourceUuid != null ? sourceUuid.hashCode() : 0);
354 int temp = (int) lockId;
355 for (int i = 1; i < Long.SIZE / Integer.SIZE; i++) {
356 temp ^= (lockId >>> (i * Integer.SIZE));
357 }
358 return hash + temp;
359 }
360
361 private boolean wasLockedConcurrently() {
362 return concurrent;
363 }
364
365 public Lock lock(long timeout, UUID uuid, long lockId) {
366 concurrent = true;
367 multiplicity++;
368 this.timeout = timeout;
369 return this;
370 }
371
372 private void unlock(long timestamp) {
373 if (--multiplicity == 0) {
374 unlockTimestamp = timestamp;
375 }
376 }
377 }
378 }