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.strategy;
17
18 import java.io.Serializable;
19 import java.util.Comparator;
20 import java.util.UUID;
21 import java.util.concurrent.atomic.AtomicLong;
22
23 import net.sf.ehcache.hibernate.regions.EhcacheTransactionalDataRegion;
24
25 import org.hibernate.cache.CacheException;
26 import org.hibernate.cache.access.SoftLock;
27 import org.hibernate.cfg.Settings;
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
30
31 /***
32 * Superclass for all Ehcache specific read/write AccessStrategy implementations.
33 *
34 * @param <T> the type of the enclosed cache region
35 *
36 * @author Chris Dennis
37 */
38 abstract class AbstractReadWriteEhcacheAccessStrategy<T extends EhcacheTransactionalDataRegion> extends AbstractEhcacheAccessStrategy<T> {
39
40 private static final Logger LOG = LoggerFactory.getLogger(AbstractReadWriteEhcacheAccessStrategy.class);
41
42 private final UUID uuid = UUID.randomUUID();
43 private final AtomicLong nextLockId = new AtomicLong();
44
45 private final Comparator versionComparator;
46
47 /***
48 * Creates a read/write cache access strategy around the given cache region.
49 */
50 public AbstractReadWriteEhcacheAccessStrategy(T region, Settings settings) {
51 super(region, settings);
52 this.versionComparator = region.getCacheDataDescription().getVersionComparator();
53 }
54
55 /***
56 * Returns <code>null</code> if the item is not readable. Locked items are not readable, nor are items created
57 * after the start of this transaction.
58 *
59 * @see org.hibernate.cache.access.EntityRegionAccessStrategy#get(java.lang.Object, long)
60 * @see org.hibernate.cache.access.CollectionRegionAccessStrategy#get(java.lang.Object, long)
61 */
62 public final Object get(Object key, long txTimestamp) throws CacheException {
63 readLockIfNeeded(key);
64 try {
65 Lockable item = (Lockable) region.get(key);
66
67 boolean readable = item != null && item.isReadable(txTimestamp);
68 if (readable) {
69 return item.getValue();
70 } else {
71 return null;
72 }
73 } finally {
74 readUnlockIfNeeded(key);
75 }
76 }
77
78 /***
79 * Returns <code>false</code> and fails to put the value if there is an existing un-writeable item mapped to this
80 * key.
81 *
82 * @see org.hibernate.cache.access.EntityRegionAccessStrategy#putFromLoad(java.lang.Object, java.lang.Object, long, java.lang.Object, boolean)
83 * @see org.hibernate.cache.access.CollectionRegionAccessStrategy#putFromLoad(java.lang.Object, java.lang.Object, long, java.lang.Object, boolean)
84 */
85 @Override
86 public final boolean putFromLoad(Object key, Object value, long txTimestamp, Object version, boolean minimalPutOverride)
87 throws CacheException {
88 region.writeLock(key);
89 try {
90 Lockable item = (Lockable) region.get(key);
91 boolean writeable = item == null || item.isWriteable(txTimestamp, version, versionComparator);
92 if (writeable) {
93 region.put(key, new Item(value, version, region.nextTimestamp()));
94 return true;
95 } else {
96 return false;
97 }
98 } finally {
99 region.writeUnlock(key);
100 }
101 }
102
103 /***
104 * Soft-lock a cache item.
105 *
106 * @see org.hibernate.cache.access.EntityRegionAccessStrategy#lockItem(java.lang.Object, java.lang.Object)
107 * @see org.hibernate.cache.access.CollectionRegionAccessStrategy#lockItem(java.lang.Object, java.lang.Object)
108 */
109 public final SoftLock lockItem(Object key, Object version) throws CacheException {
110 region.writeLock(key);
111 try {
112 Lockable item = (Lockable) region.get(key);
113 long timeout = region.nextTimestamp() + region.getTimeout();
114 final Lock lock = (item == null) ? new Lock(timeout, uuid, nextLockId(), version) : item.lock(timeout, uuid, nextLockId());
115 region.put(key, lock);
116 return lock;
117 } finally {
118 region.writeUnlock(key);
119 }
120 }
121
122 /***
123 * Soft-unlock a cache item.
124 *
125 * @see org.hibernate.cache.access.EntityRegionAccessStrategy#unlockItem(java.lang.Object, org.hibernate.cache.access.SoftLock)
126 * @see org.hibernate.cache.access.CollectionRegionAccessStrategy#unlockItem(java.lang.Object, org.hibernate.cache.access.SoftLock)
127 */
128 public final void unlockItem(Object key, SoftLock lock) throws CacheException {
129 region.writeLock(key);
130 try {
131 Lockable item = (Lockable) region.get(key);
132
133 if ((item != null) && item.isUnlockable(lock)) {
134 decrementLock(key, (Lock) item);
135 } else {
136 handleLockExpiry(key, item);
137 }
138 } finally {
139 region.writeUnlock(key);
140 }
141 }
142
143 private long nextLockId() {
144 return nextLockId.getAndIncrement();
145 }
146
147 /***
148 * Unlock and re-put the given key, lock combination.
149 */
150 protected void decrementLock(Object key, Lock lock) {
151 lock.unlock(region.nextTimestamp());
152 region.put(key, lock);
153 }
154
155 /***
156 * Handle the timeout of a previous lock mapped to this key
157 */
158 protected void handleLockExpiry(Object key, Lockable lock) {
159 LOG.warn("Cache " + region.getName() + " Key " + key + " Lockable : " + lock + "\n"
160 + "A soft-locked cache entry was expired by the underlying Ehcache. "
161 + "If this happens regularly you should consider increasing the cache timeouts and/or capacity limits");
162 long ts = region.nextTimestamp() + region.getTimeout();
163
164 Lock newLock = new Lock(ts, uuid, nextLockId.getAndIncrement(), null);
165 newLock.unlock(ts);
166 region.put(key, newLock);
167 }
168
169 /***
170 * Read lock the entry for the given key if internal cache locks will not provide correct exclusion.
171 */
172 private void readLockIfNeeded(Object key) {
173 if (region.locksAreIndependentOfCache()) {
174 region.readLock(key);
175 }
176 }
177
178 /***
179 * Read unlock the entry for the given key if internal cache locks will not provide correct exclusion.
180 */
181 private void readUnlockIfNeeded(Object key) {
182 if (region.locksAreIndependentOfCache()) {
183 region.readUnlock(key);
184 }
185 }
186
187 /***
188 * Interface type implemented by all wrapper objects in the cache.
189 */
190 protected static interface Lockable {
191
192 /***
193 * Returns <code>true</code> if the enclosed value can be read by a transaction started at the given time.
194 */
195 public boolean isReadable(long txTimestamp);
196
197 /***
198 * Returns <code>true</code> if the enclosed value can be replaced with one of the given version by a
199 * transaction started at the given time.
200 */
201 public boolean isWriteable(long txTimestamp, Object version, Comparator versionComparator);
202
203 /***
204 * Returns the enclosed value.
205 */
206 public Object getValue();
207
208 /***
209 * Returns <code>true</code> if the given lock can be unlocked using the given SoftLock instance as a handle.
210 */
211 public boolean isUnlockable(SoftLock lock);
212
213 /***
214 * Locks this entry, stamping it with the UUID and lockId given, with the lock timeout occuring at the specified
215 * time. The returned Lock object can be used to unlock the entry in the future.
216 */
217 public Lock lock(long timeout, UUID uuid, long lockId);
218 }
219
220 /***
221 * Wrapper type representing unlocked items.
222 */
223 protected static final class Item implements Serializable, Lockable {
224
225 private static final long serialVersionUID = 1L;
226 private final Object value;
227 private final Object version;
228 private final long timestamp;
229
230 /***
231 * Creates an unlocked item wrapping the given value with a version and creation timestamp.
232 */
233 Item(Object value, Object version, long timestamp) {
234 this.value = value;
235 this.version = version;
236 this.timestamp = timestamp;
237 }
238
239 /***
240 * {@inheritDoc}
241 */
242 public boolean isReadable(long txTimestamp) {
243 return txTimestamp > timestamp;
244 }
245
246 /***
247 * {@inheritDoc}
248 */
249 public boolean isWriteable(long txTimestamp, Object newVersion, Comparator versionComparator) {
250 return version != null && versionComparator.compare(version, newVersion) < 0;
251 }
252
253 /***
254 * {@inheritDoc}
255 */
256 public Object getValue() {
257 return value;
258 }
259
260 /***
261 * {@inheritDoc}
262 */
263 public boolean isUnlockable(SoftLock lock) {
264 return false;
265 }
266
267 /***
268 * {@inheritDoc}
269 */
270 public Lock lock(long timeout, UUID uuid, long lockId) {
271 return new Lock(timeout, uuid, lockId, version);
272 }
273 }
274
275 /***
276 * Wrapper type representing locked items.
277 */
278 protected static final class Lock implements Serializable, Lockable, SoftLock {
279
280 private static final long serialVersionUID = 2L;
281
282 private final UUID sourceUuid;
283 private final long lockId;
284 private final Object version;
285
286 private long timeout;
287 private boolean concurrent;
288 private int multiplicity = 1;
289 private long unlockTimestamp;
290
291 /***
292 * Creates a locked item with the given identifiers and object version.
293 */
294 Lock(long timeout, UUID sourceUuid, long lockId, Object version) {
295 this.timeout = timeout;
296 this.lockId = lockId;
297 this.version = version;
298 this.sourceUuid = sourceUuid;
299 }
300
301 /***
302 * {@inheritDoc}
303 */
304 public boolean isReadable(long txTimestamp) {
305 return false;
306 }
307
308 /***
309 * {@inheritDoc}
310 */
311 public boolean isWriteable(long txTimestamp, Object newVersion, Comparator versionComparator) {
312 if (txTimestamp > timeout) {
313
314 return true;
315 }
316 if (multiplicity > 0) {
317
318 return false;
319 }
320 return version == null ? txTimestamp > unlockTimestamp : versionComparator.compare(version, newVersion) < 0;
321 }
322
323 /***
324 * {@inheritDoc}
325 */
326 public Object getValue() {
327 return null;
328 }
329
330 /***
331 * {@inheritDoc}
332 */
333 public boolean isUnlockable(SoftLock lock) {
334 return equals(lock);
335 }
336
337 /***
338 * {@inheritDoc}
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 /***
352 * {@inheritDoc}
353 */
354 @Override
355 public int hashCode() {
356 int hash = (sourceUuid != null ? sourceUuid.hashCode() : 0);
357 int temp = (int) lockId;
358 for (int i = 1; i < Long.SIZE / Integer.SIZE; i++) {
359 temp ^= (lockId >>> (i * Integer.SIZE));
360 }
361 return hash + temp;
362 }
363
364 /***
365 * Returns true if this Lock has been concurrently locked by more than one transaction.
366 */
367 public boolean wasLockedConcurrently() {
368 return concurrent;
369 }
370
371 /***
372 * {@inheritDoc}
373 */
374 public Lock lock(long timeout, UUID uuid, long lockId) {
375 concurrent = true;
376 multiplicity++;
377 this.timeout = timeout;
378 return this;
379 }
380
381 /***
382 * Unlocks this Lock, and timestamps the unlock event.
383 */
384 public void unlock(long timestamp) {
385 if (--multiplicity == 0) {
386 unlockTimestamp = timestamp;
387 }
388 }
389
390 /***
391 * {@inheritDoc}
392 */
393 @Override
394 public String toString() {
395 StringBuilder sb = new StringBuilder("Lock Source-UUID:" + sourceUuid + " Lock-ID:" + lockId);
396 return sb.toString();
397 }
398 }
399 }