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.transaction.local;
17  
18  import net.sf.ehcache.CacheEntry;
19  import net.sf.ehcache.CacheException;
20  import net.sf.ehcache.Ehcache;
21  import net.sf.ehcache.Element;
22  import net.sf.ehcache.TransactionController;
23  import net.sf.ehcache.search.attribute.AttributeExtractor;
24  import net.sf.ehcache.store.ElementValueComparator;
25  import net.sf.ehcache.store.Store;
26  import net.sf.ehcache.store.compound.ReadWriteCopyStrategy;
27  import net.sf.ehcache.transaction.AbstractTransactionStore;
28  import net.sf.ehcache.transaction.DeadLockException;
29  import net.sf.ehcache.transaction.SoftLock;
30  import net.sf.ehcache.transaction.SoftLockFactory;
31  import net.sf.ehcache.transaction.TransactionAwareAttributeExtractor;
32  import net.sf.ehcache.transaction.TransactionException;
33  import net.sf.ehcache.transaction.TransactionInterruptedException;
34  import net.sf.ehcache.transaction.TransactionTimeoutException;
35  import net.sf.ehcache.util.LargeSet;
36  import net.sf.ehcache.util.SetWrapperList;
37  import net.sf.ehcache.writer.CacheWriterManager;
38  
39  import org.slf4j.Logger;
40  import org.slf4j.LoggerFactory;
41  
42  import java.util.HashMap;
43  import java.util.Iterator;
44  import java.util.List;
45  import java.util.Map;
46  import java.util.Map.Entry;
47  import java.util.Set;
48  
49  /***
50   * A Store implementation with support for local transactions
51   *
52   * @author Ludovic Orban
53   */
54  public class LocalTransactionStore extends AbstractTransactionStore {
55  
56      private static final Logger LOG = LoggerFactory.getLogger(LocalTransactionStore.class.getName());
57  
58      private final TransactionController transactionController;
59      private final SoftLockFactory softLockFactory;
60      private final Ehcache cache;
61      private final String cacheName;
62      private final ElementValueComparator comparator;
63  
64  
65      /***
66       * Create a new LocalTransactionStore instance
67       * @param transactionController the TransactionController
68       * @param softLockFactory the SoftLockFactory
69       * @param cache the cache
70       * @param store the underlying store
71       * @param copyStrategy the configured CopyStrategy
72       */
73      public LocalTransactionStore(TransactionController transactionController, SoftLockFactory softLockFactory, Ehcache cache,
74                                   Store store, ReadWriteCopyStrategy<Element> copyStrategy) {
75          super(store, copyStrategy);
76          this.transactionController = transactionController;
77          this.softLockFactory = softLockFactory;
78          this.cache = cache;
79          this.comparator = cache.getCacheConfiguration().getElementValueComparatorConfiguration().getElementComparatorInstance();
80          this.cacheName = cache.getName();
81      }
82  
83      /***
84       * Get the cache using this store
85       * @return the cache using this store
86       */
87      Ehcache getCache() {
88          return cache;
89      }
90  
91      private LocalTransactionContext getCurrentTransactionContext() {
92          LocalTransactionContext currentTransactionContext = transactionController.getCurrentTransactionContext();
93          if (currentTransactionContext == null) {
94              throw new TransactionException("transaction not started");
95          }
96          return currentTransactionContext;
97      }
98  
99      private void assertNotTimedOut() {
100         if (getCurrentTransactionContext().timedOut()) {
101             throw new TransactionTimeoutException("transaction [" + getCurrentTransactionContext().getTransactionId() + "] timed out");
102         }
103         if (Thread.interrupted()) {
104             throw new TransactionInterruptedException("transaction [" + getCurrentTransactionContext().getTransactionId() +
105                     "] interrupted");
106         }
107     }
108 
109     private long timeBeforeTimeout() {
110         return Math.max(0, getCurrentTransactionContext().getExpirationTimestamp() - System.currentTimeMillis());
111     }
112 
113     private Element createElement(Object key, SoftLock softLock) {
114         Element element = new Element(key, softLock);
115         element.setEternal(true);
116         element.setPinned(true);
117         return element;
118     }
119 
120     private boolean cleanupExpiredSoftLock(Element oldElement, SoftLock softLock) {
121         if (softLock.isExpired()) {
122             softLock.lock();
123             softLock.freeze();
124             try {
125                 Element frozenElement = softLock.getFrozenElement();
126                 if (frozenElement != null) {
127                     underlyingStore.replace(oldElement, frozenElement, comparator);
128                 } else {
129                     underlyingStore.removeElement(oldElement, comparator);
130                 }
131             } finally {
132                 softLock.unfreeze();
133                 softLock.unlock();
134             }
135             return true;
136         }
137         return false;
138     }
139 
140     /* transactional methods */
141 
142     /***
143      * {@inheritDoc}
144      */
145     public boolean put(Element e) throws CacheException {
146         if (e == null) {
147             return true;
148         }
149 
150         final Element element = copyElementForWrite(e);
151         final Object key = element.getObjectKey();
152         while (true) {
153             assertNotTimedOut();
154 
155             Element oldElement = underlyingStore.getQuiet(key);
156             if (oldElement == null) {
157                 SoftLock softLock = softLockFactory.createSoftLock(getCurrentTransactionContext().getTransactionId(), key,
158                         element, null);
159                 softLock.lock();
160                 Element newElement = createElement(key, softLock);
161                 oldElement = underlyingStore.putIfAbsent(newElement);
162                 if (oldElement == null) {
163                     // CAS succeeded, soft lock is in store, job done.
164                     getCurrentTransactionContext().registerSoftLock(cacheName, this, softLock);
165                     LOG.debug("put: cache [{}] key [{}] was not in, soft lock inserted", cacheName, key);
166                     return true;
167                 } else {
168                     // CAS failed, something with that key may now be in store, restart.
169                     softLock.unlock();
170                     LOG.debug("put: cache [{}] key [{}] was not in, soft lock insertion failed, retrying...", cacheName, key);
171                     continue;
172                 }
173             } else {
174                 Object value = oldElement.getObjectValue();
175                 if (value instanceof SoftLock) {
176                     SoftLock softLock = (SoftLock) value;
177 
178                     if (cleanupExpiredSoftLock(oldElement, softLock)) {
179                         LOG.debug("put: cache [{}] key [{}] guarded by expired soft lock, cleaned up {}",
180                                 new Object[] {cacheName, key, softLock});
181                         continue;
182                     }
183 
184                     if (softLock.getTransactionID().equals(getCurrentTransactionContext().getTransactionId())) {
185                         softLock.updateElement(element);
186                         underlyingStore.put(oldElement);
187                         getCurrentTransactionContext().updateSoftLock(cacheName, softLock);
188 
189                         LOG.debug("put: cache [{}] key [{}] soft locked in current transaction, replaced old value with new one under" +
190                                 " soft lock", cacheName, key);
191                         // replaced old value with new one under soft lock, job done.
192                         return false;
193                     } else {
194                         LOG.debug("put: cache [{}] key [{}] soft locked in foreign transaction, waiting {}ms for soft lock to die...",
195                                 new Object[] {cacheName, key, timeBeforeTimeout()});
196                         try {
197                             boolean locked = softLock.tryLock(timeBeforeTimeout());
198                             if (!locked) {
199                                 LOG.debug("put: cache [{}] key [{}] soft locked in foreign transaction and not released before" +
200                                         " current transaction timeout", cacheName, key);
201                                 throw new DeadLockException("deadlock detected in cache [" + cacheName + "] on key [" + key + "]" +
202                                         " between current transaction [" + getCurrentTransactionContext().getTransactionId() + "]" +
203                                         " and foreign transaction [" + softLock.getTransactionID() + "]");
204                             }
205                             softLock.clearTryLock();
206                         } catch (InterruptedException ie) {
207                             Thread.currentThread().interrupt();
208                         }
209 
210                         LOG.debug("put: cache [{}] key [{}] soft locked in foreign transaction, soft lock died, retrying...",
211                                 cacheName, key);
212                         // once the soft lock got unlocked we don't know what's in the store anymore, restart.
213                         continue;
214                     }
215                 } else {
216                     SoftLock softLock = softLockFactory.createSoftLock(getCurrentTransactionContext().getTransactionId(), key,
217                             element, oldElement);
218                     softLock.lock();
219                     Element newElement = createElement(key, softLock);
220                     boolean replaced = underlyingStore.replace(oldElement, newElement, comparator);
221                     if (replaced) {
222                         // CAS succeeded, value replaced with soft lock, job done.
223                         getCurrentTransactionContext().registerSoftLock(cacheName, this, softLock);
224                         LOG.debug("put: cache [{}] key [{}] was in, replaced with soft lock", cacheName, key);
225                         return false;
226                     } else {
227                         // CAS failed, something else with that key is now in store or the key disappeared, restart.
228                         softLock.unlock();
229                         LOG.debug("put: cache [{}] key [{}] was in, replacement by soft lock failed, retrying... ", cacheName, key);
230                         continue;
231                     }
232                 }
233             }
234 
235         } // while
236     }
237 
238     /***
239      * {@inheritDoc}
240      */
241     public Element getQuiet(Object key) {
242         if (key == null) {
243             return null;
244         }
245 
246         while (true) {
247             assertNotTimedOut();
248 
249             Element oldElement = underlyingStore.getQuiet(key);
250             if (oldElement == null) {
251                 LOG.debug("getQuiet: cache [{}] key [{}] is not present", cacheName, key);
252                 return null;
253             }
254 
255             Object value = oldElement.getObjectValue();
256             if (value instanceof SoftLock) {
257                 SoftLock softLock = (SoftLock) value;
258                 if (cleanupExpiredSoftLock(oldElement, softLock)) {
259                     LOG.debug("getQuiet: cache [{}] key [{}] guarded by expired soft lock, cleaned up {}",
260                             new Object[] {cacheName, key, softLock});
261                     continue;
262                 }
263 
264                 LOG.debug("getQuiet: cache [{}] key [{}] soft locked, returning soft locked element", cacheName, key);
265                 return copyElementForRead(softLock.getElement(getCurrentTransactionContext().getTransactionId()));
266             } else {
267                 LOG.debug("getQuiet: cache [{}] key [{}] not soft locked, returning underlying element", cacheName, key);
268                 return copyElementForRead(oldElement);
269             }
270         }
271     }
272 
273     /***
274      * {@inheritDoc}
275      */
276     public Element get(Object key) {
277         if (key == null) {
278             return null;
279         }
280 
281         while (true) {
282             assertNotTimedOut();
283 
284             Element oldElement = underlyingStore.get(key);
285             if (oldElement == null) {
286                 LOG.debug("get: cache [{}] key [{}] is not present", cacheName, key);
287                 return null;
288             }
289 
290             Object value = oldElement.getObjectValue();
291             if (value instanceof SoftLock) {
292                 SoftLock softLock = (SoftLock) value;
293                 if (cleanupExpiredSoftLock(oldElement, softLock)) {
294                     LOG.debug("get: cache [{}] key [{}] guarded by expired soft lock, cleaned up {}",
295                             new Object[] {cacheName, key, softLock});
296                     continue;
297                 }
298 
299                 LOG.debug("get: cache [{}] key [{}] soft locked, returning soft locked element", cacheName, key);
300                 return copyElementForRead(softLock.getElement(getCurrentTransactionContext().getTransactionId()));
301             } else {
302                 LOG.debug("get: cache [{}] key [{}] not soft locked, returning underlying element", cacheName, key);
303                 return copyElementForRead(oldElement);
304             }
305         }
306     }
307 
308     /***
309      * {@inheritDoc}
310      */
311     public Element remove(Object key) {
312         if (key == null) {
313             return null;
314         }
315 
316         while (true) {
317             assertNotTimedOut();
318 
319             Element oldElement = underlyingStore.getQuiet(key);
320             if (oldElement == null) {
321                 SoftLock softLock = softLockFactory.createSoftLock(getCurrentTransactionContext().getTransactionId(), key,
322                         null, null);
323                 softLock.lock();
324                 Element newElement = createElement(key, softLock);
325                 oldElement = underlyingStore.putIfAbsent(newElement);
326                 if (oldElement == null) {
327                     // CAS succeeded, value is in store, job done.
328                     getCurrentTransactionContext().registerSoftLock(cacheName, this, softLock);
329                     LOG.debug("remove: cache [{}] key [{}] was not in, soft lock inserted", cacheName, key);
330                     return null;
331                 } else {
332                     // CAS failed, something with that key may now be in store, restart.
333                     softLock.unlock();
334                     LOG.debug("remove: cache [{}] key [{}] was not in, soft lock insertion failed, retrying...", cacheName, key);
335                     continue;
336                 }
337             } else {
338                 Object value = oldElement.getObjectValue();
339                 if (value instanceof SoftLock) {
340                     SoftLock softLock = (SoftLock) value;
341 
342                     if (cleanupExpiredSoftLock(oldElement, softLock)) {
343                         LOG.debug("remove: cache [{}] key [{}] guarded by expired soft lock, cleaned up {}",
344                                 new Object[] {cacheName, key, softLock});
345                         continue;
346                     }
347 
348                     if (softLock.getTransactionID().equals(getCurrentTransactionContext().getTransactionId())) {
349                         Element removed = softLock.updateElement(null);
350                         underlyingStore.put(oldElement);
351                         getCurrentTransactionContext().updateSoftLock(cacheName, softLock);
352 
353                         // replaced old value with new one under soft lock, job done.
354                         LOG.debug("remove: cache [{}] key [{}] soft locked in current transaction, replaced old value with new one under" +
355                                 " soft lock", cacheName, key);
356                         return copyElementForRead(removed);
357                     } else {
358                         try {
359                             LOG.debug("remove: cache [{}] key [{}] soft locked in foreign transaction, waiting {}ms for soft lock to" +
360                                     " die...", new Object[] {cacheName, key, timeBeforeTimeout()});
361                             boolean locked = softLock.tryLock(timeBeforeTimeout());
362                             if (!locked) {
363                                 LOG.debug("remove: cache [{}] key [{}] soft locked in foreign transaction and not released before" +
364                                         " current transaction timeout", cacheName, key);
365                                 throw new DeadLockException("deadlock detected in cache [" + cacheName + "] on key [" + key + "] between" +
366                                         " current transaction [" + getCurrentTransactionContext().getTransactionId() + "] and foreign" +
367                                         " transaction [" + softLock.getTransactionID() + "]");
368                             }
369                             softLock.clearTryLock();
370                         } catch (InterruptedException ie) {
371                             Thread.currentThread().interrupt();
372                         }
373 
374                         // once the soft lock got unlocked we don't know what's in the store anymore, restart.
375                         LOG.debug("remove: cache [{}] key [{}] soft locked in foreign transaction, soft lock died, retrying...",
376                                 cacheName, key);
377                         continue;
378                     }
379                 } else {
380                     SoftLock softLock = softLockFactory.createSoftLock(getCurrentTransactionContext().getTransactionId(), key,
381                             null, oldElement);
382                     softLock.lock();
383                     Element newElement = createElement(key, softLock);
384                     boolean replaced = underlyingStore.replace(oldElement, newElement, comparator);
385                     if (replaced) {
386                         // CAS succeeded, value replaced with soft lock, job done.
387                         getCurrentTransactionContext().registerSoftLock(cacheName, this, softLock);
388                         LOG.debug("remove: cache [{}] key [{}] was in, replaced with soft lock", cacheName, key);
389                         return copyElementForRead(oldElement);
390                     } else {
391                         // CAS failed, something else with that key is now in store or the key disappeared, restart.
392                         softLock.unlock();
393                         LOG.debug("remove: cache [{}] key [{}] was in, replacement by soft lock failed, retrying...", cacheName, key);
394                         continue;
395                     }
396                 }
397             }
398 
399         } // while
400     }
401 
402     /***
403      * {@inheritDoc}
404      */
405     public List getKeys() {
406         assertNotTimedOut();
407 
408         Set<Object> keys = new LargeSet<Object>() {
409             @Override
410             public int sourceSize() {
411                 return underlyingStore.getSize();
412             }
413 
414             @Override
415             public Iterator<Object> sourceIterator() {
416                 @SuppressWarnings("unchecked")
417                 Iterator<Object> iterator = underlyingStore.getKeys().iterator();
418                 return iterator;
419             }
420         };
421 
422         keys.removeAll(softLockFactory.getKeysInvisibleInContext(getCurrentTransactionContext()));
423 
424         return new SetWrapperList(keys);
425     }
426 
427     /***
428      * {@inheritDoc}
429      */
430     public int getSize() {
431         assertNotTimedOut();
432 
433         int sizeModifier = 0;
434         sizeModifier -= softLockFactory.getKeysInvisibleInContext(getCurrentTransactionContext()).size();
435         return underlyingStore.getSize() + sizeModifier;
436     }
437 
438     /***
439      * {@inheritDoc}
440      */
441     public int getTerracottaClusteredSize() {
442         if (transactionController.getCurrentTransactionContext() == null) {
443             return underlyingStore.getTerracottaClusteredSize();
444         }
445 
446         int sizeModifier = 0;
447         sizeModifier -= softLockFactory.getKeysInvisibleInContext(getCurrentTransactionContext()).size();
448         return underlyingStore.getTerracottaClusteredSize() + sizeModifier;
449     }
450 
451     /***
452      * {@inheritDoc}
453      */
454     public boolean containsKey(Object key) {
455         assertNotTimedOut();
456 
457         return getKeys().contains(key);
458     }
459 
460     /***
461      * {@inheritDoc}
462      */
463     public void removeAll() throws CacheException {
464         assertNotTimedOut();
465 
466         List keys = getKeys();
467         for (Object key : keys) {
468             remove(key);
469         }
470     }
471 
472     /***
473      * {@inheritDoc}
474      */
475     public boolean putWithWriter(final Element element, final CacheWriterManager writerManager) throws CacheException {
476         if (element == null) {
477             return true;
478         }
479         assertNotTimedOut();
480 
481         boolean put = put(element);
482         getCurrentTransactionContext().addListener(new TransactionListener() {
483             public void beforeCommit() {
484                 if (writerManager != null) {
485                     writerManager.put(element);
486                 } else {
487                     cache.getWriterManager().put(element);
488                 }
489             }
490             public void afterCommit() {
491             }
492             public void afterRollback() {
493             }
494         });
495         return put;
496     }
497 
498     /***
499      * {@inheritDoc}
500      */
501     public Element removeWithWriter(final Object key, final CacheWriterManager writerManager) throws CacheException {
502         if (key == null) {
503             return null;
504         }
505         assertNotTimedOut();
506 
507         Element removed = remove(key);
508         final CacheEntry cacheEntry = new CacheEntry(key, getQuiet(key));
509         getCurrentTransactionContext().addListener(new TransactionListener() {
510             public void beforeCommit() {
511                 if (writerManager != null) {
512                     writerManager.remove(cacheEntry);
513                 } else {
514                     cache.getWriterManager().remove(cacheEntry);
515                 }
516             }
517             public void afterCommit() {
518             }
519             public void afterRollback() {
520             }
521         });
522         return removed;
523     }
524 
525     /***
526      * {@inheritDoc}
527      */
528     public Element putIfAbsent(Element e) throws NullPointerException {
529         if (e == null) {
530             throw new NullPointerException("element cannot be null");
531         }
532         if (e.getObjectKey() == null) {
533             throw new NullPointerException("element key cannot be null");
534         }
535 
536         final Element element = copyElementForWrite(e);
537         final Object key = element.getObjectKey();
538         while (true) {
539             assertNotTimedOut();
540 
541             Element oldElement = underlyingStore.getQuiet(key);
542             if (oldElement == null || !(oldElement.getObjectValue() instanceof SoftLock)) {
543                 SoftLock softLock = softLockFactory.createSoftLock(getCurrentTransactionContext().getTransactionId(), key,
544                         element, oldElement);
545                 softLock.lock();
546                 Element newElement = createElement(key, softLock);
547                 oldElement = underlyingStore.putIfAbsent(newElement);
548                 if (oldElement == null) {
549                     // CAS succeeded, soft lock is in store, job done.
550                     getCurrentTransactionContext().registerSoftLock(cacheName, this, softLock);
551                     LOG.debug("putIfAbsent: cache [{}] key [{}] was not in, soft lock inserted", cacheName, key);
552                     return null;
553                 } else {
554                     // CAS failed, something with that key may now be in store, job done.
555                     softLock.unlock();
556                     LOG.debug("putIfAbsent: cache [{}] key [{}] was not in, soft lock insertion failed", cacheName, key);
557 
558                     // oldElement may contain a soft lock -> check for that case
559                     Object oldElementObjectValue = oldElement.getObjectValue();
560                     if (oldElementObjectValue instanceof SoftLock) {
561                         SoftLock oldElementSoftLock = (SoftLock) oldElementObjectValue;
562                         return copyElementForRead(oldElementSoftLock.getElement(getCurrentTransactionContext().getTransactionId()));
563                     } else {
564                         return copyElementForRead(oldElement);
565                     }
566                 }
567             } else {
568                 SoftLock softLock = (SoftLock) oldElement.getObjectValue();
569 
570                 if (cleanupExpiredSoftLock(oldElement, softLock)) {
571                     LOG.debug("putIfAbsent: cache [{}] key [{}] guarded by expired soft lock, cleaned up {}",
572                             new Object[] {cacheName, key, softLock});
573                     continue;
574                 }
575 
576                 if (softLock.getTransactionID().equals(getCurrentTransactionContext().getTransactionId())) {
577                     Element currentElement = softLock.getElement(getCurrentTransactionContext().getTransactionId());
578                     if (currentElement == null) {
579                         softLock.updateElement(element);
580                         underlyingStore.put(oldElement);
581                         getCurrentTransactionContext().updateSoftLock(cacheName, softLock);
582 
583                         LOG.debug("putIfAbsent: cache [{}] key [{}] soft locked in current transaction, replaced null with new element" +
584                                 " under soft lock", cacheName, key);
585                         // replaced null with new one under soft lock, job done.
586                         return null;
587                     } else {
588                         LOG.debug("putIfAbsent: cache [{}] key [{}] soft locked in current transaction, old element is not null",
589                                 cacheName, key);
590                         // not replaced old value with new one, job done.
591                         return copyElementForRead(currentElement);
592                     }
593                 } else {
594                     LOG.debug("putIfAbsent: cache [{}] key [{}] soft locked in foreign transaction, waiting {}ms for soft lock to die...",
595                             new Object[] {cacheName, key, timeBeforeTimeout()});
596                     try {
597                         boolean locked = softLock.tryLock(timeBeforeTimeout());
598                         if (!locked) {
599                             LOG.debug("putIfAbsent: cache [{}] key [{}] soft locked in foreign transaction and not released before" +
600                                     " current transaction timeout", cacheName, key);
601                             throw new DeadLockException("deadlock detected in cache [" + cacheName + "] on key [" + key + "] between" +
602                                     " current transaction [" + getCurrentTransactionContext().getTransactionId() + "] and foreign" +
603                                     " transaction [" + softLock.getTransactionID() + "]");
604                         }
605                         softLock.clearTryLock();
606                     } catch (InterruptedException ie) {
607                         Thread.currentThread().interrupt();
608                     }
609 
610                     LOG.debug("putIfAbsent: cache [{}] key [{}] soft locked in foreign transaction, soft lock died, retrying...",
611                             cacheName, key);
612                     // once the soft lock got unlocked we don't know what's in the store anymore, restart.
613                     continue;
614                 }
615             }
616 
617         } // while
618     }
619 
620     /***
621      * {@inheritDoc}
622      */
623     public Element removeElement(Element e, ElementValueComparator comparator) throws NullPointerException {
624         if (e == null) {
625             throw new NullPointerException("element cannot be null");
626         }
627         if (e.getObjectKey() == null) {
628             throw new NullPointerException("element key cannot be null");
629         }
630         if (comparator == null) {
631             throw new NullPointerException("comparator cannot be null");
632         }
633 
634         final Element element = copyElementForWrite(e);
635         final Object key = element.getObjectKey();
636         while (true) {
637             assertNotTimedOut();
638 
639             Element oldElement = underlyingStore.getQuiet(key);
640             if (oldElement == null) {
641                 LOG.debug("removeElement: cache [{}] key [{}] was not in, nothing removed", cacheName, key);
642                 return null;
643             } else {
644                 Object value = oldElement.getObjectValue();
645                 if (value instanceof SoftLock) {
646                     SoftLock softLock = (SoftLock) value;
647 
648                     if (cleanupExpiredSoftLock(oldElement, softLock)) {
649                         LOG.debug("removeElement: cache [{}] key [{}] guarded by expired soft lock, cleaned up {}",
650                                 new Object[] {cacheName, key, softLock});
651                         continue;
652                     }
653 
654                     if (softLock.getTransactionID().equals(getCurrentTransactionContext().getTransactionId())) {
655                         Element currentElement = softLock.getElement(getCurrentTransactionContext().getTransactionId());
656                         if (comparator.equals(element, currentElement)) {
657                             Element removed = softLock.updateElement(null);
658                             underlyingStore.put(oldElement);
659                             getCurrentTransactionContext().updateSoftLock(cacheName, softLock);
660 
661                             // replaced old element with null under soft lock, job done.
662                             LOG.debug("removeElement: cache [{}] key [{}] soft locked in current transaction, replaced old element" +
663                                     " with null under soft lock", cacheName, key);
664                             return copyElementForRead(removed);
665                         } else {
666                             // old element is not equals to element to remove, job done.
667                             LOG.debug("removeElement: cache [{}] key [{}] soft locked in current transaction, old element did not" +
668                                     " match element to remove", cacheName, key);
669                             return null;
670                         }
671                     } else {
672                         try {
673                             LOG.debug("removeElement: cache [{}] key [{}] soft locked in foreign transaction, waiting {}ms for soft" +
674                                     " lock to die...", new Object[] {cacheName, key, timeBeforeTimeout()});
675                             boolean locked = softLock.tryLock(timeBeforeTimeout());
676                             if (!locked) {
677                                 LOG.debug("removeElement: cache [{}] key [{}] soft locked in foreign transaction and not released" +
678                                         " before current transaction timeout", cacheName, key);
679                                 throw new DeadLockException("deadlock detected in cache [" + cacheName + "] on key [" + key + "] between" +
680                                         " current transaction [" + getCurrentTransactionContext().getTransactionId() + "] and foreign" +
681                                         " transaction [" + softLock.getTransactionID() + "]");
682                             }
683                             softLock.clearTryLock();
684                         } catch (InterruptedException ie) {
685                             Thread.currentThread().interrupt();
686                         }
687 
688                         // once the soft lock got unlocked we don't know what's in the store anymore, restart.
689                         LOG.debug("removeElement: cache [{}] key [{}] soft locked in foreign transaction, soft lock died, retrying...",
690                                 cacheName, key);
691                         continue;
692                     }
693                 } else {
694                     SoftLock softLock = softLockFactory.createSoftLock(getCurrentTransactionContext().getTransactionId(), key,
695                             null, oldElement);
696                     softLock.lock();
697                     Element newElement = createElement(key, softLock);
698 
699                     boolean replaced = underlyingStore.replace(oldElement, newElement, comparator);
700                     if (replaced) {
701                         // CAS succeeded, value replaced with soft lock, job done.
702                         getCurrentTransactionContext().registerSoftLock(cacheName, this, softLock);
703                         LOG.debug("removeElement: cache [{}] key [{}] was in, replaced with soft lock", cacheName, key);
704                         return copyElementForRead(oldElement);
705                     } else {
706                         // CAS failed, something else with that key is now in store or the key disappeared, job done.
707                         softLock.unlock();
708                         LOG.debug("removeElement: cache [{}] key [{}] was in, replacement by soft lock failed", cacheName, key);
709                         return null;
710                     }
711                 }
712             }
713 
714         } // while
715     }
716 
717     /***
718      * {@inheritDoc}
719      */
720     public boolean replace(Element oe, Element ne, ElementValueComparator comparator)
721             throws NullPointerException, IllegalArgumentException {
722         if (oe == null) {
723             throw new NullPointerException("old cannot be null");
724         }
725         if (oe.getObjectKey() == null) {
726             throw new NullPointerException("old key cannot be null");
727         }
728         if (ne == null) {
729             throw new NullPointerException("element cannot be null");
730         }
731         if (ne.getObjectKey() == null) {
732             throw new NullPointerException("element key cannot be null");
733         }
734         if (comparator == null) {
735             throw new NullPointerException("comparator cannot be null");
736         }
737         if (!oe.getKey().equals(ne.getKey())) {
738             throw new IllegalArgumentException("old and element keys are not equal");
739         }
740 
741         final Element old = copyElementForWrite(oe);
742         final Element element = copyElementForWrite(ne);
743         final Object key = element.getObjectKey();
744         while (true) {
745             assertNotTimedOut();
746 
747             Element oldElement = underlyingStore.getQuiet(key);
748             if (oldElement == null) {
749                 LOG.debug("replace: cache [{}] key [{}] was not in, nothing replaced", cacheName, key);
750                 return false;
751             } else {
752                 Object value = oldElement.getObjectValue();
753                 if (value instanceof SoftLock) {
754                     SoftLock softLock = (SoftLock) value;
755 
756                     if (cleanupExpiredSoftLock(oldElement, softLock)) {
757                         LOG.debug("replace: cache [{}] key [{}] guarded by expired soft lock, cleaned up {}",
758                                 new Object[] {cacheName, key, softLock});
759                         continue;
760                     }
761 
762                     if (softLock.getTransactionID().equals(getCurrentTransactionContext().getTransactionId())) {
763                         Element currentElement = softLock.getElement(getCurrentTransactionContext().getTransactionId());
764                         if (comparator.equals(old, currentElement)) {
765                             softLock.updateElement(element);
766                             underlyingStore.put(oldElement);
767                             getCurrentTransactionContext().updateSoftLock(cacheName, softLock);
768 
769                             // replaced old element with new one under soft lock, job done.
770                             LOG.debug("replace: cache [{}] key [{}] soft locked in current transaction, replaced old element with" +
771                                     " new one under soft lock", cacheName, key);
772                             return true;
773                         } else {
774                             // old element is not equals to element to remove, job done.
775                             LOG.debug("replace: cache [{}] key [{}] soft locked in current transaction, old element did not match" +
776                                     " element to replace", cacheName, key);
777                             return false;
778                         }
779                     } else {
780                         try {
781                             LOG.debug("replace: cache [{}] key [{}] soft locked in foreign transaction, waiting {}ms for" +
782                                     " soft lock to die...", new Object[] {cacheName, key, timeBeforeTimeout()});
783                             boolean locked = softLock.tryLock(timeBeforeTimeout());
784                             if (!locked) {
785                                 LOG.debug("replace: cache [{}] key [{}] soft locked in foreign transaction and not released before" +
786                                         " current transaction timeout", cacheName, key);
787                                 throw new DeadLockException("deadlock detected in cache [" + cacheName + "] on key [" + key + "]" +
788                                         " between current transaction [" + getCurrentTransactionContext().getTransactionId() + "]" +
789                                         " and foreign transaction [" + softLock.getTransactionID() + "]");
790                             }
791                             softLock.clearTryLock();
792                         } catch (InterruptedException ie) {
793                             Thread.currentThread().interrupt();
794                         }
795 
796                         // once the soft lock got unlocked we don't know what's in the store anymore, restart.
797                         LOG.debug("replace: cache [{}] key [{}] soft locked in foreign transaction, soft lock died, retrying...",
798                                 cacheName, key);
799                         continue;
800                     }
801                 } else {
802                     SoftLock softLock = softLockFactory.createSoftLock(getCurrentTransactionContext().getTransactionId(), key,
803                             element, oldElement);
804                     softLock.lock();
805                     Element newElement = createElement(key, softLock);
806 
807                     boolean replaced = underlyingStore.replace(oldElement, newElement, comparator);
808                     if (replaced) {
809                         // CAS succeeded, value replaced with soft lock, job done.
810                         getCurrentTransactionContext().registerSoftLock(cacheName, this, softLock);
811                         LOG.debug("replace: cache [{}] key [{}] was in, replaced with soft lock", cacheName, key);
812                         return true;
813                     } else {
814                         // CAS failed, something else with that key is now in store or the key disappeared, job done.
815                         softLock.unlock();
816                         LOG.debug("replace: cache [{}] key [{}] was in, replacement by soft lock failed", cacheName, key);
817                         return false;
818                     }
819                 }
820             }
821 
822         } // while
823     }
824 
825     /***
826      * {@inheritDoc}
827      */
828     public Element replace(Element e) throws NullPointerException {
829         if (e == null) {
830             throw new NullPointerException("element cannot be null");
831         }
832 
833         final Element element = copyElementForWrite(e);
834         final Object key = element.getObjectKey();
835         if (key == null) {
836             throw new NullPointerException("element key cannot be null");
837         }
838         while (true) {
839             assertNotTimedOut();
840 
841             Element oldElement = underlyingStore.getQuiet(key);
842             if (oldElement == null) {
843                 LOG.debug("replace: cache [{}] key [{}] was not in, nothing replaced", cacheName, key);
844                 return null;
845             } else {
846                 Object value = oldElement.getObjectValue();
847                 if (value instanceof SoftLock) {
848                     SoftLock softLock = (SoftLock) value;
849 
850                     if (cleanupExpiredSoftLock(oldElement, softLock)) {
851                         LOG.debug("replace: cache [{}] key [{}] guarded by expired soft lock, cleaned up {}",
852                                 new Object[] {cacheName, key, softLock});
853                         continue;
854                     }
855 
856                     if (softLock.getTransactionID().equals(getCurrentTransactionContext().getTransactionId())) {
857                         Element currentElement = softLock.getElement(getCurrentTransactionContext().getTransactionId());
858                         if (currentElement != null) {
859                             Element replaced = softLock.updateElement(element);
860                             underlyingStore.put(oldElement);
861                             getCurrentTransactionContext().updateSoftLock(cacheName, softLock);
862 
863                             // replaced old element with new one under soft lock, job done.
864                             LOG.debug("replace: cache [{}] key [{}] soft locked in current transaction, replaced old element with" +
865                                     " new one under soft lock", cacheName, key);
866                             return copyElementForRead(replaced);
867                         } else {
868                             // old element is not equals to element to remove, job done.
869                             LOG.debug("replace: cache [{}] key [{}] soft locked in current transaction, old element was null," +
870                                     " not replaced", cacheName, key);
871                             return null;
872                         }
873                     } else {
874                         try {
875                             LOG.debug("replace: cache [{}] key [{}] soft locked in foreign transaction, waiting {}ms for soft lock" +
876                                     " to die...", new Object[] {cacheName, key, timeBeforeTimeout()});
877                             boolean locked = softLock.tryLock(timeBeforeTimeout());
878                             if (!locked) {
879                                 LOG.debug("replace: cache [{}] key [{}] soft locked in foreign transaction and not released before" +
880                                         " current transaction timeout", cacheName, key);
881                                 throw new DeadLockException("deadlock detected in cache [" + cacheName + "] on key [" + key + "]" +
882                                         " between current transaction [" + getCurrentTransactionContext().getTransactionId() + "]" +
883                                         " and foreign transaction [" + softLock.getTransactionID() + "]");
884                             }
885                             softLock.clearTryLock();
886                         } catch (InterruptedException ie) {
887                             Thread.currentThread().interrupt();
888                         }
889 
890                         // once the soft lock got unlocked we don't know what's in the store anymore, restart.
891                         LOG.debug("replace: cache [{}] key [{}] soft locked in foreign transaction, soft lock died, retrying...",
892                                 cacheName, key);
893                         continue;
894                     }
895                 } else {
896                     SoftLock softLock = softLockFactory.createSoftLock(getCurrentTransactionContext().getTransactionId(), key,
897                             element, oldElement);
898                     softLock.lock();
899                     Element newElement = createElement(key, softLock);
900 
901                     Element replaced = underlyingStore.replace(newElement);
902                     if (replaced != null) {
903                         // CAS succeeded, value replaced with soft lock, job done.
904                         getCurrentTransactionContext().registerSoftLock(cacheName, this, softLock);
905                         LOG.debug("replace: cache [{}] key [{}] was in, replaced with soft lock", cacheName, key);
906                         return copyElementForRead(replaced);
907                     } else {
908                         // CAS failed, something else with that key is now in store or the key disappeared, job done.
909                         softLock.unlock();
910                         LOG.debug("replace: cache [{}] key [{}] was in, replacement by soft lock failed", cacheName, key);
911                         return null;
912                     }
913                 }
914             }
915 
916         } // while
917     }
918 
919     /***
920      * Commit work of the specified soft locks
921      * @param softLocks the soft locks to commit
922      */
923     void commit(List<SoftLock> softLocks) {
924         LOG.debug("committing {} soft lock(s) in cache {}", softLocks.size(), cache.getName());
925         for (SoftLock softLock : softLocks) {
926             Element element = softLock.getFrozenElement();
927             if (element != null) {
928                 underlyingStore.put(element);
929             } else {
930                 underlyingStore.remove(softLock.getKey());
931             }
932         }
933     }
934 
935     /***
936      * Rollback work of the specified soft locks
937      * @param softLocks the soft locks to rollback
938      */
939     void rollback(List<SoftLock> softLocks) {
940         LOG.debug("rolling back {} soft lock(s) in cache {}", softLocks.size(), cache.getName());
941         for (SoftLock softLock : softLocks) {
942             Element element = softLock.getFrozenElement();
943             if (element != null) {
944                 underlyingStore.put(element);
945             } else {
946                 underlyingStore.remove(softLock.getKey());
947             }
948         }
949     }
950 
951     /***
952      * {@inheritDoc}
953      */
954     @Override
955     public void setAttributeExtractors(Map<String, AttributeExtractor> extractors) {
956         Map<String, AttributeExtractor> wrappedExtractors = new HashMap(extractors.size());
957         for (Entry<String, AttributeExtractor> e : extractors.entrySet()) {
958             wrappedExtractors.put(e.getKey(), new TransactionAwareAttributeExtractor(copyStrategy, e.getValue()));
959         }
960         underlyingStore.setAttributeExtractors(wrappedExtractors);
961     }
962 }