001: /*
002: * All content copyright (c) 2003-2006 Terracotta, Inc., except as may otherwise be noted in a separate copyright
003: * notice. All rights reserved.
004: */
005: package com.tc.object.tx;
006:
007: import com.tc.exception.TCClassNotFoundException;
008: import com.tc.exception.TCLockUpgradeNotSupportedError;
009: import com.tc.logging.TCLogger;
010: import com.tc.logging.TCLogging;
011: import com.tc.management.beans.tx.ClientTxMonitorMBean;
012: import com.tc.net.protocol.tcm.ChannelIDProvider;
013: import com.tc.object.ClientObjectManager;
014: import com.tc.object.LiteralValues;
015: import com.tc.object.ObjectID;
016: import com.tc.object.TCObject;
017: import com.tc.object.appevent.NonPortableEventContextFactory;
018: import com.tc.object.appevent.ReadOnlyObjectEvent;
019: import com.tc.object.appevent.ReadOnlyObjectEventContext;
020: import com.tc.object.appevent.UnlockedSharedObjectEvent;
021: import com.tc.object.appevent.UnlockedSharedObjectEventContext;
022: import com.tc.object.dmi.DmiDescriptor;
023: import com.tc.object.dna.api.DNA;
024: import com.tc.object.dna.api.DNAException;
025: import com.tc.object.loaders.Namespace;
026: import com.tc.object.lockmanager.api.LockID;
027: import com.tc.object.lockmanager.api.LockLevel;
028: import com.tc.object.lockmanager.api.ThreadLockManager;
029: import com.tc.object.lockmanager.api.WaitListener;
030: import com.tc.object.logging.RuntimeLogger;
031: import com.tc.object.session.SessionID;
032: import com.tc.object.util.ReadOnlyException;
033: import com.tc.text.Banner;
034: import com.tc.util.Assert;
035: import com.tc.util.ClassUtils;
036:
037: import java.util.Collection;
038: import java.util.Iterator;
039: import java.util.LinkedList;
040: import java.util.List;
041: import java.util.Map;
042: import java.util.Set;
043: import java.util.Map.Entry;
044:
045: /**
046: * @author steve
047: */
048: public class ClientTransactionManagerImpl implements
049: ClientTransactionManager {
050: private static final TCLogger logger = TCLogging
051: .getLogger(ClientTransactionManagerImpl.class);
052:
053: private final ThreadLocal transaction = new ThreadLocal() {
054: protected synchronized Object initialValue() {
055: return new ThreadTransactionContext();
056: }
057: };
058:
059: // We need to remove initialValue() here because read auto locking now calls Manager.isDsoMonitored() which will
060: // checks if isTransactionLogging is disabled. If it runs in the context of class loading, it will try to load
061: // the class ThreadTransactionContext and thus throws a LinkageError.
062: private final ThreadLocal txnLogging = new ThreadLocal();
063:
064: private final ClientTransactionFactory txFactory;
065: private final RemoteTransactionManager remoteTxManager;
066: private final ClientObjectManager objectManager;
067: private final ThreadLockManager lockManager;
068: private final NonPortableEventContextFactory appEventContextFactory;
069: private final LiteralValues literalValues = new LiteralValues();
070:
071: private final WaitListener waitListener = new WaitListener() {
072: public void handleWaitEvent() {
073: return;
074: }
075: };
076:
077: private final ChannelIDProvider cidProvider;
078:
079: private final ClientTxMonitorMBean txMonitor;
080:
081: private final boolean sendErrors = System
082: .getProperty("project.name") != null;
083:
084: public ClientTransactionManagerImpl(ChannelIDProvider cidProvider,
085: ClientObjectManager objectManager,
086: ThreadLockManager lockManager,
087: ClientTransactionFactory txFactory,
088: RemoteTransactionManager remoteTxManager,
089: RuntimeLogger runtimeLogger,
090: final ClientTxMonitorMBean txMonitor) {
091: this .cidProvider = cidProvider;
092: this .txFactory = txFactory;
093: this .remoteTxManager = remoteTxManager;
094: this .objectManager = objectManager;
095: this .objectManager.setTransactionManager(this );
096: this .lockManager = lockManager;
097: this .txMonitor = txMonitor;
098: this .appEventContextFactory = new NonPortableEventContextFactory(
099: cidProvider);
100: }
101:
102: public int queueLength(String lockName) {
103: final LockID lockID = lockManager.lockIDFor(lockName);
104: return lockManager.queueLength(lockID);
105: }
106:
107: public int waitLength(String lockName) {
108: final LockID lockID = lockManager.lockIDFor(lockName);
109: return lockManager.waitLength(lockID);
110: }
111:
112: public int localHeldCount(String lockName, int lockLevel) {
113: final LockID lockID = lockManager.lockIDFor(lockName);
114: return lockManager.localHeldCount(lockID, lockLevel);
115: }
116:
117: public boolean isHeldByCurrentThread(String lockName, int lockLevel) {
118: if (isTransactionLoggingDisabled()) {
119: return true;
120: }
121: final LockID lockID = lockManager.lockIDFor(lockName);
122: return lockManager.localHeldCount(lockID, lockLevel) > 0;
123: }
124:
125: public boolean isLocked(String lockName, int lockLevel) {
126: final LockID lockID = lockManager.lockIDFor(lockName);
127: return lockManager.isLocked(lockID, lockLevel);
128: }
129:
130: public void lock(String lockName, int lockLevel) {
131: final LockID lockID = lockManager.lockIDFor(lockName);
132: lockManager.lock(lockID, lockLevel);
133: }
134:
135: public void unlock(String lockName) {
136: final LockID lockID = lockManager.lockIDFor(lockName);
137: if (lockID != null) {
138: lockManager.unlock(lockID);
139: getThreadTransactionContext().removeLock(lockID);
140: }
141: }
142:
143: public boolean tryBegin(String lockName, WaitInvocation timeout,
144: int lockLevel) {
145: logTryBegin0(lockName, lockLevel);
146:
147: if (isTransactionLoggingDisabled()
148: || objectManager.isCreationInProgress()) {
149: return true;
150: }
151:
152: final TxnType txnType = getTxnTypeFromLockLevel(lockLevel);
153: ClientTransaction currentTransaction = getTransactionOrNull();
154:
155: if ((currentTransaction != null)
156: && lockLevel == LockLevel.CONCURRENT) {
157: // make formatter sane
158: throw new AssertionError(
159: "Can't acquire concurrent locks in a nested lock context.");
160: }
161:
162: final LockID lockID = lockManager.lockIDFor(lockName);
163: boolean isLocked = lockManager.tryLock(lockID, timeout,
164: lockLevel);
165: if (!isLocked) {
166: return isLocked;
167: }
168:
169: pushTxContext(lockID, txnType);
170:
171: if (currentTransaction == null) {
172: createTxAndInitContext();
173: } else {
174: currentTransaction
175: .setTransactionContext(this .peekContext());
176: }
177:
178: return isLocked;
179: }
180:
181: public boolean begin(String lockName, int lockLevel) {
182: logBegin0(lockName, lockLevel);
183:
184: if (isTransactionLoggingDisabled()
185: || objectManager.isCreationInProgress()) {
186: return false;
187: }
188:
189: final TxnType txnType = getTxnTypeFromLockLevel(lockLevel);
190: ClientTransaction currentTransaction = getTransactionOrNull();
191:
192: final LockID lockID = lockManager.lockIDFor(lockName);
193:
194: pushTxContext(lockID, txnType);
195:
196: if (currentTransaction == null) {
197: createTxAndInitContext();
198: } else {
199: currentTransaction
200: .setTransactionContext(this .peekContext());
201: }
202:
203: try {
204: lockManager.lock(lockID, lockLevel);
205: return true;
206: } catch (TCLockUpgradeNotSupportedError e) {
207: popTransaction(lockID);
208: if (peekContext() != null) {
209: currentTransaction.setTransactionContext(peekContext());
210: setTransaction(currentTransaction);
211: }
212: throw e;
213: }
214: }
215:
216: private TxnType getTxnTypeFromLockLevel(int lockLevel) {
217: switch (lockLevel) {
218: case LockLevel.READ:
219: return TxnType.READ_ONLY;
220: case LockLevel.CONCURRENT:
221: return TxnType.CONCURRENT;
222: case LockLevel.WRITE:
223: return TxnType.NORMAL;
224: case LockLevel.SYNCHRONOUS_WRITE:
225: return TxnType.NORMAL;
226: default:
227: throw Assert
228: .failure("don't know how to translate lock level "
229: + lockLevel);
230: }
231: }
232:
233: public void wait(String lockName, WaitInvocation call, Object object)
234: throws UnlockedSharedObjectException, InterruptedException {
235: final ClientTransaction topTxn = getTransactionOrNull();
236:
237: if (topTxn == null) {
238: throw new IllegalMonitorStateException();
239: }
240:
241: LockID lockID = lockManager.lockIDFor(lockName);
242:
243: if (!lockManager.isLocked(lockID, LockLevel.WRITE)) {
244: throw new IllegalMonitorStateException();
245: }
246:
247: commit(lockID, topTxn, true);
248:
249: try {
250: lockManager.wait(lockID, call, object, waitListener);
251: } finally {
252: createTxAndInitContext();
253: }
254: }
255:
256: public void notify(String lockName, boolean all, Object object)
257: throws UnlockedSharedObjectException {
258: final ClientTransaction currentTxn = getTransactionOrNull();
259:
260: if (currentTxn == null) {
261: throw new IllegalMonitorStateException();
262: }
263:
264: LockID lockID = lockManager.lockIDFor(lockName);
265:
266: if (!lockManager.isLocked(lockID, LockLevel.WRITE)) {
267: throw new IllegalMonitorStateException();
268: }
269:
270: currentTxn.addNotify(lockManager.notify(lockID, all));
271: }
272:
273: private void logTryBegin0(String lockID, int type) {
274: if (logger.isDebugEnabled()) {
275: logger.debug("tryBegin(): lockID="
276: + (lockID == null ? "null" : lockID) + ", type = "
277: + type);
278: }
279: }
280:
281: private void logBegin0(String lockID, int type) {
282: if (logger.isDebugEnabled()) {
283: logger.debug("begin(): lockID="
284: + (lockID == null ? "null" : lockID) + ", type = "
285: + type);
286: }
287: }
288:
289: private ClientTransaction getTransactionOrNull() {
290: ThreadTransactionContext tx = getThreadTransactionContext();
291: return tx.getCurrentTransaction();
292: }
293:
294: private ThreadTransactionContext getThreadTransactionContext() {
295: return (ThreadTransactionContext) this .transaction.get();
296: }
297:
298: public ClientTransaction getTransaction()
299: throws UnlockedSharedObjectException {
300: return getTransaction(null);
301: }
302:
303: private ClientTransaction getTransaction(Object context)
304: throws UnlockedSharedObjectException {
305: ClientTransaction tx = getTransactionOrNull();
306: if (tx == null) {
307:
308: String type = context == null ? null : context.getClass()
309: .getName();
310: String errorMsg = "Attempt to access a shared object outside the scope of a shared lock. "
311: + "\nAll access to shared objects must be within the scope of one or more shared locks defined in your Terracotta configuration. "
312: + "\nPlease alter the locks section of your Terracotta configuration so that this access is auto-locked or protected by a named lock."
313: + "\n\nFor more information on this issue, please visit our Troubleshooting Guide at:\n http://terracotta.org/kit/troubleshooting\n";
314: String details = "";
315: if (type != null) {
316: details += "Shared Object Type: " + type;
317: }
318:
319: throw new UnlockedSharedObjectException(errorMsg, Thread
320: .currentThread().getName(), cidProvider
321: .getChannelID().toLong(), details);
322: }
323: return tx;
324: }
325:
326: public void checkWriteAccess(Object context) {
327: if (isTransactionLoggingDisabled()) {
328: return;
329: }
330:
331: // First check if we have any TXN context at all (else exception thrown)
332: ClientTransaction txn;
333:
334: try {
335: txn = getTransaction(context);
336: } catch (UnlockedSharedObjectException usoe) {
337: if (sendErrors) {
338: UnlockedSharedObjectEventContext eventContext = appEventContextFactory
339: .createUnlockedSharedObjectEventContext(
340: context, usoe);
341: objectManager.sendApplicationEvent(context,
342: new UnlockedSharedObjectEvent(eventContext));
343: }
344:
345: throw usoe;
346: }
347:
348: // make sure we're not in a read-only transaction
349: // txn.readOnlyCheck();
350: if (txn.getTransactionType() == TxnType.READ_ONLY) {
351: ReadOnlyException roe = makeReadOnlyException(null);
352:
353: if (sendErrors) {
354: ReadOnlyObjectEventContext eventContext = appEventContextFactory
355: .createReadOnlyObjectEventContext(context, roe);
356: objectManager.sendApplicationEvent(context,
357: new ReadOnlyObjectEvent(eventContext));
358: }
359:
360: throw roe;
361: }
362: }
363:
364: /**
365: * In order to support ReentrantLock, the TransactionContext that is going to be removed when doing a commit may not
366: * always be at the top of a stack because an reentrant lock could issue a lock within a synchronized block (although
367: * it is highly not recommended). Therefore, when a commit is issued, we need to search back the stack from the top of
368: * the stack to find the appropriate TransactionContext to be removed. Most likely, the TransactionContext to be
369: * removed will be on the top of the stack. Therefore, the performance should be make must difference. Only in some
370: * weird situations where reentrantLock is mixed with synchronized block will the TransactionContext to be removed be
371: * found otherwise.
372: */
373: public void commit(String lockName)
374: throws UnlockedSharedObjectException {
375: logCommit0();
376: if (isTransactionLoggingDisabled()
377: || objectManager.isCreationInProgress()) {
378: return;
379: }
380:
381: // ClientTransaction tx = popTransaction();
382: ClientTransaction tx = getTransaction();
383: LockID lockID = lockManager.lockIDFor(lockName);
384: if (lockID == null || lockID.isNull()) {
385: lockID = tx.getLockID();
386: }
387: boolean hasCommitted = commit(lockID, tx, false);
388:
389: popTransaction(lockManager.lockIDFor(lockName));
390:
391: if (peekContext() != null) {
392: if (hasCommitted) {
393: createTxAndInitContext();
394: } else {
395: // If the current transaction has not committed, we will reuse the current transaction
396: // so that the current changes will have a chance to commit at the next commit point.
397: tx.setTransactionContext(peekContext());
398: setTransaction(tx);
399: }
400: }
401: }
402:
403: private void createTxAndInitContext() {
404: ClientTransaction ctx = txFactory.newInstance();
405: ctx.setTransactionContext(peekContext());
406: setTransaction(ctx);
407: }
408:
409: private ClientTransaction popTransaction() {
410: ThreadTransactionContext ttc = getThreadTransactionContext();
411: return ttc.popCurrentTransaction();
412: }
413:
414: private ClientTransaction popTransaction(LockID lockID) {
415: if (lockID == null || lockID.isNull()) {
416: return popTransaction();
417: }
418: ThreadTransactionContext ttc = getThreadTransactionContext();
419: return ttc.popCurrentTransaction(lockID);
420: }
421:
422: private TransactionContext peekContext(LockID lockID) {
423: ThreadTransactionContext ttc = getThreadTransactionContext();
424: return ttc.peekContext(lockID);
425: }
426:
427: private TransactionContext peekContext() {
428: ThreadTransactionContext ttc = getThreadTransactionContext();
429: return ttc.peekContext();
430: }
431:
432: public boolean isLockOnTopStack(String lockName) {
433: final LockID lockID = lockManager.lockIDFor(lockName);
434: TransactionContext tc = peekContext();
435: if (tc == null) {
436: return false;
437: }
438: return (tc.getLockID().equals(lockID));
439: }
440:
441: private void pushTxContext(LockID lockID, TxnType txnType) {
442: ThreadTransactionContext ttc = getThreadTransactionContext();
443: ttc.pushContext(lockID, txnType);
444: }
445:
446: private void logCommit0() {
447: if (logger.isDebugEnabled())
448: logger.debug("commit()");
449: }
450:
451: private boolean commit(LockID lockID,
452: ClientTransaction currentTransaction, boolean isWaitContext) {
453: try {
454: return commitInternal(lockID, currentTransaction,
455: isWaitContext);
456: } catch (Throwable t) {
457: remoteTxManager.stopProcessing();
458: Banner
459: .errorBanner("Terracotta client shutting down due to error "
460: + t);
461: logger.error(t);
462: if (t instanceof Error) {
463: throw (Error) t;
464: }
465: if (t instanceof RuntimeException) {
466: throw (RuntimeException) t;
467: }
468: throw new RuntimeException(t);
469: }
470: }
471:
472: private boolean commitInternal(LockID lockID,
473: ClientTransaction currentTransaction, boolean isWaitContext) {
474: Assert.assertNotNull("transaction", currentTransaction);
475:
476: try {
477: disableTransactionLogging();
478:
479: // If the current transactionContext is READ_ONLY, there is no need to commit.
480: TransactionContext tc = peekContext(lockID);
481: if (tc.getType().equals(TxnType.READ_ONLY)) {
482: txMonitor.committedReadTransaction();
483: return false;
484: }
485:
486: boolean hasPendingCreateObjects = objectManager
487: .hasPendingCreateObjects();
488: if (hasPendingCreateObjects) {
489: objectManager.addPendingCreateObjectsToTransaction();
490: }
491:
492: currentTransaction.setAlreadyCommitted();
493: if (currentTransaction.hasChangesOrNotifies()
494: || hasPendingCreateObjects) {
495: if (txMonitor.isEnabled()) {
496: currentTransaction.updateMBean(txMonitor);
497: }
498: remoteTxManager.commit(currentTransaction);
499: }
500: return true;
501: } finally {
502: enableTransactionLogging();
503:
504: // always try to unlock even if we are throwing an exception
505: // if (!isWaitContext && !currentTransaction.isNull()) {
506: // lockManager.unlock(currentTransaction.getLockID());
507: // }
508: if (!isWaitContext && !currentTransaction.isNull()) {
509: if (lockID != null && !lockID.isNull()) {
510: lockManager.unlock(lockID);
511: } else {
512: throw new AssertionError(
513: "Trying to unlock with lockID = null!");
514: }
515: }
516: }
517: }
518:
519: private void basicApply(Collection objectChanges, Map newRoots,
520: boolean force) throws DNAException {
521:
522: List l = new LinkedList();
523:
524: for (Iterator i = objectChanges.iterator(); i.hasNext();) {
525: DNA dna = (DNA) i.next();
526: TCObject tcobj = null;
527: Assert.assertTrue(dna.isDelta());
528: try {
529: // This is a major hack to prevent distributed method calls
530: // sent to apps that don't have the right classes from dying
531: // This should be fixed in a better way some day :-)
532: objectManager.getClassFor(Namespace
533: .parseClassNameIfNecessary(dna.getTypeName()),
534: dna.getDefiningLoaderDescription());
535: tcobj = objectManager.lookup(dna.getObjectID());
536: } catch (ClassNotFoundException cnfe) {
537: logger
538: .warn("Could not apply change because class not local:"
539: + dna.getTypeName());
540: continue;
541: }
542: // Important to have a hard reference to the object while we apply
543: // changes so that it doesn't get gc'd on us
544: Object obj = tcobj == null ? null : tcobj.getPeerObject();
545: l.add(obj);
546: if (obj != null) {
547: try {
548: tcobj.hydrate(dna, force);
549: } catch (ClassNotFoundException cnfe) {
550: logger
551: .warn("Could not apply change because class not local:"
552: + cnfe.getMessage());
553: throw new TCClassNotFoundException(cnfe);
554: }
555: }
556: }
557:
558: for (Iterator i = newRoots.entrySet().iterator(); i.hasNext();) {
559: Entry entry = (Entry) i.next();
560: String rootName = (String) entry.getKey();
561: ObjectID newRootID = (ObjectID) entry.getValue();
562: objectManager.replaceRootIDIfNecessary(rootName, newRootID);
563: }
564: }
565:
566: public void receivedAcknowledgement(SessionID sessionID,
567: TransactionID transactionID) {
568: this .remoteTxManager.receivedAcknowledgement(sessionID,
569: transactionID);
570: }
571:
572: public void receivedBatchAcknowledgement(TxnBatchID batchID) {
573: this .remoteTxManager.receivedBatchAcknowledgement(batchID);
574: }
575:
576: public void apply(TxnType txType, List lockIDs,
577: Collection objectChanges, Set lookupObjectIDs, Map newRoots) {
578: // beginNull(TxnType.NORMAL);
579: try {
580: disableTransactionLogging();
581: basicApply(objectChanges, newRoots, false);
582: } finally {
583: // removeTopTransaction();
584: enableTransactionLogging();
585: }
586: }
587:
588: // private void removeTopTransaction() {
589: // this.getThreadTransactionContext().popCurrentTransaction();
590: // }
591: //
592: // private void beginNull(TxnType txType) {
593: // this.beginNull(LockID.NULL_ID, txType);
594: // }
595:
596: // private void beginNull(LockID lockID, TxnType type) {
597: // ClientTransaction current = getTransactionOrNull();
598: // this.pushTxContext(lockID, type);
599: // if (current == null) {
600: // current = txFactory.newNullInstance(lockID, type);
601: // setTransaction(current);
602: // }
603: // }
604:
605: public void literalValueChanged(TCObject source, Object newValue,
606: Object oldValue) {
607: if (isTransactionLoggingDisabled()) {
608: return;
609: }
610:
611: try {
612: disableTransactionLogging();
613:
614: Object pojo = source.getPeerObject();
615: ClientTransaction tx;
616:
617: try {
618: tx = getTransaction(pojo);
619: } catch (UnlockedSharedObjectException usoe) {
620: if (sendErrors) {
621: UnlockedSharedObjectEventContext eventContext = appEventContextFactory
622: .createUnlockedSharedObjectEventContext(
623: pojo, usoe);
624: objectManager
625: .sendApplicationEvent(pojo,
626: new UnlockedSharedObjectEvent(
627: eventContext));
628: }
629:
630: throw usoe;
631: }
632:
633: if (tx.getTransactionType() == TxnType.READ_ONLY) {
634: ReadOnlyException roe = makeReadOnlyException("Failed To Change Value in: "
635: + newValue.getClass().getName());
636:
637: if (sendErrors) {
638: ReadOnlyObjectEventContext eventContext = appEventContextFactory
639: .createReadOnlyObjectEventContext(pojo, roe);
640: objectManager.sendApplicationEvent(pojo,
641: new ReadOnlyObjectEvent(eventContext));
642: }
643:
644: throw roe;
645: }
646:
647: tx.literalValueChanged(source, newValue, oldValue);
648:
649: } finally {
650: enableTransactionLogging();
651: }
652:
653: }
654:
655: public void fieldChanged(TCObject source, String classname,
656: String fieldname, Object newValue, int index) {
657: if (isTransactionLoggingDisabled()) {
658: return;
659: }
660:
661: try {
662: disableTransactionLogging();
663:
664: Object pojo = source.getPeerObject();
665:
666: ClientTransaction tx;
667: try {
668: tx = getTransaction(pojo);
669: } catch (UnlockedSharedObjectException usoe) {
670: if (sendErrors) {
671: UnlockedSharedObjectEventContext eventContext = appEventContextFactory
672: .createUnlockedSharedObjectEventContext(
673: pojo, classname, fieldname, usoe);
674: objectManager
675: .sendApplicationEvent(pojo,
676: new UnlockedSharedObjectEvent(
677: eventContext));
678: }
679:
680: throw usoe;
681: }
682:
683: if (tx.getTransactionType() == TxnType.READ_ONLY) {
684: ReadOnlyException roe = makeReadOnlyException("Failed To Modify Field: "
685: + fieldname + " in " + classname);
686: if (sendErrors) {
687: ReadOnlyObjectEventContext eventContext = appEventContextFactory
688: .createReadOnlyObjectEventContext(pojo,
689: classname, fieldname, roe);
690: objectManager.sendApplicationEvent(pojo,
691: new ReadOnlyObjectEvent(eventContext));
692: }
693: throw roe;
694: }
695:
696: logFieldChanged0(source, classname, fieldname, newValue, tx);
697:
698: if (newValue != null
699: && literalValues.isLiteralInstance(newValue)) {
700: tx.fieldChanged(source, classname, fieldname, newValue,
701: index);
702: } else {
703: if (newValue != null) {
704: objectManager.checkPortabilityOfField(newValue,
705: fieldname, pojo);
706: }
707:
708: TCObject tco = objectManager.lookupOrCreate(newValue);
709:
710: tx.fieldChanged(source, classname, fieldname, tco
711: .getObjectID(), index);
712:
713: // record the reference in this transaction -- This is to solve the race condition of transactions
714: // that reference objects newly "created" in other transactions that may not commit before us
715: if (newValue != null) {
716: tx.createObject(tco);
717: }
718: }
719: } finally {
720: enableTransactionLogging();
721: }
722: }
723:
724: public void arrayChanged(TCObject source, int startPos,
725: Object array, int length) {
726: if (isTransactionLoggingDisabled()) {
727: return;
728: }
729: try {
730: disableTransactionLogging();
731: Object pojo = source.getPeerObject();
732: ClientTransaction tx;
733:
734: try {
735: tx = getTransaction(pojo);
736: } catch (UnlockedSharedObjectException usoe) {
737: if (sendErrors) {
738: UnlockedSharedObjectEventContext eventContext = appEventContextFactory
739: .createUnlockedSharedObjectEventContext(
740: pojo, usoe);
741: objectManager
742: .sendApplicationEvent(pojo,
743: new UnlockedSharedObjectEvent(
744: eventContext));
745: }
746:
747: throw usoe;
748: }
749:
750: if (tx.getTransactionType() == TxnType.READ_ONLY) {
751: ReadOnlyException roe = makeReadOnlyException("Failed To Modify Array: "
752: + pojo.getClass().getName());
753:
754: if (sendErrors) {
755: ReadOnlyObjectEventContext eventContext = appEventContextFactory
756: .createReadOnlyObjectEventContext(pojo, roe);
757: objectManager.sendApplicationEvent(pojo,
758: new ReadOnlyObjectEvent(eventContext));
759: }
760: throw roe;
761:
762: }
763:
764: if (!ClassUtils.isPrimitiveArray(array)) {
765: Object[] objArray = (Object[]) array;
766: for (int i = 0; i < length; i++) {
767:
768: Object element = objArray[i];
769: if (!literalValues.isLiteralInstance(element)) {
770: if (element != null)
771: objectManager.checkPortabilityOfField(
772: element, String.valueOf(i), pojo);
773:
774: TCObject tco = objectManager
775: .lookupOrCreate(element);
776: objArray[i] = tco.getObjectID();
777: // record the reference in this transaction -- This is to solve the race condition of transactions
778: // that reference objects newly "created" in other transactions that may not commit before us
779: if (element != null)
780: tx.createObject(tco);
781: }
782: }
783: }
784:
785: tx.arrayChanged(source, startPos, array, length);
786:
787: } finally {
788: enableTransactionLogging();
789: }
790: }
791:
792: private void logFieldChanged0(TCObject source, String classname,
793: String fieldname, Object newValue, ClientTransaction tx) {
794: if (logger.isDebugEnabled())
795: logger.debug("fieldChanged(source=" + source
796: + ", classname=" + classname + ", fieldname="
797: + fieldname + ", newValue=" + newValue + ", tx="
798: + tx);
799: }
800:
801: public void logicalInvoke(TCObject source, int method,
802: String methodName, Object[] parameters) {
803: if (isTransactionLoggingDisabled()) {
804: return;
805: }
806:
807: try {
808: disableTransactionLogging();
809:
810: Object pojo = source.getPeerObject();
811: ClientTransaction tx;
812:
813: try {
814: tx = getTransaction(pojo);
815: } catch (UnlockedSharedObjectException usoe) {
816: if (sendErrors) {
817: UnlockedSharedObjectEventContext eventContext = appEventContextFactory
818: .createUnlockedSharedObjectEventContext(
819: pojo, usoe);
820: pojo = objectManager
821: .cloneAndInvokeLogicalOperation(pojo,
822: methodName, parameters);
823: objectManager
824: .sendApplicationEvent(pojo,
825: new UnlockedSharedObjectEvent(
826: eventContext));
827: }
828:
829: throw usoe;
830: }
831:
832: if (tx.getTransactionType() == TxnType.READ_ONLY) {
833: ReadOnlyException roe = makeReadOnlyException("Failed Method Call: "
834: + methodName);
835:
836: if (sendErrors) {
837: ReadOnlyObjectEventContext eventContext = appEventContextFactory
838: .createReadOnlyObjectEventContext(pojo, roe);
839: pojo = objectManager
840: .cloneAndInvokeLogicalOperation(pojo,
841: methodName, parameters);
842: objectManager.sendApplicationEvent(pojo,
843: new ReadOnlyObjectEvent(eventContext));
844: }
845: throw roe;
846: }
847:
848: for (int i = 0; i < parameters.length; i++) {
849: Object p = parameters[i];
850: boolean isLiteral = literalValues.isLiteralInstance(p);
851: if (!isLiteral) {
852: if (p != null) {
853: objectManager.checkPortabilityOfLogicalAction(
854: parameters, i, methodName, pojo);
855: }
856:
857: TCObject tco = objectManager.lookupOrCreate(p);
858: parameters[i] = tco.getObjectID();
859: if (p != null) {
860: // record the reference in this transaction -- This is to solve the race condition of transactions
861: // that reference objects newly "created" in other transactions that may not commit before us
862: tx.createObject(tco);
863: }
864: }
865: }
866:
867: tx.logicalInvoke(source, method, parameters, methodName);
868: } finally {
869: enableTransactionLogging();
870: }
871: }
872:
873: private ReadOnlyException makeReadOnlyException(String details) {
874: long vmId = cidProvider.getChannelID().toLong();
875:
876: final ReadOnlyException roe;
877:
878: if (details != null) {
879: roe = new ReadOnlyException(READ_ONLY_TEXT, Thread
880: .currentThread().getName(), vmId, details);
881: } else {
882: roe = new ReadOnlyException(READ_ONLY_TEXT, Thread
883: .currentThread().getName(), vmId);
884: }
885: System.err.println(roe.getMessage());
886: return roe;
887: }
888:
889: private void setTransaction(ClientTransaction tx) {
890: getThreadTransactionContext().setCurrentTransaction(tx);
891: }
892:
893: public void createObject(TCObject source) {
894: getTransaction().createObject(source);
895: }
896:
897: public void createRoot(String name, ObjectID rootID) {
898: getTransaction().createRoot(name, rootID);
899: }
900:
901: public void addReference(TCObject tco) {
902: ClientTransaction txn = getTransactionOrNull();
903: if (txn != null) {
904: txn.createObject(tco);
905: }
906: }
907:
908: public ChannelIDProvider getChannelIDProvider() {
909: return cidProvider;
910: }
911:
912: public void disableTransactionLogging() {
913: ThreadTransactionLoggingStack txnStack = (ThreadTransactionLoggingStack) txnLogging
914: .get();
915: if (txnStack == null) {
916: txnStack = new ThreadTransactionLoggingStack();
917: txnLogging.set(txnStack);
918: }
919: txnStack.increment();
920: }
921:
922: public void enableTransactionLogging() {
923: ThreadTransactionLoggingStack txnStack = (ThreadTransactionLoggingStack) txnLogging
924: .get();
925: Assert.assertNotNull(txnStack);
926: final int size = txnStack.decrement();
927: Assert.assertTrue("size=" + size, size >= 0);
928: }
929:
930: public boolean isTransactionLoggingDisabled() {
931: Object txnStack = txnLogging.get();
932: return (txnStack != null)
933: && (((ThreadTransactionLoggingStack) txnStack).get() > 0);
934: }
935:
936: public static class ThreadTransactionLoggingStack {
937: int callCount = 0;
938:
939: public int increment() {
940: return ++callCount;
941: }
942:
943: public int decrement() {
944: return --callCount;
945: }
946:
947: public int get() {
948: return callCount;
949: }
950: }
951:
952: public void addDmiDescriptor(DmiDescriptor dd) {
953: getTransaction().addDmiDescritor(dd);
954: }
955:
956: private static final String READ_ONLY_TEXT = "Current transaction with read-only access attempted to modify a shared object. "
957: + "\nPlease alter the locks section of your Terracotta configuration so that the methods involved in this transaction have read/write access.";
958:
959: }
|