View Javadoc

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         // create new lock that times out immediately
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                 // if timedout then allow write
314                 return true;
315             }
316             if (multiplicity > 0) {
317                 // if still locked then disallow write
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 }