=== modified file 'src/com/goldencode/p2j/main/StandardServer.java'
--- old/src/com/goldencode/p2j/main/StandardServer.java	2022-05-16 07:25:30 +0000
+++ new/src/com/goldencode/p2j/main/StandardServer.java	2022-09-06 18:22:04 +0000
@@ -196,6 +196,13 @@
 **     CA  20220405          Added authentication and authorization for web requests.  When this is enabled, 
 **                           the target API call will be executed under the authenticated FWD context, and not 
 **                           the agent's context.
+**     CA  20220901          Refactored scope notification support: ScopeableFactory was removed, and the 
+**                           registration is now specific to each type of scopeable.  For each case, the block
+**                           will be registered for scope support (for that particular scopeable) only when
+**                           the scopeable is 'active' (i.e. unnamed streams or accumulators are used).  This
+**                           allows a lazy registration of scopeables, to avoid the unnecessary overhead of
+**                           processing all the scopeables for each and every block.
+**     CA  20220906          Moved the interactive client cleaner to LogicalTerminal.registerCleaner().
 */
 
 /*
@@ -1166,11 +1173,7 @@
    /**
     * Register default services:
     * <ol>
-    * <li> {@link SharedVariableManager} </li>
-    * <li> {@link UnnamedStreams} </li>
-    * <li> {@link LogicalTerminal} </li>
     * <li> {@link Persistence} </li>
-    * <li> {@link AccumulatorManager} </li>
     * <li> {@link WebServer} </li>
     * <li> custom account extension plugin </li>
     * </ol>
@@ -1237,33 +1240,6 @@
          @Override
          public void initialize()
          {
-            SharedVariableManager.initialize();
-         }
-      });
-
-      serverHooks.add(new AbstractInitTermListener()
-      {
-         @Override
-         public void initialize()
-         {
-            UnnamedStreams.initialize();
-         }
-      });
-      
-      serverHooks.add(new AbstractInitTermListener()
-      {
-         @Override
-         public void initialize()
-         {
-            LogicalTerminal.initialize();
-         }
-      });
-      
-      serverHooks.add(new AbstractInitTermListener()
-      {
-         @Override
-         public void initialize()
-         {
             ServerKeyStore.initialize();
          }
       });
@@ -1309,16 +1285,7 @@
             }
          }
       });
-      
-      serverHooks.add(new AbstractInitTermListener()
-      {
-         @Override
-         public void initialize()
-         {
-            AccumulatorManager.initialize();
-         }
-      });
-      
+
       // register controller for method execution tracing
       serverHooks.add(new AbstractInitTermListener()
       {
@@ -1712,6 +1679,9 @@
                                    false,
                                    false);
       
+      // make sure that the global block gets a special notification - but only for non-appserver sessions.
+      LogicalTerminal.registerCleaner();
+      
       Object result = null;
       try
       {

=== modified file 'src/com/goldencode/p2j/persist/AdaptiveQuery.java'
--- old/src/com/goldencode/p2j/persist/AdaptiveQuery.java	2022-07-25 23:00:26 +0000
+++ new/src/com/goldencode/p2j/persist/AdaptiveQuery.java	2022-09-06 18:22:04 +0000
@@ -329,6 +329,12 @@
 **                           DmoMeta to avoid duplicating their collection and storage.
 **     OM  20220726          Added recordAlreadyFound flag for block next/prev iterations on unique
 **                           find-by-rowid queries.
+**     CA  20220901          Refactored scope notification support: ScopeableFactory was removed, and the 
+**                           registration is now specific to each type of scopeable.  For each case, the block
+**                           will be registered for scope support (for that particular scopeable) only when
+**                           the scopeable is 'active' (i.e. unnamed streams or accumulators are used).  This
+**                           allows a lazy registration of scopeables, to avoid the unnecessary overhead of
+**                           processing all the scopeables for each and every block.
 */
 
 /*
@@ -1075,7 +1081,7 @@
    {
       closed = false;
       
-      int scope = TransactionManager.findNearestExternal();
+      int scope = BufferManager.get().findNearestExternal();
       RecordBuffer[] buffers = getRecordBuffers();
       
       for (RecordBuffer buffer : buffers)

=== modified file 'src/com/goldencode/p2j/persist/BufferManager.java'
--- old/src/com/goldencode/p2j/persist/BufferManager.java	2022-07-07 16:14:44 +0000
+++ new/src/com/goldencode/p2j/persist/BufferManager.java	2022-09-06 18:22:04 +0000
@@ -444,6 +444,21 @@
 **                           were added to the collection during iterating it, resulting in a 
 **                           ConcurrentModificationException (refs #6356). 
 **     CA  20220707          Cache the converted Java names for the dynamic buffers.
+**     CA  20220901          Refactored scope notification support: ScopeableFactory was removed, and the 
+**                           registration is now specific to each type of scopeable.  For each case, the block
+**                           will be registered for scope support (for that particular scopeable) only when
+**                           the scopeable is 'active' (i.e. unnamed streams or accumulators are used).  This
+**                           allows a lazy registration of scopeables, to avoid the unnecessary overhead of
+**                           processing all the scopeables for each and every block.
+**     CA  20220906          Removed state which is no longer used (PersistentProcScope.loadedBuffers and
+**                           undoData) and removed methods which are not being invoked.
+**                           Moved the parameter assigner scopeable support to AbstractParameter (as all 
+**                           parameters require scope support, not just fields).
+**                           Extracted all BufferManager state which requires notification for 
+**                           'isImportantBlock' to an external TxWrapper class (including the private class 
+**                           here), to allow the scopeable BufferManager state related only to buffers (and  
+**                           not transaction related) to be registered for scope notifications in a lazy 
+**                           manner, when buffers are accessed/used/opened.
 */
 
 /*
@@ -512,13 +527,12 @@
 import com.goldencode.p2j.oo.lang.*;
 import com.goldencode.p2j.persist.dirty.*;
 import com.goldencode.p2j.persist.id.*;
-import com.goldencode.p2j.persist.lock.*;
-import com.goldencode.p2j.persist.orm.*;
 import com.goldencode.p2j.security.*;
 import com.goldencode.p2j.util.*;
 import com.goldencode.p2j.util.LogHelper;
 import com.goldencode.p2j.util.ErrorManager;
 import com.goldencode.util.*;
+import com.goldencode.util.Stack;
 
 /**
  * Manages all record buffers within the current user context.  This includes
@@ -553,10 +567,8 @@
  */
 public final class BufferManager
 implements Scopeable,
-           Commitable,
            BatchListener,
-           StopConditionVetoHandler,
-           Resettable
+           StopConditionVetoHandler
 {
    /** Logger */
    private static final Logger LOG = LogHelper.getLogger(BufferManager.class.getName());
@@ -568,9 +580,6 @@
       @Override protected BufferManager initialValue() { return (new BufferManager()); }
    };
    
-   /** Flag indicating whether class was initialized, i.e. {@link #initialize} was called */
-   private static boolean initialized = false;
-   
    /** A cache of buffer-related fields defined in a specific legacy-converted class. */
    private static final Map<Class<?>, Set<Field>> classBufferFields = new ConcurrentHashMap<>();
    
@@ -578,15 +587,6 @@
    // intentionally package private for direct use from RecordBuffer
    final Map<BufferType, MutableInteger> changeScopes = new HashMap<>();
    
-   /** Transaction finalizable to manage transaction state locally (avoid context-local calls) */
-   private final Finalizable xactFin = new Finalizable()
-   {
-      @Override public void finished() { transactionDepth = -1; }
-      @Override public void deleted()  { }
-      @Override public void iterate()  { }
-      @Override public void retry()    { }
-   };
-   
    /** Finalizable which clears batch mode at the appropriate times */
    private final Finalizable batchCleaner = new Finalizable()
    {
@@ -595,10 +595,7 @@
       @Override public void iterate()  { RecordBuffer.cleanupBatchMode(BufferManager.this); }
       @Override public void retry()    { RecordBuffer.cleanupBatchMode(BufferManager.this); }
    };
-   
-   /** Field assigner for function/procedure parameter assign-backs */
-   private final Scopeable fieldScopes = FieldAssigner.getScopeable();
-   
+
    /** The buffers which will be registered on the next scope entry. */
    private final Set<RecordBuffer> pendingBuffers = Collections.newSetFromMap(new IdentityHashMap<>());
    
@@ -622,14 +619,6 @@
     */
    private final ScopedDictionary<Object, Map<String, RecordBuffer>> byLegacyName = new ScopedDictionary<>();
    
-   /**
-    * Scoped dictionary which key set contains all buffers into which a new record was loaded (or
-    * the old record was reloaded) into corresponding scopes. Value set doesn't contain any useful
-    * information and is used as a temporary data storage during copying from one scope level to
-    * another.
-    */
-   private final ScopedDictionary<RecordBuffer, Boolean> loadedBuffers = new ScopedDictionary<>();
-   
    /** Buffers whose scopes have been opened, but are not yet initialized */
    private final Map<RecordBuffer, Boolean> uninitializedOpenBuffers = new WeakHashMap<>();
    
@@ -637,13 +626,6 @@
    private final ScopedList<RecordBuffer> openBuffers = new ScopedList<>();
    
    /**
-    * The buffers touched via a Create, Update or Delete operation, in each scope. If the full transaction
-    * block is not {@link #commit committed} or {@link #rollback rolled back}, then the buffers will be merged 
-    * in the previous scope. 
-    */
-   private final TailScopedDictionary<RecordBuffer, Boolean> dirtyBuffers = new TailScopedDictionary<>();
-   
-   /**
     * Scoped dictionary which contains currently active static temp tables (i.e. table scope was
     * opened and not yet closed). Keyed by a {@code TempTableKey} compound from the legacy table
     * name and the procedure where the temp table was defined.
@@ -662,18 +644,12 @@
    private final ScopedDictionary<OutputTableHandleCopier, Integer> thOutputParameters =
       new ScopedDictionary<>();
    
-   /** Database to transaction wrapper instance map */
-   private final Map<Database, TxWrapper> txWrapperMap = new HashMap<>();
-   
-   /** Transaction wrappers not yet activated */
-   private final Map<Database, TxWrapper> inactiveTxWrappers = new HashMap<>();
+   /** Track the external scopes pushed on {@link BufferManager}. */
+   private final Stack<Boolean> externalScope = new Stack<>();
    
    /** Active persistence contexts (those which have an open database session) */
    private final Set<Persistence.Context> activePersistenceContexts = Collections.newSetFromMap(new IdentityHashMap<>());
    
-   /** All undoable actions scoped by block and keyed by buffer type */
-   private final ScopedDictionary<BufferType, UndoData> undoData = new ScopedDictionary<>();
-   
    /** Stack of batch mode data */
    private final ArrayDeque<BatchModeData> batchModeStack = new ArrayDeque<>();
    
@@ -686,30 +662,21 @@
    /** Helper to use the ProcedureManager without any context local lookups. */
    private final ProcedureManager.ProcedureHelper pm = ProcedureManager.getProcedureHelper();
    
-   /** Counter of in-use buffers for each database. */
-   private final Map<Database, MutableInteger> activeDatabases = new HashMap<>();
-   
    /** Transaction helper */
    private final TransactionManager.TransactionHelper txHelper;
    
-   /** Block depth at which full application transaction began, or -1 if not in a transaction */
-   private int transactionDepth = -1;
+   /** Tx wrapper helper. */
+   private TxWrapper.TxWrapperHelper txWrapperHelper;
+   
+   /** Change broker instance. */
+   private final ChangeBroker changeBroker;
    
    /** Connection manager */
    private ConnectionManager connMgr = null;
    
-   /** Context-local access to unique constraint tracker */
-   private UniqueTracker.Context uniqueTrackerCtx = UniqueTracker.getContext();
-   
    /** Number of nested queries being processed */
    private int queryDepth = 0;
    
-   /** Counter of the number of scope transitions (push or pop) */
-   private long scopeTransitions = 0L;
-   
-   /** Counter of the number of application-level transactions */
-   private long transactionCount = 0L;
-   
    /** Map of bound buffers to there destination, for each procedure. */
    private Map<TemporaryBuffer, Map<Object, Set<TemporaryBuffer>>> bindingBuffers = new IdentityHashMap<>();
    
@@ -732,15 +699,14 @@
    private BufferManager()
    {
       byLegacyName.setIdentityKeys(true);
-      loadedBuffers.setIdentityKeys(true);
-      dirtyBuffers.setIdentityKeys(true);
       thOutputParameters.setIdentityKeys(true);
       
       txHelper = TransactionManager.getTransactionHelper();
       
       txHelper.registerBatchListener(this);
       txHelper.registerStopVetoHandler(this);
-      txHelper.registerResettable(this);
+      
+      changeBroker = ChangeBroker.get();
       
       if (LOG.isLoggable(Level.FINE))
       {
@@ -749,22 +715,12 @@
    }
    
    /**
-    * Retrieve the context-local instance of this class, instantiating it
-    * first if necessary.
+    * Retrieve the context-local instance of this class, instantiating it first if necessary.
     *
     * @return  Context-local instance of this class.
-    * @throws  IllegalStateException
-    *            when {@link BufferManager} is not activated, i.e. {@link
-    *            #initialize()} is not called.
     */
    public static BufferManager get()
    {
-      if (!BufferManager.initialized)
-      {
-         throw new IllegalStateException(
-            "An attempt to use uninitialized BufferManager. Is persistence activated?");
-      }
-      
       BufferManager bm = context.get();
       if (bm.connMgr == null)
       {
@@ -779,10 +735,68 @@
             throw new RuntimeException("Multiple ConnectionManager in same context.");
          }
       }
+      
+      if (bm.txWrapperHelper == null)
+      {
+         bm.txWrapperHelper = TxWrapper.getHelper(bm);
+      }
+      
       return bm;
    }
    
    /**
+    * Register the {@link BufferManager} instance as for scope notifications at the current block.
+    * 
+    * @param    bufferManager
+    *           The {@link BufferManager} instance.
+    */
+   public static void registerScopeable(BufferManager bufferManager)
+   {
+      if (bufferManager == null)
+      {
+         bufferManager = get();
+      }
+      
+      // both ChangeBroker and BufferManager need to be added
+      bufferManager.txHelper.registerBlockScopeable(bufferManager);
+      bufferManager.txHelper.registerBlockScopeable(bufferManager.changeBroker);
+   }
+
+   /**
+    * Register the {@link BufferManager} instance as pending scopeable, to be added to the next block.
+    * 
+    * @param    bufferManager
+    *           The {@link BufferManager} instance.
+    */
+   public static void registerPendingScopeable(BufferManager bufferManager)
+   {
+      if (bufferManager == null)
+      {
+         bufferManager = get();
+      }
+      
+      // both ChangeBroker and BufferManager need to be added
+      bufferManager.txHelper.registerPendingScopeable(bufferManager);
+      bufferManager.txHelper.registerPendingScopeable(bufferManager.changeBroker);
+   }
+   
+   /**
+    * Register the {@link TxWrapper} scopeable as pending for the next opened block.
+    * 
+    * @param    bufferManager
+    *           The {@link BufferManager} instance.
+    */
+   public static void registerTxScopeable(BufferManager bufferManager)
+   {
+      if (bufferManager == null)
+      {
+         bufferManager = get();
+      }
+      
+      bufferManager.txWrapperHelper.registerPendingScopeable();
+   }
+   
+   /**
     * Cleanup of pending resources. This is called when the external procedure constructor failed 
     * to build a new object because of an exception thrown in a member initialization (shared 
     * frame/table).
@@ -794,21 +808,7 @@
       bm.pendingBufferClasses.clear();
       bm.pendingStaticTempTables.clear();
    }
-   
-   /**
-    * Register with the {@link com.goldencode.p2j.util.TransactionManager
-    * TransactionManager} a factory object which creates instances of this
-    * class, so that they can be registered to receive notifications of
-    * runtime scope start and finish events.
-    * <p>
-    * This method should be invoked once during the server bootstrap phase.
-    */
-   static void initialize()
-   {
-      TransactionManager.registerScopeableFactory(BufferManager::get);
-      BufferManager.initialized = true;
-   }
-   
+
    /**
     * Register this buffer as 'dirty' in the current scope.
     * <p>
@@ -821,7 +821,12 @@
     */
    public void registerDirtyBuffer(RecordBuffer recordBuffer)
    {
-      dirtyBuffers.addEntry(false, recordBuffer, false);
+      // although no state in BufferManager is touched when registering a dirty buffer, we need to register
+      // it with the current block, as there is logic dependent on the 'depth' of the application stack which
+      // uses buffers
+      registerScopeable(this);
+      
+      txWrapperHelper.registerDirtyBuffer(recordBuffer);
    }
    
    /**
@@ -912,127 +917,6 @@
    }
    
    /**
-    * A full transaction or sub-transaction is committing. Notify all dirty buffers in the current scope.
-    * 
-    * @param   transaction
-    *          {@code true} if full transaction; {@code false} if sub-transaction.
-    */
-   @Override
-   public void commit(boolean transaction)
-   {
-      Map<RecordBuffer, Boolean> buffers = dirtyBuffers.getDictionaryAtScope(0, false);
-      if (buffers != null && !buffers.isEmpty())
-      {
-         for (RecordBuffer buf : buffers.keySet())
-         {
-            buf.commit(transaction);
-         }
-         if (transaction)
-         {
-            buffers.clear();
-         }
-      }
-   }
-   
-   /**
-    * A full transaction or sub-transaction is rolling back. Notify all dirty buffers in the current scope.
-    * 
-    * @param   transaction
-    *          {@code true} if full transaction; {@code false} if sub-transaction.
-    */
-   @Override
-   public void rollback(boolean transaction)
-   {
-      Map<RecordBuffer, Boolean> buffers = dirtyBuffers.getDictionaryAtScope(0, false);
-      if (buffers != null && !buffers.isEmpty())
-      {
-         for (RecordBuffer buf : buffers.keySet())
-         {
-            buf.rollback(transaction);
-         }
-         if (transaction)
-         {
-            buffers.clear();
-         }
-      }
-   }
-
-   /**
-    * A block is exiting or iterating normally within a transaction. Notify all dirty buffers in the current
-    * scope.
-    * 
-    * @param   transaction
-    *          {@code true} if full transaction; {@code false} if sub-transaction.
-    * @param   aggressiveFlush
-    *          {@code true} if transaction manager is in aggressive subtransaction
-    *          flush mode, indicating that any transient buffers should be validated and
-    *          flushed, regardless of other state.
-    */
-   @Override
-   public void validate(boolean transaction, boolean aggressiveFlush)
-   {
-      TailMap<RecordBuffer, Boolean> buffers = dirtyBuffers.getDictionaryAtScope(0, false);
-      if (buffers != null && !buffers.isEmpty())
-      {
-         ArrayList<Map<RecordBuffer, Boolean>> validatedAddedDirtyBuffers = null;
-         try (AutoCloseable itersafe = buffers.flagIterating())
-         {
-            for (RecordBuffer buf : buffers.keySet())
-            {
-               buf.validate(transaction, aggressiveFlush);
-               
-               // The next block is rarely entered, only in edge cases.
-               // Details:
-               // The invocation of buf.validate can (for example) result in
-               // an ABL publish + subscribe (from an OEDB write trigger),
-               // resulting in more buffers that need to be validated.
-               // Since we are iterating, the extra buffers are added to
-               // a standalone 'tail' collection. We validate them immediately,
-               // and add them to dirtyBuffers after iterating has completed.
-               Map<RecordBuffer, Boolean> addedDirtyBuffers = buffers.pop();
-               while (addedDirtyBuffers != null)
-               {
-                  // Validate all additions
-                  addedDirtyBuffers.keySet().forEach((v) -> v.validate(transaction, aggressiveFlush));
-                  
-                  // Store the validated additions to add them afterwards
-                  // A list is used for clarity and to facilitate possible
-                  // removal logic in the future (which can't happen at the moment).
-                  if (validatedAddedDirtyBuffers == null)
-                  {
-                     validatedAddedDirtyBuffers = new ArrayList<>();
-                  }
-                  validatedAddedDirtyBuffers.add(addedDirtyBuffers);
-                  
-                  // Fetch possible new additions of the added validate invocations.
-                  // We take control of this collection, at the TailMap it
-                  // is forgotten.
-                  addedDirtyBuffers = buffers.pop();
-               }
-            }
-         }
-         catch (Exception e)
-         {
-            throw new IllegalStateException("Exception at dirtyBuffers.flagIterating block", e);
-         }
-         
-         // The next block is rarely entered, only in edge cases, see above.
-         if (validatedAddedDirtyBuffers != null)
-         {
-            for (Map<RecordBuffer, Boolean> bufmap : validatedAddedDirtyBuffers)
-            {
-               // Note: addEntry(false, ...) because:
-               //       We do not need the global scope.
-               //       At the top of this method, scope 0 is used, not scope -1 (global).
-               bufmap.forEach((k,v) -> dirtyBuffers.addEntry(false, k, v));
-            }
-         }
-         
-         // do not clear the buffers here, only commit/rollback can clear it
-      }
-   }
-   
-   /**
     * Query the scope level which currently is open. This is the number of &quot;important&quot; scopes;
     * NO_TRANSACTION blocks inside an application transaction are ignored. So, this number may be lower
     * than the actual number of nested block scopes open, as tracked by the {@code TransactionManager}.
@@ -1045,6 +929,27 @@
    }
    
    /**
+    * Find the nearest external block (from the bottom of the stack).
+    * 
+    * @return   See above.
+    */
+   public int findNearestExternal()
+   {
+      int idx = externalScope.size() - 1;
+      while (idx > 0)
+      {
+         if (externalScope.get(idx))
+         {
+            return idx + 1;
+         }
+         
+         idx = idx - 1;
+      }
+      
+      return -1;
+   }
+   
+   /**
     * Check the specified exception and determine whether we want to allow it
     * to be honored by the {@link TransactionManager}, or whether we want to
     * veto the STOP at this scope.
@@ -1107,56 +1012,7 @@
     */
    public void beginTx(boolean inTx, boolean fullTx, int blockDepth)
    {
-      if (inTx)
-      {
-         if (fullTx)
-         {
-            // create a database xaction wrapper for each connected database, but do not
-            // activate it yet
-            txWrapperMap.clear();         // should be empty already
-            inactiveTxWrappers.clear();   // should be empty already
-            List<Database> databases = connMgr.getActiveDatabases();
-            for (Database database : databases)
-            {
-               createTxWrapper(database);
-            }
-            
-            transactionDepth = blockDepth - 1;  // match TM transaction level
-            transactionCount++;
-            
-            // register for master transaction finish/iterate to clear transaction depth
-            txHelper.registerTransactionFinish(xactFin, true);
-         }
-         
-         // register various services for callbacks if current block has transaction properties
-         if (txHelper.currentTransactionLevel() != TransactionManager.NO_TRANSACTION)
-         {
-            // register savepoint manager for callbacks at full and sub-transaction blocks
-            for (TxWrapper txw : txWrapperMap.values())
-            {
-               txw.registerSavepointHooks(fullTx);
-            }
-            
-            // register to get Commitable notifications for open buffers
-            txHelper.registerCommitAt(blockDepth - 1, this);
-            
-            // register unique tracker context to get Commitable/Finalizable notifications
-            uniqueTrackerCtx.register(txHelper, blockDepth - 1);
-         }
-      }
-      
-      scopeTransitions++;
-      
-      if (fullTx)
-      {
-         // activate a pending TxWrapper for any database with a buffer that already is open
-         Iterator<Database> iter = activeDatabases.keySet().iterator();
-         while (iter.hasNext())
-         {
-            Database database = iter.next();
-            maybeActivateTxWrapper(database);
-         }
-      }
+      txWrapperHelper.beginTx(inTx, fullTx, blockDepth);
    }
    
    /**
@@ -1169,7 +1025,7 @@
     */
    public void endTx(boolean inTx, boolean fullTx)
    {
-      scopeTransitions++;
+      txWrapperHelper.endTx(inTx, fullTx);
    }
    
    /**
@@ -1178,7 +1034,7 @@
    public void endTxPost()
    {
       // if any DBs were used in this tx block and remained connected, disconnect now
-      connMgr.transactionEnded();
+      txWrapperHelper.endTxPost();
    }
    
    /**
@@ -1197,12 +1053,6 @@
    @Override
    public void scopeStart()
    {
-      if (!isImportantBlockTransition())
-      {
-         // nothing to do
-         return;
-      }
-      
       BlockType bt = txHelper.getBlockType();
       if (bt.equals(BlockType.INTERNAL_PROC) ||
           bt.equals(BlockType.FUNCTION)      ||
@@ -1228,13 +1078,10 @@
       
       allBuffers.addScope(null);
       byLegacyName.addScope(null);
-      loadedBuffers.addScope(null);
-      dirtyBuffers.addScope(null);
       openBuffers.pushScope();
       openStaticTempTables.addScope(null);
       thOutputParameters.addScope(null);
-      undoData.addScope(null);
-      fieldScopes.scopeStart();
+      externalScope.push(txHelper.isExternalBlock());
       
       BatchModeData crtBatchScope = batchModeStack.peek();
       if (crtBatchScope != null && crtBatchScope.batchDepth == BatchModeData.INTERNAL)
@@ -1287,11 +1134,6 @@
          LOG.log(Level.FINER,
                  Utils.describeContext() + ":  block scope " + blockDepth + " started");
       }
-      
-      boolean inTx = txHelper.isTransaction();
-      boolean fullTx = inTx && txHelper.isFullTransaction();
-      
-      beginTx(inTx, fullTx, blockDepth);
    }
    
    /**
@@ -1301,12 +1143,6 @@
    @Override
    public void scopeFinished()
    {
-      if (!isImportantBlockTransition())
-      {
-         // nothing to do
-         return;
-      }
-      
       PersistentProcScope ppScope = null;
       
       if (!txHelper.isGlobalBlock())
@@ -1327,76 +1163,10 @@
          }
       }
       
-      // here we will be out of sync with the TM on a transaction boundary, because our
-      // transaction depth is cleared with a transaction finalizable, which is called before this
-      // scope finished notification, so we rely on the TM.isTransaction() instead of our own
-      boolean inTx = txHelper.isTransaction();
-      boolean fullTx = txHelper.isFullTransaction();
-      
-      endTx(inTx, fullTx);
-      
-      Map<RecordBuffer, Boolean> dirtyBufs = dirtyBuffers.getDictionaryAtScope(0, false);
-      dirtyBuffers.deleteScope();
-      if (dirtyBufs != null && !dirtyBufs.isEmpty() && dirtyBuffers.size() > 1)
-      {
-         // do not merge into the global block, as all changes should have been already committed or rolled
-         // back.  this is required as temporary buffers can be changed without an active transaction present
-         // on the stack: in this case, they will be pushed to the previous scope until the global block is
-         // reached, when they will be discarded.
-         
-         for (RecordBuffer buffer : dirtyBufs.keySet())
-         {
-            // merge into previous scope, but only buffers with records which were changed, as the record 
-            // associated (at initial dirty registration of the buffer) may have been flushed to the database,
-            // and the current record may not be 'dirty'
-            if (buffer.getCurrentRecord() != null)
-            {
-               Record record = buffer.getCurrentRecord();
-               if (buffer.isTouched() || 
-                   record.checkState(DmoState.NEW)      ||
-                   record.checkState(DmoState.STALE)    ||
-                   record.checkState(DmoState.DELETING) ||
-                   record.checkState(DmoState.DELETED)  ||
-                   record.checkState(DmoState.INVALID)  ||
-                   record.checkState(DmoState.CHANGED)  ||
-                   record.checkState(DmoState.TRACKED))
-               {
-                  dirtyBuffers.addEntry(false, buffer, false);
-               }
-            }
-         }
-      }
-      undoData.deleteScope();
-      
-      // Copy the set of buffers contained in the current scope of the
-      // loadedBuffers (which is about to be removed) to the enclosing scope.
-      // Only buffers which are open at the enclosing or a higher scope
-      // should be copied.
-      if (loadedBuffers.size() > 1)
-      {
-         Set<RecordBuffer> currentLoadedBuffers = loadedBuffers.getDictionaryAtScope(0, false).keySet();
-         
-         Collection<RecordBuffer> currentOpenBuffers = openBuffers.sublistAtScope(0);
-         
-         for (RecordBuffer buffer : currentLoadedBuffers)
-         {
-            if (buffer.getOpenScopeCount() > 1 || !currentOpenBuffers.contains(buffer))
-            {
-               // if this buffer is missing at the current scope of open
-               // buffers or it presents there but has open scope count > 1,
-               // that means that it presents at a higher level and it should
-               // be copied to the enclosing scope
-               loadedBuffers.setValueAtScope(buffer, 1, false);
-               buffer.addLoadedBuffersScope(loadedBuffers.size() - 1);
-            }
-         }
-      }
       if (ppScope != null)
       {
          // save the data for this external procedure, as these will need to be removed from the
          // global scope, when the procedure gets deleted.
-         Map<RecordBuffer, Boolean> thisLoadedBuffers = loadedBuffers.getDictionaryAtScope(0, false);
-         ppScope.loadedBuffers.putAll(thisLoadedBuffers);
          
          List<RecordBuffer> thisOpenBuffers = openBuffers.sublistAtScope(0);
          ppScope.openBuffers.addAll(thisOpenBuffers);
@@ -1411,13 +1181,7 @@
          ppScope.openStaticTempTables.putAll(thisOpenStaticTempTables);
          
          // add all these to the global scope, too
-         
-         if (!thisLoadedBuffers.isEmpty())
-         {
-            loadedBuffers.getDictionaryAtScope(loadedBuffers.size() - 1, true)
-                         .putAll(thisLoadedBuffers);
-         }
-         
+
          if (!thisOpenBuffers.isEmpty())
          {
             openBuffers.addAll(true, thisOpenBuffers);
@@ -1448,13 +1212,12 @@
          }
       }
       
-      loadedBuffers.deleteScope();
       openBuffers.popScope();
       allBuffers.deleteScope();
       byLegacyName.deleteScope();
-      fieldScopes.scopeFinished();
       openStaticTempTables.deleteScope();
       thOutputParameters.deleteScope();
+      externalScope.pop();
       BatchModeData bmd = batchModeStack.pop();
       if (bmd.batchDepth == BatchModeData.INTERNAL)
       {
@@ -1467,11 +1230,6 @@
                  Utils.describeContext() + ":  block scope " + txHelper.getNestingLevel() + " finished");
       }
       
-      if (fullTx)
-      {
-         endTxPost();
-      }
-      
       maybeCloseSessions();
    }
    
@@ -1479,8 +1237,7 @@
     * Provides notification that the external procedure scope has been deleted.
     * <p>
     * If we are deleting a persistent procedure, clean all its data from the global scope for the
-    * following dictionaries: {@link #loadedBuffers}, {@link #openBuffers}, {@link #allBuffers}, 
-    * {@link #openStaticTempTables}.
+    * following dictionaries: {@link #openBuffers}, {@link #allBuffers}, {@link #openStaticTempTables}.
     */
    @Override
    public void scopeDeleted()
@@ -1502,13 +1259,6 @@
       {
          // remove all these from the global scope
          
-         if (!scope.loadedBuffers.isEmpty())
-         {
-            loadedBuffers.getDictionaryAtScope(loadedBuffers.size() - 1, true)
-                         .keySet()
-                         .removeAll(scope.loadedBuffers.keySet());
-         }
-         
          if (!scope.openBuffers.isEmpty())
          {
             openBuffers.removeAllFrom(true, scope.openBuffers);
@@ -1536,6 +1286,17 @@
    }
    
    /**
+    * Get the {@link ScopeId} for the instance.
+    * 
+    * @return   {@link ScopeId#BUFFER_MANAGER}.
+    */
+   @Override
+   public ScopeId getScopeId()
+   {
+      return ScopeId.BUFFER_MANAGER;
+   }
+   
+   /**
     * Provides a notification that a batch is starting or ending.  Enters or
     * exits batch assignment mode for all record buffers in this context,
     * depending on the value of <code>start</code>.
@@ -1560,42 +1321,6 @@
    }
    
    /**
-    * Releases the buffers into which a new record was loaded (or the old
-    * record was reloaded) into the current or deeper scopes, or only clears
-    * the list of buffers pending to be released.
-    *
-    * @param clearOnly
-    *        If <code>true</code> then clear the list of buffers pending to be
-    *        released, otherwise perform release of these buffers and clear
-    *        the list after that.
-    */
-   public void resetState(boolean clearOnly)
-   {
-      Map<RecordBuffer, Boolean> buffers = loadedBuffers.getDictionaryAtScope(0, false);
-      if (buffers == null || buffers.isEmpty())
-      {
-         return;
-      }
-      
-      if (!clearOnly)
-      {
-         for (RecordBuffer buffer : buffers.keySet())
-         {
-            if (!buffer.isPendingRollbackProcessed())
-            {
-               buffer.release(false, false);
-            }
-            else
-            {
-               buffer.resetPendingRollbackProcessed();
-            }
-         }
-      }
-      
-      buffers.clear();
-   }
-   
-   /**
     * Marks the table for postponed delete, if it is possible. Postponed delete can take place if
     * the table is used as [INPUT-]OUTPUT TABLE-HANDLE parameter.
     *
@@ -1648,45 +1373,7 @@
     */
    public boolean isTransaction()
    {
-      return transactionDepth >= 0;
-   }
-   
-   /**
-    * Check whether the block at the specified block depth is within an application-level
-    * transaction.
-    * 
-    * @param   level
-    *          Zero-based block depth (counting from the outermost scope).
-    * 
-    * @return  {@code true} if we are in a transaction, else {@code false}.
-    */
-   public boolean isTransactionAt(int level)
-   {
-      return transactionDepth >= 0 && transactionDepth <= level;
-   }
-   
-   /**
-    * Check whether the current block marks (and is within) the boundary of an application-level
-    * full transaction.
-    * 
-    * @return  {@code true} if we are at (and within) a full transaction boundary, else {@code
-    *          false}.
-    */
-   public boolean isFullTransaction()
-   {
-      return transactionDepth >= 0 && getOpenBufferScopes() - 1 == transactionDepth;
-   }
-   
-   /**
-    * Get the zero-based block depth (counting from the outermost scope) at which the current,
-    * full, application-level transaction (if any) began.
-    * 
-    * @return  Zero-based block depth of current, full transaction, or -1 if we are not currently
-    *          within a transaction.
-    */
-   public int getFullTransactionBlock()
-   {
-      return transactionDepth;
+      return txWrapperHelper.isTransaction();
    }
    
    /**
@@ -1699,7 +1386,17 @@
     */
    public long getScopeTransitions()
    {
-      return scopeTransitions;
+      return txWrapperHelper.getScopeTransitions();
+   }
+   
+   /**
+    * Get the open buffers from the current scope.
+    * 
+    * @return   See above.
+    */
+   Collection<RecordBuffer> getOpenBuffers()
+   {
+      return openBuffers.scopes() == 0 ? Collections.EMPTY_LIST : openBuffers.sublistAtScope(0);
    }
    
    /**
@@ -1709,7 +1406,7 @@
     */
    long getTransactionCount()
    {
-      return transactionCount;
+      return txWrapperHelper.getTransactionCount();
    }
    
    /**
@@ -1752,6 +1449,7 @@
          throw new UnsupportedOperationException(
            "A buffer can't be bound to more than one table in the same program!");
       }
+
       dstBuffers.add(dstBuf);
       
       bindBuffer(dstBuf, srcBuf);
@@ -1870,37 +1568,6 @@
    }
    
    /**
-    * Register an undoable which is responsible for setting the buffer's 
-    * proper current record after all reversibles are processed.
-    * 
-    * @param   buffer
-    *          Record buffer for which a change is being tracked for undo
-    *          purposes.  It is assumed the buffer has been initialized.
-    * @param   undoTarget
-    *          The undoable responsible with setting the buffer back to its
-    *          proper record.
-    */
-   void registerBufferUndoable(RecordBuffer buffer, Undoable undoTarget)
-   {
-      Map<BufferType, UndoData> dict = undoData.getDictionaryAtScope(0, true);
-      
-      RecordBuffer master = buffer.getMasterBuffer();
-      BufferType key = master.getBufferType();
-      
-      if (dict != null)
-      {
-         UndoData ud = dict.get(key);
-         if (ud == null)
-         {
-            ud = new UndoData();
-            dict.put(key, ud);
-         }
-         
-         ud.bufferUndoable = undoTarget.deepCopy();
-      }
-   }
-   
-   /**
     * Register a newly defined record buffer in the current scope.  Buffers are deregistered
     * implicitly when the scope in which they were registered ends.
     *
@@ -1937,6 +1604,8 @@
     */
    void registerPending(Class<?> def, RecordBuffer buffer)
    {
+      registerPendingScopeable(this);
+      
       pendingBuffers.add(buffer);
       
       registerPendingBufferClass(buffer, def);
@@ -1983,7 +1652,6 @@
       
       int[] allBufferScopes = buffer.getAllBuferScopes();
       int[] byLegacyNameScopes = buffer.getByLegacyNameScopes();
-      int[] loadedBuffersScopes = buffer.getLoadedBuffersScope();
       int[] openBuffersScopes = buffer.getOpenBuffersScope();
       
       int scopeSize = allBuffers.size();
@@ -2029,22 +1697,7 @@
       // remove from global scope always
       // openBuffers.removeFrom(openScopes - 1, buffer);
       
-      int loadedScopes = loadedBuffers.size();
-      for (int scope : loadedBuffersScopes)
-      {
-         int depth = loadedScopes - scope;
-         Map<RecordBuffer, Boolean> loaded = loadedBuffers.getDictionaryAtScope(depth, false);
-         if (loaded != null)
-         {
-            loaded.remove(buffer);
-         }
-      }
-      // remove from global scope always
-      Map<RecordBuffer, Boolean> loaded = loadedBuffers.getDictionaryAtScope(-1, false);
-      if (loaded != null)
-      {
-         loaded.remove(buffer);
-      }
+      txWrapperHelper.deregisterDynamicBuffer(buffer);
       
       if (referent != null)
       {
@@ -2053,7 +1706,6 @@
          {
             procScope.allBuffers.remove(key);
             byLegacy.accept(procScope.byLegacyName);
-            procScope.loadedBuffers.remove(buffer);
             procScope.openBuffers.remove(buffer);
          }
       }
@@ -2082,6 +1734,8 @@
     */
    void startBatchAssignMode(boolean internal)
    {
+      registerScopeable(this);
+      
       int currentScope = getOpenBufferScopes();
       BatchModeData bmd = batchModeStack.peek();
       
@@ -2382,20 +2036,6 @@
    }
    
    /**
-    * Remove all <code>Reversible</code> objects associated with the given buffer from their
-    * respective scoped dictionaries.
-    * 
-    * @param   buffer
-    *          Buffer associated with the <code>Reversible</code>s to be removed.
-    */
-   void removeAllReversibles(TemporaryBuffer buffer)
-   {
-      // delete entries from current and parent scopes
-      BufferType bufType = buffer.getMasterBuffer().getBufferType();
-      undoData.removeEntryThroughScope(bufType, 1);
-   }
-   
-   /**
     * Called by {@link RecordBuffer} when a new buffer scope is opened. It registers the open scope with the
     * buffer manager, so that the buffer can receive block scope start and stop notifications, and be
     * registered for commit/rollback/validate processing when a full transaction is begun.
@@ -2457,6 +2097,8 @@
     */
    void registerPendingStaticTempTable(StaticTempTable table, boolean global)
    {
+      registerPendingScopeable(this);
+
       pendingStaticTempTables.put(table, global);
    }
    
@@ -2658,6 +2300,8 @@
     */
    void openScopeAt(int openScopeDepth, RecordBuffer buffer)
    {
+      // TODO: this seems to be called only for 'openScopeDepth == 0'.
+
       if (buffer.isActive())
       {
          if (openScopeDepth == 0)
@@ -2759,11 +2403,7 @@
     */
    void maybeActivateTxWrapper(Database database)
    {
-      TxWrapper txw = inactiveTxWrappers.remove(database);
-      if (txw != null)
-      {
-         txw.activate();
-      }
+      txWrapperHelper.maybeActivateTxWrapper(database);
    }
    
    /**
@@ -2932,18 +2572,7 @@
     */
    void notifyRecordWasLoaded(RecordBuffer recordBuffer)
    {
-      if (recordBuffer.getCurrentRecord() != null && !recordBuffer.isDynamic())
-      {
-         pm.notifyStaticRecordWasLoaded(recordBuffer);
-      }
-      
-      if (recordBuffer.getCurrentRecord() == null || recordBuffer.isTemporary())
-      {
-         return;
-      }
-      
-      loadedBuffers.addEntry(false, recordBuffer, false);
-      recordBuffer.addLoadedBuffersScope(loadedBuffers.size());
+      txWrapperHelper.notifyRecordWasLoaded(recordBuffer);
    }
    
    /**
@@ -3035,41 +2664,6 @@
    }
    
    /**
-    * Create an inactive transaction wrapper for the given database. It will be activated the
-    * first time an open buffer scope is detected or when a new buffer scope is opened, at which
-    * time a database-level transaction will be opened.
-    * 
-    * @param   database
-    *          Database for which this transaction wrapper is created.
-    * 
-    * @return  Transaction wrapper object.
-    */
-   private TxWrapper createTxWrapper(Database database)
-   {
-      TxWrapper txw = new TxWrapper(database);
-      txWrapperMap.put(database, txw);
-      inactiveTxWrappers.put(database, txw);
-      
-      return txw;
-   }
-   
-   /**
-    * Compose a message suitable for logging.  Reports open buffer scopes and
-    * context information in addition to a custom message.
-    *
-    * @param   persistence
-    *          Persistence object which will provide context info.
-    * @param   text
-    *          Custom message text.
-    *
-    * @return  Composed message text.
-    */
-   private String message(Persistence persistence, String text)
-   {
-      return persistence.message("scope " + getOpenBufferScopes() + ": " + text);
-   }
-   
-   /**
     * Add the source buffers to the destination map.
     * 
     * @param    dst
@@ -3103,34 +2697,7 @@
     */
    private void trackDatabase(RecordBuffer buffer, boolean add)
    {
-      if (add)
-      {
-         Database database = buffer.getDatabase();
-         MutableInteger count = activeDatabases.get(database);
-         if (count == null)
-         {
-            activeDatabases.put(database, count = new MutableInteger(0));
-         }
-         count.set(count.get() + 1);
-      }
-      else if (buffer.isActive() && buffer.getOpenScopeCount() > 0)
-      {
-         Database database = buffer.getDatabase();
-         MutableInteger count = activeDatabases.get(database);
-         if (count == null)
-         {
-            throw new IllegalStateException("Tracking for " + database + " is unbalanced!");
-         }
-         
-         if (count.get() == 1)
-         {
-            activeDatabases.remove(database);
-         }
-         else
-         {
-            count.set(count.get() - 1);
-         }
-      }
+      txWrapperHelper.trackDatabase(buffer, add);
    }
    
    /**
@@ -3147,442 +2714,9 @@
     * @return  {@code true} if the block is considered &quot;important&quot; by the above criteria, else
     *          {@code false}.
     */
-   private boolean isImportantBlockTransition()
-   {
-      return !txHelper.isTransaction() ||
-             txHelper.currentTransactionLevel() != TransactionManager.NO_TRANSACTION;
-   }
-   
-   /**
-    * Storage mechanism for maps of reversible/undoable actions, keyed by
-    * primary key.  One instance of this class holds all such objects for a
-    * single buffer in a single scope.
-    */
-   private static class UndoData
-   {
-      /** Undoable which will set a no-undo buffer to its proper record after a rollback. */
-      Undoable bufferUndoable;
-   }
-   
-   /**
-    * An object which manages the "master" {@code Commitable} and {@code Finalizable} events of an
-    * application transaction. It creates a database transaction to correspond with the application
-    * transaction, and as it receives events from the {@code TransactionManager}, it delegates related
-    * responsibilities to various components of the persistence framework.
-    */
-   private class TxWrapper
-   implements Finalizable,
-              Commitable
-   {
-      /** Backing database */
-      private final Database database;
-      
-      /** Dirty share context */
-      private final DirtyShareContext dirtyContext;
-      
-      /** Savepoint manager (can be {@code null} */
-      private final SavepointManager savepointManager;
-      
-      /** Persistence context, for access to database session and transaction */
-      private Persistence.Context persistenceCtx;
-      
-      /** Roll back the transaction when block finishes, if not previously acted upon */
-      private boolean autoRollback = false;
-      
-      /** {@code true} for an initial transaction only until the first commit or rollback */
-      private boolean initialTx = false;
-      
-      /**
-       * The sole constructor.
-       *
-       * @param   database
-       *          The {@code Database} where the transaction takes place.
-       */
-      TxWrapper(Database database)
-      {
-         this.database = database;
-         // Because the BufferManager manages all record buffers within the current user context, we cannot
-         // separate the DIRTY-READ from the normal tables at this moment. Since this instance is only used
-         // for final cleanup, we acquire the context for the DIRTY-READ database so that context will be
-         // cleaned up correctly. The non-DIRTY-READ information will be ignored.
-         this.dirtyContext = DirtyShareFactory.getContextInstance(database);
-         this.savepointManager =
-            (DatabaseManager.TEMP_TABLE_DB.equals((database)) && DatabaseManager.FORCE_NO_UNDO_TEMP_TABLES)
-            ? null
-            : new SavepointManager();
-         
-         // register for master transaction commit/rollback
-         txHelper.registerTransactionCommit(this, true);
-         
-         // register for master transaction finish/iterate
-         txHelper.registerTransactionFinish(this, true);
-      }
-      
-      /**
-       * Implementation of {@code Finalizable} interface: provides a notification that the block whose scope
-       * in which the object is registered is about to iterate and attempt another pass.  This provides an
-       * opportunity to preserve state from the previous pass and make preparations for the coming pass.
-       */
-      @Override
-      public void iterate()
-      {
-         if (!isActive())
-         {
-            return;
-         }
-         
-         if (LOG.isLoggable(Level.FINE))
-         {
-            LOG.log(Level.FINE, message(persistenceCtx.getPersistence(), "master transaction iterate"));
-         }
-         
-         end();
-         begin();
-      }
-      
-      /**
-       * Implementation of {@code Finalizable} interface: provides a notification that the block whose scope
-       * in which the object is registered is about to retry the same iteration (all loop control data is
-       * unchanged). This provides an opportunity to clear or reset state from the previous pass.
-       */
-      @Override
-      public void retry()
-      {
-         if (!isActive())
-         {
-            return;
-         }
-         
-         if (LOG.isLoggable(Level.FINE))
-         {
-            LOG.log(Level.FINE,
-                    message(persistenceCtx.getPersistence(), "master transaction retry"));
-         }
-         
-         end();
-         begin();
-      }
-      
-      /**
-       * Implementation of {@code Finalizable} interface: Provides a notification that the scope in which the
-       * object is registered is ending and the object's reference may or may not be lost after this method
-       * is called. This provides a natural and standard mechanism to clean-up any resources that may need
-       * to be closed explicitly such as operating system files or sockets.
-       */
-      @Override
-      public void finished()
-      {
-         try
-         {
-            if (autoRollback)
-            {
-               if (isActive() && LOG.isLoggable(Level.FINE))
-               {
-                  LOG.log(Level.FINE,
-                          message(persistenceCtx.getPersistence(), "triggering auto-rollback"));
-               }
-               
-               rollback(true);
-            }
-         }
-         finally
-         {
-            if (isActive() && LOG.isLoggable(Level.FINE))
-            {
-               LOG.log(Level.FINE,
-                       message(persistenceCtx.getPersistence(), "master transaction finished"));
-            }
-            
-            txWrapperMap.remove(database);
-            
-            if (!isActive())
-            {
-               inactiveTxWrappers.remove(database);
-               
-               return;
-            }
-            
-            end();
-         }
-      }
-      
-      /**
-       * Implementation of {@code Finalizable} interface: provides a notification that the external program
-       * in which the object is registered is being deleted and the object's reference may be lost after this
-       * method is called.
-       */
-      @Override
-      public void deleted()
-      {
-         // no-op
-      }
-      
-      /**
-       * Implementation of {@code Commitable} interface: saves any temporary state to the persistent form or
-       * storage that is associated with this class.
-       *
-       * @param    transaction
-       *           {@code true} if this is a full transaction and {@code false} if this is only a
-       *           sub-transaction (a nested scope with transaction support).
-       */
-      @Override
-      public void commit(boolean transaction)
-      {
-         if (!isActive())
-         {
-            return;
-         }
-         
-         if (LOG.isLoggable(Level.FINE))
-         {
-            LOG.log(Level.FINE,
-                    message(persistenceCtx.getPersistence(), "master transaction commit"));
-         }
-         
-         try
-         {
-            initialTx = false;
-            autoRollback = false;
-            persistenceCtx.commit();
-         }
-         catch (PersistenceException exc)
-         {
-            if (LOG.isLoggable(Level.SEVERE))
-            {
-               LOG.log(Level.SEVERE,
-                       message(persistenceCtx.getPersistence(), "commit error"), exc);
-            }
-            
-            // No good way to recover from this.  Let it abend.
-            throw new RuntimeException(exc);
-         }
-         finally
-         {
-            // Reclaim any primary keys which had been waiting to be recycled,
-            // pending a commit.
-            if (DatabaseManager.TEMP_TABLE_DB.equals(database))
-            {
-               Commitable ctx = TemporaryBuffer.getContextCommitable();
-               ctx.commit(transaction);
-            }
-         }
-      }
-      
-      /**
-       * Implementation of {@code Commitable} interface: notifies that a rollback has just occurred (all state
-       * maintained through the {@link Undoable} interface has been already rolled back). This notification
-       * can be used to implement cleanup of any custom state that needs to be maintained in this case. It can
-       * also be used to implement a rollback that is completely independent of the {@code Undoable}
-       * (externally driven) interface.
-       *
-       * @param    transaction
-       *           {@code true} if this is a full transaction and {@code false} if this is only a
-       *           sub-transaction (a nested scope with transaction support).
-       */
-      @Override
-      public void rollback(boolean transaction)
-      {
-         if (!isActive())
-         {
-            return;
-         }
-         
-         if (LOG.isLoggable(Level.FINE))
-         {
-            LOG.log(Level.FINE,
-                    message(persistenceCtx.getPersistence(), "master transaction rollback"));
-         }
-         
-         boolean isTempDB = DatabaseManager.TEMP_TABLE_DB.equals(database);
-         
-         if (isTempDB && savepointManager == null)
-         {
-            // we force all temp-tables to be no-undo, so instead of rollback, do a 
-            // commit of the current transaction
-            commit(transaction);
-            
-            return;
-         }
-         
-         try
-         {
-            initialTx = false;
-            autoRollback = false;
-            persistenceCtx.rollback(false);
-         }
-         catch (PersistenceException exc)
-         {
-            if (LOG.isLoggable(Level.SEVERE))
-            {
-               LOG.log(Level.SEVERE,
-                       message(persistenceCtx.getPersistence(), "rollback error"), exc);
-            }
-            
-            // No good way to recover from this.  Let it abend.
-            throw new RuntimeException(exc);
-         }
-         
-         if (isTempDB)
-         {
-            // clear any primary keys which had been waiting to be recycled, pending a commit
-            Commitable ctx = TemporaryBuffer.getContextCommitable();
-            ctx.rollback(transaction);
-         }
-      }
-
-      /**
-       * Implementation of {@code Commitable} interface: Notifies that a block is about to be normally exited
-       * or normally iterated and that any deferred validation processing should be executed.
-       * <p>
-       * This particular implementation is a NOP.
-       * 
-       * @param   transaction
-       *          Ignored.
-       * @param   aggressiveFlush
-       *          Ignored.
-       */
-      @Override
-      public void validate(boolean transaction, boolean aggressiveFlush)
-      {
-         // no-op
-      }
-      
-      /**
-       * Registers the commitable and finalizable for savepoint manager, if any.
-       * 
-       * @param   fullTx
-       *          {@code true} if this is a full-transaction.
-       */
-      void registerSavepointHooks(boolean fullTx)
-      {
-         if (savepointManager != null)
-         {
-            savepointManager.registerBlockHooks(txHelper, fullTx);
-         }
-      }
-      
-      /**
-       * Activates the transaction. This entails starting a database-level transaction and activating a
-       * savepoint manager for that transaction.
-       */
-      void activate()
-      {
-         if (isActive())
-         {
-            return;
-         }
-         
-         Persistence persistence = PersistenceFactory.getInstance(database);
-         persistenceCtx = persistence.getContext();
-         
-         if (LOG.isLoggable(Level.FINE))
-         {
-            LOG.log(Level.FINE,
-                    message(persistenceCtx.getPersistence(),
-                            "registered for master transaction commit/finished"));
-            
-            persistenceCtx.enableTransactionTrace(true);
-         }
-         
-         begin();
-         initialTx = true;
-      }
-      
-      /**
-       * Checks whether this {@code TxWrapper} is active.
-       * 
-       * @return  {@code true} only if {@code TxWrapper} was activated.
-       */
-      boolean isActive()
-      {
-         return (persistenceCtx != null);
-      }
-      
-      /**
-       * Convert the current object to its string representation to be used during debugging.
-       * 
-       * @return  A short string representation of this object to be used while debugging.
-       */
-      @Override
-      public String toString()
-      {
-         return "TxWrapper{database=" + database + '}';
-      }
-      
-      /** Starts the actual transaction at persistence level. */
-      private void begin()
-      {
-         try
-         {
-            if (!persistenceCtx.beginTransaction(savepointManager) && !initialTx)
-            {
-               // We are here because we tried to begin a database level transaction and one is
-               // already open. This situation generally is invalid, except in an edge case
-               // where a new transaction is opened while in TM's iterate worker. This can happen
-               // if, for example, a new database is accessed during the execution of a trigger
-               // fired during iterate processing. The only case we have seen of this so far is
-               // due to a record being released during RB's iterate processing, which then fires
-               // a write trigger, which accesses a buffer in a separate database (e.g., for
-               // audit purposes) which did not previously have a transaction open. This in turn
-               // invokes this object's c'tor, which registers for master finish notification,
-               // but also opens a new transaction. We then get here a moment later during master
-               // finish processing. Commit has not yet been called, since commitable processing
-               // for that block already has occurred. The solution is to check the initialTx
-               // flag and simply ignore the fact that a new transaction could not be created if
-               // that flag is true. The flag will be set to false on the first commit or
-               // rollback.
-               Persistence persistence = persistenceCtx.getPersistence();
-               if (LOG.isLoggable(Level.FINE))
-               {
-                  Throwable beginTxTrace = persistence.getBeginTxTrace();
-                  if (beginTxTrace != null)
-                  {
-                     String msg = "Current transaction previously started at location...";
-                     LOG.log(Level.FINE, persistence.message(msg), beginTxTrace);
-                  }
-               }
-               
-               throw new IllegalStateException(
-                  message(persistence, "database transaction already active"));
-            }
-            
-            autoRollback = true;
-         }
-         catch (PersistenceException exc)
-         {
-            // TODO: proper error handling
-            throw new StopConditionException(exc);
-         }
-      }
-      
-      /** Shuts down the transaction at persistence level and performs key management. */
-      private void end()
-      {
-         // clear nursery
-         persistenceCtx.getNursery().transactionEnded();
-         
-         // clean up dirty shares for this transaction
-         if (dirtyContext != null)
-         {
-            dirtyContext.cleanup();
-         }
-         
-         // release or downgrade record locks which may have been acquired during the transaction
-         RecordLockContext lockCtx = persistenceCtx.getRecordLockContext();
-         Set<Long> released = lockCtx.transactionEnded();
-         
-         // reclaim any primary keys for records deleted and committed or created and rolled back
-         // during the transaction, provided those keys have been fully released (not just
-         // downgraded) by the end of the transaction; note that this may "leak" reclaiming some
-         // primary keys downgraded and still held as share locks by open buffers, for buffers
-         // whose scope extends beyond the end of the transaction
-         reclaimPendingKeys(persistenceCtx.getPersistence(), released);
-         
-         // Allow the connection manager to perform its end of transaction cleanup.  This must
-         // occur after all transaction processing, because it may result in tearing down the
-         // database's session factory.
-         connMgr.transactionEnded(database);
-      }
+   public boolean isImportantBlockTransition()
+   {
+      return txWrapperHelper.isImportantBlockTransition();
    }
    
    /**
@@ -3660,12 +2794,6 @@
        */
       private final Map<Object, Map<String, RecordBuffer>> byLegacyName = new IdentityHashMap<>();
       
-      /**
-       * All buffers into which a new record was loaded (or the old record was reloaded), for a
-       * persistent procedure.
-       */
-      private final Map<RecordBuffer, Boolean> loadedBuffers = new HashMap<>();
-      
       /** Open record buffers for a persistent procedure. */
       private final List<RecordBuffer> openBuffers = new LinkedList<>();
       

=== modified file 'src/com/goldencode/p2j/persist/ChangeBroker.java'
--- old/src/com/goldencode/p2j/persist/ChangeBroker.java	2022-03-25 19:17:12 +0000
+++ new/src/com/goldencode/p2j/persist/ChangeBroker.java	2022-09-06 18:22:04 +0000
@@ -98,6 +98,17 @@
 **                           Use an identity HashSet where possible.
 **     CA  20220309          Added isQueried(), which checks if a temp-table has any listeners registered for
 **                           events which are a P2JQuery.
+**     CA  20220901          Refactored scope notification support: ScopeableFactory was removed, and the 
+**                           registration is now specific to each type of scopeable.  For each case, the block
+**                           will be registered for scope support (for that particular scopeable) only when
+**                           the scopeable is 'active' (i.e. unnamed streams or accumulators are used).  This
+**                           allows a lazy registration of scopeables, to avoid the unnecessary overhead of
+**                           processing all the scopeables for each and every block.
+**     CA  20220906          Extracted all BufferManager state which requires notification for 
+**                           'isImportantBlock' to an external TxWrapper class (including the private class 
+**                           here), to allow the scopeable BufferManager state related only to buffers (and  
+**                           not transaction related) to be registered for scope notifications in a lazy 
+**                           manner, when buffers are accessed/used/opened.
 */
 
 /*
@@ -253,19 +264,6 @@
    }
    
    /**
-    * Register with the {@link com.goldencode.p2j.util.TransactionManager
-    * TransactionManager} a factory object which creates instances of this
-    * class, so that they can be registered to receive notifications of
-    * runtime scope start and finish events.
-    * <p>
-    * This method should be invoked once during the server bootstrap phase.
-    */
-   static void initialize()
-   {
-      TransactionManager.registerScopeableFactory(ChangeBroker::get);
-   }
-   
-   /**
     * Add a scope to the listeners dictionary whenever a new runtime scope
     * opens.
     */
@@ -332,6 +330,17 @@
    }
    
    /**
+    * Get the {@link ScopeId} for the instance.
+    * 
+    * @return   {@link ScopeId#CHANGE_BROKER}.
+    */
+   @Override
+   public ScopeId getScopeId()
+   {
+      return ScopeId.CHANGE_BROKER;
+   }
+   
+   /**
     * Register a listener to receive DMO property change notifications.  The
     * listener will receive notifications of any changes to DMOs of the types
     * for which it is registered, as they occur in this scope or in scopes
@@ -347,6 +356,8 @@
     */
    void addListener(RecordChangeListener listener)
    {
+      BufferManager.registerScopeable(null);
+      
       addListener(listener, listeners.size());
    }
    
@@ -383,6 +394,8 @@
     */
    void addListener(RecordChangeListener listener, int depth)
    {
+      BufferManager.registerScopeable(null);
+      
       boolean global = (depth == 0);
       
       Iterator<RecordBuffer> iter = listener.recordBuffers();

=== modified file 'src/com/goldencode/p2j/persist/CompoundQuery.java'
--- old/src/com/goldencode/p2j/persist/CompoundQuery.java	2022-07-07 18:38:00 +0000
+++ new/src/com/goldencode/p2j/persist/CompoundQuery.java	2022-09-06 18:22:04 +0000
@@ -224,6 +224,12 @@
 **                           its own block and not via a QUERY resource). 
 **     IAS 20220510          Resetting activeIndex before iterating over the components for current(LockType)
 **     IAS 20220707          Resetting nullOuter on new iteration step.
+**     CA  20220901          Refactored scope notification support: ScopeableFactory was removed, and the 
+**                           registration is now specific to each type of scopeable.  For each case, the block
+**                           will be registered for scope support (for that particular scopeable) only when
+**                           the scopeable is 'active' (i.e. unnamed streams or accumulators are used).  This
+**                           allows a lazy registration of scopeables, to avoid the unnecessary overhead of
+**                           processing all the scopeables for each and every block.
 */
 
 /*
@@ -763,7 +769,7 @@
       activeIndex = -1;
       preselected = false;
       
-      int lowestScope = registerRecordChangeListeners();
+      boolean globalScope = registerRecordChangeListeners();
       
       super.open();
       
@@ -772,7 +778,7 @@
          preselectResults();
       }
       
-      initReferenceRowSupport(lowestScope);
+      initReferenceRowSupport(globalScope);
    }
    
    /**
@@ -1880,11 +1886,11 @@
     * Init structures required to use reference row: create the map of all buffers, register
     * parameter filters for sub-queries and register this query as a record change listener.
     *
-    * @param   blockDepth
-    *          Depth of the block at which this query is registered as record change listener.
-    *          <code>0</code> for global scope.
+    * @param   globalScope
+    *          Flag indicating if the block at which this query is registered as record change listener is
+    *          the global scope.
     */
-   protected void initReferenceRowSupport(int blockDepth)
+   protected void initReferenceRowSupport(boolean globalScope)
    {
       if (!preselect && buffersMap == null)
       {
@@ -1909,7 +1915,7 @@
          }
          
          ChangeBroker.get().addListener(this);
-         if (blockDepth == 0)
+         if (globalScope)
          {
             unregisterOnCleanup = true;
          }
@@ -2064,7 +2070,7 @@
    @SuppressWarnings("unchecked")
    protected Object[] retrieve(int navigation, LockType lockType, boolean iterating, boolean peek)
    {
-      initReferenceRowSupport(-1);
+      initReferenceRowSupport(false);
       
       Object[] rowData;
       
@@ -2398,14 +2404,14 @@
    /**
     * Register the query within each component as a record change listener.
     * 
-    * @return  The lowest scope found among all the components' record buffers.
+    * @return  <code>true</code> if the global scope is used for registration.
     */
-   private int registerRecordChangeListeners()
+   private boolean registerRecordChangeListeners()
    {
       // Find for each compound query component the most outer scope among
       // nearest external scope and the outermost scopes of buffers used by
       // this component and register the component into this scope.
-      int externalScope = TransactionManager.findNearestExternal();
+      int externalScope = BufferManager.get().findNearestExternal();
       int lowestScope = externalScope;
       
       for (CompoundComponent comp : getComponents())
@@ -2428,7 +2434,7 @@
          }
       }
       
-      return lowestScope;
+      return lowestScope == 0;
    }
    
    /**

=== modified file 'src/com/goldencode/p2j/persist/DataSet.java'
--- old/src/com/goldencode/p2j/persist/DataSet.java	2022-04-22 22:17:35 +0000
+++ new/src/com/goldencode/p2j/persist/DataSet.java	2022-09-06 18:22:04 +0000
@@ -62,6 +62,12 @@
 **     CA  20220106 Fixed CREATE-LIKE when relations are involved, their active flag was not being copied 
 **                  properly.
 **     OM  20220422 Allow Xml/JsonImport to load records into buffer without exposing package private methods.
+**     CA  20220901 Refactored scope notification support: ScopeableFactory was removed, and the registration 
+**                  is now specific to each type of scopeable.  For each case, the block will be registered 
+**                  for scope support (for that particular scopeable) only when the scopeable is 'active' 
+**                  (i.e. unnamed streams or accumulators are used).  This allows a lazy registration of 
+**                  scopeables, to avoid the unnecessary overhead of processing all the scopeables for each
+**                  and every block.
 */
 
 /*
@@ -403,6 +409,8 @@
          throw new IllegalStateException("Parameter mode (INPUT/OUTPUT) was not specified!");
       }
       
+      DataSetManager.registerScopeable();
+      
       StaticDataSet localDS = (StaticDataSet) dsTarget;
       if (!dsSrc.isRemoteParameter())
       {
@@ -638,6 +646,8 @@
          throw new IllegalStateException("Parameter mode (INPUT/OUTPUT) was not specified!");
       }
       
+      DataSetManager.registerScopeable();
+      
       if (output)
       {
          TransactionManager.registerCurrent(dsHandle);

=== modified file 'src/com/goldencode/p2j/persist/DataSetManager.java'
--- old/src/com/goldencode/p2j/persist/DataSetManager.java	2020-10-03 16:02:03 +0000
+++ new/src/com/goldencode/p2j/persist/DataSetManager.java	2022-09-06 18:22:04 +0000
@@ -2,7 +2,7 @@
 ** Module   : DataSetManager.java
 ** Abstract : Manages all the DataSets, taking care for matching shared ones. 
 **
-** Copyright (c) 2019-2020, Golden Code Development Corporation.
+** Copyright (c) 2019-2022, Golden Code Development Corporation.
 **
 ** -#- -I- --Date-- ---------------------------------Description---------------------------------
 ** 001 OM  20190403 Created initial version.
@@ -11,6 +11,12 @@
 **     OM  20190731 Added REFERENCE-ONLY support.
 ** 004 CA  20200110 More fixes for DATASET parameters and resource delete.
 ** 005 CA  20201003 Allow dictionaries with an identity map.
+**     CA  20220901 Refactored scope notification support: ScopeableFactory was removed, and the registration 
+**                  is now specific to each type of scopeable.  For each case, the block will be registered 
+**                  for scope support (for that particular scopeable) only when the scopeable is 'active' 
+**                  (i.e. unnamed streams or accumulators are used).  This allows a lazy registration of 
+**                  scopeables, to avoid the unnecessary overhead of processing all the scopeables for each
+**                  and every block.
 */
 
 /*
@@ -116,14 +122,22 @@
    
    /** A dictionary of BY-REFERENCE dataset parameters. */
    private final ScopedDictionary<DataSet, Integer> byReferenceParameters = new ScopedDictionary<>();
+
+   /** The transaction helper. */
+   private final TransactionManager.TransactionHelper tm;
    
    /**
     * Default c'tor.  Marks dictionaries which can use identity maps.
     */
    private DataSetManager()
    {
+      tm = TransactionManager.getTransactionHelper();
       dshOutputParams.setIdentityKeys(true);
       byReferenceParameters.setIdentityKeys(true);
+      
+      // there is a bug with static datasets: instead of registering as 'pending' for next scope, they are
+      // registering in the callee's scope... so, for now, push an 'implicit' scope here.
+      scopeStart();
    }
    
    /**
@@ -152,18 +166,13 @@
       return instance.get();
    }
    
-   
    /**
-    * Register with the {@link com.goldencode.p2j.util.TransactionManager
-    * TransactionManager} a factory object which creates instances of this
-    * class, so that they can be registered to receive notifications of
-    * runtime scope start and finish events.
-    * <p>
-    * This method should be invoked once during the server bootstrap phase.
+    * Register the {@link DataSetManager} for scope notifications at the current block.
     */
-   static void initialize()
+   public static void registerScopeable()
    {
-      TransactionManager.registerScopeableFactory(DataSetManager::instance);
+      DataSetManager mgr = instance();
+      mgr.tm.registerBlockScopeable(mgr);
    }
    
    /**
@@ -180,6 +189,8 @@
     */
    public void register(DataSet dataSet, String legacyName, boolean isShared, boolean isNew)
    {
+      tm.registerPendingScopeable(this);
+      
       if (!dataSet._dynamic())
       {
          registry.addEntry(false, legacyName.toLowerCase(), dataSet);
@@ -252,6 +263,17 @@
    }
    
    /**
+    * Get the {@link ScopeId} for the instance.
+    * 
+    * @return   {@link ScopeId#DATA_SET_MANAGER}.
+    */
+   @Override
+   public ScopeId getScopeId()
+   {
+      return ScopeId.DATA_SET_MANAGER;
+   }
+   
+   /**
     * Check if the given dataset is passed as a BY-REFERENCE parameter.
     * 
     * @param    ds

=== modified file 'src/com/goldencode/p2j/persist/Persistence.java'
--- old/src/com/goldencode/p2j/persist/Persistence.java	2022-08-17 00:05:18 +0000
+++ new/src/com/goldencode/p2j/persist/Persistence.java	2022-09-06 18:22:04 +0000
@@ -569,6 +569,12 @@
 **     IAS 20220608          Provide additional information in reportUDFVersion().
 **     IAS 20220712          Create missing UDFs
 **     IAS 20220816          Do not use 'udf' schema for non-PostgreSQL UDFs
+**     CA  20220901          Refactored scope notification support: ScopeableFactory was removed, and the 
+**                           registration is now specific to each type of scopeable.  For each case, the block
+**                           will be registered for scope support (for that particular scopeable) only when
+**                           the scopeable is 'active' (i.e. unnamed streams or accumulators are used).  This
+**                           allows a lazy registration of scopeables, to avoid the unnecessary overhead of
+**                           processing all the scopeables for each and every block.
 */
 
 /*
@@ -819,8 +825,6 @@
     * Initialize various infrastructure components of the persistence framework:
     * <ul>
     *   <li>{@code DataTypeHelper}, which is used when preprocessing FQL where clauses.
-    *   <li>{@code BufferManager}, which tracks open buffers and transactions.
-    *   <li>{@code ChangeBroker}, which dispatches notifications about changes to DMO properties.
     *   <li>{@code DatabaseManager}, which configures the ORM environment for the databases
     *       to which the framework connects.
     *   <li>{@code P2OLookup}, instances of which are used for runtime, on-the-fly query and
@@ -835,22 +839,16 @@
     *          if any error is encountered initializing the persistence framework.
     *
     * @see     DataTypeHelper#initialize()
-    * @see     BufferManager#initialize()
-    * @see     ChangeBroker#initialize()
     * @see     DatabaseManager#initialize()
     * @see     TemporaryDatabaseManager#initialize()
-    * @see     DataSetManager#initialize()
     * @see     P2OLookup#initialize()
     */
    public static void initialize()
    throws PersistenceException
    {
       DataTypeHelper.initialize();
-      BufferManager.initialize();
-      ChangeBroker.initialize();
       TemporaryDatabaseManager.initialize();
       DatabaseManager.initialize();
-      DataSetManager.initialize();
       
       // if runtime conversion services are required, initialize schema lookup resources
       if (ConversionPool.isInitialized())

=== modified file 'src/com/goldencode/p2j/persist/RecordBuffer.java'
--- old/src/com/goldencode/p2j/persist/RecordBuffer.java	2022-08-10 20:01:47 +0000
+++ new/src/com/goldencode/p2j/persist/RecordBuffer.java	2022-09-06 18:22:04 +0000
@@ -1217,6 +1217,18 @@
 **                           must be used, as the buffer instance may be proxied.  Fixed OM/20220707.
 **     OM  20220809          Use a temporary virtual scope while invoking the db triggers to block the 
 **                           release() to be recursively invoked at the end of a trigger().
+**     CA  20220901          Refactored scope notification support: ScopeableFactory was removed, and the 
+**                           registration is now specific to each type of scopeable.  For each case, the block
+**                           will be registered for scope support (for that particular scopeable) only when
+**                           the scopeable is 'active' (i.e. unnamed streams or accumulators are used).  This
+**                           allows a lazy registration of scopeables, to avoid the unnecessary overhead of
+**                           processing all the scopeables for each and every block.
+**     CA  20220906          BufferManager's undoable support is no longer being used, so it was removed.
+**                           Extracted all BufferManager state which requires notification for 
+**                           'isImportantBlock' to an external TxWrapper class (including the private class 
+**                           here), to allow the scopeable BufferManager state related only to buffers (and  
+**                           not transaction related) to be registered for scope notifications in a lazy 
+**                           manner, when buffers are accessed/used/opened.
 */
 
 /*
@@ -1723,9 +1735,6 @@
       public void reset() { setCurrentRecord(null, false, false, false, false); }
    };
    
-   /** Track whether a rollback action is pending */
-   private boolean pendingRollbackProcessed = false;
-   
    /**
     * Flag to mark this buffer as changed by another user on a multi-user 
     * environment. Its value is will be returned by currentChanged() method.
@@ -3783,7 +3792,7 @@
                }
                
                // heuristically convert from SQL name to legacy name
-               Iterator<Property> fields = buffer.dmoInfo.getFields(false);
+               Iterator<Property> fields = buffer.getDmoInfo().getFields(false);
                while (fields.hasNext())
                {
                   Property prop = fields.next();
@@ -3940,6 +3949,7 @@
       BufferReference srcProxy = (BufferReference) srcDmo;
       RecordBuffer srcBuf = srcProxy.buffer();
       RecordBuffer dstBuf = dstProxy.buffer();
+      dstBuf.bufferManager.registerDirtyBuffer(dstBuf);
       
       srcBuf.initialize();
       dstBuf.initialize();
@@ -4231,6 +4241,8 @@
          return new logical(false);
       }
       
+      dstBuf.bufferManager.registerDirtyBuffer(dstBuf);
+      
       boolean created = false;
       Record dstRec = dstBuf.getCurrentRecord();
       if (dstRec == null)
@@ -4537,7 +4549,10 @@
                                    String legacyName)
    {
       BufferImpl buffer = (BufferImpl) proxy;
-      Class<? extends DataModelObject> dmoIface = buffer.buffer().getDMOInterface();
+      RecordBuffer rbuf = buffer.buffer();
+      BufferManager.registerPendingScopeable(rbuf.bufferManager);
+
+      Class<? extends DataModelObject> dmoIface = rbuf.getDMOInterface();
       Class<?> dmoClass = buffer.buffer().getDMOImplementationClass();
       
       Class<?>[] interfaces = TempRecord.class.isAssignableFrom(dmoClass)
@@ -6225,9 +6240,6 @@
       else if (inChangeScope && transaction)
       {
          resetChangeScope();
-         
-         // ensure the record is set back to the last used record when rollback completes
-         bufferManager.registerBufferUndoable(this, new LightweightUndoable());
       }
    }
    
@@ -6318,8 +6330,6 @@
             bufferManager.reclaimPendingKeys(persistence, released);
          }
       }
-      
-      resetPendingRollbackProcessed();
    }
    
    /**
@@ -6862,6 +6872,8 @@
          return false;
       }
       
+      BufferManager.registerScopeable(bufferManager);
+      
       // Get IndexHelper for this database.
       indexHelper = IndexHelper.get(database);
       
@@ -7456,6 +7468,8 @@
     */
    protected void openScope()
    {
+      BufferManager.registerScopeable(bufferManager);
+      
       if (retrying)
       {
          // reset the flag
@@ -7549,6 +7563,10 @@
     */
    protected void openScopeAt(int blockDepth)
    {
+      // TODO: blockDepth param is always zero...
+      
+      BufferManager.registerScopeable(bufferManager);
+
       // Register this scope with the buffer manager.
       bufferManager.openScopeAt(blockDepth, this);
       
@@ -10196,24 +10214,6 @@
    }
    
    /**
-    * Get current state of 'pending rollback processed' flag.
-    * 
-    * @return <code>true</code> if pending rollback was processed. 
-    */
-   protected boolean isPendingRollbackProcessed()
-   {
-      return pendingRollbackProcessed;
-   }
-   
-   /**
-    * Reset 'pending rollback processed' flag. 
-    */
-   protected void resetPendingRollbackProcessed()
-   {
-      pendingRollbackProcessed = false;
-   }
-   
-   /**
     * Returns the value of a field in the record of this buffer.
     * 
     * @param   field
@@ -11261,12 +11261,7 @@
       }
       
       boolean sameRecord = areDMOsSame(this.currentRecord, newCrtRecord);
-      
-      if (!sameRecord)
-      {
-         resetPendingRollbackProcessed();
-      }
-      
+
       if (newCrtRecord != null)
       {
          sameRecord = sameRecord && this.dirtyCopy == dirtyCopy;

=== modified file 'src/com/goldencode/p2j/persist/TemporaryBuffer.java'
--- old/src/com/goldencode/p2j/persist/TemporaryBuffer.java	2022-08-30 11:30:53 +0000
+++ new/src/com/goldencode/p2j/persist/TemporaryBuffer.java	2022-09-06 18:22:04 +0000
@@ -565,6 +565,12 @@
 **                           DmoMeta to avoid duplicating their collection and storage.
 **     CA  20220830          Temp-tables sent via a web-service (like REST) may not contain all the fields,
 **                           and the missing fields will default to unknown.
+**     CA  20220906          BufferManager's undoable support is no longer being used, so it was removed.
+**                           Extracted all BufferManager state which requires notification for 
+**                           'isImportantBlock' to an external TxWrapper class (including the private class 
+**                           here), to allow the scopeable BufferManager state related only to buffers (and  
+**                           not transaction related) to be registered for scope notifications in a lazy 
+**                           manner, when buffers are accessed/used/opened.
 */
 
 /*
@@ -5689,6 +5695,8 @@
    @Override
    protected void openScope()
    {
+      BufferManager.registerScopeable(bufferManager);
+      
       if (referenceOnly)
       {
          return;
@@ -5760,6 +5768,8 @@
    @Override
    protected void openScopeAt(int blockDepth)
    {
+      BufferManager.registerScopeable(bufferManager);
+
       if (referenceOnly)
       {
          return;
@@ -7013,10 +7023,6 @@
          {
             local.emptyTableFlags.remove(multiplexID);
             
-            // remove any maps of reversible/undoables pinned by this buffer, since the table
-            // associated with this buffer is about to be dropped
-            bufferManager.removeAllReversibles(this);
-            
             // prepare the table to be dropped when the database session closes
             local.dropTable(tempTableResource(), getTable(), dmoIface);
          }
@@ -7037,10 +7043,6 @@
          {
             local.emptyTableFlags.remove(multiplexID);
             
-            // remove any maps of reversible/undoables pinned by this buffer, since the virtual
-            // table associated with this buffer is gone
-            bufferManager.removeAllReversibles(this);
-            
             // even if the SQL table cannot be deleted at this time because there are other sibling tables
             // with different multiplex value, this temp-table is done now and its legacy information is not
             // accessed anymore, so it must be dropped to save memory 

=== modified file 'src/com/goldencode/p2j/persist/trigger/DatabaseTriggerManager.java'
--- old/src/com/goldencode/p2j/persist/trigger/DatabaseTriggerManager.java	2022-08-17 07:58:35 +0000
+++ new/src/com/goldencode/p2j/persist/trigger/DatabaseTriggerManager.java	2022-09-06 18:22:04 +0000
@@ -55,6 +55,12 @@
 **     TW  20220803          Error-status:error must be set for return error in method, while return error
 **                           in old-style ABL function must remain the same. Refs: #6567.
 **     TW  20220817          Error-status:error must be set for return error in static method.
+**     CA  20220901          Refactored scope notification support: ScopeableFactory was removed, and the 
+**                           registration is now specific to each type of scopeable.  For each case, the block
+**                           will be registered for scope support (for that particular scopeable) only when
+**                           the scopeable is 'active' (i.e. unnamed streams or accumulators are used).  This
+**                           allows a lazy registration of scopeables, to avoid the unnecessary overhead of
+**                           processing all the scopeables for each and every block.
 */
 
 /*
@@ -116,7 +122,7 @@
 import com.goldencode.p2j.persist.orm.*;
 import com.goldencode.p2j.security.*;
 import com.goldencode.p2j.util.*;
-import com.goldencode.p2j.util.LogHelper;
+
 import java.lang.reflect.*;
 import java.util.*;
 import java.util.logging.Level;
@@ -166,8 +172,6 @@
     */
    private DatabaseTriggerManager()
    {
-      // I want to receive enter/leave bloc events
-      tm.registerScopeable(this);
       if (LOG.isLoggable(Level.FINE))
       {
          LOG.log(Level.FINE, "DatabaseTriggerManager registered with TransactionManager.");
@@ -456,6 +460,8 @@
                                               boolean hasOldBuffer)
    {
       DatabaseTriggerManager manager = get();
+      manager.tm.registerTopLevelScopeable(manager);
+      
       if (manager.stack.isEmpty())
       {
          // sanity check failed
@@ -558,6 +564,8 @@
          throw new IllegalArgumentException("Use this form of register only for ASSIGN triggers.");
       }
       DatabaseTriggerManager manager = get();
+      manager.tm.registerTopLevelScopeable(manager);
+      
       if (manager.stack.isEmpty())
       {
          // sanity check failed
@@ -601,6 +609,8 @@
       }
       
       DatabaseTriggerManager manager = get();
+      manager.tm.registerTopLevelScopeable(manager);
+      
       if (manager.stack.isEmpty())
       {
          // sanity check failed
@@ -633,6 +643,8 @@
       }
       
       DatabaseTriggerManager manager = get();
+      manager.tm.registerTopLevelScopeable(manager);
+      
       if (manager.stack.isEmpty())
       {
          // sanity check failed
@@ -656,6 +668,8 @@
    public static boolean disableDumpTriggers(Class<? extends Buffer> bufferCls)
    {
       DatabaseTriggerManager manager = get();
+      manager.tm.registerTopLevelScopeable(manager);
+      
       if (manager.stack.isEmpty())
       {
          return false;
@@ -697,6 +711,8 @@
                                              boolean allowReplication)
    {
       DatabaseTriggerManager manager = get();
+      manager.tm.registerTopLevelScopeable(manager);
+      
       if (manager.stack.isEmpty())
       {
          return false;
@@ -910,10 +926,7 @@
    public void scopeStart()
    {
       // triggers get notifications at all top level blocks
-      if (tm.isTopLevelBlock())
-      {
-         stack.addFirst(new TriggerBlockLayer()); // push();
-      }
+      stack.addFirst(new TriggerBlockLayer()); // push();
    }
    
    /**
@@ -926,6 +939,17 @@
    }
    
    /**
+    * Get the {@link ScopeId} for the instance.
+    * 
+    * @return   {@link ScopeId#DATABASE_TRIGGER_MANAGER}.
+    */
+   @Override
+   public ScopeId getScopeId()
+   {
+      return ScopeId.DATABASE_TRIGGER_MANAGER;
+   }
+   
+   /**
     * Provides a notification that a scope is about to be exited. The manager only acts on
     * top-level scope. Each time a scope is exited a the last layer of triggers is removed from
     * internal stack.
@@ -934,16 +958,13 @@
    public void scopeFinished()
    {
       // triggers get notifications at all top level blocks
-      if (tm.isTopLevelBlock())
-      {
-         if (stack.isEmpty())
-         {
-            LOG.log(Level.WARNING, "Invalid scope finished.");
-         }
-         else
-         {
-            stack.removeFirst(); // pop();
-         }
+      if (stack.isEmpty())
+      {
+         LOG.log(Level.WARNING, "Invalid scope finished.");
+      }
+      else
+      {
+         stack.removeFirst(); // pop();
       }
    }
    

=== modified file 'src/com/goldencode/p2j/ui/GenericFrame.java'
--- old/src/com/goldencode/p2j/ui/GenericFrame.java	2022-08-09 17:11:28 +0000
+++ new/src/com/goldencode/p2j/ui/GenericFrame.java	2022-09-06 18:22:04 +0000
@@ -955,6 +955,12 @@
 **                           widget is explicitly specified.
 **     EVL 20220809          Fixed the regression from recent HIDDEN attribute reset change.  Only widgets
 **                           defined in DISPLAY statement should be affected.
+**     CA  20220901          Refactored scope notification support: ScopeableFactory was removed, and the 
+**                           registration is now specific to each type of scopeable.  For each case, the block
+**                           will be registered for scope support (for that particular scopeable) only when
+**                           the scopeable is 'active' (i.e. unnamed streams or accumulators are used).  This
+**                           allows a lazy registration of scopeables, to avoid the unnecessary overhead of
+**                           processing all the scopeables for each and every block.
 */
 
 /*
@@ -1849,6 +1855,9 @@
    public Object invoke(Object proxy, Method method, Object[] args)
    throws Throwable
    {
+      // frame is accessed, register scopeable
+      TransactionManager.registerBlockScopeable(LogicalTerminal.getInstance());
+      
       int    op         = -1;
       String widgetName = null;
 
@@ -8721,6 +8730,8 @@
       // push down the screen definition
       if (force)
       {
+         TransactionManager.registerBlockScopeable(LogicalTerminal.getInstance());
+
          // synchronous push
          ScreenDefinition[] sd = new ScreenDefinition[] {frameDef};
          LogicalTerminal.pushScreenDefinition(sd);

=== modified file 'src/com/goldencode/p2j/ui/LogicalTerminal.java'
--- old/src/com/goldencode/p2j/ui/LogicalTerminal.java	2022-08-17 07:58:35 +0000
+++ new/src/com/goldencode/p2j/ui/LogicalTerminal.java	2022-09-06 18:22:04 +0000
@@ -995,6 +995,13 @@
 **     TW  20220803          Error-status:error must be set for return error in method, while return error
 **                           in old-style ABL function must remain the same. Refs: #6567.
 **     TW  20220817          Error-status:error must be set for return error in static method.
+**     CA  20220901          Refactored scope notification support: ScopeableFactory was removed, and the 
+**                           registration is now specific to each type of scopeable.  For each case, the block
+**                           will be registered for scope support (for that particular scopeable) only when
+**                           the scopeable is 'active' (i.e. unnamed streams or accumulators are used).  This
+**                           allows a lazy registration of scopeables, to avoid the unnecessary overhead of
+**                           processing all the scopeables for each and every block.
+**     CA  20220906          Moved the interactive client cleaner to LogicalTerminal.registerCleaner().
 */
 
 /*
@@ -1621,6 +1628,44 @@
    {
       defaultTimeout = timeout;
    }
+
+   /**
+    * Register a cleaner for interactive sessions, where a pause is shown before terminating the client.
+    */
+   public static void registerCleaner()
+   {
+      if (!AppServerManager.isRemote() && !TransactionManager._isHeadless())
+      {
+         Finalizable cleaner = new Finalizable()
+         {
+            @Override
+            public void finished()
+            {
+               LogicalTerminal.getInstance().pauseBeforeEnd(TransactionManager.isProcessingQuit());
+            }
+            
+            @Override
+            public void iterate()
+            {
+               // nothing to do here 
+            }
+            
+            @Override
+            public void retry()
+            {
+               // nothing to do here 
+            }
+            
+            @Override
+            public void deleted()
+            {
+               // nothing to do here
+            }
+         };
+            
+         TransactionManager.registerFinalizable(cleaner, true);
+      }
+   }
    
    /**
     * Rings a bell on the terminal.
@@ -1703,7 +1748,7 @@
       }
       
       LogicalTerminal lt = locate();
-      lt.scopeTrack.forceHiding();
+      lt.forceHiding();
       lt.client.hideAll(noPause, targetWindowWorker(hWin, false));
       
       // TODO get frames from target window?
@@ -9364,7 +9409,7 @@
       
       LogicalTerminal lt = locate();
       
-      lt.scopeTrack.frameViewed(frame);
+      lt.frameViewed(frame);
       frame.setHadView();
 
       // refresh editable frames list
@@ -9405,7 +9450,7 @@
       
       LogicalTerminal lt = locate();
       
-      lt.scopeTrack.frameViewed(frame);
+      lt.frameViewed(frame);
       frame.setHadView();
 
       // refresh editable frames list
@@ -9519,7 +9564,7 @@
       ScreenBuffer[] frameBuf = (frame == null) ? null : lt.getEditableServerScreenBuffers(frame); 
       int          windowId = (frame == null) ? WindowConfig.RESOLVE_WINDOW : frame.getWindowId();
       
-      lt.scopeTrack.frameViewed(frame);
+      lt.frameViewed(frame);
       FieldGroup fg;
       if (frame != null)
       {
@@ -9549,7 +9594,7 @@
    {
       LogicalTerminal lt = locate();
       
-      lt.scopeTrack.frameViewed(frame);
+      lt.frameViewed(frame);
       
       view(frame.getFrameId(),
            lt.getEditableServerScreenBuffers(frame),
@@ -9577,7 +9622,7 @@
    {
       LogicalTerminal lt = locate();
       
-      lt.scopeTrack.frameViewed(frame);
+      lt.frameViewed(frame);
       
       view(frame.getFrameId(),
            lt.getEditableServerScreenBuffers(frame),
@@ -9625,7 +9670,7 @@
    {
       LogicalTerminal lt = locate();
       
-      lt.scopeTrack.frameViewed(frame);
+      lt.frameViewed(frame);
       
       WidgetConfig[] wcfgs = lt.client.view(frame.getFrameId(),
                                             lt.getEditableServerScreenBuffers(frame),
@@ -9669,7 +9714,7 @@
    {
       LogicalTerminal lt = locate();
       
-      lt.scopeTrack.frameViewed(frame);
+      lt.frameViewed(frame);
       
       FieldGroup fg = frame.getFieldGroup();
       
@@ -9724,7 +9769,7 @@
    {
       LogicalTerminal lt = locate();
       
-      lt.scopeTrack.frameViewed(frame);
+      lt.frameViewed(frame);
       
       WidgetConfig[] wcfgs = lt.client.view(frame.getFrameId(),
                                             lt.getEditableServerScreenBuffers(frame),
@@ -9760,7 +9805,7 @@
    {
       LogicalTerminal lt = locate();
       
-      lt.scopeTrack.frameViewed(frame);
+      lt.frameViewed(frame);
       
       view(frame.getFrameId(),
            frameBuf,
@@ -9810,7 +9855,7 @@
       // call the client
       try
       {
-         lt.scopeTrack.frameViewed(frame);
+         lt.frameViewed(frame);
          
          int[] tabItemList = frame.updateTabItemsList(widgetIds, UpdatesOp.PROMPT_FOR);
          
@@ -10155,7 +10200,9 @@
       // we haven't had the chance to mark the next partition yet because
       // scopeStart() won't be called until the business logic initializer
       // is complete
-      locate().level2definitions.add(frameDef);
+      LogicalTerminal lt = locate();
+      lt.level2definitions.add(frameDef);
+      lt.tm.registerPendingScopeable(lt);
    }
 
    /**
@@ -10171,7 +10218,9 @@
       // we haven't had the chance to mark the next partition yet because
       // scopeStart() won't be called until the business logic initializer
       // is complete
-      locate().level2descriptions.add(menuDescr);
+      LogicalTerminal lt = locate();
+      lt.level2descriptions.add(menuDescr);
+      lt.tm.registerPendingScopeable(lt);
    }
    
    /**
@@ -10193,6 +10242,8 @@
    public static void processDeferredPush(boolean fromLevel2)
    {
       LogicalTerminal lt = locate();
+      lt.tm.registerBlockScopeable(lt);
+
       if (fromLevel2)
       {
          lt.definitions.markPartition();
@@ -11372,7 +11423,14 @@
     */
    static void deregisterFrame(int frameId)
    {
-      locate().deregisterFrameInt(frameId);
+      LogicalTerminal lt = locate();
+      
+      // scopeable registration must be done in any location where a frame state (requiring scope support) is
+      // being 'touched' - this includes frame de-registration, even if the current block is not explicitly
+      // using frames
+      lt.tm.registerBlockScopeable(lt);
+
+      lt.deregisterFrameInt(frameId);
    }
    
    /**
@@ -11815,8 +11873,35 @@
          return frameRegistry.get(frameId);
       }
    }
+   
+   /**
+    * Notify scope that frame is viewed.
+    * <p>
+    * This also registeres {@link LogicalTerminal} for scope notifications at the current block.
+    *  
+    * @param   frame
+    *          Frame which is about to be viewed.
+    */
+   private void frameViewed(GenericFrame frame)
+   {
+      tm.registerBlockScopeable(this);
+      
+      scopeTrack.frameViewed(frame);
+   }
 
    /**
+    * Clear map of viewed frames.
+    * <p>
+    * This also registeres {@link LogicalTerminal} for scope notifications at the current block.
+    */
+   private void forceHiding()
+   {
+      tm.registerBlockScopeable(this);
+      
+      scopeTrack.forceHiding();
+   }
+   
+   /**
     * Save the {@link UIStatement} running for each registered frame.
     * 
     * @return   A map with each frame's UI statement.
@@ -13298,23 +13383,6 @@
    }
    
    /**
-    * Register with the {@link com.goldencode.p2j.util.TransactionManager TransactionManager} a
-    * factory object which creates instances of this class, so that they can be registered to
-    * receive notifications of runtime scope start and finish events.
-    * <p>
-    * This method should be invoked once during the server bootstrap phase.
-    * <p>
-    * The registered factory functions conditionally. It uses the {@link 
-    * TransactionManager#_isHeadless()} to check whether the context has been declared headless.
-    */
-   public static void initialize()
-   {
-      TransactionManager.registerScopeableFactory(
-         () -> TransactionManager._isHeadless() ? null : LogicalTerminal.locate() 
-      );
-   }
-   
-   /**
     * Process a notification that a new block scope is about to be entered.
     * <p>
     * In the Logical Terminal, a new top level block scope causes creation of
@@ -13325,6 +13393,9 @@
    @Override
    public void scopeStart()
    {
+      // scopeable registration is done in different places - if changes are made, ensure that scopeable 
+      // support is registered properly.
+      
       // register at each scope, it is important to instantiate the scope
       // processor BEFORE the state.interactions counter is reset below
       tm.registerFinalizable(new ScopeProcessor(), false);
@@ -13342,39 +13413,6 @@
          state.interactions = 0;
       }
       
-      // make sure that the global block gets a special notification - but only for non-appserver sessions.
-      if (tm.isGlobalBlock() && !tm.isRemote())
-      {
-         Finalizable cleaner = new Finalizable()
-         {
-            @Override
-            public void finished()
-            {
-               pauseBeforeEnd(tm.isProcessingQuit());
-            }
-            
-            @Override
-            public void iterate()
-            {
-               // nothing to do here 
-            }
-            
-            @Override
-            public void retry()
-            {
-               // nothing to do here 
-            }
-            
-            @Override
-            public void deleted()
-            {
-               // nothing to do here
-            }
-         };
-         
-         tm.registerFinalizable(cleaner, true);
-      }
-
       // add a new scope for each top-level block
       if (tm.isTopLevelBlock())
       {
@@ -13463,6 +13501,17 @@
    }
    
    /**
+    * Get the {@link ScopeId} for the instance.
+    * 
+    * @return   {@link ScopeId#LOGICAL_TERMINAL}.
+    */
+   @Override
+   public ScopeId getScopeId()
+   {
+      return ScopeId.LOGICAL_TERMINAL;
+   }
+   
+   /**
     * Called by the protocol driver to obtain the latest state that should
     * be sent to the other side of the connection.  This is called just 
     * before a "real" message is sent and any changes provided will be
@@ -13589,6 +13638,12 @@
          
          if (cs.deregister != null)
          {
+            // scopeable registration must be done in any location where a frame state (requiring scope 
+            // support) is being 'touched' - this includes frame de-registration, even if the current block 
+            // is not explicitly using frames
+
+            tm.registerBlockScopeable(this);
+
             for (int i : cs.deregister)
             {
                deregisterFrameInt(i);
@@ -13605,6 +13660,8 @@
          
          if (cs.wrapped != null && scopeTrack != null)
          {
+            tm.registerBlockScopeable(this);
+            
             for (int id : cs.wrapped)
             {
                scopeTrack.frameWrapped(id);

=== modified file 'src/com/goldencode/p2j/ui/MenuContainerWidget.java'
--- old/src/com/goldencode/p2j/ui/MenuContainerWidget.java	2020-11-17 20:03:34 +0000
+++ new/src/com/goldencode/p2j/ui/MenuContainerWidget.java	2022-09-06 18:22:04 +0000
@@ -2,7 +2,7 @@
 ** Module   : MenuContainerWidget.java
 ** Abstract : The base class for MenuWidget and SubMenuWidget.
 **
-** Copyright (c) 2015-2020, Golden Code Development Corporation.
+** Copyright (c) 2015-2022, Golden Code Development Corporation.
 **
 ** -#- -I- --Date-- ---------------------------------Description----------------------------------
 ** 001 VIG 20150115 Created initial version.
@@ -34,6 +34,12 @@
 **     EVL 20201021 More optimization for getAttr() usage or widget ID getting.
 **     EVL 20201022 Optimized attributes flush implementation.
 **     CA  20201117 Do not use resourceDelete to delete the widget - use the delete() API for this code.
+**     CA  20220901 Refactored scope notification support: ScopeableFactory was removed, and the registration 
+**                  is now specific to each type of scopeable.  For each case, the block will be registered 
+**                  for scope support (for that particular scopeable) only when the scopeable is 'active' 
+**                  (i.e. unnamed streams or accumulators are used).  This allows a lazy registration of 
+**                  scopeables, to avoid the unnecessary overhead of processing all the scopeables for each
+**                  and every block.
 */
 /*
 ** This program is free software: you can redistribute it and/or modify
@@ -494,6 +500,8 @@
       // push down the screen definition
       if (force)
       {
+         TransactionManager.registerBlockScopeable(LogicalTerminal.getInstance());
+
          // synchronous push
          MenuDescription[] sd = new MenuDescription[] {root.menuDef};
          LogicalTerminal.pushMenuDescription(sd);

=== modified file 'src/com/goldencode/p2j/ui/TriggerManager.java'
--- old/src/com/goldencode/p2j/ui/TriggerManager.java	2022-01-26 15:36:52 +0000
+++ new/src/com/goldencode/p2j/ui/TriggerManager.java	2022-09-06 18:22:04 +0000
@@ -56,6 +56,12 @@
 **                           the resource gets deleted (including widgets).
 **     CA  20210917          Javadoc fixes.
 **     SBI 20220126          Fixed scopeFinished to add preserved events before all events from the target list.
+**     CA  20220901          Refactored scope notification support: ScopeableFactory was removed, and the 
+**                           registration is now specific to each type of scopeable.  For each case, the block
+**                           will be registered for scope support (for that particular scopeable) only when
+**                           the scopeable is 'active' (i.e. unnamed streams or accumulators are used).  This
+**                           allows a lazy registration of scopeables, to avoid the unnecessary overhead of
+**                           processing all the scopeables for each and every block.
 */
 
 /*
@@ -190,6 +196,8 @@
                                EventList             events, 
                                int                   tid)
    {
+      TransactionManager.registerBlockScopeable(LogicalTerminal.getInstance());
+
       events.setTriggerId(tid);
 
       // insert the latest trigger def at the front of the list. all created triggers are saved,
@@ -261,6 +269,8 @@
    @SuppressWarnings("unchecked")
    public static void deregister(ScopedDictionary<?,?> registry, EventList el)
    {
+      TransactionManager.registerBlockScopeable(LogicalTerminal.getInstance());
+
       LinkedList<EventList> registryScope = null;
 
       // DEBUG: System.err.printf("DEREGISTER\nBEFORE %s", dumpRegistry(registry));

=== modified file 'src/com/goldencode/p2j/util/AbstractParameter.java'
--- old/src/com/goldencode/p2j/util/AbstractParameter.java	2021-06-14 10:34:33 +0000
+++ new/src/com/goldencode/p2j/util/AbstractParameter.java	2022-09-06 18:22:04 +0000
@@ -3,7 +3,7 @@
 ** Abstract : Common logic for assigns [input-]output parameter values (database fields and normal
 **            variables) upon exit from procedure/function
 **
-** Copyright (c) 2015-2021, Golden Code Development Corporation.
+** Copyright (c) 2015-2022, Golden Code Development Corporation.
 **
 ** -#- -I- --Date-- ---------------------------------------Description----------------------------------------
 ** 001 OM  20150702 First version based on extracted code form AbstractSimpleParameter and
@@ -18,6 +18,13 @@
 **                  in the parameter instance must be replaced.
 **     CA  20210609 Reworked INPUT/INPUT-OUTPUT parameters to a new approach, where they are explicitly 
 **                  initialized at the method's execution, and not at the caller's arguments.
+**     CA  20220901 Refactored scope notification support: ScopeableFactory was removed, and the registration 
+**                  is now specific to each type of scopeable.  For each case, the block will be registered 
+**                  for scope support (for that particular scopeable) only when the scopeable is 'active' 
+**                  (i.e. unnamed streams or accumulators are used).  This allows a lazy registration of 
+**                  scopeables, to avoid the unnecessary overhead of processing all the scopeables for each
+**                  and every block.
+**     CA  20220906 Parameter scope notifications is required for all OUTPUT parameters, not just fields.
 */
 /*
 ** This program is free software: you can redistribute it and/or modify
@@ -76,6 +83,7 @@
 
 import com.goldencode.p2j.persist.*;
 import com.goldencode.p2j.security.*;
+
 import java.lang.reflect.*;
 import java.util.*;
 import java.util.logging.*;
@@ -134,6 +142,15 @@
    };
    
    /**
+    * Default constructor - register the {@link WorkArea scopeable} instance as pending for the next started
+    * block.
+    */
+   public AbstractParameter()
+   {
+      TransactionManager.registerPendingScopeable(getScopeable());
+   }
+   
+   /**
     * Retrieve a {@code Scopeable} for the current context. This allows an external object to
     * notify this class' current context of scope open and close events.
     *
@@ -338,6 +355,17 @@
       }
       
       /**
+       * Get the {@link ScopeId} for the instance.
+       * 
+       * @return   {@link ScopeId#ABSTRACT_PARAMETER}.
+       */
+      @Override
+      public ScopeId getScopeId()
+      {
+         return ScopeId.ABSTRACT_PARAMETER;
+      }
+      
+      /**
        * Get the object which manages field assigners for the current scope.
        *
        * @return  Current scope.

=== modified file 'src/com/goldencode/p2j/util/Accumulator.java'
--- old/src/com/goldencode/p2j/util/Accumulator.java	2019-03-26 11:53:52 +0000
+++ new/src/com/goldencode/p2j/util/Accumulator.java	2022-09-06 18:22:04 +0000
@@ -2,7 +2,7 @@
 ** Module   : Accumulator.java
 ** Abstract : Abstract base class for concrete accumulator implementations
 **
-** Copyright (c) 2004-2019, Golden Code Development Corporation.
+** Copyright (c) 2004-2022, Golden Code Development Corporation.
 **
 ** -#- -I- --Date-- --JPRM-- -----------------------------Description-----------------------------
 ** 001 ECF 20051227   @23778 Created initial version. Base class for
@@ -117,6 +117,12 @@
 **                           when reset() called without accumulate().
 ** 028 CA  20190326          Implemented TransactionHelper instead of direct usage of the static   
 **                           API.  This allows the elimination of context local usage in TM.
+** 029 CA  20220901          Refactored scope notification support: ScopeableFactory was removed, and the 
+**                           registration is now specific to each type of scopeable.  For each case, the block
+**                           will be registered for scope support (for that particular scopeable) only when
+**                           the scopeable is 'active' (i.e. unnamed streams or accumulators are used).  This
+**                           allows a lazy registration of scopeables, to avoid the unnecessary overhead of
+**                           processing all the scopeables for each and every block.
 */
 /*
 ** This program is free software: you can redistribute it and/or modify
@@ -1099,6 +1105,18 @@
    {
       // no-op
    }
+   
+   /**
+    * Get the {@link ScopeId} for the instance.
+    * 
+    * @return   <code>null</code>, as the scope notification is done via {@link AccumulatorManager}.
+    */
+   @Override
+   public ScopeId getScopeId()
+   {
+      // no ID necessary, as it does not get registered explicitly, is called via AccumulatorManager
+      return null;
+   }
 
    /**
     * Process a notification that a new block scope has been entered. Each 

=== modified file 'src/com/goldencode/p2j/util/AccumulatorManager.java'
--- old/src/com/goldencode/p2j/util/AccumulatorManager.java	2021-03-10 18:41:30 +0000
+++ new/src/com/goldencode/p2j/util/AccumulatorManager.java	2022-09-06 18:22:04 +0000
@@ -2,7 +2,7 @@
 ** Module   : AccumulatorManager.java
 ** Abstract : provides block-behavior for the accumulators.
 **
-** Copyright (c) 2008-2021, Golden Code Development Corporation.
+** Copyright (c) 2008-2022, Golden Code Development Corporation.
 **
 ** -#- -I- --Date-- -T- --JPRM-- ----------------Description-----------------
 ** 001 CA  20080129   @37024 Created initial version. Provides context
@@ -33,6 +33,12 @@
 ** 010 CA  20200930          Refactored to remove the singleton scopeable instance (which forced the scope 
 **                           notifications to use context-local state).
 **     CA  20210310          Small performance improvement.
+**     CA  20220901          Refactored scope notification support: ScopeableFactory was removed, and the 
+**                           registration is now specific to each type of scopeable.  For each case, the block
+**                           will be registered for scope support (for that particular scopeable) only when
+**                           the scopeable is 'active' (i.e. unnamed streams or accumulators are used).  This
+**                           allows a lazy registration of scopeables, to avoid the unnecessary overhead of
+**                           processing all the scopeables for each and every block.
 */
 /*
 ** This program is free software: you can redistribute it and/or modify
@@ -107,46 +113,26 @@
  * on a static basis but instead requires an instance method implementation.
  * These instance methods then call the static counterparts which is where
  * the content-local versions of the resource pools are accessed.
- * <p>
- * The initialization processing is implemented in the {@link #initialize}
- * method and it is called during server startup based on an entry in the
- * directory until the path "/server/&lt;serverId&gt;/startup" or in
- * "/server/default/startup".
  *
  * @author    CA
  */
 public class AccumulatorManager
-implements Scopeable,
-           ScopeableFactory
+implements Scopeable
 {
    /** Pool of accums which is context-local. Initializes upon first use. */ 
    private static ContextContainer context = new ContextContainer();
    
    /** The container with the instance state. */
    private final WorkArea wa = new WorkArea();
-   
-   /**
-    * Initialize the accumulator framework. This registers with the
-    * {@link TransactionManager TransactionManager} a factory object which
-    * creates a <code>Scopeable</code> instance.
-    *
-    * @throws  IllegalStateException
-    *          If invoked more than once.
-    */
-   public static void initialize()
-   {
-      TransactionManager.registerScopeableFactory(() -> context.get());
-   }
-   
-   /**
-    * Provides the singleton instance of this class as a
-    * <code>Scopeable</code>.
-    *
-    * @return  This object.
-    */
-   public Scopeable createScopeable()
-   {
-      return this;
+
+   /**
+    * Get the {@link AccumulatorManager} context-local instance.
+    * 
+    * @return   See above.
+    */
+   public static AccumulatorManager getInstance()
+   {
+      return context.get();
    }
    
    /**
@@ -157,7 +143,9 @@
     */
    public static void addAccumulator(Accumulator accum)
    {
-      WorkArea wa = context.get().wa;
+      AccumulatorManager mgr = context.get();
+      WorkArea wa = mgr.wa;
+      wa.tm.registerPendingScopeable(mgr);
       
       if (wa.pendingSet == null)
       {
@@ -239,6 +227,17 @@
       // no-op
    }
    
+   /**
+    * Get the {@link ScopeId} for the instance.
+    * 
+    * @return   {@link ScopeId#ACCUMULATOR_MANAGER}.
+    */
+   @Override
+   public ScopeId getScopeId()
+   {
+      return ScopeId.ACCUMULATOR_MANAGER;
+   }
+   
    /** 
     * Stores global data relating to the state of the current context.
     */

=== modified file 'src/com/goldencode/p2j/util/Agent.java'
--- old/src/com/goldencode/p2j/util/Agent.java	2022-04-05 17:52:31 +0000
+++ new/src/com/goldencode/p2j/util/Agent.java	2022-09-06 18:22:04 +0000
@@ -81,6 +81,12 @@
 **     CA  20220329 Added support for REST services written directly in Java.
 **     CA  20220405 Added authentication and authorization for web requests.  When this is enabled, the target
 **                  API call will be executed under the authenticated FWD context, and not the agent's context.
+**     CA  20220901 Refactored scope notification support: ScopeableFactory was removed, and the registration 
+**                  is now specific to each type of scopeable.  For each case, the block will be registered 
+**                  for scope support (for that particular scopeable) only when the scopeable is 'active' 
+**                  (i.e. unnamed streams or accumulators are used).  This allows a lazy registration of 
+**                  scopeables, to avoid the unnecessary overhead of processing all the scopeables for each
+**                  and every block.
 */
 
 /*
@@ -2592,6 +2598,8 @@
     */
    private void preProcessArguments(String requesterModes, Object[] args)
    {
+      ControlFlowOps.resetReturnValueSetInRootScope();
+      
       String modes = requesterModes;
       if (modes == null || modes.length() != args.length)
       {
@@ -2859,6 +2867,8 @@
       
       // CA: 20210405: this comment is no longer valid.  it is left as a 'warning' for future readers in case
       // the LT/TM initialization order is changed again and STATE-RESET mode gets broken...
+      // CA: 20220831: ScopeableFactory was removed and the comment bellow is no longer valid, but still left
+      // for historical purposes.
       
       // make sure the TM is loaded before LT, as the LT context-local instance is dependent on TM
       // when creating the ScopeableFactory instances, LT will be loaded by TM and this instance
@@ -2877,8 +2887,6 @@
       DatabaseTriggerManager.get();
 
       ProcedureManager.load();
-      ArrayAssigner.load();
-      ObjectOps.load();
 
       // Global scope for keeping buffers states (like BufferManager.allBuffers). This scope must be explicitly
       // popped when the agent context gets reset or when the agent terminates, so the dynamic DMOs and any 

=== modified file 'src/com/goldencode/p2j/util/ArrayAssigner.java'
--- old/src/com/goldencode/p2j/util/ArrayAssigner.java	2021-06-09 14:35:48 +0000
+++ new/src/com/goldencode/p2j/util/ArrayAssigner.java	2022-09-06 18:22:04 +0000
@@ -2,7 +2,7 @@
 ** Module   : ArrayAssigner.java
 ** Abstract : automates the assignment of each element of a BDT array
 **
-** Copyright (c) 2005-2021, Golden Code Development Corporation.
+** Copyright (c) 2005-2022, Golden Code Development Corporation.
 **
 ** -#- -I- --Date-- ---------------------------------------Description----------------------------------------
 ** 001 GES 20051104   @23229 Provides a simple helper to automate the assignment of each element
@@ -63,6 +63,12 @@
 **     CA  20210322          Fixed dynamic arrays used in persistent programs or OO instances.
 **     CA  20210609          Reworked INPUT/INPUT-OUTPUT parameters to a new approach, where they are explicitly 
 **                           initialized at the method's execution, and not at the caller's arguments.
+**     CA  20220901          Refactored scope notification support: ScopeableFactory was removed, and the 
+**                           registration is now specific to each type of scopeable.  For each case, the block
+**                           will be registered for scope support (for that particular scopeable) only when
+**                           the scopeable is 'active' (i.e. unnamed streams or accumulators are used).  This
+**                           allows a lazy registration of scopeables, to avoid the unnecessary overhead of
+**                           processing all the scopeables for each and every block.
 */
 
 /*
@@ -138,22 +144,11 @@
       protected WorkArea initialValue()
       {
          WorkArea wa = new WorkArea();
-         TransactionManager.registerScopeable(wa);
          return wa;
       };
    };
 
    /**
-    * Ensure all the context-local data has initialized (to be able to listen for scope 
-    * notifications).
-    */
-   static void load()
-   {
-      // needed to ensure the variable is loaded, so it listens for scope notifications
-      local.get();
-   }
-
-   /**
     * Assigns (mutates the content of) each matching element of a target
     * array based on the corresponding element of the source array.  The
     * source and target arrays must be assignable to each other (must be
@@ -1958,6 +1953,17 @@
       }
       
       /**
+       * Get the {@link ScopeId} for the instance.
+       * 
+       * @return   {@link ScopeId#ARRAY_ASSIGNER}.
+       */
+      @Override
+      public ScopeId getScopeId()
+      {
+         return ScopeId.ARRAY_ASSIGNER;
+      }
+      
+      /**
        * Returns <code>true</code> if the supplied array argument has been
        * previously registered, <code>false</code> otherwise.
        * 
@@ -2010,6 +2016,8 @@
          {
             if (pending)
             {
+               tm.registerPendingScopeable(this);
+               
                pendingDynamicArrays.add(array);
             }
             else

=== modified file 'src/com/goldencode/p2j/util/BlockDefinition.java'
--- old/src/com/goldencode/p2j/util/BlockDefinition.java	2022-01-20 16:18:02 +0000
+++ new/src/com/goldencode/p2j/util/BlockDefinition.java	2022-09-06 18:22:04 +0000
@@ -48,6 +48,12 @@
 ** 025 GES 20210809          Added finallyBlock runnable.
 **     CA  20220120          Improved performance for finalizables, keep them already sorted by their weight.
 **                           ConditionListener's are kept in their own list.
+**     CA  20220901          Refactored scope notification support: ScopeableFactory was removed, and the 
+**                           registration is now specific to each type of scopeable.  For each case, the block
+**                           will be registered for scope support (for that particular scopeable) only when
+**                           the scopeable is 'active' (i.e. unnamed streams or accumulators are used).  This
+**                           allows a lazy registration of scopeables, to avoid the unnecessary overhead of
+**                           processing all the scopeables for each and every block.
 */
 
 /*
@@ -106,6 +112,7 @@
 package com.goldencode.p2j.util;
 
 import java.util.*;
+import java.util.BitSet;
 import java.util.function.*;
 
 import com.goldencode.p2j.security.*;
@@ -240,6 +247,23 @@
     */
    public List<Finalizable>[] finalizables = new ArrayList[WeightFactor.WEIGHTS_ARRAY_SIZE];
    
+   /**
+    * The list of {@link Scopeable} instances which are required to be processed in this block.  
+    * <p>
+    * This list is dynamically adjusted - it can be added a new scopeable before or after the block has started.
+    * If the new instance is registered after the block has started, the {@link Scopeable#scopeStart()} will 
+    * be executed at the time of registration.
+    * <p>
+    * The map key is the scopeable's class, as only an instance of each type can be registered. 
+    */
+   public List<Scopeable> scopeList = null;
+   
+   /**
+    * A bitset to mark which {@link Scopeable} instances are registered at this block, using the 
+    * {@link ScopeId} as returned by {@link Scopeable#getScopeId()}.
+    */
+   public BitSet scopeables = null;
+   
    /** The list of condition listeners.  Processed in the registration order.. */
    public List<ConditionListener> conditionListeners = new ArrayList<>(2);
    

=== modified file 'src/com/goldencode/p2j/util/BlockManager.java'
--- old/src/com/goldencode/p2j/util/BlockManager.java	2022-08-17 07:58:35 +0000
+++ new/src/com/goldencode/p2j/util/BlockManager.java	2022-09-06 18:22:04 +0000
@@ -216,6 +216,12 @@
 **                           in old-style ABL function must remain the same. Refs: #6567.
 **     TW  20220804          Harden against NPE.
 **     TW  20220817          Error-status:error must be set for return error in static method.
+**     CA  20220901          Refactored scope notification support: ScopeableFactory was removed, and the 
+**                           registration is now specific to each type of scopeable.  For each case, the block
+**                           will be registered for scope support (for that particular scopeable) only when
+**                           the scopeable is 'active' (i.e. unnamed streams or accumulators are used).  This
+**                           allows a lazy registration of scopeables, to avoid the unnecessary overhead of
+**                           processing all the scopeables for each and every block.
 */
 
 /*
@@ -527,8 +533,6 @@
       
       // ensure certain context-local data has loaded
       ProcedureManager.load();
-      ArrayAssigner.load();
-      ObjectOps.load();
 
       topLevelBlock(null, referent, null, lvl, BlockType.EXTERNAL_PROC, block);
    }
@@ -588,8 +592,6 @@
       
       // ensure certain context-local data has loaded
       ProcedureManager.load();
-      ArrayAssigner.load();
-      ObjectOps.load();
 
       topLevelBlock(def, referent, null, lvl, BlockType.EXTERNAL_PROC, block);
    }

=== modified file 'src/com/goldencode/p2j/util/ControlFlowOps.java'
--- old/src/com/goldencode/p2j/util/ControlFlowOps.java	2022-08-17 07:58:35 +0000
+++ new/src/com/goldencode/p2j/util/ControlFlowOps.java	2022-09-06 18:22:04 +0000
@@ -245,6 +245,12 @@
 **     TW  20220806          Error-status:error must be set for return error in dynamic-invoke of method
 **                           of ABL class. Fixes xfer testc ErrorStatusFlowTester.w Test06. 
 **     TW  20220817          Error-status:error must be set for return error in static method.
+**     CA  20220901          Refactored scope notification support: ScopeableFactory was removed, and the 
+**                           registration is now specific to each type of scopeable.  For each case, the block
+**                           will be registered for scope support (for that particular scopeable) only when
+**                           the scopeable is 'active' (i.e. unnamed streams or accumulators are used).  This
+**                           allows a lazy registration of scopeables, to avoid the unnecessary overhead of
+**                           processing all the scopeables for each and every block.
 */
 
 /*
@@ -451,6 +457,14 @@
    }
 
    /**
+    * Reset the {@link WorkArea#returnValueSetInRootScope} flag.
+    */
+   public static void resetReturnValueSetInRootScope()
+   {
+      work.obtain().returnValueSetInRootScope = false;
+   }
+   
+   /**
     * Sets the most recent RETURN-VALUE for the current user's session.
     *
     * @param    returnValue
@@ -8086,7 +8100,6 @@
     * Stores global data relating to the state of the current context.
     */
    private static class WorkArea
-   implements Scopeable
    {  
       /** Helper to use the ProcedureManager without any context local lookups. */
       private final ProcedureManager.ProcedureHelper pm = ProcedureManager.getProcedureHelper();
@@ -8169,36 +8182,6 @@
          inHandleResolvers.add(internalResolver);
          inHandleResolvers.add(internalProcSuperResolver);
          inHandleResolvers.add(sessionSuperResolver);
-
-         TransactionManager.registerScopeable(this);
-      }
-
-      /**
-       * Provides a notification that a new scope is about to be entered.
-       */
-      @Override
-      public void scopeStart()
-      {
-         if (tm.isRootNestingLevel())
-         {
-            returnValueSetInRootScope = false;
-         }
-      }
-
-      /**
-       * No-op.
-       */
-      @Override
-      public void scopeFinished()
-      {
-      }
-
-      /**
-       * No-op.
-       */
-      @Override
-      public void scopeDeleted()
-      {
       }
    }
    

=== modified file 'src/com/goldencode/p2j/util/ObjectOps.java'
--- old/src/com/goldencode/p2j/util/ObjectOps.java	2022-02-09 14:18:48 +0000
+++ new/src/com/goldencode/p2j/util/ObjectOps.java	2022-09-06 18:22:04 +0000
@@ -61,6 +61,12 @@
 **     CA  20220203 A small performance improvement for scopeFinished.
 **     CA  20220208 Objects deserialized from a remote appserver call can be automatically registered as known
 **                  instances.  Fixed a 'pendingAssign' bug when the root block exits. 
+**     CA  20220901 Refactored scope notification support: ScopeableFactory was removed, and the registration 
+**                  is now specific to each type of scopeable.  For each case, the block will be registered 
+**                  for scope support (for that particular scopeable) only when the scopeable is 'active' 
+**                  (i.e. unnamed streams or accumulators are used).  This allows a lazy registration of 
+**                  scopeables, to avoid the unnecessary overhead of processing all the scopeables for each
+**                  and every block.
 */
 
 /*
@@ -963,7 +969,7 @@
       // if an error was throw from within a c'tor (or UNDO, THROW with ROUTINE-LEVEL ON ERROR UNDO, THROW.)
       // then the initialization failed and call the destructors for all initialized classes
       
-      pendingInitialize(ref);
+      pendingInitialize(wa, ref);
       
       boolean ok = false;
       boolean err = false;
@@ -1491,17 +1497,7 @@
    {
       loadClass(cls);
    }
-   
-   /**
-    * Ensure all the context-local data has initialized (to be able to listen for scope 
-    * notifications).
-    */
-   static void load()
-   {
-      // needed to ensure the variable is loaded, so it listens for scope notifications
-      local.get();
-   }
-   
+
    /**
     * Increment the reference counter for this instance.
     * 
@@ -1612,6 +1608,8 @@
       WorkArea wa = local.get();
       
       wa.pendingObjects.addAll(Arrays.asList(objects));
+      
+      wa.tm.registerPendingScopeable(wa);
    }
 
    /**
@@ -1642,6 +1640,8 @@
          return;
       
       WorkArea wa = local.get();
+      wa.tm.registerBlockScopeable(wa);
+      
       Set<_BaseObject_> pending = wa.pendingAssigned.peek();
       if (pending == Collections.EMPTY_SET)
       {
@@ -1657,16 +1657,32 @@
     * @param    ref
     *           The legacy instance.
     */
-   static void pendingInitialize(_BaseObject_ ref)
+   private static void pendingInitialize(WorkArea wa, _BaseObject_ ref)
    {
-      WorkArea wa = local.get();
-      Set<_BaseObject_> pending = wa.pendingInitialized.peek();
-      if (pending == Collections.EMPTY_SET)
-      {
-         wa.pendingInitialized.pop();
-         wa.pendingInitialized.push(pending = Collections.newSetFromMap(new IdentityHashMap<>()));
-      }
-      pending.add(ref);
+      List<Object> savePending = wa.pendingObjects.isEmpty() ? null : new ArrayList<>(wa.pendingObjects);
+      try
+      {
+         // clear and restore the pending objects, as 'registerBlockScopeable' may register current block for
+         // scope notifications, but the 'pending' objects are not ours.
+         wa.pendingObjects.clear();
+         wa.tm.registerBlockScopeable(wa);
+         
+         Set<_BaseObject_> pending = wa.pendingInitialized.peek();
+         if (pending == Collections.EMPTY_SET)
+         {
+            wa.pendingInitialized.pop();
+            wa.pendingInitialized.push(pending = Collections.newSetFromMap(new IdentityHashMap<>()));
+         }
+         pending.add(ref);
+      }
+      finally
+      {
+         wa.pendingObjects.clear();
+         if (savePending != null)
+         {
+            wa.pendingObjects.addAll(savePending);
+         }
+      }
    }
    
    /**
@@ -2591,15 +2607,7 @@
       
       /** The map of instantiated {@link LegacyClass} objects. */
       private Map<Class<? extends _BaseObject_>, LegacyClass> legacyClasses = new IdentityHashMap<>();
-      
-      /**
-       * Initialize by registering this instance for scope notifications.
-       */
-      public WorkArea()
-      {
-         TransactionManager.registerScopeable(this);
-      }
-      
+
       /**
        * Provides a notification that a new scope is about to be entered.
        * <p>
@@ -2799,6 +2807,17 @@
       }
 
       /**
+       * Get the {@link ScopeId} for the instance.
+       * 
+       * @return   {@link ScopeId#OBJECT_OPS}.
+       */
+      @Override
+      public ScopeId getScopeId()
+      {
+         return ScopeId.OBJECT_OPS;
+      }
+      
+      /**
        * Check if a destructor with the given name is defined in the specified class.
        * 
        * @param    cls

=== modified file 'src/com/goldencode/p2j/util/ProcedureManager.java'
--- old/src/com/goldencode/p2j/util/ProcedureManager.java	2022-08-17 07:58:35 +0000
+++ new/src/com/goldencode/p2j/util/ProcedureManager.java	2022-09-06 18:22:04 +0000
@@ -201,6 +201,12 @@
 **                  in old-style ABL function must remain the same. Refs: #6567.
 **     TW  20220806 Error-status:error must be set for return error in method. Javadoc fix.
 **     TW  20220817 Error-status:error must be set for return error in static method.
+**     CA  20220901 Refactored scope notification support: ScopeableFactory was removed, and the registration 
+**                  is now specific to each type of scopeable.  For each case, the block will be registered 
+**                  for scope support (for that particular scopeable) only when the scopeable is 'active' 
+**                  (i.e. unnamed streams or accumulators are used).  This allows a lazy registration of 
+**                  scopeables, to avoid the unnecessary overhead of processing all the scopeables for each
+**                  and every block.
 */
 
 /*
@@ -480,6 +486,8 @@
          return;
       }
       
+      BufferManager.registerScopeable(BufferManager.get());
+      
       Object referent = _thisProcedure(wa);
       ProcedureData pp = locateProcedure(wa, referent);
 
@@ -4922,6 +4930,8 @@
       @Override
       public void scopeStart()
       {
+         // WARNING: in TransactionManager, this is called only for top-level blocks.
+
          BlockType bt = tm.getBlockType();
          
          if (bt.equals(BlockType.EXTERNAL_PROC) ||
@@ -4992,6 +5002,8 @@
       @Override
       public void scopeFinished()
       {
+         // WARNING: in TransactionManager, this is called only for top-level blocks.
+
          BlockType bt = tm.getBlockType();
          if (bt.equals(BlockType.EXTERNAL_PROC) ||
              bt.equals(BlockType.INTERNAL_PROC) ||
@@ -5127,6 +5139,19 @@
       }
       
       /**
+       * Get the {@link ScopeId} for the instance.
+       * 
+       * @return   <code>null</code>, as the scope notification is done explicitly via 
+       *           {@link TransactionManager}.
+       */
+      @Override
+      public ScopeId getScopeId()
+      {
+         // no ID required, as this is called explicitly in TransactionManager
+         return null;
+      }
+      
+      /**
        * Get the map of session's super-procedures.
        */
       public Map<Object, ProcedureData> getSessionSuperProcedures()

=== modified file 'src/com/goldencode/p2j/util/Scopeable.java'
--- old/src/com/goldencode/p2j/util/Scopeable.java	2017-04-01 23:33:34 +0000
+++ new/src/com/goldencode/p2j/util/Scopeable.java	2022-09-06 18:22:04 +0000
@@ -3,14 +3,20 @@
 ** Abstract : the interface for objects whose changes must be externally 
 **            initialized at scope start and finalized at scope exit
 **
-** Copyright (c) 2005-2017, Golden Code Development Corporation.
+** Copyright (c) 2005-2022, Golden Code Development Corporation.
 **
 ** -#- -I- --Date-- --JPRM-- -----------------------------Description-----------------------------
 ** 001 GES 20050817   @22162 Created initial version, providing an interface for objects needing 
 **                           initialization and termination callbacks at the start and end of 
 **                           every block entered and exited.
-** 005 CA  20131013          Added scopeDeleted() API, which will be called when the instantiating 
+** 002 CA  20131013          Added scopeDeleted() API, which will be called when the instantiating 
 **                           procedure where this scopeable instance was registered gets deleted.
+** 003 CA  20220901          Refactored scope notification support: ScopeableFactory was removed, and the 
+**                           registration is now specific to each type of scopeable.  For each case, the block
+**                           will be registered for scope support (for that particular scopeable) only when
+**                           the scopeable is 'active' (i.e. unnamed streams or accumulators are used).  This
+**                           allows a lazy registration of scopeables, to avoid the unnecessary overhead of
+**                           processing all the scopeables for each and every block.
 */
 /*
 ** This program is free software: you can redistribute it and/or modify
@@ -90,4 +96,12 @@
     * Provides a notification that an external scope is about to be deleted.
     */
    public void scopeDeleted();
+   
+   /**
+    * Get the {@link ScopeId} for the instance.
+    * 
+    * @return   The scope ID, or <code>null</code> for cases when the scope notification is not done via
+    *           {@link BlockDefinition#scopeList}.
+    */
+   public ScopeId getScopeId();
 }
\ No newline at end of file

=== removed file 'src/com/goldencode/p2j/util/ScopeableFactory.java'
--- old/src/com/goldencode/p2j/util/ScopeableFactory.java	2017-04-01 23:33:34 +0000
+++ new/src/com/goldencode/p2j/util/ScopeableFactory.java	1970-01-01 00:00:00 +0000
@@ -1,92 +0,0 @@
-/*
-** Module   : ScopeableFactory.java
-** Abstract : factory of Scopeable objects
-**
-** Copyright (c) 2005-2017, Golden Code Development Corporation.
-**
-** -#- -I- --Date-- -T- --JPRM-- ----------------Description-----------------
-** 001 ECF 20050920 NEW   @30221 Created initial version. Defines a single
-**                               method which serves up a Scopeable instance.
-** 002 GES 20061009 CHG   @30222 Minor javadoc enhancement.
-*/
-/*
-** This program is free software: you can redistribute it and/or modify
-** it under the terms of the GNU Affero General Public License as
-** published by the Free Software Foundation, either version 3 of the
-** License, or (at your option) any later version.
-**
-** This program is distributed in the hope that it will be useful,
-** but WITHOUT ANY WARRANTY; without even the implied warranty of
-** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-** GNU Affero General Public License for more details.
-**
-** You may find a copy of the GNU Affero GPL version 3 at the following
-** location: https://www.gnu.org/licenses/agpl-3.0.en.html
-** 
-** Additional terms under GNU Affero GPL version 3 section 7:
-** 
-**   Under Section 7 of the GNU Affero GPL version 3, the following additional
-**   terms apply to the works covered under the License.  These additional terms
-**   are non-permissive additional terms allowed under Section 7 of the GNU
-**   Affero GPL version 3 and may not be removed by you.
-** 
-**   0. Attribution Requirement.
-** 
-**     You must preserve all legal notices or author attributions in the covered
-**     work or Appropriate Legal Notices displayed by works containing the covered
-**     work.  You may not remove from the covered work any author or developer
-**     credit already included within the covered work.
-** 
-**   1. No License To Use Trademarks.
-** 
-**     This license does not grant any license or rights to use the trademarks
-**     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
-**     of Golden Code Development Corporation. You are not authorized to use the
-**     name Golden Code, FWD, or the names of any author or contributor, for
-**     publicity purposes without written authorization.
-** 
-**   2. No Misrepresentation of Affiliation.
-** 
-**     You may not represent yourself as Golden Code Development Corporation or FWD.
-** 
-**     You may not represent yourself for publicity purposes as associated with
-**     Golden Code Development Corporation, FWD, or any author or contributor to
-**     the covered work, without written authorization.
-** 
-**   3. No Misrepresentation of Source or Origin.
-** 
-**     You may not represent the covered work as solely your work.  All modified
-**     versions of the covered work must be marked in a reasonable way to make it
-**     clear that the modified work is not originating from Golden Code Development
-**     Corporation or FWD.  All modified versions must contain the notices of
-**     attribution required in this license.
-*/
-
-package com.goldencode.p2j.util;
-
-/**
- * A factory which creates {@link Scopeable} objects to be notified of scope
- * openings and closings by the {@link TransactionManager}. Classes which
- * implement this interface typically are {@link
- * TransactionManager#registerScopeableFactory registered} during the
- * bootstrap phase of the server environment.
- */
-public interface ScopeableFactory
-{
-   /**
-    * Return a <code>Scopeable</code> instance. The
-    * <code>TransactionManager</code> registers this instance with each
-    * context local work area it creates.
-    * <p>
-    * This method may optionally determine that the <code>Scopeable</code>
-    * type created by this factory is not appropriate for this particular
-    * context (account).  In this case a <code>null</code> must be returned
-    * which tells the <code>TransactionManager</code> not to register any
-    * <code>Scopeable</code> for this factory.
-    *
-    * @return   An object which the <code>TransactionManager</code> notifies
-    *           of runtime scope openings and closings or <code>null</code>
-    *           if no object should be registered in this context.
-    */
-   public Scopeable createScopeable();
-}
\ No newline at end of file

=== modified file 'src/com/goldencode/p2j/util/SharedVariableManager.java'
--- old/src/com/goldencode/p2j/util/SharedVariableManager.java	2022-02-06 12:20:41 +0000
+++ new/src/com/goldencode/p2j/util/SharedVariableManager.java	2022-09-06 18:22:04 +0000
@@ -107,6 +107,12 @@
 **                           not persistent.
 **     OM  20220115          APIs for queries accept a list of static BROWSE names for detecting errors.
 **     CA  20220206          In scopeDeleted, use the ProcedureHelper instance to avoid context-local lookup.
+**     CA  20220901          Refactored scope notification support: ScopeableFactory was removed, and the 
+**                           registration is now specific to each type of scopeable.  For each case, the block
+**                           will be registered for scope support (for that particular scopeable) only when
+**                           the scopeable is 'active' (i.e. unnamed streams or accumulators are used).  This
+**                           allows a lazy registration of scopeables, to avoid the unnecessary overhead of
+**                           processing all the scopeables for each and every block.
 */
 
 /*
@@ -213,11 +219,6 @@
  * on a static basis but instead requires an instance method implementation.
  * These instance methods then call the static counterparts which is where
  * the content-local versions of the resource pools are accessed.
- * <p>
- * The initialization processing is implemented in the {@link #initialize}
- * method and it is called during server startup based on an entry in the
- * directory until the path "/server/&lt;serverId&gt;/startup" or in
- * "/server/default/startup".
  */
 public class SharedVariableManager
 implements Scopeable
@@ -233,19 +234,6 @@
    
    /** The instance state. */
    private final WorkArea wa = new WorkArea();
-    
-   /**
-    * Initialize the shared variable framework. This registers with the
-    * {@link TransactionManager TransactionManager} a factory object which
-    * creates a {@link Scopeable} instance.
-    *
-    * @throws  IllegalStateException
-    *          If invoked more than once.
-    */
-   public static void initialize()
-   {
-      TransactionManager.registerScopeableFactory(() -> local.get());
-   }
    
    /**
     * Adds a variable to the next scope that is opened.
@@ -1866,6 +1854,17 @@
    }
 
    /**
+    * Get the {@link ScopeId} for the instance.
+    * 
+    * @return   {@link ScopeId#SHARED_VARIABLE_MANAGER}.
+    */
+   @Override
+   public ScopeId getScopeId()
+   {
+      return ScopeId.SHARED_VARIABLE_MANAGER;
+   }
+   
+   /**
     * Cleanup the {@link WorkArea#bufferCreator} for the specific buffer instance.
     * 
     * @param    buffer
@@ -1986,6 +1985,9 @@
     */
    private static void addWorker(SharedRegistry sr, ScopeLevel scope, String name, Object var)
    {
+      SharedVariableManager mgr = local.get();
+      mgr.wa.tm.registerPendingScopeable(mgr);
+      
       if (scope == ScopeLevel.NEXT)
       {
          if (sr.pending == null)

=== modified file 'src/com/goldencode/p2j/util/TransactionManager.java'
--- old/src/com/goldencode/p2j/util/TransactionManager.java	2022-07-12 13:55:19 +0000
+++ new/src/com/goldencode/p2j/util/TransactionManager.java	2022-09-06 18:22:04 +0000
@@ -495,6 +495,17 @@
 **                           only when the root block is popped (this includes appserver agents).
 **     RFB 20220712          Added isRootExternal method which is similar to isRootNestingLevel, but it uses
 **                           the nearest external block nesting instead of the root. Ref #6578.
+**     CA  20220901          Refactored scope notification support: ScopeableFactory was removed, and the 
+**                           registration is now specific to each type of scopeable.  For each case, the block
+**                           will be registered for scope support (for that particular scopeable) only when
+**                           the scopeable is 'active' (i.e. unnamed streams or accumulators are used).  This
+**                           allows a lazy registration of scopeables, to avoid the unnecessary overhead of
+**                           processing all the scopeables for each and every block.
+**     CA  20220906          Extracted all BufferManager state which requires notification for 
+**                           'isImportantBlock' to an external TxWrapper class (including the private class 
+**                           here), to allow the scopeable BufferManager state related only to buffers (and  
+**                           not transaction related) to be registered for scope notifications in a lazy 
+**                           manner, when buffers are accessed/used/opened.
 */
 
 /*
@@ -553,6 +564,7 @@
 package com.goldencode.p2j.util;
 
 import java.util.*;
+import java.util.BitSet;
 import java.util.logging.*;
 import com.goldencode.util.Stack;
 import com.goldencode.p2j.net.*;
@@ -560,6 +572,7 @@
 import com.goldencode.p2j.persist.meta.*;
 import com.goldencode.p2j.security.*;
 import com.goldencode.p2j.security.SecurityManager;
+import com.goldencode.p2j.ui.LogicalTerminal;
 
 import static com.goldencode.p2j.util.BlockType.*;
 
@@ -700,12 +713,6 @@
     * TRANSACTION-MODE AUTOMATIC language statement is in effect).
     */
    private static final int APPSERVER_ROOT_BLOCK_INDEX = 0;
-
-   /**
-    * JVM-wide list of objects which create Scopeable objects to be registered in each user's
-    * session.  By design this cannot be context-local.
-    */
-   private static final List<ScopeableFactory> scopeableFactories = new ArrayList<>();
    
    /** Logger (this is JVM-wide rather than being context-local). */
    private static final Logger LOG = LogHelper.getLogger(TransactionManager.class.getName());
@@ -852,24 +859,6 @@
    {
       return workArea.obtain().op;
    }
-   
-   /**
-    * Register an object which will be asked to provide a
-    * <code>Scopeable</code> each time a new work area context is created.
-    * The resulting object is added to the work area's list of objects to
-    * be notified as runtime scopes are started and finished.
-    * <p>
-    * These factories typically are registered during server bootstrap and
-    * are not removed during the lifetime of the server process. A factory
-    * should only be registered once per server process.
-    *
-    * @param   factory
-    *          Factory to be registered.
-    */
-   public static void registerScopeableFactory(ScopeableFactory factory)
-   {
-      scopeableFactories.add(factory);
-   }
 
    /**
     * Adds the given object into the list of objects for the current block
@@ -973,36 +962,30 @@
    }
 
    /**
-    * Adds the given object into the list of global objects needing scope 
-    * related notifications upon the entry and exit from every block. This
-    * list is context-local so this registration is specific to the current
-    * context.
-    * <p>
-    * <b>WARNING: this method should be called before the first call to
-    * {@link #pushScope}. This is necessary since this list is global and
-    * there is no history of which notifications have been provided. Thus,
-    * if <code>pushScope</code> has been called before this method, then
-    * the notifications of scope start for any scopes that have already been
-    * added will never be seen by the target AND more importantly the 
-    * registered target will receive end scope notifications for those
-    * scopes even though there was no matching start scope notification!
-    * If this method can't be called from the static initializer of this
-    * class, the target must implement a facility that is safe with this
-    * limitation (<code>scopeFinished</code> being called for scopes that
-    * never had a matching <code>scopeStart</code>).</b>
-    * <p>
-    * These registrations are global and will not be removed.
-    *
+    * Registers the instance for scope notifications at the current block.
+    * <p>
+    * This will call {@link Scopeable#scopeStart()} to notify for the scope processing.
+    * 
     * @param    target
-    *           The object reference to be notified on scope start and end. 
-    *           This cannot be <code>null</code>.
+    *           The scopeable instance.
     */
-   public static void registerScopeable(Scopeable target)
+   public static void registerBlockScopeable(Scopeable target)
    {
-      registerScopeable(workArea.obtain(), target);
+      registerBlockScopeable(workArea.obtain(), target);
    }
 
    /**
+    * Register the target scopeable as pending, to be added to the next block.
+    * 
+    * @param    target
+    *           The scopeable instance.
+    */
+   public static void registerPendingScopeable(Scopeable target)
+   {
+      registerPendingScopeable(workArea.obtain(), target);
+   }
+   
+   /**
     * Register the given <code>Resettable</code> object in order to reset it
     * at specific points of block processing. 
     *
@@ -3545,50 +3528,63 @@
    }
    
    /**
-    * Adds the given object into the list of global objects needing scope 
-    * related notifications upon the entry and exit from every block. This
-    * list is context-local so this registration is specific to the current
-    * context.
-    * <p>
-    * <b>WARNING: this method should be called before the first call to
-    * {@link #pushScope}. This is necessary since this list is global and
-    * there is no history of which notifications have been provided. Thus,
-    * if <code>pushScope</code> has been called before this method, then
-    * the notifications of scope start for any scopes that have already been
-    * added will never be seen by the target AND more importantly the 
-    * registered target will receive end scope notifications for those
-    * scopes even though there was no matching start scope notification!
-    * If this method can't be called from the static initializer of this
-    * class, the target must implement a facility that is safe with this
-    * limitation (<code>scopeFinished</code> being called for scopes that
-    * never had a matching <code>scopeStart</code>).</b>
-    * <p>
-    * These registrations are global and will not be removed.
-    *
-    * @param    wa
-    *           Context local work area.  Must not be {@code null}.
-    * @param    target
-    *           The object reference to be notified on scope start and end. 
-    *           This cannot be <code>null</code>.
-    */
-   private static void registerScopeable(WorkArea wa, Scopeable target)
-   {
-      // prevent ConcurrentModificationException
-      if (wa.nowIterating == wa.scopeList)
-      {
-         if (LOG.isLoggable(Level.WARNING))
+    * Registers the instance for scope notifications at the current block.
+    * <p>
+    * This will call {@link Scopeable#scopeStart()} to notify for the scope processing.
+    * 
+    * @param    wa
+    *           The {@link WorkArea} instance.
+    * @param    target
+    *           The scopeable instance.
+    */
+   private static void registerBlockScopeable(WorkArea wa, Scopeable target)
+   {
+      BlockDefinition block = wa.blocks.peek();
+      registerScopeable(block, target, true);
+   }
+   
+   /**
+    * Register the target scopeable as pending, to be added to the next block.
+    * 
+    * @param    wa
+    *           The {@link WorkArea} instance.
+    * @param    target
+    *           The scopeable instance.
+    */
+   private static void registerPendingScopeable(WorkArea wa, Scopeable target)
+   {
+      wa.pendingScopeables.add(target);
+   }
+
+   /**
+    * Registers the instance for scope notifications at the current block.
+    * 
+    * @param    block
+    *           The target block.
+    * @param    target
+    *           The scopeable instance.
+    * @param    start
+    *           Flag indicating if {@link Scopeable#scopeStart()} should be executed.
+    */
+   private static void registerScopeable(BlockDefinition block, Scopeable target, boolean start)
+   {
+      if (block.scopeList == null)
+      {
+         block.scopeList = new ArrayList<>();
+         block.scopeables = new BitSet();
+      }
+      
+      int id = target.getScopeId().ordinal();
+      if (!block.scopeables.get(id))
+      {
+         block.scopeables.set(id, true);
+         block.scopeList.add(target);
+         if (start)
          {
-            log(Level.WARNING,
-                "registerScopeable",
-                "Illegal attempt to register Scopeable",
-                new Throwable());
+            // process the scope start
+            target.scopeStart();
          }
-         
-         return;
       }
-      
-      // add the object to the list to receive scope notifications
-      wa.scopeList.add(target);
    }
    
    /**
@@ -3762,6 +3758,7 @@
       wa.trackedUndoables = null;
       wa.trackUndoables = false;
       wa.deferredDynExt = null;
+      wa.pendingScopeables.clear();
    }
    
    /**
@@ -4364,6 +4361,17 @@
                                                 blockType,
                                                 block);
       
+      if ((props & PROP_ENDKEY) != 0 && wa.lt != null && wa.isInteractive()) // wa.lt is NULL for headless mode
+      {
+         // LT must not be added for agents or batch mode
+         // this is registered directly at the block, to avoid using the 'pending'
+         
+         // WARNING: LT has dependencies on top-level blocks (including external); currently, such blocks have
+         // by default the ENDKEY property - if this registration changes, attention is needed to register
+         // LT scopeable for these blocks. 
+         registerScopeable(blk, wa.lt, false);
+      }
+      
       // add our block
       wa.blocks.push(blk);
       
@@ -4401,6 +4409,17 @@
          }
       }
       
+      if (wa.bm.isImportantBlockTransition()) 
+      {
+         BufferManager.registerTxScopeable(wa.bm);
+      }
+      
+      if (isGlobalBlock(wa))
+      {
+         // the global block must be added, for the scoped dictionary's global block.
+         BufferManager.registerPendingScopeable(wa.bm);
+      }
+
       // notify Scopeables that scope has opened
       processScopeNotifications(wa, blk, true);
       
@@ -7387,61 +7406,96 @@
          log(Level.FINE, "processScopeNotifications", "start = " + start);
       }
       
+      if (!wa.pendingScopeables.isEmpty())
+      {
+         for (Scopeable target : wa.pendingScopeables)
+         {
+            registerScopeable(blk, target, false);
+         }
+         wa.pendingScopeables.clear();
+      }
+      
+      // check scopeables inherited from previous block
+      BlockDefinition prev = getBlock(wa, 1);
+      int accumMgrId = ScopeId.ACCUMULATOR_MANAGER.ordinal();
+      if (!blk.topLevel                                               && 
+          prev != null                                                && 
+          prev.scopeables != null                                     && 
+          prev.scopeables.get(accumMgrId)                             &&
+          (blk.scopeables == null || !blk.scopeables.get(accumMgrId)))
+      {
+         registerScopeable(blk, AccumulatorManager.getInstance(), false);
+      }
+      
       Object prevReferent = wa.pm.getProcessedProcedure();
 
       try
       {
-         Object referent = null;
-         boolean delayed = false;
+         if (!(blk.topLevel || blk.scopeables != null))
+         {
+            // nothing to notify
+            return;
+         }
+         
+         Object referent;
+         boolean delayed;
          // can not call for start, as the procedure is not yet initialized
-         if (!start && isTopLevelBlock(wa) && !isGlobalBlock(wa))
+         if (!start && blk.topLevel && !isGlobalBlock(wa))
          {
             referent = wa.pm._thisProcedure();
             wa.pm.setProcessedProcedure(referent);
             
-            if (getNearestTopLevelType(wa) == EXTERNAL_PROC)
-            {
-               delayed = wa.pm._isPersistent(referent) && !wa.pm._isDelayedDelete(referent);
-            }
+            delayed = getNearestTopLevelType(wa) == EXTERNAL_PROC && 
+                      wa.pm._isPersistent(referent)               && 
+                      !wa.pm._isDelayedDelete(referent);
+         }
+         else
+         {
+            delayed = false;
+            referent = null;
          }
          
-         if (start)
+         if (start && blk.topLevel)
          {
             // procedure scopes need to be opened first
             wa.procScopeable.scopeStart();
          }
          
-         wa.nowIterating = wa.scopeList;
-         for (Scopeable target : wa.scopeList)
+         if (blk.scopeList != null)
          {
-            try
+            wa.nowIterating = blk.scopeList;
+            for (int i = 0; i < blk.scopeList.size(); i++)
             {
-               if (start)
-               {
-                  target.scopeStart();
-               }
-               else
-               {
-                  target.scopeFinished();
-                  
-                  if (delayed)
+               Scopeable target = blk.scopeList.get(i);
+               try
+               {
+                  if (start)
                   {
-                     wa.pm.addScopeable(referent, target);
+                     target.scopeStart();
                   }
                   else
                   {
-                     target.scopeDeleted();
+                     target.scopeFinished();
+                     
+                     if (delayed)
+                     {
+                        wa.pm.addScopeable(referent, target);
+                     }
+                     else
+                     {
+                        target.scopeDeleted();
+                     }
                   }
                }
-            }
-            
-            catch (Throwable thr)
-            {
-               deferError(wa, (start ? "scopeStart" : "scopeFinished"), thr, false);
+               
+               catch (Throwable thr)
+               {
+                  deferError(wa, (start ? "scopeStart" : "scopeFinished"), thr, false);
+               }
             }
          }
          
-         if (!start)
+         if (!start && blk.topLevel)
          {
             // procedure scopes need to be finished last
             wa.procScopeable.scopeFinished();
@@ -7766,33 +7820,44 @@
       }
    
       /**
-       * Adds the given object into the list of global objects needing scope 
-       * related notifications upon the entry and exit from every block. This
-       * list is context-local so this registration is specific to the current
-       * context.
-       * <p>
-       * <b>WARNING: this method should be called before the first call to
-       * {@link #pushScope}. This is necessary since this list is global and
-       * there is no history of which notifications have been provided. Thus,
-       * if <code>pushScope</code> has been called before this method, then
-       * the notifications of scope start for any scopes that have already been
-       * added will never be seen by the target AND more importantly the 
-       * registered target will receive end scope notifications for those
-       * scopes even though there was no matching start scope notification!
-       * If this method can't be called from the static initializer of this
-       * class, the target must implement a facility that is safe with this
-       * limitation (<code>scopeFinished</code> being called for scopes that
-       * never had a matching <code>scopeStart</code>).</b>
-       * <p>
-       * These registrations are global and will not be removed.
-       *
-       * @param    target
-       *           The object reference to be notified on scope start and end. 
-       *           This cannot be <code>null</code>.
-       */
-      public void registerScopeable(Scopeable target)
-      {
-         TransactionManager.registerScopeable(wa, target);
+       * Registers the instance for scope notifications at the current block.
+       * <p>
+       * This will call {@link Scopeable#scopeStart()} to notify for the scope processing.
+       * 
+       * @param    target
+       *           The scopeable instance.
+       */
+      public void registerBlockScopeable(Scopeable target)
+      {
+         TransactionManager.registerBlockScopeable(wa, target);
+      }
+      
+      /**
+       * Register the target scopeable as pending, to be added to the next block.
+       * 
+       * @param    target
+       *           The scopeable instance.
+       */
+      public void registerPendingScopeable(Scopeable target)
+      {
+         TransactionManager.registerPendingScopeable(wa, target);
+      }
+      
+      /**
+       * Registers the instance for scope notifications at the nearest {@link #findNearestTopLevel() top level}
+       * block.
+       * <p>
+       * WARNING: this must be called only for instances which require notifications only for 
+       * top-level blocks, as this will registered the instance at the nearest top-level block and call 
+       * {@link Scopeable#scopeStart()} to emulate as if it was called when the top-level block started.
+       * 
+       * @param    target
+       *           The scopeable instance.
+       */
+      public void registerTopLevelScopeable(Scopeable target)
+      {
+         BlockDefinition block = nearestTopLevel(wa);
+         registerScopeable(block, target, true);
       }
    
       /**
@@ -10100,9 +10165,15 @@
 
       /** Helper to use the ErrorManager without any context local lookups. */
       public ErrorManager.ErrorHelper em;
-
-      /** Registry of all objects needing start and end scope callbacks. */
-      private final List<Scopeable> scopeList = new ArrayList<>();
+      
+      /** The buffer manager instance, active only on server-side. */
+      public BufferManager bm;
+
+      /** The logical terminal instance, active only on server-side. */
+      public LogicalTerminal lt;
+
+      /** The set of pending scopeables to be registered for the next block. */
+      private final Set<Scopeable> pendingScopeables = Collections.newSetFromMap(new IdentityHashMap<>());
       
       /**
        * Map of Commitables to get a commit notification after all other commits occur in a full transaction;
@@ -10245,6 +10316,24 @@
       /** Cached value of the {@link AppServerManager#isRemote()} state.*/
       private Boolean remote = null;
       
+      /** Flag indicating if this is a user-interactive session. */
+      private Boolean interactive = null;
+      
+      /**
+       * Check if the current session is interactive (i.e. not appserver agent or batch process).
+       * 
+       * @return    See above.
+       */
+      private boolean isInteractive()
+      {
+         if (interactive == null)
+         {
+            interactive = !(isRemote() || LogicalTerminal.isInBatchMode());
+         }
+         
+         return interactive;
+      }
+      
       /**
        * Check if this session is an appserver session or not.
        * 
@@ -10750,19 +10839,7 @@
       /**
        * Obtains the context-local instance of the contained global work
        * area.  The first time this method is invoked in a context, a new
-       * <code>WorkArea</code> instance is lazily created.  This is done here
-       * rather than using the more elegant mechanism of overriding the
-       * superclass' <code>initialValue</code> method, because we must ensure
-       * we do not trigger an infinite loop.  Infinite looping can occur
-       * because a new work area requires that each {@link ScopeableFactory}
-       * create its {@link Scopeable} instance.  These objects may call back
-       * into the <code>TransactionManager</code> to register for various
-       * service hooks, which will in turn trigger a call to this method.
-       * By implementing the <code>WorkArea</code> instantiation here, we can
-       * set the <code>ContextContainer</code>'s contents <i>before</i> the
-       * factories are asked to create their <code>Scopeable</code>s.  Thus,
-       * any recursion into this method during <code>Scopeable</code>
-       * initialization is safe.
+       * <code>WorkArea</code> instance is lazily created.
        *
        * @return  The work area associated with this context.
        */
@@ -10796,23 +10873,15 @@
             
             wa.pm = ProcedureManager.getProcedureHelper();
             wa.em = ErrorManager.getErrorHelper();
+            if (!SessionManager.get().isLeaf())
+            {
+               // only on server-side
+               wa.bm = BufferManager.get();
+               wa.lt = (wa.headless ? null : LogicalTerminal.getInstance());
+            }
             
             wa.em.setHeadless(wa.headless);
-            
-            // create all scopeable objects to be registered using the
-            // registered Scopeable factories
-            for (ScopeableFactory factory : scopeableFactories)
-            {
-               Scopeable scope = factory.createScopeable();
-               
-               // non-null return values must be added to the list otherwise
-               // it is silently bypassed
-               if (scope != null)
-               {
-                  wa.scopeList.add(scope);
-               }
-            }
-            
+
             wa.procScopeable = ProcedureManager.getScopeable();
          }
          

=== modified file 'src/com/goldencode/p2j/util/TypeFactory.java'
--- old/src/com/goldencode/p2j/util/TypeFactory.java	2022-01-20 16:27:48 +0000
+++ new/src/com/goldencode/p2j/util/TypeFactory.java	2022-09-06 18:22:04 +0000
@@ -29,6 +29,12 @@
 **                  argument.
 **     VVT 20210917 Javadoc fixed. 
 **     CA  20220120 Allow OO vars to be registered as pending, at their definition.
+**     CA  20220901 Refactored scope notification support: ScopeableFactory was removed, and the registration 
+**                  is now specific to each type of scopeable.  For each case, the block will be registered 
+**                  for scope support (for that particular scopeable) only when the scopeable is 'active' 
+**                  (i.e. unnamed streams or accumulators are used).  This allows a lazy registration of 
+**                  scopeables, to avoid the unnecessary overhead of processing all the scopeables for each
+**                  and every block.
 */
 
 /*
@@ -717,6 +723,8 @@
    public static <V extends Object> jobject<V> jobject(Class<V> cls)
    {
       jobject<V> res = new jobject<V>(cls);
+
+      ObjectOps.registerPending(res);
       
       return res;
    }

=== modified file 'src/com/goldencode/p2j/util/UnnamedStreams.java'
--- old/src/com/goldencode/p2j/util/UnnamedStreams.java	2022-01-14 22:38:33 +0000
+++ new/src/com/goldencode/p2j/util/UnnamedStreams.java	2022-09-06 18:22:04 +0000
@@ -65,6 +65,12 @@
 **                           and scopeFinished. Once something non-null is added to the closer stack, then
 **                           it's the job of executeOnReturn to pop the stack, otherwise, it's on the
 **                           scopeFinished. Ref #5880.
+**     CA  20220901          Refactored scope notification support: ScopeableFactory was removed, and the 
+**                           registration is now specific to each type of scopeable.  For each case, the block
+**                           will be registered for scope support (for that particular scopeable) only when
+**                           the scopeable is 'active' (i.e. unnamed streams or accumulators are used).  This
+**                           allows a lazy registration of scopeables, to avoid the unnecessary overhead of
+**                           processing all the scopeables for each and every block.
 */
 
 /*
@@ -329,21 +335,6 @@
       
       work.get().wa.both = false;
    }      
-   
-   /**
-    * Register with the {@link com.goldencode.p2j.util.TransactionManager TransactionManager} a
-    * factory object which creates instances of this class, so that they can be registered to
-    * receive notifications of runtime scope start and finish events.
-    * <p>
-    * This method should be invoked once during the server bootstrap phase.
-    * <p>
-    * The registered factory functions conditionally. It uses the {@link
-    * TransactionManager#_isHeadless()} to check whether the context has been declared headless.
-    */
-   public static void initialize()
-   {
-      TransactionManager.registerScopeableFactory(() -> TransactionManager._isHeadless() ? null : work.get());
-   }
 
    /**
     * Provides a notification that a new scope is about to be entered. Used
@@ -351,13 +342,10 @@
     */
    public void scopeStart()
    {
-      if (wa.tm.isTopLevelBlock())
-      {
-         wa.lastIn.push(wa.in);
-         wa.lastOut.push(wa.out);
-         wa.closerIn.push(null);
-         wa.closerOut.push(null);
-      }
+      wa.lastIn.push(wa.in);
+      wa.lastOut.push(wa.out);
+      wa.closerIn.push(null);
+      wa.closerOut.push(null);
    }
    
    /**
@@ -367,19 +355,16 @@
     */
    public void scopeFinished()
    {
-      if (wa.tm.isTopLevelBlock())
-      {
-         wa.lastIn.pop();
-         wa.lastOut.pop();
+      wa.lastIn.pop();
+      wa.lastOut.pop();
 
-         if (wa.closerIn.peek() == null)
-         {
-            wa.closerIn.pop();
-         }
-         if (wa.closerOut.peek() == null)
-         {
-            wa.closerOut.pop();
-         }
+      if (wa.closerIn.peek() == null)
+      {
+         wa.closerIn.pop();
+      }
+      if (wa.closerOut.peek() == null)
+      {
+         wa.closerOut.pop();
       }
    }
 
@@ -393,6 +378,17 @@
    {
       // no-op
    }
+   
+   /**
+    * Get the {@link ScopeId} for the instance.
+    * 
+    * @return   {@link ScopeId#UNNAMED_STREAMS}.
+    */
+   @Override
+   public ScopeId getScopeId()
+   {
+      return ScopeId.UNNAMED_STREAMS;
+   }
 
    /**
     * State of the output side of the unnamed stream.
@@ -449,6 +445,8 @@
     */
    private static void assignInWorker(WorkArea wa, Stream in, boolean register)
    {
+      wa.tm.registerTopLevelScopeable(work.get());
+
       // default input for this top level scope
       Stream def = wa.lastIn.peek();
       
@@ -508,6 +506,8 @@
     */
    private static void assignOutWorker(WorkArea wa, Stream out, boolean register)
    {
+      wa.tm.registerTopLevelScopeable(work.get());
+      
       // save ids before we make any state changes
       int oldId = ((wa.out == null) ? -1 : ((RemoteStream) wa.out).getId()); 
       int newId = ((out    == null) ? -1 : ((RemoteStream) out).getId());

