Project

General

Profile

scopeable_6650_patch_20220906a.patch

Constantin Asofiei, 09/06/2022 02:23 PM

Download (171 KB)

View differences:

new/src/com/goldencode/p2j/main/StandardServer.java 2022-09-06 18:22:04 +0000
196 196
**     CA  20220405          Added authentication and authorization for web requests.  When this is enabled, 
197 197
**                           the target API call will be executed under the authenticated FWD context, and not 
198 198
**                           the agent's context.
199
**     CA  20220901          Refactored scope notification support: ScopeableFactory was removed, and the 
200
**                           registration is now specific to each type of scopeable.  For each case, the block
201
**                           will be registered for scope support (for that particular scopeable) only when
202
**                           the scopeable is 'active' (i.e. unnamed streams or accumulators are used).  This
203
**                           allows a lazy registration of scopeables, to avoid the unnecessary overhead of
204
**                           processing all the scopeables for each and every block.
205
**     CA  20220906          Moved the interactive client cleaner to LogicalTerminal.registerCleaner().
199 206
*/
200 207

  
201 208
/*
......
1166 1173
   /**
1167 1174
    * Register default services:
1168 1175
    * <ol>
1169
    * <li> {@link SharedVariableManager} </li>
1170
    * <li> {@link UnnamedStreams} </li>
1171
    * <li> {@link LogicalTerminal} </li>
1172 1176
    * <li> {@link Persistence} </li>
1173
    * <li> {@link AccumulatorManager} </li>
1174 1177
    * <li> {@link WebServer} </li>
1175 1178
    * <li> custom account extension plugin </li>
1176 1179
    * </ol>
......
1237 1240
         @Override
1238 1241
         public void initialize()
1239 1242
         {
1240
            SharedVariableManager.initialize();
1241
         }
1242
      });
1243

  
1244
      serverHooks.add(new AbstractInitTermListener()
1245
      {
1246
         @Override
1247
         public void initialize()
1248
         {
1249
            UnnamedStreams.initialize();
1250
         }
1251
      });
1252
      
1253
      serverHooks.add(new AbstractInitTermListener()
1254
      {
1255
         @Override
1256
         public void initialize()
1257
         {
1258
            LogicalTerminal.initialize();
1259
         }
1260
      });
1261
      
1262
      serverHooks.add(new AbstractInitTermListener()
1263
      {
1264
         @Override
1265
         public void initialize()
1266
         {
1267 1243
            ServerKeyStore.initialize();
1268 1244
         }
1269 1245
      });
......
1309 1285
            }
1310 1286
         }
1311 1287
      });
1312
      
1313
      serverHooks.add(new AbstractInitTermListener()
1314
      {
1315
         @Override
1316
         public void initialize()
1317
         {
1318
            AccumulatorManager.initialize();
1319
         }
1320
      });
1321
      
1288

  
1322 1289
      // register controller for method execution tracing
1323 1290
      serverHooks.add(new AbstractInitTermListener()
1324 1291
      {
......
1712 1679
                                   false,
1713 1680
                                   false);
1714 1681
      
1682
      // make sure that the global block gets a special notification - but only for non-appserver sessions.
1683
      LogicalTerminal.registerCleaner();
1684
      
1715 1685
      Object result = null;
1716 1686
      try
1717 1687
      {
new/src/com/goldencode/p2j/persist/AdaptiveQuery.java 2022-09-06 18:22:04 +0000
329 329
**                           DmoMeta to avoid duplicating their collection and storage.
330 330
**     OM  20220726          Added recordAlreadyFound flag for block next/prev iterations on unique
331 331
**                           find-by-rowid queries.
332
**     CA  20220901          Refactored scope notification support: ScopeableFactory was removed, and the 
333
**                           registration is now specific to each type of scopeable.  For each case, the block
334
**                           will be registered for scope support (for that particular scopeable) only when
335
**                           the scopeable is 'active' (i.e. unnamed streams or accumulators are used).  This
336
**                           allows a lazy registration of scopeables, to avoid the unnecessary overhead of
337
**                           processing all the scopeables for each and every block.
332 338
*/
333 339

  
334 340
/*
......
1075 1081
   {
1076 1082
      closed = false;
1077 1083
      
1078
      int scope = TransactionManager.findNearestExternal();
1084
      int scope = BufferManager.get().findNearestExternal();
1079 1085
      RecordBuffer[] buffers = getRecordBuffers();
1080 1086
      
1081 1087
      for (RecordBuffer buffer : buffers)
new/src/com/goldencode/p2j/persist/BufferManager.java 2022-09-06 18:22:04 +0000
444 444
**                           were added to the collection during iterating it, resulting in a 
445 445
**                           ConcurrentModificationException (refs #6356). 
446 446
**     CA  20220707          Cache the converted Java names for the dynamic buffers.
447
**     CA  20220901          Refactored scope notification support: ScopeableFactory was removed, and the 
448
**                           registration is now specific to each type of scopeable.  For each case, the block
449
**                           will be registered for scope support (for that particular scopeable) only when
450
**                           the scopeable is 'active' (i.e. unnamed streams or accumulators are used).  This
451
**                           allows a lazy registration of scopeables, to avoid the unnecessary overhead of
452
**                           processing all the scopeables for each and every block.
453
**     CA  20220906          Removed state which is no longer used (PersistentProcScope.loadedBuffers and
454
**                           undoData) and removed methods which are not being invoked.
455
**                           Moved the parameter assigner scopeable support to AbstractParameter (as all 
456
**                           parameters require scope support, not just fields).
457
**                           Extracted all BufferManager state which requires notification for 
458
**                           'isImportantBlock' to an external TxWrapper class (including the private class 
459
**                           here), to allow the scopeable BufferManager state related only to buffers (and  
460
**                           not transaction related) to be registered for scope notifications in a lazy 
461
**                           manner, when buffers are accessed/used/opened.
447 462
*/
448 463

  
449 464
/*
......
512 527
import com.goldencode.p2j.oo.lang.*;
513 528
import com.goldencode.p2j.persist.dirty.*;
514 529
import com.goldencode.p2j.persist.id.*;
515
import com.goldencode.p2j.persist.lock.*;
516
import com.goldencode.p2j.persist.orm.*;
517 530
import com.goldencode.p2j.security.*;
518 531
import com.goldencode.p2j.util.*;
519 532
import com.goldencode.p2j.util.LogHelper;
520 533
import com.goldencode.p2j.util.ErrorManager;
521 534
import com.goldencode.util.*;
535
import com.goldencode.util.Stack;
522 536

  
523 537
/**
524 538
 * Manages all record buffers within the current user context.  This includes
......
553 567
 */
554 568
public final class BufferManager
555 569
implements Scopeable,
556
           Commitable,
557 570
           BatchListener,
558
           StopConditionVetoHandler,
559
           Resettable
571
           StopConditionVetoHandler
560 572
{
561 573
   /** Logger */
562 574
   private static final Logger LOG = LogHelper.getLogger(BufferManager.class.getName());
......
568 580
      @Override protected BufferManager initialValue() { return (new BufferManager()); }
569 581
   };
570 582
   
571
   /** Flag indicating whether class was initialized, i.e. {@link #initialize} was called */
572
   private static boolean initialized = false;
573
   
574 583
   /** A cache of buffer-related fields defined in a specific legacy-converted class. */
575 584
   private static final Map<Class<?>, Set<Field>> classBufferFields = new ConcurrentHashMap<>();
576 585
   
......
578 587
   // intentionally package private for direct use from RecordBuffer
579 588
   final Map<BufferType, MutableInteger> changeScopes = new HashMap<>();
580 589
   
581
   /** Transaction finalizable to manage transaction state locally (avoid context-local calls) */
582
   private final Finalizable xactFin = new Finalizable()
583
   {
584
      @Override public void finished() { transactionDepth = -1; }
585
      @Override public void deleted()  { }
586
      @Override public void iterate()  { }
587
      @Override public void retry()    { }
588
   };
589
   
590 590
   /** Finalizable which clears batch mode at the appropriate times */
591 591
   private final Finalizable batchCleaner = new Finalizable()
592 592
   {
......
595 595
      @Override public void iterate()  { RecordBuffer.cleanupBatchMode(BufferManager.this); }
596 596
      @Override public void retry()    { RecordBuffer.cleanupBatchMode(BufferManager.this); }
597 597
   };
598
   
599
   /** Field assigner for function/procedure parameter assign-backs */
600
   private final Scopeable fieldScopes = FieldAssigner.getScopeable();
601
   
598

  
602 599
   /** The buffers which will be registered on the next scope entry. */
603 600
   private final Set<RecordBuffer> pendingBuffers = Collections.newSetFromMap(new IdentityHashMap<>());
604 601
   
......
622 619
    */
623 620
   private final ScopedDictionary<Object, Map<String, RecordBuffer>> byLegacyName = new ScopedDictionary<>();
624 621
   
625
   /**
626
    * Scoped dictionary which key set contains all buffers into which a new record was loaded (or
627
    * the old record was reloaded) into corresponding scopes. Value set doesn't contain any useful
628
    * information and is used as a temporary data storage during copying from one scope level to
629
    * another.
630
    */
631
   private final ScopedDictionary<RecordBuffer, Boolean> loadedBuffers = new ScopedDictionary<>();
632
   
633 622
   /** Buffers whose scopes have been opened, but are not yet initialized */
634 623
   private final Map<RecordBuffer, Boolean> uninitializedOpenBuffers = new WeakHashMap<>();
635 624
   
......
637 626
   private final ScopedList<RecordBuffer> openBuffers = new ScopedList<>();
638 627
   
639 628
   /**
640
    * The buffers touched via a Create, Update or Delete operation, in each scope. If the full transaction
641
    * block is not {@link #commit committed} or {@link #rollback rolled back}, then the buffers will be merged 
642
    * in the previous scope. 
643
    */
644
   private final TailScopedDictionary<RecordBuffer, Boolean> dirtyBuffers = new TailScopedDictionary<>();
645
   
646
   /**
647 629
    * Scoped dictionary which contains currently active static temp tables (i.e. table scope was
648 630
    * opened and not yet closed). Keyed by a {@code TempTableKey} compound from the legacy table
649 631
    * name and the procedure where the temp table was defined.
......
662 644
   private final ScopedDictionary<OutputTableHandleCopier, Integer> thOutputParameters =
663 645
      new ScopedDictionary<>();
664 646
   
665
   /** Database to transaction wrapper instance map */
666
   private final Map<Database, TxWrapper> txWrapperMap = new HashMap<>();
667
   
668
   /** Transaction wrappers not yet activated */
669
   private final Map<Database, TxWrapper> inactiveTxWrappers = new HashMap<>();
647
   /** Track the external scopes pushed on {@link BufferManager}. */
648
   private final Stack<Boolean> externalScope = new Stack<>();
670 649
   
671 650
   /** Active persistence contexts (those which have an open database session) */
672 651
   private final Set<Persistence.Context> activePersistenceContexts = Collections.newSetFromMap(new IdentityHashMap<>());
673 652
   
674
   /** All undoable actions scoped by block and keyed by buffer type */
675
   private final ScopedDictionary<BufferType, UndoData> undoData = new ScopedDictionary<>();
676
   
677 653
   /** Stack of batch mode data */
678 654
   private final ArrayDeque<BatchModeData> batchModeStack = new ArrayDeque<>();
679 655
   
......
686 662
   /** Helper to use the ProcedureManager without any context local lookups. */
687 663
   private final ProcedureManager.ProcedureHelper pm = ProcedureManager.getProcedureHelper();
688 664
   
689
   /** Counter of in-use buffers for each database. */
690
   private final Map<Database, MutableInteger> activeDatabases = new HashMap<>();
691
   
692 665
   /** Transaction helper */
693 666
   private final TransactionManager.TransactionHelper txHelper;
694 667
   
695
   /** Block depth at which full application transaction began, or -1 if not in a transaction */
696
   private int transactionDepth = -1;
668
   /** Tx wrapper helper. */
669
   private TxWrapper.TxWrapperHelper txWrapperHelper;
670
   
671
   /** Change broker instance. */
672
   private final ChangeBroker changeBroker;
697 673
   
698 674
   /** Connection manager */
699 675
   private ConnectionManager connMgr = null;
700 676
   
701
   /** Context-local access to unique constraint tracker */
702
   private UniqueTracker.Context uniqueTrackerCtx = UniqueTracker.getContext();
703
   
704 677
   /** Number of nested queries being processed */
705 678
   private int queryDepth = 0;
706 679
   
707
   /** Counter of the number of scope transitions (push or pop) */
708
   private long scopeTransitions = 0L;
709
   
710
   /** Counter of the number of application-level transactions */
711
   private long transactionCount = 0L;
712
   
713 680
   /** Map of bound buffers to there destination, for each procedure. */
714 681
   private Map<TemporaryBuffer, Map<Object, Set<TemporaryBuffer>>> bindingBuffers = new IdentityHashMap<>();
715 682
   
......
732 699
   private BufferManager()
733 700
   {
734 701
      byLegacyName.setIdentityKeys(true);
735
      loadedBuffers.setIdentityKeys(true);
736
      dirtyBuffers.setIdentityKeys(true);
737 702
      thOutputParameters.setIdentityKeys(true);
738 703
      
739 704
      txHelper = TransactionManager.getTransactionHelper();
740 705
      
741 706
      txHelper.registerBatchListener(this);
742 707
      txHelper.registerStopVetoHandler(this);
743
      txHelper.registerResettable(this);
708
      
709
      changeBroker = ChangeBroker.get();
744 710
      
745 711
      if (LOG.isLoggable(Level.FINE))
746 712
      {
......
749 715
   }
750 716
   
751 717
   /**
752
    * Retrieve the context-local instance of this class, instantiating it
753
    * first if necessary.
718
    * Retrieve the context-local instance of this class, instantiating it first if necessary.
754 719
    *
755 720
    * @return  Context-local instance of this class.
756
    * @throws  IllegalStateException
757
    *            when {@link BufferManager} is not activated, i.e. {@link
758
    *            #initialize()} is not called.
759 721
    */
760 722
   public static BufferManager get()
761 723
   {
762
      if (!BufferManager.initialized)
763
      {
764
         throw new IllegalStateException(
765
            "An attempt to use uninitialized BufferManager. Is persistence activated?");
766
      }
767
      
768 724
      BufferManager bm = context.get();
769 725
      if (bm.connMgr == null)
770 726
      {
......
779 735
            throw new RuntimeException("Multiple ConnectionManager in same context.");
780 736
         }
781 737
      }
738
      
739
      if (bm.txWrapperHelper == null)
740
      {
741
         bm.txWrapperHelper = TxWrapper.getHelper(bm);
742
      }
743
      
782 744
      return bm;
783 745
   }
784 746
   
785 747
   /**
748
    * Register the {@link BufferManager} instance as for scope notifications at the current block.
749
    * 
750
    * @param    bufferManager
751
    *           The {@link BufferManager} instance.
752
    */
753
   public static void registerScopeable(BufferManager bufferManager)
754
   {
755
      if (bufferManager == null)
756
      {
757
         bufferManager = get();
758
      }
759
      
760
      // both ChangeBroker and BufferManager need to be added
761
      bufferManager.txHelper.registerBlockScopeable(bufferManager);
762
      bufferManager.txHelper.registerBlockScopeable(bufferManager.changeBroker);
763
   }
764

  
765
   /**
766
    * Register the {@link BufferManager} instance as pending scopeable, to be added to the next block.
767
    * 
768
    * @param    bufferManager
769
    *           The {@link BufferManager} instance.
770
    */
771
   public static void registerPendingScopeable(BufferManager bufferManager)
772
   {
773
      if (bufferManager == null)
774
      {
775
         bufferManager = get();
776
      }
777
      
778
      // both ChangeBroker and BufferManager need to be added
779
      bufferManager.txHelper.registerPendingScopeable(bufferManager);
780
      bufferManager.txHelper.registerPendingScopeable(bufferManager.changeBroker);
781
   }
782
   
783
   /**
784
    * Register the {@link TxWrapper} scopeable as pending for the next opened block.
785
    * 
786
    * @param    bufferManager
787
    *           The {@link BufferManager} instance.
788
    */
789
   public static void registerTxScopeable(BufferManager bufferManager)
790
   {
791
      if (bufferManager == null)
792
      {
793
         bufferManager = get();
794
      }
795
      
796
      bufferManager.txWrapperHelper.registerPendingScopeable();
797
   }
798
   
799
   /**
786 800
    * Cleanup of pending resources. This is called when the external procedure constructor failed 
787 801
    * to build a new object because of an exception thrown in a member initialization (shared 
788 802
    * frame/table).
......
794 808
      bm.pendingBufferClasses.clear();
795 809
      bm.pendingStaticTempTables.clear();
796 810
   }
797
   
798
   /**
799
    * Register with the {@link com.goldencode.p2j.util.TransactionManager
800
    * TransactionManager} a factory object which creates instances of this
801
    * class, so that they can be registered to receive notifications of
802
    * runtime scope start and finish events.
803
    * <p>
804
    * This method should be invoked once during the server bootstrap phase.
805
    */
806
   static void initialize()
807
   {
808
      TransactionManager.registerScopeableFactory(BufferManager::get);
809
      BufferManager.initialized = true;
810
   }
811
   
811

  
812 812
   /**
813 813
    * Register this buffer as 'dirty' in the current scope.
814 814
    * <p>
......
821 821
    */
822 822
   public void registerDirtyBuffer(RecordBuffer recordBuffer)
823 823
   {
824
      dirtyBuffers.addEntry(false, recordBuffer, false);
824
      // although no state in BufferManager is touched when registering a dirty buffer, we need to register
825
      // it with the current block, as there is logic dependent on the 'depth' of the application stack which
826
      // uses buffers
827
      registerScopeable(this);
828
      
829
      txWrapperHelper.registerDirtyBuffer(recordBuffer);
825 830
   }
826 831
   
827 832
   /**
......
912 917
   }
913 918
   
914 919
   /**
915
    * A full transaction or sub-transaction is committing. Notify all dirty buffers in the current scope.
916
    * 
917
    * @param   transaction
918
    *          {@code true} if full transaction; {@code false} if sub-transaction.
919
    */
920
   @Override
921
   public void commit(boolean transaction)
922
   {
923
      Map<RecordBuffer, Boolean> buffers = dirtyBuffers.getDictionaryAtScope(0, false);
924
      if (buffers != null && !buffers.isEmpty())
925
      {
926
         for (RecordBuffer buf : buffers.keySet())
927
         {
928
            buf.commit(transaction);
929
         }
930
         if (transaction)
931
         {
932
            buffers.clear();
933
         }
934
      }
935
   }
936
   
937
   /**
938
    * A full transaction or sub-transaction is rolling back. Notify all dirty buffers in the current scope.
939
    * 
940
    * @param   transaction
941
    *          {@code true} if full transaction; {@code false} if sub-transaction.
942
    */
943
   @Override
944
   public void rollback(boolean transaction)
945
   {
946
      Map<RecordBuffer, Boolean> buffers = dirtyBuffers.getDictionaryAtScope(0, false);
947
      if (buffers != null && !buffers.isEmpty())
948
      {
949
         for (RecordBuffer buf : buffers.keySet())
950
         {
951
            buf.rollback(transaction);
952
         }
953
         if (transaction)
954
         {
955
            buffers.clear();
956
         }
957
      }
958
   }
959

  
960
   /**
961
    * A block is exiting or iterating normally within a transaction. Notify all dirty buffers in the current
962
    * scope.
963
    * 
964
    * @param   transaction
965
    *          {@code true} if full transaction; {@code false} if sub-transaction.
966
    * @param   aggressiveFlush
967
    *          {@code true} if transaction manager is in aggressive subtransaction
968
    *          flush mode, indicating that any transient buffers should be validated and
969
    *          flushed, regardless of other state.
970
    */
971
   @Override
972
   public void validate(boolean transaction, boolean aggressiveFlush)
973
   {
974
      TailMap<RecordBuffer, Boolean> buffers = dirtyBuffers.getDictionaryAtScope(0, false);
975
      if (buffers != null && !buffers.isEmpty())
976
      {
977
         ArrayList<Map<RecordBuffer, Boolean>> validatedAddedDirtyBuffers = null;
978
         try (AutoCloseable itersafe = buffers.flagIterating())
979
         {
980
            for (RecordBuffer buf : buffers.keySet())
981
            {
982
               buf.validate(transaction, aggressiveFlush);
983
               
984
               // The next block is rarely entered, only in edge cases.
985
               // Details:
986
               // The invocation of buf.validate can (for example) result in
987
               // an ABL publish + subscribe (from an OEDB write trigger),
988
               // resulting in more buffers that need to be validated.
989
               // Since we are iterating, the extra buffers are added to
990
               // a standalone 'tail' collection. We validate them immediately,
991
               // and add them to dirtyBuffers after iterating has completed.
992
               Map<RecordBuffer, Boolean> addedDirtyBuffers = buffers.pop();
993
               while (addedDirtyBuffers != null)
994
               {
995
                  // Validate all additions
996
                  addedDirtyBuffers.keySet().forEach((v) -> v.validate(transaction, aggressiveFlush));
997
                  
998
                  // Store the validated additions to add them afterwards
999
                  // A list is used for clarity and to facilitate possible
1000
                  // removal logic in the future (which can't happen at the moment).
1001
                  if (validatedAddedDirtyBuffers == null)
1002
                  {
1003
                     validatedAddedDirtyBuffers = new ArrayList<>();
1004
                  }
1005
                  validatedAddedDirtyBuffers.add(addedDirtyBuffers);
1006
                  
1007
                  // Fetch possible new additions of the added validate invocations.
1008
                  // We take control of this collection, at the TailMap it
1009
                  // is forgotten.
1010
                  addedDirtyBuffers = buffers.pop();
1011
               }
1012
            }
1013
         }
1014
         catch (Exception e)
1015
         {
1016
            throw new IllegalStateException("Exception at dirtyBuffers.flagIterating block", e);
1017
         }
1018
         
1019
         // The next block is rarely entered, only in edge cases, see above.
1020
         if (validatedAddedDirtyBuffers != null)
1021
         {
1022
            for (Map<RecordBuffer, Boolean> bufmap : validatedAddedDirtyBuffers)
1023
            {
1024
               // Note: addEntry(false, ...) because:
1025
               //       We do not need the global scope.
1026
               //       At the top of this method, scope 0 is used, not scope -1 (global).
1027
               bufmap.forEach((k,v) -> dirtyBuffers.addEntry(false, k, v));
1028
            }
1029
         }
1030
         
1031
         // do not clear the buffers here, only commit/rollback can clear it
1032
      }
1033
   }
1034
   
1035
   /**
1036 920
    * Query the scope level which currently is open. This is the number of &quot;important&quot; scopes;
1037 921
    * NO_TRANSACTION blocks inside an application transaction are ignored. So, this number may be lower
1038 922
    * than the actual number of nested block scopes open, as tracked by the {@code TransactionManager}.
......
1045 929
   }
1046 930
   
1047 931
   /**
932
    * Find the nearest external block (from the bottom of the stack).
933
    * 
934
    * @return   See above.
935
    */
936
   public int findNearestExternal()
937
   {
938
      int idx = externalScope.size() - 1;
939
      while (idx > 0)
940
      {
941
         if (externalScope.get(idx))
942
         {
943
            return idx + 1;
944
         }
945
         
946
         idx = idx - 1;
947
      }
948
      
949
      return -1;
950
   }
951
   
952
   /**
1048 953
    * Check the specified exception and determine whether we want to allow it
1049 954
    * to be honored by the {@link TransactionManager}, or whether we want to
1050 955
    * veto the STOP at this scope.
......
1107 1012
    */
1108 1013
   public void beginTx(boolean inTx, boolean fullTx, int blockDepth)
1109 1014
   {
1110
      if (inTx)
1111
      {
1112
         if (fullTx)
1113
         {
1114
            // create a database xaction wrapper for each connected database, but do not
1115
            // activate it yet
1116
            txWrapperMap.clear();         // should be empty already
1117
            inactiveTxWrappers.clear();   // should be empty already
1118
            List<Database> databases = connMgr.getActiveDatabases();
1119
            for (Database database : databases)
1120
            {
1121
               createTxWrapper(database);
1122
            }
1123
            
1124
            transactionDepth = blockDepth - 1;  // match TM transaction level
1125
            transactionCount++;
1126
            
1127
            // register for master transaction finish/iterate to clear transaction depth
1128
            txHelper.registerTransactionFinish(xactFin, true);
1129
         }
1130
         
1131
         // register various services for callbacks if current block has transaction properties
1132
         if (txHelper.currentTransactionLevel() != TransactionManager.NO_TRANSACTION)
1133
         {
1134
            // register savepoint manager for callbacks at full and sub-transaction blocks
1135
            for (TxWrapper txw : txWrapperMap.values())
1136
            {
1137
               txw.registerSavepointHooks(fullTx);
1138
            }
1139
            
1140
            // register to get Commitable notifications for open buffers
1141
            txHelper.registerCommitAt(blockDepth - 1, this);
1142
            
1143
            // register unique tracker context to get Commitable/Finalizable notifications
1144
            uniqueTrackerCtx.register(txHelper, blockDepth - 1);
1145
         }
1146
      }
1147
      
1148
      scopeTransitions++;
1149
      
1150
      if (fullTx)
1151
      {
1152
         // activate a pending TxWrapper for any database with a buffer that already is open
1153
         Iterator<Database> iter = activeDatabases.keySet().iterator();
1154
         while (iter.hasNext())
1155
         {
1156
            Database database = iter.next();
1157
            maybeActivateTxWrapper(database);
1158
         }
1159
      }
1015
      txWrapperHelper.beginTx(inTx, fullTx, blockDepth);
1160 1016
   }
1161 1017
   
1162 1018
   /**
......
1169 1025
    */
1170 1026
   public void endTx(boolean inTx, boolean fullTx)
1171 1027
   {
1172
      scopeTransitions++;
1028
      txWrapperHelper.endTx(inTx, fullTx);
1173 1029
   }
1174 1030
   
1175 1031
   /**
......
1178 1034
   public void endTxPost()
1179 1035
   {
1180 1036
      // if any DBs were used in this tx block and remained connected, disconnect now
1181
      connMgr.transactionEnded();
1037
      txWrapperHelper.endTxPost();
1182 1038
   }
1183 1039
   
1184 1040
   /**
......
1197 1053
   @Override
1198 1054
   public void scopeStart()
1199 1055
   {
1200
      if (!isImportantBlockTransition())
1201
      {
1202
         // nothing to do
1203
         return;
1204
      }
1205
      
1206 1056
      BlockType bt = txHelper.getBlockType();
1207 1057
      if (bt.equals(BlockType.INTERNAL_PROC) ||
1208 1058
          bt.equals(BlockType.FUNCTION)      ||
......
1228 1078
      
1229 1079
      allBuffers.addScope(null);
1230 1080
      byLegacyName.addScope(null);
1231
      loadedBuffers.addScope(null);
1232
      dirtyBuffers.addScope(null);
1233 1081
      openBuffers.pushScope();
1234 1082
      openStaticTempTables.addScope(null);
1235 1083
      thOutputParameters.addScope(null);
1236
      undoData.addScope(null);
1237
      fieldScopes.scopeStart();
1084
      externalScope.push(txHelper.isExternalBlock());
1238 1085
      
1239 1086
      BatchModeData crtBatchScope = batchModeStack.peek();
1240 1087
      if (crtBatchScope != null && crtBatchScope.batchDepth == BatchModeData.INTERNAL)
......
1287 1134
         LOG.log(Level.FINER,
1288 1135
                 Utils.describeContext() + ":  block scope " + blockDepth + " started");
1289 1136
      }
1290
      
1291
      boolean inTx = txHelper.isTransaction();
1292
      boolean fullTx = inTx && txHelper.isFullTransaction();
1293
      
1294
      beginTx(inTx, fullTx, blockDepth);
1295 1137
   }
1296 1138
   
1297 1139
   /**
......
1301 1143
   @Override
1302 1144
   public void scopeFinished()
1303 1145
   {
1304
      if (!isImportantBlockTransition())
1305
      {
1306
         // nothing to do
1307
         return;
1308
      }
1309
      
1310 1146
      PersistentProcScope ppScope = null;
1311 1147
      
1312 1148
      if (!txHelper.isGlobalBlock())
......
1327 1163
         }
1328 1164
      }
1329 1165
      
1330
      // here we will be out of sync with the TM on a transaction boundary, because our
1331
      // transaction depth is cleared with a transaction finalizable, which is called before this
1332
      // scope finished notification, so we rely on the TM.isTransaction() instead of our own
1333
      boolean inTx = txHelper.isTransaction();
1334
      boolean fullTx = txHelper.isFullTransaction();
1335
      
1336
      endTx(inTx, fullTx);
1337
      
1338
      Map<RecordBuffer, Boolean> dirtyBufs = dirtyBuffers.getDictionaryAtScope(0, false);
1339
      dirtyBuffers.deleteScope();
1340
      if (dirtyBufs != null && !dirtyBufs.isEmpty() && dirtyBuffers.size() > 1)
1341
      {
1342
         // do not merge into the global block, as all changes should have been already committed or rolled
1343
         // back.  this is required as temporary buffers can be changed without an active transaction present
1344
         // on the stack: in this case, they will be pushed to the previous scope until the global block is
1345
         // reached, when they will be discarded.
1346
         
1347
         for (RecordBuffer buffer : dirtyBufs.keySet())
1348
         {
1349
            // merge into previous scope, but only buffers with records which were changed, as the record 
1350
            // associated (at initial dirty registration of the buffer) may have been flushed to the database,
1351
            // and the current record may not be 'dirty'
1352
            if (buffer.getCurrentRecord() != null)
1353
            {
1354
               Record record = buffer.getCurrentRecord();
1355
               if (buffer.isTouched() || 
1356
                   record.checkState(DmoState.NEW)      ||
1357
                   record.checkState(DmoState.STALE)    ||
1358
                   record.checkState(DmoState.DELETING) ||
1359
                   record.checkState(DmoState.DELETED)  ||
1360
                   record.checkState(DmoState.INVALID)  ||
1361
                   record.checkState(DmoState.CHANGED)  ||
1362
                   record.checkState(DmoState.TRACKED))
1363
               {
1364
                  dirtyBuffers.addEntry(false, buffer, false);
1365
               }
1366
            }
1367
         }
1368
      }
1369
      undoData.deleteScope();
1370
      
1371
      // Copy the set of buffers contained in the current scope of the
1372
      // loadedBuffers (which is about to be removed) to the enclosing scope.
1373
      // Only buffers which are open at the enclosing or a higher scope
1374
      // should be copied.
1375
      if (loadedBuffers.size() > 1)
1376
      {
1377
         Set<RecordBuffer> currentLoadedBuffers = loadedBuffers.getDictionaryAtScope(0, false).keySet();
1378
         
1379
         Collection<RecordBuffer> currentOpenBuffers = openBuffers.sublistAtScope(0);
1380
         
1381
         for (RecordBuffer buffer : currentLoadedBuffers)
1382
         {
1383
            if (buffer.getOpenScopeCount() > 1 || !currentOpenBuffers.contains(buffer))
1384
            {
1385
               // if this buffer is missing at the current scope of open
1386
               // buffers or it presents there but has open scope count > 1,
1387
               // that means that it presents at a higher level and it should
1388
               // be copied to the enclosing scope
1389
               loadedBuffers.setValueAtScope(buffer, 1, false);
1390
               buffer.addLoadedBuffersScope(loadedBuffers.size() - 1);
1391
            }
1392
         }
1393
      }
1394 1166
      if (ppScope != null)
1395 1167
      {
1396 1168
         // save the data for this external procedure, as these will need to be removed from the
1397 1169
         // global scope, when the procedure gets deleted.
1398
         Map<RecordBuffer, Boolean> thisLoadedBuffers = loadedBuffers.getDictionaryAtScope(0, false);
1399
         ppScope.loadedBuffers.putAll(thisLoadedBuffers);
1400 1170
         
1401 1171
         List<RecordBuffer> thisOpenBuffers = openBuffers.sublistAtScope(0);
1402 1172
         ppScope.openBuffers.addAll(thisOpenBuffers);
......
1411 1181
         ppScope.openStaticTempTables.putAll(thisOpenStaticTempTables);
1412 1182
         
1413 1183
         // add all these to the global scope, too
1414
         
1415
         if (!thisLoadedBuffers.isEmpty())
1416
         {
1417
            loadedBuffers.getDictionaryAtScope(loadedBuffers.size() - 1, true)
1418
                         .putAll(thisLoadedBuffers);
1419
         }
1420
         
1184

  
1421 1185
         if (!thisOpenBuffers.isEmpty())
1422 1186
         {
1423 1187
            openBuffers.addAll(true, thisOpenBuffers);
......
1448 1212
         }
1449 1213
      }
1450 1214
      
1451
      loadedBuffers.deleteScope();
1452 1215
      openBuffers.popScope();
1453 1216
      allBuffers.deleteScope();
1454 1217
      byLegacyName.deleteScope();
1455
      fieldScopes.scopeFinished();
1456 1218
      openStaticTempTables.deleteScope();
1457 1219
      thOutputParameters.deleteScope();
1220
      externalScope.pop();
1458 1221
      BatchModeData bmd = batchModeStack.pop();
1459 1222
      if (bmd.batchDepth == BatchModeData.INTERNAL)
1460 1223
      {
......
1467 1230
                 Utils.describeContext() + ":  block scope " + txHelper.getNestingLevel() + " finished");
1468 1231
      }
1469 1232
      
1470
      if (fullTx)
1471
      {
1472
         endTxPost();
1473
      }
1474
      
1475 1233
      maybeCloseSessions();
1476 1234
   }
1477 1235
   
......
1479 1237
    * Provides notification that the external procedure scope has been deleted.
1480 1238
    * <p>
1481 1239
    * If we are deleting a persistent procedure, clean all its data from the global scope for the
1482
    * following dictionaries: {@link #loadedBuffers}, {@link #openBuffers}, {@link #allBuffers}, 
1483
    * {@link #openStaticTempTables}.
1240
    * following dictionaries: {@link #openBuffers}, {@link #allBuffers}, {@link #openStaticTempTables}.
1484 1241
    */
1485 1242
   @Override
1486 1243
   public void scopeDeleted()
......
1502 1259
      {
1503 1260
         // remove all these from the global scope
1504 1261
         
1505
         if (!scope.loadedBuffers.isEmpty())
1506
         {
1507
            loadedBuffers.getDictionaryAtScope(loadedBuffers.size() - 1, true)
1508
                         .keySet()
1509
                         .removeAll(scope.loadedBuffers.keySet());
1510
         }
1511
         
1512 1262
         if (!scope.openBuffers.isEmpty())
1513 1263
         {
1514 1264
            openBuffers.removeAllFrom(true, scope.openBuffers);
......
1536 1286
   }
1537 1287
   
1538 1288
   /**
1289
    * Get the {@link ScopeId} for the instance.
1290
    * 
1291
    * @return   {@link ScopeId#BUFFER_MANAGER}.
1292
    */
1293
   @Override
1294
   public ScopeId getScopeId()
1295
   {
1296
      return ScopeId.BUFFER_MANAGER;
1297
   }
1298
   
1299
   /**
1539 1300
    * Provides a notification that a batch is starting or ending.  Enters or
1540 1301
    * exits batch assignment mode for all record buffers in this context,
1541 1302
    * depending on the value of <code>start</code>.
......
1560 1321
   }
1561 1322
   
1562 1323
   /**
1563
    * Releases the buffers into which a new record was loaded (or the old
1564
    * record was reloaded) into the current or deeper scopes, or only clears
1565
    * the list of buffers pending to be released.
1566
    *
1567
    * @param clearOnly
1568
    *        If <code>true</code> then clear the list of buffers pending to be
1569
    *        released, otherwise perform release of these buffers and clear
1570
    *        the list after that.
1571
    */
1572
   public void resetState(boolean clearOnly)
1573
   {
1574
      Map<RecordBuffer, Boolean> buffers = loadedBuffers.getDictionaryAtScope(0, false);
1575
      if (buffers == null || buffers.isEmpty())
1576
      {
1577
         return;
1578
      }
1579
      
1580
      if (!clearOnly)
1581
      {
1582
         for (RecordBuffer buffer : buffers.keySet())
1583
         {
1584
            if (!buffer.isPendingRollbackProcessed())
1585
            {
1586
               buffer.release(false, false);
1587
            }
1588
            else
1589
            {
1590
               buffer.resetPendingRollbackProcessed();
1591
            }
1592
         }
1593
      }
1594
      
1595
      buffers.clear();
1596
   }
1597
   
1598
   /**
1599 1324
    * Marks the table for postponed delete, if it is possible. Postponed delete can take place if
1600 1325
    * the table is used as [INPUT-]OUTPUT TABLE-HANDLE parameter.
1601 1326
    *
......
1648 1373
    */
1649 1374
   public boolean isTransaction()
1650 1375
   {
1651
      return transactionDepth >= 0;
1652
   }
1653
   
1654
   /**
1655
    * Check whether the block at the specified block depth is within an application-level
1656
    * transaction.
1657
    * 
1658
    * @param   level
1659
    *          Zero-based block depth (counting from the outermost scope).
1660
    * 
1661
    * @return  {@code true} if we are in a transaction, else {@code false}.
1662
    */
1663
   public boolean isTransactionAt(int level)
1664
   {
1665
      return transactionDepth >= 0 && transactionDepth <= level;
1666
   }
1667
   
1668
   /**
1669
    * Check whether the current block marks (and is within) the boundary of an application-level
1670
    * full transaction.
1671
    * 
1672
    * @return  {@code true} if we are at (and within) a full transaction boundary, else {@code
1673
    *          false}.
1674
    */
1675
   public boolean isFullTransaction()
1676
   {
1677
      return transactionDepth >= 0 && getOpenBufferScopes() - 1 == transactionDepth;
1678
   }
1679
   
1680
   /**
1681
    * Get the zero-based block depth (counting from the outermost scope) at which the current,
1682
    * full, application-level transaction (if any) began.
1683
    * 
1684
    * @return  Zero-based block depth of current, full transaction, or -1 if we are not currently
1685
    *          within a transaction.
1686
    */
1687
   public int getFullTransactionBlock()
1688
   {
1689
      return transactionDepth;
1376
      return txWrapperHelper.isTransaction();
1690 1377
   }
1691 1378
   
1692 1379
   /**
......
1699 1386
    */
1700 1387
   public long getScopeTransitions()
1701 1388
   {
1702
      return scopeTransitions;
1389
      return txWrapperHelper.getScopeTransitions();
1390
   }
1391
   
1392
   /**
1393
    * Get the open buffers from the current scope.
1394
    * 
1395
    * @return   See above.
1396
    */
1397
   Collection<RecordBuffer> getOpenBuffers()
1398
   {
1399
      return openBuffers.scopes() == 0 ? Collections.EMPTY_LIST : openBuffers.sublistAtScope(0);
1703 1400
   }
1704 1401
   
1705 1402
   /**
......
1709 1406
    */
1710 1407
   long getTransactionCount()
1711 1408
   {
1712
      return transactionCount;
1409
      return txWrapperHelper.getTransactionCount();
1713 1410
   }
1714 1411
   
1715 1412
   /**
......
1752 1449
         throw new UnsupportedOperationException(
1753 1450
           "A buffer can't be bound to more than one table in the same program!");
1754 1451
      }
1452

  
1755 1453
      dstBuffers.add(dstBuf);
1756 1454
      
1757 1455
      bindBuffer(dstBuf, srcBuf);
......
1870 1568
   }
1871 1569
   
1872 1570
   /**
1873
    * Register an undoable which is responsible for setting the buffer's 
1874
    * proper current record after all reversibles are processed.
1875
    * 
1876
    * @param   buffer
1877
    *          Record buffer for which a change is being tracked for undo
1878
    *          purposes.  It is assumed the buffer has been initialized.
1879
    * @param   undoTarget
1880
    *          The undoable responsible with setting the buffer back to its
1881
    *          proper record.
1882
    */
1883
   void registerBufferUndoable(RecordBuffer buffer, Undoable undoTarget)
1884
   {
1885
      Map<BufferType, UndoData> dict = undoData.getDictionaryAtScope(0, true);
1886
      
1887
      RecordBuffer master = buffer.getMasterBuffer();
1888
      BufferType key = master.getBufferType();
1889
      
1890
      if (dict != null)
1891
      {
1892
         UndoData ud = dict.get(key);
1893
         if (ud == null)
1894
         {
1895
            ud = new UndoData();
1896
            dict.put(key, ud);
1897
         }
1898
         
1899
         ud.bufferUndoable = undoTarget.deepCopy();
1900
      }
1901
   }
1902
   
1903
   /**
1904 1571
    * Register a newly defined record buffer in the current scope.  Buffers are deregistered
1905 1572
    * implicitly when the scope in which they were registered ends.
1906 1573
    *
......
1937 1604
    */
1938 1605
   void registerPending(Class<?> def, RecordBuffer buffer)
1939 1606
   {
1607
      registerPendingScopeable(this);
1608
      
1940 1609
      pendingBuffers.add(buffer);
1941 1610
      
1942 1611
      registerPendingBufferClass(buffer, def);
......
1983 1652
      
1984 1653
      int[] allBufferScopes = buffer.getAllBuferScopes();
1985 1654
      int[] byLegacyNameScopes = buffer.getByLegacyNameScopes();
1986
      int[] loadedBuffersScopes = buffer.getLoadedBuffersScope();
1987 1655
      int[] openBuffersScopes = buffer.getOpenBuffersScope();
1988 1656
      
1989 1657
      int scopeSize = allBuffers.size();
......
2029 1697
      // remove from global scope always
2030 1698
      // openBuffers.removeFrom(openScopes - 1, buffer);
2031 1699
      
2032
      int loadedScopes = loadedBuffers.size();
2033
      for (int scope : loadedBuffersScopes)
2034
      {
2035
         int depth = loadedScopes - scope;
2036
         Map<RecordBuffer, Boolean> loaded = loadedBuffers.getDictionaryAtScope(depth, false);
2037
         if (loaded != null)
2038
         {
2039
            loaded.remove(buffer);
2040
         }
2041
      }
2042
      // remove from global scope always
2043
      Map<RecordBuffer, Boolean> loaded = loadedBuffers.getDictionaryAtScope(-1, false);
2044
      if (loaded != null)
2045
      {
2046
         loaded.remove(buffer);
2047
      }
1700
      txWrapperHelper.deregisterDynamicBuffer(buffer);
2048 1701
      
2049 1702
      if (referent != null)
2050 1703
      {
......
2053 1706
         {
2054 1707
            procScope.allBuffers.remove(key);
2055 1708
            byLegacy.accept(procScope.byLegacyName);
2056
            procScope.loadedBuffers.remove(buffer);
2057 1709
            procScope.openBuffers.remove(buffer);
2058 1710
         }
2059 1711
      }
......
2082 1734
    */
2083 1735
   void startBatchAssignMode(boolean internal)
2084 1736
   {
1737
      registerScopeable(this);
1738
      
2085 1739
      int currentScope = getOpenBufferScopes();
2086 1740
      BatchModeData bmd = batchModeStack.peek();
2087 1741
      
......
2382 2036
   }
2383 2037
   
2384 2038
   /**
2385
    * Remove all <code>Reversible</code> objects associated with the given buffer from their
2386
    * respective scoped dictionaries.
2387
    * 
2388
    * @param   buffer
2389
    *          Buffer associated with the <code>Reversible</code>s to be removed.
2390
    */
2391
   void removeAllReversibles(TemporaryBuffer buffer)
2392
   {
2393
      // delete entries from current and parent scopes
2394
      BufferType bufType = buffer.getMasterBuffer().getBufferType();
2395
      undoData.removeEntryThroughScope(bufType, 1);
2396
   }
2397
   
2398
   /**
2399 2039
    * Called by {@link RecordBuffer} when a new buffer scope is opened. It registers the open scope with the
2400 2040
    * buffer manager, so that the buffer can receive block scope start and stop notifications, and be
2401 2041
    * registered for commit/rollback/validate processing when a full transaction is begun.
......
2457 2097
    */
2458 2098
   void registerPendingStaticTempTable(StaticTempTable table, boolean global)
2459 2099
   {
2100
      registerPendingScopeable(this);
2101

  
2460 2102
      pendingStaticTempTables.put(table, global);
2461 2103
   }
2462 2104
   
......
2658 2300
    */
2659 2301
   void openScopeAt(int openScopeDepth, RecordBuffer buffer)
2660 2302
   {
2303
      // TODO: this seems to be called only for 'openScopeDepth == 0'.
2304

  
2661 2305
      if (buffer.isActive())
2662 2306
      {
2663 2307
         if (openScopeDepth == 0)
......
2759 2403
    */
2760 2404
   void maybeActivateTxWrapper(Database database)
2761 2405
   {
2762
      TxWrapper txw = inactiveTxWrappers.remove(database);
2763
      if (txw != null)
2764
      {
2765
         txw.activate();
2766
      }
2406
      txWrapperHelper.maybeActivateTxWrapper(database);
2767 2407
   }
2768 2408
   
2769 2409
   /**
......
2932 2572
    */
2933 2573
   void notifyRecordWasLoaded(RecordBuffer recordBuffer)
2934 2574
   {
2935
      if (recordBuffer.getCurrentRecord() != null && !recordBuffer.isDynamic())
2936
      {
2937
         pm.notifyStaticRecordWasLoaded(recordBuffer);
2938
      }
2939
      
2940
      if (recordBuffer.getCurrentRecord() == null || recordBuffer.isTemporary())
2941
      {
2942
         return;
2943
      }
2944
      
2945
      loadedBuffers.addEntry(false, recordBuffer, false);
2946
      recordBuffer.addLoadedBuffersScope(loadedBuffers.size());
2575
      txWrapperHelper.notifyRecordWasLoaded(recordBuffer);
2947 2576
   }
2948 2577
   
2949 2578
   /**
......
3035 2664
   }
3036 2665
   
3037 2666
   /**
3038
    * Create an inactive transaction wrapper for the given database. It will be activated the
3039
    * first time an open buffer scope is detected or when a new buffer scope is opened, at which
3040
    * time a database-level transaction will be opened.
3041
    * 
3042
    * @param   database
3043
    *          Database for which this transaction wrapper is created.
3044
    * 
3045
    * @return  Transaction wrapper object.
3046
    */
3047
   private TxWrapper createTxWrapper(Database database)
3048
   {
3049
      TxWrapper txw = new TxWrapper(database);
3050
      txWrapperMap.put(database, txw);
3051
      inactiveTxWrappers.put(database, txw);
3052
      
3053
      return txw;
3054
   }
3055
   
3056
   /**
3057
    * Compose a message suitable for logging.  Reports open buffer scopes and
3058
    * context information in addition to a custom message.
3059
    *
3060
    * @param   persistence
3061
    *          Persistence object which will provide context info.
3062
    * @param   text
3063
    *          Custom message text.
3064
    *
3065
    * @return  Composed message text.
3066
    */
3067
   private String message(Persistence persistence, String text)
3068
   {
3069
      return persistence.message("scope " + getOpenBufferScopes() + ": " + text);
3070
   }
3071
   
3072
   /**
3073 2667
    * Add the source buffers to the destination map.
3074 2668
    * 
3075 2669
    * @param    dst
......
3103 2697
    */
3104 2698
   private void trackDatabase(RecordBuffer buffer, boolean add)
3105 2699
   {
3106
      if (add)
3107
      {
3108
         Database database = buffer.getDatabase();
3109
         MutableInteger count = activeDatabases.get(database);
3110
         if (count == null)
3111
         {
3112
            activeDatabases.put(database, count = new MutableInteger(0));
3113
         }
3114
         count.set(count.get() + 1);
3115
      }
3116
      else if (buffer.isActive() && buffer.getOpenScopeCount() > 0)
3117
      {
3118
         Database database = buffer.getDatabase();
3119
         MutableInteger count = activeDatabases.get(database);
3120
         if (count == null)
3121
         {
3122
            throw new IllegalStateException("Tracking for " + database + " is unbalanced!");
3123
         }
3124
         
3125
         if (count.get() == 1)
3126
         {
3127
            activeDatabases.remove(database);
3128
         }
3129
         else
3130
         {
3131
            count.set(count.get() - 1);
3132
         }
3133
      }
2700
      txWrapperHelper.trackDatabase(buffer, add);
3134 2701
   }
3135 2702
   
3136 2703
   /**
......
3147 2714
    * @return  {@code true} if the block is considered &quot;important&quot; by the above criteria, else
3148 2715
    *          {@code false}.
3149 2716
    */
3150
   private boolean isImportantBlockTransition()
3151
   {
3152
      return !txHelper.isTransaction() ||
3153
             txHelper.currentTransactionLevel() != TransactionManager.NO_TRANSACTION;
3154
   }
3155
   
3156
   /**
3157
    * Storage mechanism for maps of reversible/undoable actions, keyed by
3158
    * primary key.  One instance of this class holds all such objects for a
3159
    * single buffer in a single scope.
3160
    */
3161
   private static class UndoData
3162
   {
3163
      /** Undoable which will set a no-undo buffer to its proper record after a rollback. */
3164
      Undoable bufferUndoable;
3165
   }
3166
   
3167
   /**
3168
    * An object which manages the "master" {@code Commitable} and {@code Finalizable} events of an
3169
    * application transaction. It creates a database transaction to correspond with the application
3170
    * transaction, and as it receives events from the {@code TransactionManager}, it delegates related
3171
    * responsibilities to various components of the persistence framework.
3172
    */
3173
   private class TxWrapper
3174
   implements Finalizable,
3175
              Commitable
3176
   {
3177
      /** Backing database */
3178
      private final Database database;
3179
      
3180
      /** Dirty share context */
3181
      private final DirtyShareContext dirtyContext;
3182
      
3183
      /** Savepoint manager (can be {@code null} */
3184
      private final SavepointManager savepointManager;
3185
      
3186
      /** Persistence context, for access to database session and transaction */
3187
      private Persistence.Context persistenceCtx;
3188
      
3189
      /** Roll back the transaction when block finishes, if not previously acted upon */
3190
      private boolean autoRollback = false;
3191
      
3192
      /** {@code true} for an initial transaction only until the first commit or rollback */
3193
      private boolean initialTx = false;
3194
      
3195
      /**
3196
       * The sole constructor.
3197
       *
3198
       * @param   database
3199
       *          The {@code Database} where the transaction takes place.
3200
       */
3201
      TxWrapper(Database database)
3202
      {
3203
         this.database = database;
3204
         // Because the BufferManager manages all record buffers within the current user context, we cannot
3205
         // separate the DIRTY-READ from the normal tables at this moment. Since this instance is only used
3206
         // for final cleanup, we acquire the context for the DIRTY-READ database so that context will be
3207
         // cleaned up correctly. The non-DIRTY-READ information will be ignored.
3208
         this.dirtyContext = DirtyShareFactory.getContextInstance(database);
3209
         this.savepointManager =
3210
            (DatabaseManager.TEMP_TABLE_DB.equals((database)) && DatabaseManager.FORCE_NO_UNDO_TEMP_TABLES)
3211
            ? null
3212
            : new SavepointManager();
3213
         
3214
         // register for master transaction commit/rollback
3215
         txHelper.registerTransactionCommit(this, true);
3216
         
3217
         // register for master transaction finish/iterate
3218
         txHelper.registerTransactionFinish(this, true);
3219
      }
3220
      
3221
      /**
3222
       * Implementation of {@code Finalizable} interface: provides a notification that the block whose scope
3223
       * in which the object is registered is about to iterate and attempt another pass.  This provides an
3224
       * opportunity to preserve state from the previous pass and make preparations for the coming pass.
3225
       */
3226
      @Override
3227
      public void iterate()
3228
      {
3229
         if (!isActive())
3230
         {
3231
            return;
3232
         }
3233
         
3234
         if (LOG.isLoggable(Level.FINE))
3235
         {
3236
            LOG.log(Level.FINE, message(persistenceCtx.getPersistence(), "master transaction iterate"));
3237
         }
3238
         
3239
         end();
3240
         begin();
3241
      }
3242
      
3243
      /**
3244
       * Implementation of {@code Finalizable} interface: provides a notification that the block whose scope
3245
       * in which the object is registered is about to retry the same iteration (all loop control data is
3246
       * unchanged). This provides an opportunity to clear or reset state from the previous pass.
3247
       */
3248
      @Override
3249
      public void retry()
3250
      {
3251
         if (!isActive())
3252
         {
3253
            return;
3254
         }
3255
         
3256
         if (LOG.isLoggable(Level.FINE))
3257
         {
3258
            LOG.log(Level.FINE,
3259
                    message(persistenceCtx.getPersistence(), "master transaction retry"));
3260
         }
3261
         
3262
         end();
3263
         begin();
3264
      }
3265
      
3266
      /**
3267
       * Implementation of {@code Finalizable} interface: Provides a notification that the scope in which the
3268
       * object is registered is ending and the object's reference may or may not be lost after this method
3269
       * is called. This provides a natural and standard mechanism to clean-up any resources that may need
3270
       * to be closed explicitly such as operating system files or sockets.
3271
       */
3272
      @Override
3273
      public void finished()
3274
      {
3275
         try
3276
         {
3277
            if (autoRollback)
3278
            {
3279
               if (isActive() && LOG.isLoggable(Level.FINE))
3280
               {
3281
                  LOG.log(Level.FINE,
3282
                          message(persistenceCtx.getPersistence(), "triggering auto-rollback"));
3283
               }
3284
               
3285
               rollback(true);
3286
            }
3287
         }
3288
         finally
3289
         {
3290
            if (isActive() && LOG.isLoggable(Level.FINE))
3291
            {
3292
               LOG.log(Level.FINE,
3293
                       message(persistenceCtx.getPersistence(), "master transaction finished"));
3294
            }
3295
            
3296
            txWrapperMap.remove(database);
3297
            
3298
            if (!isActive())
3299
            {
3300
               inactiveTxWrappers.remove(database);
3301
               
3302
               return;
3303
            }
3304
            
3305
            end();
3306
         }
3307
      }
3308
      
3309
      /**
3310
       * Implementation of {@code Finalizable} interface: provides a notification that the external program
3311
       * in which the object is registered is being deleted and the object's reference may be lost after this
3312
       * method is called.
3313
       */
3314
      @Override
3315
      public void deleted()
3316
      {
3317
         // no-op
3318
      }
3319
      
3320
      /**
3321
       * Implementation of {@code Commitable} interface: saves any temporary state to the persistent form or
3322
       * storage that is associated with this class.
3323
       *
3324
       * @param    transaction
3325
       *           {@code true} if this is a full transaction and {@code false} if this is only a
3326
       *           sub-transaction (a nested scope with transaction support).
3327
       */
3328
      @Override
3329
      public void commit(boolean transaction)
3330
      {
3331
         if (!isActive())
3332
         {
3333
            return;
3334
         }
3335
         
3336
         if (LOG.isLoggable(Level.FINE))
3337
         {
3338
            LOG.log(Level.FINE,
3339
                    message(persistenceCtx.getPersistence(), "master transaction commit"));
3340
         }
3341
         
3342
         try
3343
         {
3344
            initialTx = false;
3345
            autoRollback = false;
3346
            persistenceCtx.commit();
3347
         }
3348
         catch (PersistenceException exc)
3349
         {
3350
            if (LOG.isLoggable(Level.SEVERE))
3351
            {
3352
               LOG.log(Level.SEVERE,
3353
                       message(persistenceCtx.getPersistence(), "commit error"), exc);
3354
            }
3355
            
3356
            // No good way to recover from this.  Let it abend.
3357
            throw new RuntimeException(exc);
3358
         }
3359
         finally
3360
         {
3361
            // Reclaim any primary keys which had been waiting to be recycled,
3362
            // pending a commit.
3363
            if (DatabaseManager.TEMP_TABLE_DB.equals(database))
3364
            {
3365
               Commitable ctx = TemporaryBuffer.getContextCommitable();
3366
               ctx.commit(transaction);
3367
            }
3368
         }
3369
      }
3370
      
3371
      /**
3372
       * Implementation of {@code Commitable} interface: notifies that a rollback has just occurred (all state
3373
       * maintained through the {@link Undoable} interface has been already rolled back). This notification
3374
       * can be used to implement cleanup of any custom state that needs to be maintained in this case. It can
3375
       * also be used to implement a rollback that is completely independent of the {@code Undoable}
3376
       * (externally driven) interface.
3377
       *
3378
       * @param    transaction
3379
       *           {@code true} if this is a full transaction and {@code false} if this is only a
3380
       *           sub-transaction (a nested scope with transaction support).
3381
       */
3382
      @Override
3383
      public void rollback(boolean transaction)
3384
      {
3385
         if (!isActive())
3386
         {
... This diff was truncated because it exceeds the maximum size that can be displayed.