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
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
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
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
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
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
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
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 }
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
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
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
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
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
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
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 }
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
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
555 softLock.unlock();
556 LOG.debug("putIfAbsent: cache [{}] key [{}] was not in, soft lock insertion failed", cacheName, key);
557
558
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
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
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
613 continue;
614 }
615 }
616
617 }
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
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
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
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
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
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 }
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
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
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
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
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
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 }
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
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
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
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
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
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 }
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 }