TransactionManager.java

/*
** Module   : TransactionManager.java
** Abstract : transaction/block properties helper methods
**
** Copyright (c) 2005-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ----------------------------------Description-----------------------------------
** 001 GES 20050809   @22096 Created initial version, supporting
**                           transaction/block properties with full
**                           support for nested blocks, undo and retry.
** 002 GES 20050825   @22242 Added global finish processing.
** 003 ECF 20050920   @22888 Various changes to support database
**                           transaction processing. Added registration
**                           and use of ScopeableFactory interface.
**                           Reversed order of Commitable notifications.
**                           Delayed notification of opening scope until
**                           after transaction (if any) is begun.
** 004 GES 20051005   @22999 Added master commit and master finish
**                           notifications that are guaranteed to occur
**                           after all other individual commits/finishes.
** 005 ECF 20051007   @23005 More changes to support database transaction
**                           processing. Changed isTransaction()'s access
**                           to public. Removed unused, private variant of
**                           isFullTransaction() and added public, no-arg
**                           variant of this method.
** 006 GES 20051007   @23007 Added rollback notification for the updated
**                           Commitable interface AND added master
**                           rollback support.
** 007 ECF 20051104   @23234 Changes to support iterate notifications.
**                           Finalizables are now notified at each call to
**                           the iterate() method.
** 008 ECF 20051104   @23261 Update to honorError() method. Added code to
**                           report to stderr the error messages embedded
**                           in an exception chain, in the case where the
**                           error is not rethrown to a higher block.
** 009 ECF 20051108   @23277 Add deferred commit/rollback error handling.
**                           This defers throwing an exception thrown by
**                           a commit/rollback notification callback and
**                           caught during processCommit/Rollback until it
**                           is safe to do so. Logging added as well.
** 010 ECF 20051108   @23397 Added isTopLevelBlock() method. Fixed
**                           popScope(), which was popping its scope too
**                           early. Use peek instead, then pop at end of
**                           method.
** 011 ECF 20051202   @23596 Allow multiple master Commitables and
**                           Finalizables to be registered. Also extended
**                           deferred error handling to Finalizable
**                           notification processing.
** 012 GES 20060123   @24019 Moved to context-local storage and fixed a
**                           couple of deferredError bugs.
** 013 GES 20060128   @24114 Added isGlobalBlock().
** 014 GES 20060130   @24142 Added processing quit state flag for global
**                           exiting.
** 015 ECF 20060130   @24225 Added registerTopLevelFinalizable() method.
**                           Also added logging to report push/pop scope
**                           events.
** 016 GES 20060202   @24228 Added support for differentiating external
**                           blocks (external procs) from other top-level
**                           blocks (any proc/func/trigger).
** 017 GES 20060203   @24245 Changed method name makeBackup() to
**                           blockSetup().  This is done because this
**                           hook now has responsibilities to handle
**                           iterate() to compensate for usage of explicit
**                           continue (which would bypass the iterate()
**                           call).
** 018 GES 20060204   @24278 Added batch notification support.
** 019 GES 20060207   @24329 Check interrupted status in strategic hooks
**                           and throw a StopConditionException in the
**                           case where the thread has its interrupted
**                           status set.
** 020 ECF 20060206   @24344 Prevent infinite recursion loop in
**                           ContextContainer.obtain. Was caused when
**                           ScopeableFactory.createScopeable called into
**                           TransactionManager while ContextContainer was
**                           still initializing WorkArea.
** 021 GES 20060302   @24856 Added removeFinalizable() to facilitate
**                           cleanup of resources before finished().
** 022 ECF 20060309   @24997 Modified auto-remove processing for master
**                           commitables and finalizables. Auto-remove
**                           processing was too aggressive;  it must occur
**                           only for popScope for a full transaction.
** 023 GES 20060314   @25060 Moved to common logging infrastructure and
**                           added major debug logging.
** 024 GES 20060322   @25193 New rollback() signature.
** 025 ECF 20060502   @25947 Added registerNearestLoopFinalizable()
**                           method. Finds nearest enclosing block which
**                           defines a loop and registers a Finalizable to
**                           it.
** 026 GES 20060510   @26056 New state to externally query if an iteration
**                           is being called from the top or the bottom of
**                           the block.
** 027 GES 20060517   @26191 Added retry notification processing.
** 028 GES 20060518   @26274 Use an identity based map for undoables to
**                           solve problem where two keys with the same
**                           value can't be separately stored in the map.
** 029 GES 20060529   @26649 Iterate processing must trip processing
**                           rollback if there is a pending rollback
**                           since we may have gotten here via a NEXT
**                           and otherwise the rollback may not occur.
**                           Deferred rollback is also triggered before
**                           getting to the labeled block in the case
**                           where the block associated with the full
**                           transaction is enclosed (more deeply nested)
**                           in the labeled block.  The rollback is then
**                           triggered at the full transaction level.
** 030 GES 20060608   @27024 Added a method to abort further processing
**                           in a block in triggering a RETRY as part of
**                           an UNDO statement (as opposed to processing
**                           a RETRY from within a catch block).
** 031 GES 20060609   @27055 Converted deferred error processing to catch
**                           and process Exception rather than the more
**                           specific ErrorConditionException. This is
**                           required whenever we have some unexpected
**                           exception (an abnormal end).  Otherwise the
**                           deferred error handling did not trigger and
**                           the state of the TM is corrupted. This had
**                           bad effects as the finally blocks called
**                           popScope() up the stack.
** 032 ECF 20060610   @27089 Add processing of master transaction retries.
**                           Finalizables registered for master
**                           transaction processing were not receiving
**                           retry notifications.
** 033 GES 20060626   @27565 Support silent error mode in the calling
**                           method from triggerErrorInCaller().
** 034 GES 20060804   @28426 Modified block processing to ensure that
**                           the Finalizable.iterate() notifications only
**                           occur from the top of the block.
** 035 GES 20060809   @28514 Added a new concept of a lightweight block
**                           which allows the differentiation between
**                           a Progress DO block and a REPEAT/FOR block
**                           (or really any other block types in
**                           Progress).  There are new transaction level
**                           constants and helpers to test and query
**                           these levels.
** 036 GES 20060908   @29363 Retry must honor pending rollbacks for the
**                           current block.
** 037 GES 20060921   @29832 Fix to force a default retry at the same 
**                           level as any pending rollbacks if the block
**                           is not specified (if it is null). Previously
**                           only the current block was retried even when
**                           an enclosing block was the level of the
**                           rollback.  Also fixed the fact that loop
**                           protection disabling must propagate up to
**                           enclosing blocks during popScope.
** 038 ECF 20061002   @30069 Fix to rollback(). Only defer rollback if
**                           current scope is not a subtransaction or a
**                           transaction. Also prevent unnecessary retry
**                           processing from popScope().
** 039 GES 20061004   @30133 Support for nested errors (an error generated
**                           during processing of another error, while
**                           nested error mode is enabled).
** 040 GES 20061009   @30219 Honor null return from createScopeable() to
**                           allow specific factories to decide NOT to
**                           register an object for a given context.
** 041 GES 20061010   @30263 Added registerNextExternal() and deferred
**                           external procedure block registration for
**                           use by objects being constructed in an
**                           initializer/constructor but needing to be
**                           registered in the next external proc's block.
** 042 ECF 20061011   @30354 Fixed methods processFinalizables() and
**                           processRetry(). Master finalizables were not
**                           being processed in the case where no
**                           non-master finalizables were registered in
**                           the same scope.
** 043 GES 20061116   @31215 Converted all internal processing from using
**                           block labels to identify blocks, into using
**                           an index into the stack of blocks.  This is
**                           a unique value whereas the label can be
**                           duplicated in nested blocks.  In particular,
**                           there was a case where the full transaction
**                           flag was being cleared because a nested
**                           block had the same label as the one that was
**                           marked as the full transaction.
** 044 ECF 20061116   @31240 Fixed sprintf spec in WorkArea. Was using %s
**                           where %i is required, raising an exception in
**                           the Format class.
** 045 GES 20070409   @32860 When deferred errors have a condition as a
**                           cause, unpack that cause and throw that
**                           instead of the wrapper.  This negates the
**                           effect of runtime components that are not
**                           Progress aware, which wrap normal conditions
**                           in more generic exceptions.  Since the 4GL
**                           compatible runtime knows how to deal with
**                           conditions, the behavior will be correct.
** 046 NVS 20070515   @33490 Fixed ArrayIndexOutOfBoundsException in the
**                           toString() method.
** 047 GES 20071213   @36341 Disallow multiple finalizable registrations
**                           for the same object in the same block.
** 048 GES 20071213   @36352 Added deferError support to scope 
**                           notification processing.
** 049 ECF 20071214   @36377 Added protection for iteration of certain
**                           WorkArea resources. To prevent a concurrent
**                           modification exception, updates to the work
**                           area's scopeList, deferredFinalizables,
**                           masterCommit, and masterFinish collections,
**                           and to the current block's finalizables and
**                           commitables collections, are disallowed while
**                           the respective resource is being iterated.
**                           A warning is logged with a stack trace. This
**                           prevents catastrophic failure, but also
**                           signals a programming error that should be
**                           fixed.
** 050 GES 20071218   @36428 Exposed the current finalizable operation
**                           in process.
** 051 GES 20071218   @36439 Added abnormalEnd() to notify the TM about
**                           any unexpected failure, allowing any database
**                           state to be rolled back (keeping integrity
**                           of the database).
** 052 CA  20080207   @37027 Added constants for different types of blocks.
**                           Added a new pushScope(...) method which 
**                           receives the type of block being entered - 
**                           only editing blocks are currently supported. 
**                           Added a method to retrieve the type of block 
**                           which is currently active.
** 053 ECF 20080218   @37118 Fixed processRollback(). Rollback processing
**                           must occur before undo processing. Otherwise,
**                           we might be performing rollback processing on
**                           the wrong records in RecordBuffer.
** 054 ECF 20080221   @37142 Added protection code to WorkArea.toString().
**                           Also rolled back #052 (@37118), which caused
**                           regressions.
** 055 ECF 20080430   @38182 Fixed processRollback(). Reinstated #053
**                           (@37118). SVL's related fix to RecordBuffer
**                           corrects the regressions reported in #054
**                           (@37142).
** 056 GES 20080505   @38210 Moved undoables to be an array for better
**                           performance.
** 057 GES 20080612   @38756 Uncaught condition exceptions should UNDO
**                           the full transaction level as they pass
**                           through, unless the condition is QUIT (which
**                           has the behavior of committing).
** 058 GES 20080619   @38888 Massive update of infinite loop protection
**                           to fix deviations from Progress behavior.
**                           This includes ILP for NEXT (honorNext()),
**                           PAUSE support for sometimes disabling ILP
**                           (depending on runtime context), special
**                           behavior for RETRY caused by an ENDKEY,
**                           a "stutter" mode for RETRY conversion et al.
**                           Fixed some "holes" in exception processing
**                           that would allow an UNDO, RETRY cause inside
**                           a transaction but which is targeted at a
**                           block that is outside the transaction, to
**                           avoid rolling back at the full transaction
**                           level. Added the ability to deregister a
**                           list of undoables for the current scope (and
**                           nested scopes).  This is needed to allow
**                           no-undo parameters to be properly supported.
** 059 GES 20080624   @38916 Limiting registration of undoables to a
**                           single instance is being removed because
**                           it forces a compareTo which is potentially
**                           dangerous since some undoables may have
**                           errors when compared to each other (in
**                           unexpected ways).  It is also expensive so
**                           it is good to avoid it for that reason.
** 060 GES 20080625   @38960 Moved to the BlockType enum.
** 061 GES 20080627   @38992 Fix for honorNext().
** 062 ECF 20080630   @39031 Added registerNext(). Defers registration of
**                           an Undoable until the next block scope opens.
** 063 CA  20080714   @39102 Fixed deregister() memory leak: the
**                           backupUndoList was keeping the state of the 
**                           undoables after the entry of the current 
**                           block, not before.
** 064 GES 20080811   @39381 New features for UNDO and RETRY processing.
** 065 CA  20080826   @39565 Added processRollbackPending - notify the
**                           commitables that a rollback is pending to be
**                           executed by a parent block.
** 066 CA  20080815   @39448 Added honorStop(). This method will decide if 
**                           the processing of a STOP condition will be
**                           vetoed at the current scope.
** 067 GES 20080818   @39858 Added some register() signatures to provide
**                           simpler defaults. Removed the commit feature
**                           of register() since it wasn't used anywhere.
** 068 GES 20080930   @39976 Reworked undo registration to allow a cleaner
**                           interface to the caller. Now it allows
**                           varargs to minimize the number of calls.
** 069 GES 20081003   @40036 Added _isRetry().
** 070 SIY 20081002   @40015 Inferred generics. Added helper method
**                           notifyCondition().
** 071 SVL 20081024   @40219 Added findNearestExternal() function. 
** 072 SIY 20081111   @40411 Call notifyCondition() if real problem is
**                           detected in abnormalEnd().
** 073 CA  20081127   @40708 Added 'block iterated' support - method
**                           hasParentIteratedBlock will check if any
**                           of the current blocks iterated at least
**                           once.
** 074 GES 20081126   @40742 Added helpers to access the properties of
**                           a block and to manage the number of
**                           interactions the current block has had.
** 075 GES 20081210   @40866 Added support for controlling the rollup
**                           flag for the current block. Honor the rollup
**                           flag for the containing block when exiting
**                           the current block.
** 076 GES 20090115   @41131 Only wrap abend exceptions once. Modified
**                           abnormalEnd() signature to avoid duplicate
**                           notifications. In places where we re-throw
**                           a condition exception, we now ensure that
**                           abnormalEnd() is used to allow the rollback
**                           processing to happen.
** 077 GES 20090422   @41917 Converted to standard string formatting.
** 078 ECF 20090714   @43189 Reworked deferred error processing. Deferred
**                           error is now a Throwable. Fixed abnormal end
**                           processing such that an exception thrown during
**                           notification or rollback does not mask the root
**                           cause of the abnormal end.
** 079 GES 20090729   @43442 Added processValidate() method to call validate()
**                           on registered Commitable instances.
** 080 CA  20090731   @43472 Added code for off-end listener registration.
**                           When a FOR or FOR EACH block is initialized, all
**                           its associated queries must have their off-end
**                           listeners registered, to allow block termination
**                           when the query goes off-end.
** 081 CA  20090804   @43506 Added getOffEndListeners - returns the topmost 
**                           set of query off-end listeners and eventually 
**                           clears that set.
** 082 ECF 20090816   @43655 Added methods to register and deregister an
**                           OutputParameterAssigner to/from the current
**                           block.
** 083 CA  20090821   @43716 When deferring a STOP condition caused by thread
**                           interruption, it will not be logged as SEVERE
**                           (to avoid bogus stacktraces in server log).
** 084 ECF 20090826   @43785 Rolled back #008 (@23261). It is unclear why this
**                           was added and it outputs directly to stderr,
**                           seemingly unnecessarily.
** 085 GES 20090831   @43804 Save and restore error mgr silent mode flag
**                           across top-level block boundaries. Removed
**                           previous stacking support for the error mgr,
**                           which was over-stacking things.
** 086 GES 20090915   @43898 At the end of retry processing, honor deferred
**                           errors and any pending stop condition.
** 087 CA  20090917   @43931 Fix abnormal connection end. If the TM was 
**                           notified to abort processing, the next
**                           honorStopCondition call will throw a 
**                           SilentUnwindException. So, no one can interfere
**                           with the shutdown.
** 088 CA  20090922   @43979 In deferError, log SilentUnwindException's using
**                           FINER instead of SEVERE level.
** 089 GES 20090924   @44011 Added a context-specific headless flag.
** 090 SVL 20091117   @44398 Added support for the Resettable objects.
** 091 CA  20091106   @44320 Protect the rollback processing against any
**                           thread interruption incoming from a CTRL-C or a 
**                           client abend, by marking it as a critical 
**                           section.
** 092 CA  20091202   @44470 Added methods for retrieving the current nesting
**                           level and the level of the block which is started
**                           a full transaction.
** 093 GES 20100330   @44786 Added setHeadless() to allow per-context control
**                           over headless mode. This can also be done via the
**                           directory, but this interface allows a dynamic
**                           approach.
** 094 ECF 20121114          Fix for Java 7: modified pushScope to deal with
**                           varargs in overloaded register method.
** 095 ECF 20130121          Fix for Java 7: Added registerGlobal to
**                           disambiguate from overloaded register methods.
** 096 CA  20130126          Save the Block instance in a block definition
**                           field (BlockDefinition.block). 
**                           Added nearestExecutingBlock.
** 097 CA  20130209          Added dynamic EXTENT and EXTENT parameter support.
** 098 SVL 20131028          Added registerFinalizableAt, registerCommitAt, isTransactionAt.
** 099 CA  20131206          Added delayed processing of code which normally would be executed
**                           by a finalize or scopeFinished implementation. This processing will
**                           call deleted() or scopeDeleted() implementations, immediately if the
**                           current THIS-PROCEDURE is not persistent or when the procedure gets
**                           explicitly deleted, otherwise.
** 100 SVL 20140106          Added "silent" parameter to deferError function, changed function
**                           access modifier.
** 101 CA  20140109          Do not cache context-local vars, as they may get reset (in case of a
**                           state-reset appserver Agent).
** 102 CA  20140224          Finalizable and scope processing needs to be aware of persistent 
**                           procedures marked for "delete-on-exit".
** 103 SVL 20140221          findNearestTopLevel was made public.
** 104 CA  20140301          Procedure's scope is notified first for start and last for finish.
** 105 SVL 20140414          Modified isGlobalBlock in order to support agent contexts.
** 106 CA  20140513          Added a weight for the context-local var, to ensure predetermined 
**                           order during context reset.
** 107 ECF 20140813          Changed access of processValidate method to package private.
** 108 SVL 20150115          Added _isHeadless which gets "headless" attribute without initiating
**                           any contexts in TM, LT etc.
** 109 SVL 20150116          Fixed _isHeadless for conversion case.
** 110 OM  20150506          Added public method nearestTopLevelType(). Upgraded code to Java7.
** 111 ECF 20150510          Added deregisterGlobalFinalizable to enable explicit removal of a
**                           finalizable from the global list.
** 112 ECF 20150801          Added support for compound query optimization.
** 113 OM  20160126          Do not create unneeded context local on abortProcessing(). Server
**                           context is automatically headless. isHeadless() made public.
**     ECF 20160128          Minor performance improvement: avoid context-local lookup when
**                           possible in isGlobalBlock.
** 114 ECF 20160206          Store the exception, if any, which triggered a rollback, and log that
**                           exception and the current one, if available, when we detect the
**                           illegal state of triggering a rollback when one already is pending.
**                           Changed exception type from UnsupportedOperationException to
**                           IllegalStateException in this case.
** 115 OM  20160223          Added cleanupDeferredFinalizables() method.
** 116 IAS 20160408          Use synthetic key as context key.
** 117 ECF 20160517          Prevent duplicate Undoable instances from being added to the global
**                           scope.
** 118 GES 20160523          Minor code cleanup.
** 119 CA  20160627          Reworked undoable support - the undoables register themselves with
**                           all blocks up the stack, until either 1. the tx block which created
**                           it is reached or 2. the last tx block where it was saved is reached.
**                           Refactored context-local resolution in the TM APIs, so that the
**                           WorkArea instance is resolved only once and then re-used.
**     CA  20160721          Reworked variable/parameter definitions and initialization to use 
**                           UndoableFactory and TypeFactory APIs (thus TM.register for undoables
**                           is no longer required in converted code).
**     CA  20160728          Fixed undo support for dynamic extent vars.  Fixed undo support for
**                           variables defined in external programs ran persistent.
** 120 CA  20160812          Performance improvements for H119: use IdentityHashMap when using
**                           ext prog instances as keys, pass the tx level when registering 
**                           dynamic extent arrays.
** 121 ECF 20160827          Additional refactoring to avoid resolving WorkArea more often than
**                           necessary.
** 122 CA  20170427          _isHeadless() will always return true for virtual sessions.
** 123 CA  20171030          Added isSuppressError.
**     ECF 20171030          Implemented transaction metadata.
** 124 ECF 20180201          Removed query optimization call from stopOffEndRegistration.
** 125 GES 20180702          Do not propagate ILP and interactions state past a top level block.
** 126 CA  20181112          Added support for the TRANSACTION resource.
** 127 CA  20190219          Added getBlock(levels).
**     GES 20190324          Refactored to create a TransactionHelper inner class which contains
**                           instance method versions of the external static TM interface.  That
**                           legacy API has its implementation moved into private static workers
**                           which take a WorkArea as the first parameter. There is an accessor
**                           to get a new instance of the TransactionHelper which contains an
**                           instance of the WorkArea.  This allows all instance method API usage
**                           in TransactionHelper to directly use the private static workers
**                           without any context local lookup.  Since the original API is intact,
**                           code can be moved to this new approach over time.
**     CA  20190325          Implemented ProcedureHelper instead of direct usage of the static
**                           API.  This allows the elimination of context local usage in 
**                           ProcedureManager.
** 128 ECF 20190714          Added support for aggressive buffer flushing mode.
** 129 CA  20190710          Made deregisterOutputParameterAssigner public.
**     CA  20190722          Changed offEndQueries type to HashSet.
**     HC  20190724          Changed abend log level.
**     CA  20190728          Sort the finalizables by their weight, ascending.  Required by the 
**                           output table/dataset cases, which needs to be performed before the 
**                           buffer's scope is closed.
**     OM  20190731          Overloaded triggerErrorInCaller to accept numeric error code.
** 130 AIL 20191119          Added more scenarios in which an error should be honored. Made aware
**                           of DATABASE TRIGGER DataType.
** 131 CA  20200312          Fixes for UNDO, THROW and RETURN ERROR, in case when a OO-like 
**                           error condition needs to be generated.
**     CA  20200324          More fixes related to OO-like error processing via AppError.
** 132 GES 20200316          Added invocation of Finalizable.entry() in blockSetup() (only the
**                           first time it is executed for a block or loop).
**     ECF 20200419          Change to BufferManager.endTxPost method signature.
** 133 AIL 20200518          Added utilities for working with blocks: getBlockAtDepth and countBlocks.
**     AIL 20200616          Added more operations on the block stack.
**     AIL 20200622          Removed getStack method.
**                           Updated copyStack method.
**     GES 20200730          Deleted setHeadless() which was dead code. Eliminated ErrorManager context-
**                           local lookups.
**     CA  20200927          Avoid context-local lookups by relying on the helper instance.
**     CA  20201003          Use an identity HashSet where possible.
**     CA  20201015          Replaced java.util.Stack with a non-synchronized custom implementation.
**     OM  20201125          Javadoc update.
**     CA  20210310          Small performance improvement related to offend query/listener processing.
**     ECF 20210504          Adjusted for API changes in BufferManager.
**     CA  20210629          Database triggers may get invoked while processing finalizable for a external
**                           program delete, so the processing procedure must be saved/restored and not set
**                           to null.
**     GES 20210809          Moved FINALLY block execution to occur after commitables/finalizables in 
**                           iterateWorker(), processRetry() or popScope(). If a finally block must be 
**                           processed, it must be explicitly enabled during each block iteration and
**                           after execution here, it is cleared.  If the finally block is not enabled
**                           using TransactionHelper.setupFinallyBlock(), then no finally processing will
**                           occur during that iteration. During execution of the finally block (in
**                           processFinallyBlock(), at the full transaction level we clear the transaction
**                           level and nested transaction level state and then restore it after the finally
**                           block executes.  This duplicates the behavior where in the 4GL, the finally
**                           block does not report it is in a transaction when executed at the full 
**                           transaction level.
**     CA  20210906          'processFinallyBlock' must be enclosed in a try/finally block, so any conditions
**                           raised from it will ensure the parent block is processed correctly.
**     OM  20211111          Added transaction support for finally blocks.
**     OM  20211201          Improved transaction support for finally blocks.
**     OM  20211203          Ignore ReturnUnwindException when processing the finally block of a proc/func.
**     CA  20211222          Fixed memory leak: when a procedure gets deleted, all its undoables must be
**                           deregistered; stale procedures can be registered with this block, when its FINALLY
**                           code is executed, so ensure DeferredDeletablesManager gets executed after the
**                           FINALLY code completes, as, the block's finalizables are already processed before
**                           FINALLY executes.
**     ECF 20220103          Do not propagate aggressive FOR flushing to nested blocks.
**     CA  20220114          Added SESSION:CLIENT-DISCONNECTED FWD extension attribute.
**                           Added SESSION:TERMINATION-HOOK, a hook to be executed when the global block is
**                           being terminated, to allow application-specific cleanup.  The cleanup code must 
**                           not perform any client-side calls, for i.e. file, socket access, UI, etc; any
**                           such code can be avoided via the CLIENT-DISCONNECTED flag.  
**     CA  20220120          Improved performance for finalizables, keep them already sorted by their weight.
**                           ConditionListener's are kept in their own list.
**     CA  20220128          TERMINATION-HOOK can not be set for remote sessions (appserver).
**     CA  20220210          Removed Commitable.rollbackPending and its notifications, as there is no actual 
**                           implementation of this method.
**     CA  20220310          Backed out a change in CA/20220210 - in popScope(), the global scope is finalized
**                           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.
**     CA  20221006          Added JMX instrumentation for process commit, rollback and validate. Refs #6814
**     CA  20221006          Lazy initialization for commitables and conditionListeners. 'finalizables' can be
**                           a set instead of a list, as their order is not important.  Refs #6824
**     CA  20221010          Performance improvements - avoid the ProcedureData lookup, by keeping a parallel 
**                           stack of this data for THIS-PROCEDURE.  Refs #6826
**     CA  20220602          TRANSACTION-MODE AUTO is opened at the agent's call scope, not the root scope.
**     CA  20220630          Fixed getBlockAtDepth - if 'blockDepth' is 0, return global block.
**     CA  20220918          Performance improvement - do not save procUndoables if there are no undoables at
**                           that external program.
**     CA  20221010          Performance improvements - avoid the ProcedureData lookup, by keeping a parallel 
**                           stack of this data for THIS-PROCEDURE.  Refs #6826
**     SVL 20230108          Improved performance by replacing some "for-each" loops with indexed "for" loops.
**     CA  20230110          A small improvement for 'addFinalizable'.
**     CA  20230116          Having a package-private inner-class field accessed from another inner-class 
**                           avoids a synthetic getter method emitted by javac.
**     HC  20230118          Eliminated some of the uses of String.toUpperCase and/or
**                           String.toLowerCase for performance.
** 134 CA  20230223          Avoid listIterator usage, as a plain for loop can be used, in 'nearestTopLevel'.
** 135 SVL 20230317          offEndQueries is now a stack: fixed the set of off-end queries being wrong when
**                           a database trigger is invoked because of buffer flush in FOR cycle init block.
** 136 GBB 20230512          Logging methods replaced by CentralLogger/ConversionStatus.
** 137 CA  20230615          The CATCH block must be executed just before the FINALLY block, in popScope.
**                           Refactored masterFinish/masterCommit to default to null, and also moved the 
**                           management of this for CATCH and FINALLY in a common block (for popScope). 
** 138 CA  20230607          TRANSACTION-MODE AUTO must preserve the tx in the root appserver block - this is
**                           required because in modes other than State-free, requests can be executed on the
**                           same agent who had TRANSACTION-MODE AUTO, and this must see as active the tx 
**                           initially set.
** 139 GBB 20230616          Remove log for exceptions caused by ApplicationRequestedStop.
** 140 CA  20230713          Fixed a regression in H137 - a RETURN from a CATCH block was leaking to previous 
**                           block, and terminating it early.
**                           Small optimization, use TM.getBlock() which returns the top of the stack, instead 
**                           of TM.getBlock(0).
** 141 CA  20230712          Fixed 'isRootExternal' when the target on the appserver is an internal entry for
**                           a persistent program.
** 142 GBB 20230825          SecurityManager context & session methods calls updated.
** 143 CA  20231026          Do not pin the Block instance to BlockDefinition, as it is never read.
**     CA  20231031          Added 'scopeStart(BlockDefinition)', required when a top-level scopeable is lazy
**                           registered while in an inner block.
** 144 CA  20231214          Fixed a 'pushScope' API which was passing UNKNOWN as the block type instead of 
**                           the parameter.
** 145 CA  20231216          Avoid iterators when processing BlockDefinition.finalizables.
**     CA  20231221          Refactored the WidgetPool scopeable support to register for processing as needed.
** 146 HC  20240222          Enabled JMX on FWD Client.
** 147 CA  20240320          Avoid BDTs within internal FWD runtime.  Replaced iterators with Java 'for',  
**                           where it applies.
** 148 AL2 20240322          Fixed typo when deregistering undoables from all tx blocks.
** 149 DDF 20240404          Reverted change made when deregistering undoables.
** 150 SP  20240422          Added processUndoables(boolean currentBlock).
** 151 DDF 20240509          Call deleted() when deregistering a Finalizable from masterFinish collection,
**                           but only for a transaction initiated by TRANSACTION-MODE AUTO.
**     DDF 20240509          Replace usage of autoRemove() with autoRemoveAndDelete().
** 152 SB  20240523          Added attemptRollback. Refs #8728.
** 153 CA  20240609          Added new overloads of 'triggerErrorInCaller'.
** 154 SVL 20240621          Pass error code to the client side in triggerErrorInCaller.
** 155 CA  20240707          If there is a pending error when executing a FINALLY block, execute this in its
**                           own 'nestedSilent' mode, as a FINALLY block can not override errors from the main
**                           block.
** 156 ES  20240711          Call cleanupPending in case of StopConditionExeption is thrown by a stop-after timer.
** 157 CA  20240809          Avoid iterator usage, for performance improvement.
** 158 SP  20240801          Extracted processCommit, processRollback and processValidate lambdas
**                           to processCommitImpl, processRollbackImpl and processValidateImpl methods.
**                           Skip using JMX timers when JMX_DEBUG flag is not set.
** 159 AS  20240924          Use triggeredInCaller attribute when creating an 
**                           ErrorConditionException in triggerErrorInCaller().
** 160 TJD 20230410          Check if rollback is already done with hadRollback function
**     TJD 20230824          Mark full transaction block as rolledBack on deferred rollback to allow proper 
**                           restore of record locks
** 161 ES  20241009          Setting undoThrow flag in case a rollback of a block happend.
** 162 CA  20240911          When resize a dynamic extent field via deserialization, undoables must be 
**                           registered in the deserialized instance, as this instance is not on stack.
** 163 OM  20241128          Multi-tenant runtime support: selected the proper persistence context, eventually
**                           based on [sharedDb] parameter.
** 164 CA  20250127          If a persistent procedure's delete is delayed, then register the BufferManager 
**                           scopeable, as this will be needed when the current scope ends, to delete the 
**                           procedure's resources.
** 165 GBB 20250403          Added getScopeLabel. BlockManager conditions to be enum constants.
** 166 ES  20250411          Combined execution logic for finally and catch blocks in processTerminationBlock method.
** 167 ES  20240221          Switch to throwing the StopConditionException through ErrorManager.
**     ES  20250228          Cleaning up the stopAfterTImer when a Progress.Lang.Stop condition was
**                           thrown.
**     ES  20250228          Register the stopAfterTimer for the WorkArea of the TM.
*/

/*
** 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;

import java.util.*;
import java.util.logging.*;

import com.goldencode.p2j.util.logging.*;
import com.goldencode.p2j.jmx.*;
import com.goldencode.p2j.net.*;
import com.goldencode.p2j.oo.lang.Stop;
import com.goldencode.p2j.persist.*;
import com.goldencode.p2j.persist.meta.*;
import com.goldencode.p2j.security.*;
import com.goldencode.p2j.security.SecurityManager;
import com.goldencode.p2j.ui.LogicalTerminal;
import com.goldencode.util.*;
import com.goldencode.util.Stack;

import static com.goldencode.p2j.util.BlockType.*;

/**
 * Progress compatible transaction/block properties static utility methods
 * which operate upon a context-local scoped dictionary of blocks.  This
 * class provides the following features:
 * <p>
 * <ul>
 *    <li> Maintains state for each block, handling the proper nesting of
 *         blocks.
 *    <li> Tracks the start and end of transactions and nested 
 *         sub-transactions.
 *    <li> Provides support for sending notifications of block start/end to
 *         registered instances of the {@link Scopeable} interface.
 *    <li> Provides a rollback service within a transaction or sub-transaction
 *         triggered by known error conditions or by explicit request.  See
 *         {@link Undoable} for externally driven rollback and see the
 *         {@link Commitable} for rollback notifications and custom rollback
 *         support.  See {@link #registerNext}, {@link #registerCurrent} and 
 *         {@link #registerCommit}. 
 *    <li> Provides a commit service triggered at known successful completion
 *         of a transaction or sub-transaction.  See {@link Commitable} and
 *         {@link #registerCommit} for commit notifications.
 *    <li> Provides a cleanup service for a callback on any exit from a given
 *         scope (whether that exit was normal or abnormal).  See the
 *         {@link Finalizable} interface and {@link #registerFinalizable}.
 *    <li> Provides an iteration service triggered by client code invocation
 *         of the iterate() method.  See the {@link Finalizable} interface
 *         and {@link #registerFinalizable}.
 *    <li> Provides a retry callback triggered by client code invocation
 *         of the needsRetry() method.  See the {@link Finalizable} interface
 *         and {@link #registerFinalizable}.
 *    <li> Provides a session-level cleanup service which is called after the 
 *         exit of the top-most scope. This global finish processing occurs
 *         when the last scope is popped from the stack of scopes.  For this
 *         reason, the current implementation assumes that the session is
 *         over at the moment that the top-most user-defined scope exits.
 *         See {@link #registerFinalizable} and note the use of the 
 *         <code>global</code> option.
 *    <li> Maintains state regarding retry logic to allow blocks or loops to 
 *         be retried as a result of error conditions being raised.
 *    <li> Maintains state and processing of retry and error stack unwinding.
 *    <li> Provides a session-level batch start and stop service.
 *    <li> At every block start, stop and iteration the current thread's
 *         <code>interrupted status</code> is checked and if the thread
 *         has been previously interrupted, a {@link StopConditionException}
 *         will be thrown.  See {@link #honorStopCondition}.
 * </ul>
 * <p>
 * The following is the mapping of Progress language features to the
 * corresponding feature in this class:
 * <p>
 * <pre>
 * Progress Name    Type                Method
 * ---------------  --------------      -----------------------------------
 * transaction      function            {@link #isTransactionActive}
 * retry            function            {@link #isRetry}
 * </pre>
 * <p>
 * This class minimizes the amount and complexity of client code by using
 * lists of objects to notify of specific events.  The following is a
 * summary of the event processing:
 * <p>
 * <pre>
 * Interface     Registration Method
 * ------------  ---------------------------
 * Undoable      {@link #registerNext} {@link #registerCurrent}
 * Commitable    {@link #registerCommit}
 * Finalizable   {@link #registerFinalizable}
 * Scopeable     {@link #registerScopeable}
 * BatchListener {@link #registerBatchListener}
 *
 * Interface     Notify Method(s)     Event
 * ------------  -------------------  ------------------------
 * Undoable      {@link #pushScope}           {@link Undoable#deepCopy}
 *               {@link #blockSetup}          {@link Undoable#deepCopy}
 *               {@link #rollback}            {@link Undoable#assign}
 *               {@link #popScope}            {@link Undoable#assign}
 * Commitable    {@link #blockSetup}          {@link Commitable#commit}
 *               {@link #popScope}            {@link Commitable#commit}
 *               {@link #rollback}            {@link Commitable#rollback}
 *               {@link #popScope}            {@link Commitable#rollback}
 * Finalizable   {@link #popScope}            {@link Finalizable#finished}
 *               {@link #blockSetup}          {@link Finalizable#iterate}
 * Scopeable     {@link #pushScope}           {@link Scopeable#scopeStart}
 *               {@link #popScope}            {@link Scopeable#scopeFinished} 
 * BatchListener {@link #batchStart}          {@link BatchListener#batchNotify}
 *               {@link #batchStop}           {@link BatchListener#batchNotify}
 * </pre>
 *
 * @author    GES
 */
public final class TransactionManager
{
   
   /** The block won't start a transaction or sub-transaction. */ 
   public static final int NO_TRANSACTION = 0;
   
   /** The block will start a sub-transaction if a transaction is active. */ 
   public static final int SUB_TRANSACTION = 1;
   
   /** The block will start a transaction or sub-transaction. */ 
   public static final int TRANSACTION = 2;
   
   /** No <code>Finalizable</code> processing is currently occurring. */
   public static final int OP_NONE = 0;
   
   /** <code>entry</code> processing is currently occurring. */
   public static final int OP_ENTRY = 1;
   
   /** <code>iterate</code> processing is currently occurring. */
   public static final int OP_ITERATE = 2;
   
   /** <code>retry</code> processing is currently occurring. */
   public static final int OP_RETRY = 3;
   
   /** <code>finished</code> processing is currently occurring. */
   public static final int OP_FINISHED = 4;
   
   /** Indicates that the block has no properties. */
   public static final int PROP_NONE = 0x00;
   
   /** ERROR property. */
   public static final int PROP_ERROR = 0x01;
   
   /** ENDKEY property. */
   public static final int PROP_ENDKEY = 0x02;
   
   /** STOP property. */
   public static final int PROP_STOP = 0x04;
   
   /** QUIT property. */
   public static final int PROP_QUIT = 0x08;
    
   /**
    * The index of the block where the automatic transaction will be opened (when a 
    * TRANSACTION-MODE AUTOMATIC language statement is in effect).
    */
   private static final int APPSERVER_ROOT_BLOCK_INDEX = 0;

   /** Instrumentation for {@link #processCommit}. */
   private static final NanoTimer COMMIT = NanoTimer.getInstance(FwdServerJMX.TimeStat.TMCommit);

   /** Instrumentation for {@link #processRollback}. */
   private static final NanoTimer ROLLBACK = NanoTimer.getInstance(FwdServerJMX.TimeStat.TMRollback);

   /** Instrumentation for {@link #processValidate}. */
   private static final NanoTimer VALIDATE = NanoTimer.getInstance(FwdServerJMX.TimeStat.TMValidate);
   
   /** Logger (this is JVM-wide rather than being context-local). */
   private static final CentralLogger LOG = CentralLogger.get(TransactionManager.class.getName());
   
   /** Context-local global data area for transaction state. */
   private static final ContextContainer workArea = new ContextContainer();
   
   /**
    * Flags whether the {@code _Trans} metadata table is active. If the flag is active the active
    * transactions for all users can be visualized using the {@code _Trans} SVT. Otherwise only
    * the transactions for current user can be queried using {@code DBTASKID()} ABL function.
    */
   private static final Map<String, TransactionTableUpdater> updaters = new CaseInsensitiveHashMap<>();
   
   /** Transaction information for each database. Access to its content must be synchronized. */
   private static final Map<String, DbTransactions> transactionData = new CaseInsensitiveHashMap<>();
   
   /**
    * Initialize a metaschema database by initializing a transaction listener and storing it for later use.
    * <p>
    * Called only at server startup.
    * 
    * @param   primaryDB
    *          Primary database for which metaschema information is being managed.
    */
   public static void initializeMeta(Database primaryDB)
   {
      String dbId = primaryDB.getId();
         
      // sync may not be mandatory here because the method is called during the initialization 
      synchronized (transactionData)
      {
         if (transactionData.containsKey(dbId))
         {
            if (LOG.isLoggable(Level.WARNING))
            {
               LOG.log(Level.WARNING, "Multiple registration of '" + dbId + "' database.");
            }
            return;
         }
         
         transactionData.put(dbId, new DbTransactions());
         
         if (MetadataManager.inUse(MetadataManager._TRANS) && TransactionTableUpdater.isEnabled())
         {
            updaters.put(dbId, new TransactionTableUpdater());
         }
      }
   }
   
   /**
    * Generate a string representation of the given transaction level.
    *
    * @param    level
    *           The transaction level to be decoded.  Must be one of
    *           <code>NO_TRANSACTION</code>, <code>SUB_TRANSACTION</code> or
    *           <code>TRANSACTION</code>.
    *
    * @return   The human readable string.
    */
   public static String decodeLevel(int level)
   {
      String txt = "UNKNOWN";
      
      switch (level)
      {
         case NO_TRANSACTION:
            txt = "NO_TRANSACTION";
            break;
         case SUB_TRANSACTION:
            txt = "SUB_TRANSACTION";
            break;
         case TRANSACTION:
            txt = "TRANSACTION";
            break;
      }
      
      return txt;
   }
   
   /**
    * Generate a string representation of the block properties of a block.
    *
    * @param    props
    *           The block properties to decode.
    *
    * @return   The human readable string.
    */
   public static String decodeProperties(int props)
   {
      StringBuilder sb = new StringBuilder();
      
      if ((props & PROP_ERROR) != 0)
      {
         sb.append(BlockManager.Condition.ERROR.name());
      }
      
      if ((props & PROP_ENDKEY) != 0)
      {
         if (sb.length() > 0)
         {
            sb.append(", ");
         }
         
         sb.append(BlockManager.Condition.ENDKEY.name());
      }
      
      if ((props & PROP_STOP) != 0)
      {
         if (sb.length() > 0)
         {
            sb.append(", ");
         }
         
         sb.append(BlockManager.Condition.STOP.name());
      }
      
      if ((props & PROP_QUIT) != 0)
      {
         if (sb.length() > 0)
         {
            sb.append(", ");
         }
         
         sb.append(BlockManager.Condition.QUIT.name());
      }
      
      return sb.toString();
   }
   
   /**
    * Reports on the current <code>Finalizable</code> operation being
    * executed.
    *
    * @return   One of the following:
    *           <ul>
    *              <li> <code>OP_NONE</code>
    *              <li> <code>OP_ITERATE</code>
    *              <li> <code>OP_RETRY</code>
    *              <li> <code>OP_FINISHED</code>
    *           </ul>
    */
   public static int currentOperation()
   {
      return workArea.obtain().op;
   }

   /**
    * Adds the given object into the list of objects for the current block
    * which require commit notification.  This form of registration is
    * useful for objects whose natural lifetime are scoped to a different
    * block than the block in which they must be committed.  For example,
    * if a global or shared resource needs to be committed in a specific 
    * scope, then this can be used in cooperation with the normal
    * {@link #registerCurrent} processing to control the commit points.
    * <p>
    * This notification will ONLY occur when there is an active transaction.
    * <p>
    * Notifications occur in LIFO order;  thus, commitables which depend upon
    * others being committed first must be registered before the objects upon
    * which they are dependent.
    * <p>
    * These registrations naturally are removed when the associated scope
    * is removed.
    *
    * @param    target
    *           The object reference to be committed on success.  This
    *           cannot be <code>null</code>.
    */
   public static void registerCommit(Commitable target)
   {
      WorkArea wa = workArea.obtain();
      registerCommitAt(wa, wa.blocks.size() - 1, target);
   }

   /**
    * Adds the given object into the list of objects which require commit notification for the
    * block at the specified depth  This form of registration is useful for objects whose natural
    * lifetime are scoped to a different block than the block in which they must be committed.
    * For example, if a global or shared resource needs to be committed in a specific scope,
    * then this can be used in cooperation with the normal {@link #registerCurrent} processing to 
    * control the commit points.
    * <p>
    * This notification will ONLY occur when there is an active transaction.
    * <p>
    * Notifications occur in LIFO order;  thus, commitables which depend upon
    * others being committed first must be registered before the objects upon
    * which they are dependent.
    * <p>
    * These registrations naturally are removed when the associated scope
    * is removed.
    *
    * @param    blockDepth
    *           Zero-based depth of the target block, starting from the outermost block.
    * @param    target
    *           The object reference to be committed on success.  This cannot be
    *           <code>null</code>.
    */
   public static void registerCommitAt(int blockDepth, Commitable target)
   {
      registerCommitAt(workArea.obtain(), blockDepth, target);
   }
   
   /**
    * Adds the given object as the transaction level reference which will get 
    * a single, "master" commit call after all other commits have been
    * processed at the full transaction level.  This will never be called
    * for sub-transaction commits, nor will it be called if the transaction
    * is abnormally exited.
    * <p>
    * This notification will ONLY occur when there is an active transaction
    * and it only will be called at the exit of the block that corresponds
    * to the full transaction.
    * <p>
    * If the full transaction block is also an iterating block, this
    * notification will occur for each iteration of the block.
    *
    * @param    target
    *           The object reference to be committed on success.  This
    *           can be <code>null</code> if one intends to clear the current
    *           reference.
    * @param    remove
    *           If <code>true</code> automatically remove this registration
    *           after the full transaction scope has ended (whether or not
    *           the commit occurs).  If <code>false</code>, this reference
    *           will remain globally until replaced.
    */
   public static void registerTransactionCommit(Commitable target, boolean remove)
   {
      WorkArea wa = workArea.obtain();
      if (wa.masterCommit == null)
      {
         wa.masterCommit = new LinkedHashMap<>();
      }
      wa.masterCommit.put(target, remove ? Boolean.TRUE : Boolean.FALSE);
   }
   
   /**
    * Register the given object as a handler which can veto the processing of
    * STOP condition processing at the current scope.  Handlers are invoked
    * in FIFO order.  Any handler can issue a veto, which forces the STOP
    * condition to be handled at a higher scope, if at all.
    * 
    * @param   h
    *          The stop condition veto handler.
    */
   public static void registerStopVetoHandler(StopConditionVetoHandler h)
   {
      WorkArea wa = workArea.obtain();
      wa.stopHandlers.add(h);
   }

   /**
    * 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 static void registerBlockScopeable(Scopeable 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. 
    *
    * @param resettable
    *        <code>Resettable</code> to be registered.
    */
   public static void registerResettable(Resettable resettable)
   {
      WorkArea wa = workArea.obtain();
      wa.resettables.add(resettable);
   }
   
   /**
    * Adds the given object into the list of objects for the specified block
    * which require iterate/retry/finish notification.  This form of
    * registration is useful for objects whose natural lifetime is scoped to
    * a block and which need a hook for cleaning up resources or handling
    * other block termination processing.
    * <p>
    * This notification will occur whether or not a transaction is active.
    * <p>
    * These registrations naturally are removed when the associated scope
    * is removed (after the {@link Finalizable#finished} method is called)
    * in the case of a non-global scope OR at the moment that the last
    * scope has been removed in the case of the global scope.
    * <p>
    * The given object will only be added if it is not already in the list.
    * <p>
    * Finalizables for a scope are processed in the same order as they were
    * registered.
    *
    * @param    target
    *           The object reference to be notified on any exit from the
    *           block specified.  This cannot be <code>null</code>.
    * @param    global 
    *           Specifies the target scope. <code>false</code> specifies the
    *           current scope, and <code>true</code> specifies the global
    *           scope.
    */
   public static void registerFinalizable(Finalizable target, boolean global)
   {
      WorkArea wa = workArea.obtain();
      registerFinalizableAt(wa, (global ? 0 : wa.blocks.size() - 1), target);
   }

   /**
    * Remove the given finalizable from the global list of finalizables.
    * 
    * @param    target
    *           Finalizable to be removed from the global list.
    */
   public static void deregisterGlobalFinalizable(Finalizable target)
   {
      deregisterGlobalFinalizable(workArea.obtain(), target);
   }

   /**
    * Adds the given object into the list of objects for the specified block
    * which require iterate/retry/finish notification.  This form of
    * registration is useful for objects whose natural lifetime is scoped to
    * a block and which need a hook for cleaning up resources or handling
    * other block termination processing.
    * <p>
    * This notification will occur whether or not a transaction is active.
    * <p>
    * These registrations naturally are removed when the associated scope
    * is removed (after the {@link Finalizable#finished} method is called)
    * in the case of a non-global scope OR at the moment that the last
    * scope has been removed in the case of the global scope.
    * <p>
    * The given object will only be added if it is not already in the list.
    * <p>
    * Finalizables for a scope are processed in the same order as they were
    * registered.
    *
    * @param    blockDepth
    *           zero-based depth of target block, starting from the outermost
    *           block. <code>0</code> indicates the global block.
    * @param    target
    *           The object reference to be notified on any exit from the
    *           block specified.  This cannot be <code>null</code>.
    */
   public static void registerFinalizableAt(int blockDepth, Finalizable target)
   {
      registerFinalizableAt(workArea.obtain(), blockDepth, target);
   }
   
   /**
    * Adds the given object into the list of objects which require
    * iterate/retry/finish notifications for the next block that is opened
    * which is a top-level block that corresponds to a Progress external 
    * procedure.
    * <p>
    * This form of registration is useful for objects whose natural lifetime
    * is scoped to the top-level block and which need a hook for cleaning up
    * resources or handling other block termination processing.  In 
    * particular, it is necessary for objects that are constructed during 
    * a class' constructor/initializer (before the external procedure block
    * has opened).  Such objects cannot simply register since their 
    * construction is not actually enclosed by the block.
    * <p>
    * This notification will occur whether or not a transaction is active.
    * <p>
    * These registrations naturally are removed when the associated scope
    * is removed (after the {@link Finalizable#finished} method is called).
    * <p>
    * The given object will only be added if it is not already in the list.
    *
    * @param    target
    *           The object reference to be notified on any exit from the
    *           top-level external block.  This cannot be <code>null</code>.
    *
    * @throws   IllegalStateException
    *           if the top-level block cannot be identified.
    */
   public static void registerNextExternal(Finalizable target)
   {
      registerNextExternal(workArea.obtain(), target);
   }
   
   /**
    * Allow finalizables to do their cleanup ({@link Finalizable#initFailure()}) now since the
    * scoped they were designed was aborted and there won't be other chance to call their
    * {@link Finalizable#finished()}.
    * <p>
    * After iteration the list is deleted.
    * <p>
    * Also, cleanup all other pending state being collected while the external program was 
    * instantiating.
    */
   public static void cleanupPending()
   {
      cleanupPending(workArea.obtain());
   }
   
   /**
    * Adds the given object into the list of objects which require
    * iterate/retry/finish notifications for the nearest enclosing 
    * top-level block.  A top-level block is generated for Progress external 
    * procedures, internal procedures, triggers and functions.  An additional
    * parameter allows registration to be specified for the specific block
    * which corresponds with an external procedure block scope in Progress.
    * <p>
    * This form of registration is useful for objects whose natural lifetime
    * is scoped to the top-level block and which need a hook for cleaning up
    * resources or handling other block termination processing.
    * <p>
    * This notification will occur whether or not a transaction is active.
    * <p>
    * These registrations naturally are removed when the associated scope
    * is removed (after the {@link Finalizable#finished} method is called).
    * <p>
    * The given object will only be added if it is not already in the list.
    * <p>
    * Finalizables for a scope are processed in the same order as they were
    * registered.
    *
    * @param    target
    *           The object reference to be notified on any exit from the
    *           top-level block.  This cannot be <code>null</code>.
    * @param    external
    *           If <code>true</code>, register with the nearest enclosing
    *           external block (external procedure), otherwise register with
    *           the nearest enclosing top-level block (which may be but is
    *           not guaranteed to be the external block).
    *
    * @throws   IllegalStateException
    *           if the top-level block cannot be identified.
    */
   public static void registerTopLevelFinalizable(Finalizable target, boolean external)
   {
      registerTopLevelFinalizable(workArea.obtain(), target, external);
   }
   
   /**
    * Adds the given object into the list of objects which require
    * iterate/retry/finish notification for the nearest enclosing, looping 
    * block.
    * <p>
    * This form of registration is useful for objects whose natural lifetime
    * is scoped to the nearest enclosing, looping block and which need a hook
    * for cleaning up resources or handling other block termination
    * processing.
    * <p>
    * This notification will occur whether or not a transaction is active.
    * <p>
    * These registrations naturally are removed when the associated scope
    * is removed (after the {@link Finalizable#finished} method is called).
    * <p>
    * The given object will only be added if it is not already in the list.
    *
    * @param    target
    *           The object reference to be notified on any exit from or
    *           iteration of the target block.  This cannot be
    *           <code>null</code>.
    *
    * @throws   IllegalStateException
    *           if no enclosing, looping block can be identified.
    */
   public static void registerNearestLoopFinalizable(Finalizable target)
   {
      registerNearestLoopFinalizable(workArea.obtain(), target);
   }
   
   /**
    * Removes the given object from the list of objects which require
    * iterate/retry/finish notifications.  This will find the first instance
    * of the given reference by walking up each block from the current to the  
    * global block. The first instance found will be removed and the method
    * will then return. If the same instance is registered in multiple scopes,
    * it would take multiple calls to remove all instances.
    * <p>
    * After this call, no notifications will be received.
    *
    * @param    target
    *           The object reference to be removed.
    *
    * @return   <code>true</code> if the target instance was found and
    *           removed.
    */
   public static boolean removeFinalizable(Finalizable target)
   {
      if (target == null)
      {
         // nothing to do
         return false;
      }
      
      return removeFinalizable(workArea.obtain(), target);
   }
   
   /**
    * Adds the given object as the transaction level reference which will get 
    * a "master" finish call after all other finish notifications have been
    * processed at the full transaction level.  This will never be called for
    * sub-transaction finish notification.
    * <p>
    * This notification will ONLY occur when there is an active transaction
    * and it only will be called at the exit of the block that corresponds
    * to the full transaction.
    * <p>
    * Multiple targets may be registered by invoking this method multiple
    * times.  However, the same object registered multiple times will receive
    * the notification only once per full transaction block exit.  If multple
    * targets are registered, they will be called in the order in which they
    * were registered.
    *
    * @param    target
    *           The object reference to be finished at full transaction block
    *           end.
    * @param    remove
    *           If <code>true</code> automatically remove this registration
    *           after the full transaction scope has ended.  If
    *           <code>false</code>, this reference will remain globally until
    *           replaced.
    */
   public static void registerTransactionFinish(Finalizable target, boolean remove)
   {
      WorkArea wa = workArea.obtain();
      if (wa.masterFinish == null)
      {
         wa.masterFinish = new LinkedHashMap<>();
      }
      wa.masterFinish.put(target, remove);
   }
   
   /**
    * Adds the given object into the batch notifier list.  This is a global 
    * list (it isn't associated with any block) that is triggered externally
    * by a call to {@link #batchStart} and {@link #batchStop}.  Every object
    * in the list will be notified in the order in which they were added to
    * the list.
    *
    * @param    target
    *           The object reference to be notified of any batch start and 
    *           stop.
    */
   public static void registerBatchListener(BatchListener target)
   {
      workArea.obtain().batchListeners.add(target);
   }
   
   /**
    * Reports whether the current block was just rolled back or a containing
    * block has a pending rollback. If there is no current transaction open,
    * then there cannot be a pending or current rollback.
    *
    * @return   <code>true</code> if there was or will be a rollback.
    */
   public static boolean hadRollback()
   {
      return hadRollback(workArea.obtain());
   }
   
   /**
    * Trigger a rollback for all registered <code>Undoable</code> objects 
    * inside the current active transaction at the scope defined by the
    * current block.  All objects that are rolled back will have their
    * values assigned based on the values backed up at the start of the
    * specified scope.  A flow control change MUST always immediately follow 
    * this method otherwise undefined results can occur.
    * <p>
    * This caller-driven control flow is required to properly duplicate the
    * semantics of Progress 4GL condition processing which always generates 
    * an implicit or explicit flow control action after every UNDO. Since the
    * flow of control change can reference a different scope than that of
    * the rollback, this can't be handled by re-throwing the exception but
    * must instead be separately driven by a deferred rollback and a caller
    * driven flow control statement such as break, continue or return. The
    * key limitation that makes this possible is the fact that the UNDO
    * (rollback) scope MUST ALWAYS BE equivalent to or more specific than
    * the scope to which flow of control will be changed.  This means that
    * one can rollback at a more deeply nested scope but change flow of
    * control to resume processing at a different (less deeply nested) scope.
    * The reverse is not possible.
    */
   public static void rollback()
   {
      rollback(null);
   }
   
   /**
    * Trigger a rollback for all registered <code>Undoable</code> objects 
    * inside the current active transaction at the scope defined
    * explicitly by the given label or implicitly if the label is
    * <code>null</code>.  All objects that are rolled back will have their
    * values assigned based on the values backed up at the start of the
    * specified scope.  If the current scope is not the target scope, the
    * given exception will be stored, the rollback will be deferred and 
    * the rollback will be processed as the scopes naturally unwind.  This
    * unwinding must be generated by a change in flow of control by the
    * calling code.  This flow control change MUST always immediately follow 
    * this method otherwise undefined results can occur.
    * <p>
    * This caller-driven control flow is required to properly duplicate the
    * semantics of Progress 4GL condition processing which always generates 
    * an implicit or explicit flow control action after every UNDO. Since the
    * flow of control change can reference a different scope than that of
    * the rollback, this can't be handled by re-throwing the exception but
    * must instead be separately driven by a deferred rollback and a caller
    * driven flow control statement such as break, continue or return. The
    * key limitation that makes this possible is the fact that the UNDO
    * (rollback) scope MUST ALWAYS BE equivalent to or more specific than
    * the scope to which flow of control will be changed.  This means that
    * one can rollback at a more deeply nested scope but change flow of
    * control to resume processing at a different (less deeply nested) scope.
    * The reverse is not possible.
    *
    * @param    label
    *           Specifies the name of the scope at which to rollback or
    *           <code>null</code> if the scope should be implicitly 
    *           determined.
    */
   public static void rollback(String label)
   {
      rollback(label, null);
   }
   
   /**
    * Trigger a rollback for all registered <code>Undoable</code> objects 
    * inside the current active transaction at the scope defined
    * explicitly by the given label or implicitly if the label is
    * <code>null</code>.  All objects that are rolled back will have their
    * values assigned based on the values backed up at the start of the
    * specified scope.  If the current scope is not the target scope, the
    * given exception will be stored, the rollback will be deferred and 
    * the rollback will be processed as the scopes naturally unwind.  This
    * unwinding must be generated by a change in flow of control by the
    * calling code.  This flow control change MUST always immediately follow 
    * this method otherwise undefined results can occur.
    * <p>
    * This caller-driven control flow is required to properly duplicate the
    * semantics of Progress 4GL condition processing which always generates 
    * an implicit or explicit flow control action after every UNDO. Since the
    * flow of control change can reference a different scope than that of
    * the rollback, this can't be handled by re-throwing the exception but
    * must instead be separately driven by a deferred rollback and a caller
    * driven flow control statement such as break, continue or return. The
    * key limitation that makes this possible is the fact that the UNDO
    * (rollback) scope MUST ALWAYS BE equivalent to or more specific than
    * the scope to which flow of control will be changed.  This means that
    * one can rollback at a more deeply nested scope but change flow of
    * control to resume processing at a different (less deeply nested) scope.
    * The reverse is not possible.
    *
    * @param    label
    *           Specifies the name of the scope at which to rollback or
    *           <code>null</code> if the scope should be implicitly 
    *           determined.
    * @param    cause
    *           The exceptional cause, if any, of the rollback.
    */
   public static void rollback(String label, Throwable cause)
   {
      rollback(workArea.obtain(), label, cause);
   }
   
   /**
    * Trigger a rollback for all registered <code>Undoable</code> objects 
    * inside the current active transaction at the nearest top level scope.
    * All objects that are rolled back will have their values assigned based
    * on the values backed up at the start of the specified scope.  If the
    * current scope is not the target scope, the given exception will be 
    * stored, the rollback will be deferred and the rollback will be processed
    * as the scopes naturally unwind.  This unwinding must be generated by a
    * change in flow of control by the calling code.  This flow control change 
    * MUST always immediately follow this method otherwise undefined results
    * can occur.
    * <p>
    * This caller-driven control flow is required to properly duplicate the
    * semantics of Progress 4GL condition processing which always generates 
    * an implicit or explicit flow control action after every UNDO. Since the
    * flow of control change can reference a different scope than that of
    * the rollback, this can't be handled by re-throwing the exception but
    * must instead be separately driven by a deferred rollback and a caller
    * driven flow control statement such as break, continue or return. The
    * key limitation that makes this possible is the fact that the UNDO
    * (rollback) scope MUST ALWAYS BE equivalent to or more specific than
    * the scope to which flow of control will be changed.  This means that
    * one can rollback at a more deeply nested scope but change flow of
    * control to resume processing at a different (less deeply nested) scope.
    * The reverse is not possible.
    */
   public static void rollbackTopLevel()
   {
      rollbackTopLevel(workArea.obtain());
   }
   
   /**
    * Initializes a new scope, the associated block definition data
    * structures.  A backup of objects that can be rolled back is deferred
    * until after the block opens (see {@link #blockSetup} and 
    * {@link #makeBackup}).
    * <p>
    * In the case of a block which is defined as <code>NO_TRANSACTION</code>,
    * the core data structures are initialized but no rollback support will
    * be provided.
    * <p>
    * This method will honor any pending thread interruption by throwing
    * a {@link StopConditionException} using {@link #honorStopCondition}.
    * <p>
    * This will create a block of <code>BlockType.UNKNOWN</code> and block
    * properties <code>PROP_NONE</code>.
    *
    * @param    label
    *           The text label associated with the block.  This usually
    *           should correspond with the label used in the source file
    *           to identify the block for purposes of the break and continue
    *           statements.
    * @param    level
    *           One of {@link #NO_TRANSACTION}, {@link #SUB_TRANSACTION} or
    *           {@link #TRANSACTION}.  This code defines the level of
    *           rollback and transaction support which should be provided.
    * @param    external
    *           <code>true</code> if this block is associated with the
    *           external procedure (in Progress terms). This controls the
    *           cleanup of certain resources such as temp-tables, frames
    *           and streams.
    * @param    topLevel
    *           <code>true</code> if this block is a method.
    * @param    loop
    *           Defines if this block is a loop or not.
    * @param    next
    *           <code>true</code> if this infinite loop protection should
    *           convert a retry into a next (continue), otherwise the retry
    *           will be converted into a leave (break).
    *
    * @throws   StopConditionException
    *           If the current thread's was previously interrupted.
    */
   public static void pushScope(String    label,
                                int       level,
                                boolean   external,
                                boolean   topLevel,
                                boolean   loop,
                                boolean   next)
   {
      pushScope(label,
                level,
                PROP_NONE,
                external,
                topLevel,
                loop,
                next,
                UNKNOWN);
   }

   /**
    * Returns the label of the current block scope.
    * 
    * @return   See above.
    */
   public static String getScopeLabel()
   {
      Stack<BlockDefinition> blocks = workArea.obtain().blocks;
      return blocks.size() > 0 ? blocks.peek().label : null;
   }
   
   /**
    * Initializes a new scope, the associated block definition data
    * structures.  A backup of objects that can be rolled back is deferred
    * until after the block opens (see {@link #blockSetup} and 
    * {@link #makeBackup}).
    * <p>
    * In the case of a block which is defined as <code>NO_TRANSACTION</code>,
    * the core data structures are initialized but no rollback support will
    * be provided.
    * <p>
    * This method will honor any pending thread interruption by throwing
    * a {@link StopConditionException} using {@link #honorStopCondition}.
    * <p>
    * This will create a block with block properties <code>PROP_NONE</code>.
    *
    * @param    label
    *           The text label associated with the block.  This usually
    *           should correspond with the label used in the source file
    *           to identify the block for purposes of the break and continue
    *           statements.
    * @param    level
    *           One of {@link #NO_TRANSACTION}, {@link #SUB_TRANSACTION} or
    *           {@link #TRANSACTION}.  This code defines the level of
    *           rollback and transaction support which should be provided.
    * @param    external
    *           <code>true</code> if this block is associated with the
    *           external procedure (in Progress terms). This controls the
    *           cleanup of certain resources such as temp-tables, frames
    *           and streams.
    * @param    topLevel
    *           <code>true</code> if this block is a method.
    * @param    loop
    *           Defines if this block is a loop or not.
    * @param    next
    *           <code>true</code> if this infinite loop protection should
    *           convert a retry into a next (continue), otherwise the retry
    *           will be converted into a leave (break).
    * @param    blockType
    *           Specifies this block's type.
    *
    * @throws   StopConditionException
    *           If the current thread's was previously interrupted.
    */
   public static void pushScope(String    label,
                                int       level,
                                boolean   external,
                                boolean   topLevel,
                                boolean   loop,
                                boolean   next,
                                BlockType blockType)
   {
      pushScope(label,
                level,
                PROP_NONE,
                external,
                topLevel,
                loop,
                next,
                blockType);
   }
   
   /**
    * Initializes a new scope, the associated block definition data
    * structures.  A backup of objects that can be rolled back is deferred
    * until after the block opens (see {@link #blockSetup} and 
    * {@link #makeBackup}).
    * <p>
    * In the case of a block which is defined as <code>NO_TRANSACTION</code>,
    * the core data structures are initialized but no rollback support will
    * be provided.
    * <p>
    * This method will honor any pending thread interruption by throwing
    * a {@link StopConditionException} using {@link #honorStopCondition}
    *
    * @param    label
    *           The text label associated with the block.  This usually
    *           should correspond with the label used in the source file
    *           to identify the block for purposes of the break and continue
    *           statements.
    * @param    level
    *           One of {@link #NO_TRANSACTION}, {@link #SUB_TRANSACTION} or
    *           {@link #TRANSACTION}.  This code defines the level of
    *           rollback and transaction support which should be provided.
    * @param    props
    *           A bitfield storing block properties which will be a
    *           combination of {@link #PROP_NONE}, {@link #PROP_ERROR},
    *           {@link #PROP_ENDKEY}, {@link #PROP_STOP} or
    *           {@link #PROP_QUIT}.
    * @param    external
    *           <code>true</code> if this block is associated with the
    *           external procedure (in Progress terms). This controls the
    *           cleanup of certain resources such as temp-tables, frames
    *           and streams.
    * @param    topLevel
    *           <code>true</code> if this block is a method.
    * @param    loop
    *           Defines if this block is a loop or not.
    * @param    next
    *           <code>true</code> if this infinite loop protection should
    *           convert a retry into a next (continue), otherwise the retry
    *           will be converted into a leave (break).
    * @param    blockType
    *           Specifies this block's type.
    *
    * @throws   StopConditionException
    *           If the current thread's was previously interrupted.
    */
   public static void pushScope(String    label,
                                int       level,
                                int       props,
                                boolean   external,
                                boolean   topLevel,
                                boolean   loop,
                                boolean   next,
                                BlockType blockType)
   {
      WorkArea wa = workArea.obtain();
      pushScope(wa, 
               label, 
               level, 
               props, 
               external, 
               topLevel, 
               loop, 
               next, 
               false, 
               blockType);
   }
   
   /**
    * Remove the current block from the stack of scopes, process any pending
    * rollbacks (if the current block is the proper target), notify any
    * objects needing commits (if the block finished successfully) and handle
    * finish processing for the block.  This method must be called from a
    * <code>finally</code> block such that this is guaranteed to be called
    * no matter how the flow of control exits the associated block (exception,
    * break, return or the flow of control naturally passing through the 
    * end of the block).
    * <p>
    * Rollback and commit processing only occur when there is an active
    * transaction.  Otherwise this processing is bypassed.  Finish processing
    * is always called.
    * <p>
    * This method implements global finish processing (as the last action
    * before returning) when the last scope is popped from the stack of
    * scopes.  For this reason, the current implementation assumes that the
    * session is over at the moment that the top-most user-defined scope
    * exits.
    * <p>
    * This method will honor any pending thread interruption by throwing
    * a {@link StopConditionException} using {@link #honorStopCondition}
    *
    * @throws   StopConditionException
    *           If the current thread's was previously interrupted.
    */
   public static void popScope()
   throws StopConditionException
   {
      popScope(workArea.obtain());
   }
   
   /**
    * Notify {@link ConditionListener} instances about condition.
    * 
    * @param   condition
    *          The condition the listeners will be notified.
    */
   public static void notifyCondition(BlockManager.Condition condition)
   {
      // do not pass null down
      if (condition == null)                   
      {
         return;
      }
      
      notifyCondition(workArea.obtain(), condition);
   }

   /**
    * Notify {@link ConditionListener} instances about exception condition.
    * 
    * @param   condition
    *          The instance of {@link ConditionException}.
    */
   public static void notifyCondition(ConditionException condition)
   {
      notifyCondition(BlockManager.exceptionToCondition(condition));
   }
   
   /**
    * Check if the latest block has initialized.
    * 
    * @return   See above.
    */
   public static boolean blockInitialized()
   {
      BlockDefinition blk = workArea.obtain().blocks.peek();

      return blk.init;
   }
   
   /**
    * Get a helper to access certain context-local state required for undoable support.
    */
   public static UndoableHelper getUndoableHelper()
   {
      return new UndoableHelper(workArea.obtain());
   }
   
   /**
    * Get a helper to access certain context-local state required for transaction support.
    */
   public static TransactionHelper getTransactionHelper()
   {
      return new TransactionHelper(workArea.obtain());
   }
   
   /**
    * Provides a callback entry point for the top of the loop.  This location 
    * is required to handle the backup of undo variables via 
    * {@link #makeBackup}.  On the second and subsequent executions of this
    * method for a given block, this method handles <code>iterate</code> 
    * notifications.
    * <p>
    * It is important to note that <code>continue</code> can be called from
    * nested blocks at arbitrary depths.  This method can handle such nesting
    * at any level. 
    * <p>
    * This method will honor any pending thread interruption by throwing
    * a {@link StopConditionException} using {@link #honorStopCondition}
    *
    * @throws   StopConditionException
    *           If the current thread's was previously interrupted.
    */
   public static void blockSetup() 
   throws StopConditionException
   {
      blockSetup(workArea.obtain());
   }
   
   /**
    * Creates or updates the backup set for all objects needing rollback 
    * support in the current block, if a transaction is active.  This is
    * split up from {@link #pushScope} and {@link #blockSetup} due to the
    * semantics of Progress in which the backup set is created or refreshed
    * after the loop control variables have been modified.
    */
   public static void makeBackup() 
   {
      backupWorker(workArea.obtain(), null);
   }
   
   /**
    * Reports if there is a current transaction which is active.
    *
    * @return    <code>true</code> if a transaction is active.
    */
   public static logical isTransactionActive()
   {
      return new logical(isTransaction());
   }
   
   /**
    * Reports if the current iteration of this block or loop is a retry.
    *
    * @return    <code>true</code> if this is a retry of this block or loop
    *            (as opposed to the first time through this iteration).
    */
   public static boolean _isRetry()
   {
      return _isRetry(workArea.obtain());
   }

   /**
    * Reports if the current iteration of this block or loop is a retry.
    *
    * @return    <code>true</code> if this is a retry of this block or loop
    *            (as opposed to the first time through this iteration).
    */
   public static logical isRetry()
   {
      return new logical(_isRetry());
   }
   
   /**
    * Records the fact that the pending RETRY is being caused by an ENDKEY
    * condition. This enables "downstream" retry processing to implement the
    * "stuttering" quirk and some behavior in honoring PAUSE as a way to
    * disable infinite loop protection.
    */
   public static void markEndkeyRetry()
   {
      markEndkeyRetry(workArea.obtain());
   }
   
   /**
    * Disables infinite loop protection for the remainder of the current 
    * iteration of the innermost block.  This should be called from any
    * runtime code that blocks for user input AND from the {@link #isRetry}
    * method.
    *
    * @param    blk
    *           The block on which to operator or <code>null</code> to
    *           operate on the current (innermost) block.
    */
   public static void disableLoopProtection(BlockDefinition blk)
   {
      if (blk == null)
      {
         // peek at the current block def on the top of the stack
         blk = workArea.obtain().blocks.peek();
      }
      
      blk.loopProtection = false;
   }
   
   /**
    * This checks if any of the enclosing loops has iterated at least once.
    * 
    * @return   <code>true</code> if any of the enclosing loops has iterated
    *           at least once.
    */
   public static boolean hasParentIteratedBlock()
   {
      return hasParentIteratedBlock(workArea.obtain());
   }
   
   /**
    * Determines if a NEXT operation with the given target block should
    * be honored or if the NEXT should be converted to a LEAVE. This is
    * a special form of infinite loop protection.
    * <p>
    * A NEXT is converted into a LEAVE when all of the following conditions
    * are true:
    * <p>
    * <ul>
    *   <li> The NEXT is associated with the UNDO, NEXT language statement or
    *        with an ON phrase that has UNDO, NEXT as the result of the given
    *        condition (e.g. ON ENDKEY UNDO, NEXT). It is important to note
    *        that Progress does not provide infinite loop protection for the
    *        NEXT language statement, so there is no conversion to LEAVE in
    *        that case.
    *   <li> The target block (whether implicitly determined by the lack of a
    *        label or explicitly determined by the presence of a label) must
    *        also have been the target block for the UNDO operation. It is
    *        possible for the NEXT to be targetted at a more enclosing block
    *        than the UNDO, in which case there is no conversion to LEAVE.
    *   <li> The target block type must be REPEAT, REPEAT WHILE or DO WHILE.
    *        These are the only loop types that normally convert RETRY to
    *        LEAVE instead of NEXT.  Likewise, these are the only loop types
    *        which honor infinite loop protection for NEXT by converting it
    *        to a LEAVE.
    *   <li> Infinite loop protection must not have been disabled for the
    *        target block.  In this case, any form of PAUSE is honored as
    *        one of the mechanisms to disable infinite loop protection.
    * </ul>
    * <p>
    * Since NEXT is only possible on a looping block, if the target block
    * is not a looping block then this method will return <code>false</code>.
    *
    * @param    label
    *           The label of the target block for the NEXT action.
    *
    * @return   <code>true</code> for NEXT, <code>false</code> for LEAVE.
    */
   public static boolean honorNext(String label)
   {
      return honorNext(workArea.obtain(), label);
   }
   
   /**
    * Determines if the currently executing block should be retried.  This
    * is where infinite loop protection is implemented if it is enabled.
    *
    * @return   <code>true</code> if the block (whether or not it is a loop)
    *           should be retried.
    */
   public static boolean needsRetry()
   {  
      return needsRetry(workArea.obtain());
   }
   
   /**
    * Accesses the state of whether there is a pending break that must be 
    * honored.  This would have been generated by the infinite loop
    * protection.
    *
    * @return   <code>true</code> if a break is pending for this block.
    */
   public static boolean isBreakPending()
   {
      return isBreakPending(workArea.obtain());
   }
   
   /**
    * Force an error to be generated such that it will be caught in the
    * caller of this method.  The stack will be unwound and normal error 
    * processing bypassed until this method returns.  This unwinding is 
    * dependent upon catch blocks being implemented with a corresponding call
    * to {@link #honorError}.
    * <p>
    * In the case where silent error mode is enabled in the calling method,
    * the "normal" stack unwinding approach cannot be used because the 
    * exception cannot be thrown. Instead, the error manager will be updated 
    * with the error data and this method will silently return.  It is
    * critical that in such cases the calling code must immediately execute a
    * <code>return</code> statement which will maintain the proper Progress
    * behavior.  The caller can check the error manager to determine that an 
    * error occurred.
    * <p>
    * Detecting the silent error mode in the caller requires that the top
    * level block be found.  The silent error flag of the caller is cached 
    * there.
    *
    * @param    text
    *           The text to use as the error message in the exception.
    *           
    * @throws   ErrorConditionException 
    */
   public static void triggerErrorInCaller(String text)
   throws ErrorConditionException
   {
      triggerErrorInCaller(workArea.obtain(), null, text);
   }   
   /**
    * Force an error to be generated such that it will be caught in the caller of this method. The
    * stack will be unwound and normal error processing bypassed until this method returns. This
    * unwinding is dependent upon catch blocks being implemented with a corresponding call to 
    * {@link #honorError}.
    * <p>
    * In the case where silent error mode is enabled in the calling method, the "normal" stack
    * unwinding approach cannot be used because the exception cannot be thrown. Instead, the error
    * manager will be updated with the error data and this method will silently return. It is
    * critical that in such cases the calling code must immediately execute a {@code return}
    * statement which will maintain the proper Progress behavior. The caller can check the error
    * manager to determine that an error occurred.
    * <p>
    * Detecting the silent error mode in the caller requires that the top level block be found.
    * The silent error flag of the caller is cached there.
    * <p>
    * NOTE: The error message will NOT be printed if the calling procedure is the 'main' one.
    *
    * @param   errNum
    *          The identifier of the error. 
    * @param   text
    *          The text to use as the error message in the exception.
    *
    * @throws  ErrorConditionException 
    */
   public static void triggerErrorInCaller(int errNum, String text)
   throws ErrorConditionException
   {
      triggerErrorInCaller(workArea.obtain(), errNum, text);
   }
   
   /**
    * Force an error to be generated such that it will be caught in the caller of this method. The
    * stack will be unwound and normal error processing bypassed until this method returns. This
    * unwinding is dependent upon catch blocks being implemented with a corresponding call to 
    * {@link #honorError}.
    * <p>
    * In the case where silent error mode is enabled in the calling method, the "normal" stack
    * unwinding approach cannot be used because the exception cannot be thrown. Instead, the error
    * manager will be updated with the error data and this method will silently return. It is
    * critical that in such cases the calling code must immediately execute a {@code return}
    * statement which will maintain the proper Progress behavior. The caller can check the error
    * manager to determine that an error occurred.
    * <p>
    * Detecting the silent error mode in the caller requires that the top level block be found.
    * The silent error flag of the caller is cached there.
    * <p>
    * NOTE: The error message will NOT be printed if the calling procedure is the 'main' one.
    *
    * @param   errNum
    *          The identifier of the error. 
    * @param   text
    *          The text to use as the error message in the exception.
    * @param   errFlag
    *          Flag indicating the ERROR-ERROR:ERROR is set. 
    * @param   prefix
    *          Prefix or not with two asterics when building the error message text.
    *
    * @throws  ErrorConditionException 
    */
   public static void triggerErrorInCaller(int errNum, String text, boolean errFlag, boolean prefix)
   throws ErrorConditionException
   {
      triggerErrorInCaller(workArea.obtain(), errNum, text, errFlag, prefix);
   }
   
   /**
    * Check if this block's caller requires errors to be suppressed.
    * 
    * @return   {@link BlockDefinition#suppressError} for {@link #nearestTopLevel} block.
    */
   public static boolean isSuppressError()
   {
      return isSuppressError(workArea.obtain());
   }
   
   /**
    * Force an error to be generated such that it will be caught in the
    * caller of this method.  The stack will be unwound and normal error 
    * processing bypassed until this method returns.  This unwinding is 
    * dependent upon catch blocks being implemented with a corresponding call
    * to {@link #honorError}.
    * <p>
    * In the case where silent error mode is enabled in the calling method,
    * the "normal" stack unwinding approach cannot be used because the 
    * exception cannot be thrown. Instead, the error manager will be updated 
    * with the error data and this method will silently return.  It is
    * critical that in such cases the calling code must immediately execute a
    * <code>return</code> statement which will maintain the proper Progress
    * behavior.  The caller can check the error manager to determine that an 
    * error occurred.
    * <p>
    * Detecting the silent error mode in the caller requires that the top
    * level block be found.  The silent error flag of the caller is cached 
    * there.
    *           
    * @throws   ErrorConditionException 
    */
   public static void triggerErrorInCaller()
   throws ErrorConditionException
   {
      triggerErrorInCaller("");
   }
   
   /**
    * Force a retry of the current block (if there is no pending rollback)
    * or retry the same block as the pending rollback, in either case
    * ensure that the flow of control will not naturally continue.
    * <p>
    * A {@link RetryUnwindException} will be thrown to unwind the stack
    * to the given scope, even if the scope is the current block.  This
    * unwinding is dependent upon catch blocks being implemented with a 
    * corresponding call to {@link #honorRetry}.
    * <p>
    * <b>This method will NEVER naturally return since an exception is ALWAYS
    * thrown.</b>  This method should generally not be called from within a
    * catch block if the intention is to retry the current block since it
    * would be outside of the block's associated <code>try</code>.  In that 
    * case, only an enclosing block could be retried.
    *
    * @throws   RetryUnwindException
    *           This method always generates this exception to ensure that
    *           natural processing will not continue.
    */
   public static void forceRetry()
   throws RetryUnwindException
   {
      forceRetry(null);
   }
   
   /**
    * Force a retry of the current block or the scope named by the given
    * label, ensuring that the flow of control will not naturally continue.
    * A {@link RetryUnwindException} will be thrown to unwind the stack
    * to the given scope, even if the scope is the current block.  This
    * unwinding is dependent upon catch blocks being implemented with a 
    * corresponding call to {@link #honorRetry}.
    * <p>
    * <b>This method will NEVER naturally return since an exception is ALWAYS
    * thrown.</b>  This method should generally not be called from within a
    * catch block if the intention is to retry the current block since it
    * would be outside of the block's associated <code>try</code>.  In that 
    * case, only an enclosing block could be retried.
    *
    * @param    label
    *           The name of the scope to retry. Specify <code>null</code> to
    *           retry the scope that has a pending rollback OR to retry
    *           the current scope if there is no pending rollback.
    *
    * @throws   RetryUnwindException
    *           This method always generates this exception to ensure that
    *           natural processing will not continue.
    */
   public static void forceRetry(String label)
   throws RetryUnwindException
   {
      forceRetry(workArea.obtain(), label);
   }
   
   /**
    * Force a retry of the current block (if there is no pending rollback)
    * or the block targeted for a pending rollback.  If a containing scope is
    * the target of the retry, then a {@link RetryUnwindException} will be
    * thrown to unwind the stack to the given scope.  This unwinding is
    * dependent upon catch blocks being implemented with a corresponding call
    * to {@link #honorRetry}.
    * <p>
    * If the current block is not the target, the retry is deferred by
    * marking the target block and then unwinding the stack.  Otherwise, 
    * this method will return naturally.  This method is only appropriate 
    * to be called from within <code>catch</code> blocks since a natural
    * return within a <code>catch</code> block will fall through to allow
    * the retry loop to engage.                
    *           
    * @throws   RetryUnwindException 
    */
   public static void triggerRetry()
   throws RetryUnwindException
   {
      triggerRetry(null);
   }
   
   /**
    * Force a retry of the current block or the scope named by the given
    * label.  If a containing scope is the target of the retry, then
    * a {@link RetryUnwindException} will be thrown to unwind the stack
    * to the given scope.  This unwinding is dependent upon catch blocks 
    * being implemented with a corresponding call to {@link #honorRetry}.
    * <p>
    * If the current block is not the target, the retry is deferred by
    * marking the target block and then unwinding the stack.  Otherwise, 
    * this method will return naturally.  This method is only appropriate 
    * to be called from within <code>catch</code> blocks since a natural
    * return within a <code>catch</code> block will fall through to allow
    * the retry loop to engage.
    *
    * @param    label
    *           The name of the scope to retry. Specify <code>null</code> to
    *           retry the scope of that has a pending rollback OR to retry
    *           the current scope if there is no pending rollback.
    *           
    * @throws   RetryUnwindException 
    */
   public static void triggerRetry(String label)
   throws RetryUnwindException
   {
      triggerRetry(workArea.obtain(), label);
   }
   
   /**
    * Dispatch the given exception to all registered stop condition handlers
    * to see if any requires that the STOP condition be vetoed at the
    * current scope.  If any of the registered handlers indicate that the STOP
    * condition should not be honored at this level, the exception is
    * rethrown, presumably to be honored at a higher scope, or not at all.  In
    * the latter case, the program running in the current context will abort.
    * 
    * @param   exc
    *          The exception to be checked.
    * 
    * @throws  StopConditionException
    *          if the STOP condition is not honored at this scope.
    */
   public static void honorStop(StopConditionException exc)
   throws StopConditionException
   {
      honorStop(workArea.obtain(), exc);
   }
   
   /**
    * Check if there is a pending retry of the current block and if so then
    * enable retry.  This is the "last half" of the processing to unwind the
    * stack up to a containing scope that is the target of a retry.  The
    * "first half" of the unwinding is implemented by throwing a
    * {@link RetryUnwindException}.  This unwinding is dependent upon catch
    * blocks being implemented to call this method.
    * <p>
    * If the current block is not the target for the pending retry, the
    * exception is re-thrown.
    *
    * @param    rue
    *           The exception to honor or ignore.
    *           
    * @throws   RetryUnwindException 
    */
   public static void honorRetry(RetryUnwindException rue)
   throws RetryUnwindException
   {
      honorRetry(workArea.obtain(), rue);
   }
   
   /**
    * Check if the current error must unwind all the way to the caller of
    * the top-level block in this method (if the ignore flag is set).  If
    * this is the case then the error is re-thrown.  Before this occurs, if
    * the current block is the top-level block of this method, then the
    * ignore flag will be cleared.  If the ignore flag is <code>false</code> 
    * then this method returns silently.
    * <p>
    * This code also honors any nested error that was generated while another
    * error was being processed AND while nested error mode was enabled.  Any
    * such stored error will be re-thrown here.
    *
    * @param    err
    *           The exception to honor or ignore.
    *           
    * @throws   ErrorConditionException 
    */
   public static void honorError(ErrorConditionException err)
   throws ErrorConditionException
   {
      honorError(workArea.obtain(), err);
   }
   
   /**
    * Notifies the transaction manager that an unexpected exception or error
    * has occurred and that any transaction must be aborted. This will
    * maintain the state of the system properly, including rolling back any
    * open transaction. Once the state is properly maintained, the triggering
    * <code>Throwable</code> will be re-thrown as a 
    * <code>RuntimeException</code>.
    * <p>
    * If the exception is of type <code>ConditionException</code> or
    * <code>RetryUnwindException</code>, this method will do nothing since
    * these are used for normal flow of control purposes and are used in
    * close coordination with the transaction manager's state.  They need
    * no other special behavior to be safe.  This method is designed to
    * handle exceptions (like <code>NullPointerException</code>) which are
    * not valid in a converted 4GL program.
    *
    * @param    thr
    *           The unexpected exception or error.
    *           
    * @throws   RuntimeException 
    */
   public static void abnormalEnd(Throwable thr)
   throws RuntimeException
   {
      abnormalEnd(thr, true);
   }
   
   /**
    * Notifies the transaction manager that an unexpected exception or error
    * has occurred and that any transaction must be aborted. This will
    * maintain the state of the system properly, including rolling back any
    * open transaction. Once the state is properly maintained, the triggering
    * <code>Throwable</code> will be re-thrown as a 
    * <code>RuntimeException</code>.
    * <p>
    * If the exception is of type <code>ConditionException</code> or
    * <code>RetryUnwindException</code>, this method will do nothing since
    * these are used for normal flow of control purposes and are used in
    * close coordination with the transaction manager's state.  They need
    * no other special behavior to be safe.  This method is designed to
    * handle exceptions (like <code>NullPointerException</code>) which are
    * not valid in a converted 4GL program.
    *
    * @param    thr
    *           The unexpected exception or error.
    * @param    needsNotify
    *           <code>true</code> if condition notification must be executed
    *           (only happens if the <code>thr</code> is a condition).
    *           
    * @throws   RuntimeException 
    */
   public static void abnormalEnd(Throwable thr, boolean needsNotify)
   throws RuntimeException
   {
      abnormalEnd(workArea.obtain(), thr, needsNotify);
   }
   
   /**
    * Reports if the given transaction level represents the case where the
    * block has no effect on transaction processing.
    *
    * @param    lvl
    *           The transaction level to test.
    *
    * @return   <code>true</code> if the level is <code>NO_TRANSACTION</code>.
    */
   public static boolean isNoTransactionLevel(int lvl)
   {
      return (lvl == NO_TRANSACTION);
   }
   
   /**
    * Reports if the given transaction level represents the case where the
    * block has creates a sub-transaction when a transaction is active.
    *
    * @param    lvl
    *           The transaction level to test.
    *
    * @return   <code>true</code> if the level is
    *           <code>SUB_TRANSACTION</code>.
    */
   public static boolean isSubTransactionLevel(int lvl)
   {
      return (lvl == SUB_TRANSACTION);
   }
   
   /**
    * Reports if the given transaction level represents the case where the
    * block has creates a new transaction if a transaction is not active or
    * creates a sub-transaction when a transaction is active.
    *
    * @param    lvl
    *           The transaction level to test.
    *
    * @return   <code>true</code> if the level is <code>TRANSACTION</code>.
    */
   public static boolean isTransactionLevel(int lvl)
   {
      return (lvl == TRANSACTION);
   }
   
   /**
    * Reports the transaction level of the current block.
    *
    * @return   The transaction level.
    */
   public static int currentTransactionLevel()
   {
      BlockDefinition blk = workArea.obtain().blocks.peek();
      return blk.level;
   }
   
   /**
    * Reports if the current scope defines the outermost transaction block
    * (which is the scope at which final commit processing must occur).
    *
    * @return   <code>true</code> if the current scope defines the top-level
    *           transaction.
    */
   public static boolean isFullTransaction()
   {
      return workArea.obtain().isFullTransaction();
   }
   
   /**
    * Reports if there is a current transaction which is active.
    *
    * @return    <code>true</code> if a transaction is active.
    */
   public static boolean isTransaction()
   {
      return workArea.obtain().isTransaction();
   }
   
   /**
    * Obtain the current transaction id for a database (DBTASKID function).
    * <p>
    * The {@code DBTASKID} function is related to _trans VST. However, the relation is not 1:1.
    * If, in a transaction, any record from a database is involved (updated OR {@code FIND},
    * regardless of the lock level - and this includes the {@code _Trans} VST, too) then the task
    * (transaction) id is incremented and the new value is returned by {@code DBTASKID} function.
    * Otherwise (all records of the database are not accessed in any way OR just read access to
    * buffers loaded before the transaction started) the {@code _Trans._Trans-Num} of the database
    * is NOT incremented and {@code DBTASKID} function returns {@code ?} (unknown value).
    * <p>
    * In other words, even if the transaction block is already being executed, {@code DBTASKID}
    * function (so this method) returns unknown value for a database, until at least a field is
    * updated or a buffer is loaded (with {@code FIND} or {@code for}). 
    *  
    * @param   db 
    *          The key name of the database.
    * 
    * @return  The the current transaction id of the database or {@code null} if the database is
    *          unknown / not connected or if the transaction id has not been set yet. 
    */
   public static Integer getTransactionId(String db)
   {
      // no need to check whether there is a transaction; the direct map lookup is much faster 
      DbTransactions dbData;
      
      // this might not be necessary. The [transactionData] is only populated during dbs init 
      synchronized (transactionData)
      {
         dbData = transactionData.get(db);
         if (dbData == null)
         {
            return null; // no known data about this database 
         }
      }
      
      synchronized (dbData)
      {
         SecurityManager sm = SecurityManager.getInstance();
         if (sm == null)
         {
            return null; // a bit strange, should not happen
         }
         
         Integer userId = sm.sessionSm.getSessionId();
         if (userId == null)
         {
            return null; // a bit strange, should not happen
         }
         
         return dbData.transactions.get(userId); // may be null if a transaction is not active 
      }
   }
   
   /**
    * Flushes all known content of {@code _Trans} metadata table to database. This is done before
    * every access to {@code _Trans} table to make sure the fresh data is accessible by the SQL.
    * 
    * @param   p
    *          The persistence to be used.
    */
   public static void flushTransMetaData(Persistence p)
   {
      if (!TransactionTableUpdater.isEnabled())
      {
         return; // the _Trans metadata table is not active
      }
      
      DbTransactions dbData;
      TransactionTableUpdater updater;
      synchronized (transactionData)
      {
         if (updaters.isEmpty())
         {
            return; // quick out
         }
         
         String dbName = p.getDatabase(Persistence.META_CTX).getId();
         updater = updaters.get(dbName);
         if (updater == null)
         {
            return; // the _Trans metadata table is not enabled for this database
         }
         
         dbData = transactionData.get(dbName);
         if (dbData == null || !dbData.isDirty)
         {
            // no known data about this database. We can safely return here, there was no
            // transaction activity for this database. 
            return;
         }
      }
      
      synchronized (dbData)
      {
         updater.flush(p, dbData.transactions);
         
         // mark database as clean (states for all transactions were flushed)
         dbData.isDirty = false;
      }
   }   
   
   /**
    * Reports if there is a current active transaction at the specified block depth.
    *
    * @param     blockDepth
    *            Zero-based block depth starting from the outermost block at which transaction
    *            status is checked.
    *
    * @return    <code>true</code> if a transaction is active at the specified block depth.
    */
   public static boolean isTransactionAt(int blockDepth)
   {
      int trans = workArea.obtain().transLevel;
      return trans != -1 && trans <= blockDepth;
   }
   
   /**
    * Reports if the current block is a top-level block (this corresponds
    * with a method-level block).
    *
    * @return  <code>true</code> if top-level block, else <code>false</code>.
    */
   public static boolean isTopLevelBlock()
   {
      return isTopLevelBlock(workArea.obtain());
   }
   
   /**
    * Reports if the current block is a external block (this corresponds
    * with a method-level block that was derived from a Progress external
    * procedure).  This will be <code>false</code> for blocks derived from
    * internal procedures, functions and triggers.
    *
    * @return   <code>true</code> if it is an external block, else 
    *           <code>false</code>.
    */
   public static boolean isExternalBlock()
   {
      BlockDefinition blk = workArea.obtain().blocks.peek();
      return blk.external;
   }
   
   /**
    * Reports if the current block is a global scoped block. For normal contexts:
    * <code>true</code> if the current block is the only block on the stack of block scopes; for
    * appserver agent contexts: <code>true</code> if the current block is one of two outermost
    * blocks ("appserver-agent" and "startup") on the stack of block scopes.
    *
    * @return   See above.
    */
   public static boolean isGlobalBlock()
   {
      WorkArea wa = workArea.obtain();
      return isGlobalBlock(wa);
   }

   /**
    * Reports if the current block has the ERROR property.
    *
    * @return   <code>true</code> if the current block has the 
    *           <code>PROP_ERROR</code> bit set in its properties bitmap.
    */
   public static boolean hasErrorProp()
   {
      return hasProperty(workArea.obtain(), PROP_ERROR);
   }
   
   /**
    * Reports if the current block has the ENDKEY property.
    *
    * @return   <code>true</code> if the current block has the 
    *           <code>PROP_ENDKEY</code> bit set in its properties bitmap.
    */
   public static boolean hasEndkeyProp()
   {
      return hasProperty(workArea.obtain(), PROP_ENDKEY);
   }
   
   /**
    * Reports if the current block has the STOP property.
    *
    * @return   <code>true</code> if the current block has the 
    *           <code>PROP_STOP</code> bit set in its properties bitmap.
    */
   public static boolean hasStopProp()
   {
      return hasProperty(workArea.obtain(), PROP_STOP);
   }
   
   /**
    * Reports if the current block has the QUIT property.
    *
    * @return   <code>true</code> if the current block has the 
    *           <code>PROP_QUIT</code> bit set in its properties bitmap.
    */
   public static boolean hasQuitProp()
   {
      return hasProperty(workArea.obtain(), PROP_QUIT);
   }
   
   /**
    * During global finish processing, this reports if the exit was caused
    * by a <code>QuitConditionException</code>.
    *
    * @return   <code>true</code> if this exit is due to a quit condition.
    */
   public static boolean isProcessingQuit()
   {
      return workArea.obtain().processingQuit;
   }
   
   /**
    * During global finish processing, this sets the flag for an exit that
    * was caused by a <code>QuitConditionException</code>.
    *
    * @param    quit
    *           <code>true</code> if this exit is due to a quit condition.
    */
   public static void setProcessingQuit(boolean quit)
   {
      workArea.obtain().processingQuit = quit;
   }
   
   /**
    * Report if this context should be treated as headless. A headless
    * session is one that has no user interaction.
    *
    * @return   <code>true</code> if this session is headless.
    */
   public static boolean isHeadless()
   {
      return workArea.obtain().headless;
   }

   /**
    * Increases the number (by 1) of interactive language statements
    * (statements which took user input except for PAUSE) that have executed
    * in the current block since the most recent start scope, iterate or
    * retry.
    */
   public static void incrementInteractions()
   {
      BlockDefinition blk = workArea.obtain().blocks.peek();
      blk.interactions++;
   }
   
   /**
    * Resets the number of interactive language statements (statements which
    * took user input except for PAUSE) that have executed in the current
    * block to 0.
    */
   public static void resetInteractions()
   {
      BlockDefinition blk = workArea.obtain().blocks.peek();
      blk.interactions = 0;
   }
   
   /**
    * Gets the number of interactive language statements (statements which
    * took user input except for PAUSE) that have executed in the current
    * block  since the most recent start scope, iterate or retry.
    *
    * @return   The number of interactions in the current block.
    */
   public static int getInteractions()
   {
      BlockDefinition blk = workArea.obtain().blocks.peek();
      return blk.interactions;
   }
   
   /**
    * Get the rollup value for the current block. If <code>true</code>,
    * then any contained blocks that exit will cause the current (containing)
    * block's interactions counter to be increased by the contained block's
    * interactions counter. Otherwise, no aggregation will occur.
    *
    * @return   The current block's rollup value.
    */
   public static boolean isRollup()
   {
      BlockDefinition blk = workArea.obtain().blocks.peek();
      return blk.rollup;
   }
   
   /**
    * Get the rollup value for the enclosing block. If <code>true</code>,
    * then the current block's exit will cause the enclosing (containing)
    * block's interactions counter to be increased by the current block's
    * interactions counter. Otherwise, no aggregation will occur.
    *
    * @return   The current block's rollup value.
    */
   public static boolean isEnclosingRollup()
   {
      return isEnclosingRollup(workArea.obtain());
   }
   
   /**
    * Set the new rollup value for the current block. If <code>true</code>,
    * then any contained blocks that exit will cause the current (containing)
    * block's interactions counter to be increased by the contained block's
    * interactions counter. Otherwise, no aggregation will occur.
    *
    * @param    rollup
    *           The new rollup value.
    */
   public static void setRollup(boolean rollup)
   {
      BlockDefinition blk = workArea.obtain().blocks.peek();
      blk.rollup = rollup;
   }
   
   /**
    * Notify all registered {@link BatchListener} objects that a batch is 
    * starting.
    */
   public static void batchStart()
   {
      processBatchNotifications(workArea.obtain(), true);
   }
      
   /**
    * Notify all registered {@link BatchListener} objects that a batch is 
    * stopping.
    */
   public static void batchStop()
   {
      processBatchNotifications(workArea.obtain(), false);
   }
   
   /**
    * Tests the interrupted status of the current thread using
    * <code>Thread.interrupted</code>, if <code>true</code> then a
    * {@link StopConditionException} will be thrown. The interrupted status
    * will be <code>true</code> if a call to <code>Thread.interrupt</code>
    * was previously made.
    * <p>
    * The thread's interrupted status is cleared by this method before the
    * <code>StopConditionException</code> is thrown.
    *
    * @throws   StopConditionException
    *           If the current thread's was previously interrupted.
    * @throws   SilentUnwindException
    *           If the connection ended prematurely end the current thread 
    *           needs to stop.
    */
   public static void honorStopCondition()
   throws StopConditionException,
          SilentUnwindException
   {
      workArea.obtain().honorStopCondition();
   }
   
   /**
    * Method called when the processing needs to be terminated. This will set a flag which will
    * indicate that the processing should be ended by raising a {@link
    * LeaveUnwindException error}, on the next {@link #honorStopCondition()} call.
    */
   public static void abortProcessing()
   {
      if (workArea.get(false) == null)
      {
         // if the context does not exist, do not create it at this time
         return;
      }
      
      WorkArea wa = workArea.obtain();
      wa.abortProcessing = true;
   }
   
   /**
    * Enable/disable nested error mode. This mode allows for storing an error
    * which is generated during the processing of a different error, so that
    * during {@link #honorError} this can be re-thrown.  This duplicates some
    * obscure behavior seen in Progress.
    *
    * @param    mode
    *           <code>true</code> to enable, <code>false</code> to disable.
    */
   public static void setNestedMode(boolean mode)
   {
      workArea.obtain().nested = mode;
   }
   
   /**
    * Reports on the enable/disable state of nested error mode. This mode
    * allows for storing an error which is generated during the processing of 
    * a different error, so that during {@link #honorError} this can be
    * re-thrown.
    *
    * @return   <code>true</code> if nested error mode is enabled.
    */
   public static boolean isNestedMode()
   {
      return workArea.obtain().nested;
   }
   
   /**
    * Returns the current block type.
    *
    * @return   The type of the current block.
    */
   public static BlockType getBlockType()
   {
      BlockDefinition blk = workArea.obtain().blocks.peek();
      
      return blk.type;
   }
   
   /**
    * Returns the index of the full transaction block, or -1 if none exists.
    *
    * @return   The index of the full transaction block, or -1 if none exists.
    */
   public static int getFullTransactionBlock()
   {
      return getFullTransactionBlock(workArea.obtain());
   }
   
   /**
    * Returns the current nesting level.
    *
    * @return   the current nesting level.
    */
   public static int getNestingLevel()
   {
      return workArea.obtain().blocks.size();
   }

   /**
    * Check if this block is the block associated with the root external program,
    * in the 4GL legacy stack.
    * 
    * @return   See above.
    */
   public static boolean isRootNestingLevel()
   {
      int nestingLevel = getNestingLevel();
      WorkArea wa = workArea.obtain();
      return nestingLevel == (wa.isRemote() ? 3 : 2);
   }
   
   /**
    * Check if this block is the block associated with the root external program,
    * in the 4GL legacy stack using the nearest external block.
    * 
    * @return   See above.
    */
   public static boolean isRootExternal()
   {
      int nestingLevel = findNearestExternal();
      WorkArea wa = workArea.obtain();
      // in case of appserver calls, the root can be an internal entry or an external program.
      // in internal entry case, 'nestingLevel' will be resolved to 2.
      return nestingLevel <= (wa.isRemote() ? 3 : 2);
   }   
   
   /**
    * Store the given exception for re-throwing during {@link #honorError} if 
    * nested error mode is enabled.  Note that only 1 error can ever be nested
    * and the last one set via this method will be the one that is honored.
    *
    * @param    err
    *           The error to store.
    */
   public static void saveNestedError(ConditionException err)
   {
      WorkArea wa = workArea.obtain();
      
      if (wa.nested)
      {
         wa.nestedError = err;
      }
   }
   
   /** 
    * Notifies this class that an interactive pause has occurred. This may
    * affect the processing of infinite loop protection depending on the
    * type of block being processed.  This state is set for the remainder of 
    * the current iteration of the innermost block.
    */
   public static void hadPause()
   {
      // peek at the current block def on the top of the stack
      BlockDefinition blk = workArea.obtain().blocks.peek();
      blk.hadPause = true;
   }
   
   /**
    * Search the stack of scopes and return the level of the first external
    * block found.
    *
    * @return   The stack level of the nearest enclosing external block or
    *           -1 if there is no block marked as an external block. Level
    *           number is 1-based starting from the outermost scope.
    */
   public static int findNearestExternal()
   {
      return findNearestExternal(workArea.obtain());
   }
   
   /**
    * This method is called before the initialization of a FOR or FOR EACH 
    * block. It will clear the {@link WorkArea#offEndQueries} set and will
    * set the {@link WorkArea#offEndRegistration} flag to ON. While this flag
    * is ON, all created queries will be collected in the 
    * {@link WorkArea#offEndQueries} set.
    * <p>
    * After the block initialization is finished, the 
    * {@link #stopOffEndRegistration()} will be called.
    */
   public static void startOffEndRegistration()
   {
      WorkArea wa = workArea.obtain();

      wa.offEndRegistration = true;
      wa.offEndQueries.push(new HashSet<>());
   }

   /**
    * Pause off-end queries registration started by {@link #startOffEndRegistration()}.
    */
   public static void pauseOffEndRegistration()
   {
      WorkArea wa = workArea.obtain();
      wa.savedOffEndRegistration = wa.offEndRegistration;
      wa.offEndRegistration = false;
   }

   /**
    * Resume off-end queries registration started by {@link #startOffEndRegistration()} and paused by
    * {@link #pauseOffEndRegistration()}.
    */
   public static void resumeOffEndRegistration()
   {
      WorkArea wa = workArea.obtain();
      wa.offEndRegistration = wa.savedOffEndRegistration;
      wa.savedOffEndRegistration = false;
   }

   /**
    * This method is called after the initialization of a FOR or FOR EACH
    * block is finished. It will save all the {@link QueryOffEndListener}s
    * associated with the registered {@link WorkArea#offEndQueries queries}.
    */
   public static void stopOffEndRegistration()
   {
      stopOffEndRegistration(workArea.obtain());
   }
   
   /**
    * Register the passed query for query off-end events. The passed queries
    * are all the queries created in the init method for a FOR or FOR EACH
    * block.
    * <p>
    * These queries will be able to end the loop when the last record is 
    * reached, by overriding any user-defined END-KEY action. The action 
    * wich is executed is the default UNDO, LEAVE action for END-KEY.
    * 
    * @param  query
    *         The query which needs to be registered.
    */
   public static void registerOffEndQuery(P2JQuery query)
   {
      registerOffEndQuery(workArea.obtain(), query);
   }
   
   /**
    * Check if the given listener is registered for query off-end events and
    * can override the default END-KEY action (which is UNDO, LEAVE), when
    * the current block is FOR or FOR EACH.
    * 
    * @param   listener
    *          A listener to be checked if is registered for off-end events.
    * 
    * @return  <code>true</code> if the listener is registered for query 
    *          off-end event.
    */
   public static boolean isOffEndListener(QueryOffEndListener listener)
   {
      return isOffEndListener(workArea.obtain(), listener);
   }
   
   /**
    * Iterate all registered off-end listeners and call the 
    * {@link QueryOffEndListener#initialize() initialize} or 
    * {@link QueryOffEndListener#finish() finish} method, depending on the 
    * flag.
    * 
    * @param   init
    *          <code>true</code> if the listeners are initialized; else, the
    *          {@link QueryOffEndListener#finish()} method is called. 
    */
   public static void processOffEndListeners(boolean init)
   {
      processOffEndListeners(workArea.obtain(), init);
   }
   
   /**
    * Get the topmost set of off-end {@link QueryOffEndListener listeners}.
    * 
    * @param   clear
    *          <code>true</code> if the top-most off-end listeners set
    *          should be cleared.
    */
   public static Set<QueryOffEndListener> getOffEndListeners(boolean clear)
   {
      return getOffEndListeners(workArea.obtain(), clear);
   }
   
   /**
    * Register the single output parameter assigner for the current block.
    * This object will be deregistered by the {@link BlockManager} when it is
    * time to process assign-backs from function/procedure output parameters
    * to the database record fields with which they are associated.
    * 
    * @param   opa
    *          An output parameter assigner.
    * 
    * @throws  IllegalStateException
    *          if a second output parameter assigner is registered for a block
    *          before the current one is deregistered.  There can be only one.
    */
   public static void registerOutputParameterAssigner(OutputParameterAssigner opa)
   {
      registerOutputParameterAssigner(workArea.obtain(), opa);
   }

   /**
    * Defer handling of an exception thrown by a commit/rollback notification
    * callback on a registered {@link Commitable}, so that iteration or pop
    * scope processing can be continued without interruption.  This ensures
    * that all <code>Commitable</code>s registered after the errant one still
    * receive their commit/rollback notifications, and that (in the case of
    * popping a scope), registered {@link Scopeable}s still receive their
    * scope finished notifications.
    * <p>
    * If no error is already pending, <code>exc</code> is stored, to be thrown
    * upon conclusion of processing the current block scope or iteration.  If
    * an error is already pending, <code>exc</code> is discarded.  In either
    * case, <code>exc</code> is logged if error-level logging is enabled.
    *
    * @param   action
    *          A brief description of the action occurring at the time the
    *          error was encountered, such as "commit" or "master rollback".
    * @param   thr
    *          The error caught during processing of a commit or rollback
    *          notification.
    *
    * @see     WorkArea#handleDeferredError
    */
   public static void deferError(String action, Throwable thr)
   {
      deferError(action, thr, false);
   }

   /**
    * Defer handling of an exception thrown by a commit/rollback notification
    * callback on a registered {@link Commitable}, so that iteration or pop
    * scope processing can be continued without interruption.  This ensures
    * that all <code>Commitable</code>s registered after the errant one still
    * receive their commit/rollback notifications, and that (in the case of
    * popping a scope), registered {@link Scopeable}s still receive their
    * scope finished notifications.
    * <p>
    * If no error is already pending, <code>exc</code> is stored, to be thrown
    * upon conclusion of processing the current block scope or iteration.  If
    * an error is already pending, <code>exc</code> is discarded.  In either
    * case, <code>exc</code> is logged if error-level logging is enabled.
    *
    * @param   action
    *          A brief description of the action occurring at the time the
    *          error was encountered, such as "commit" or "master rollback".
    * @param   thr
    *          The error caught during processing of a commit or rollback
    *          notification.
    * @param   silent
    *          If <code>true</code> then exception is not logged.
    *
    * @see     WorkArea#handleDeferredError
    */
   public static void deferError(String action, Throwable thr, boolean silent)
   {
      deferError(workArea.obtain(), action, thr, silent);
   }

   /**
    * Search the stack of scopes and return the level of the first top level
    * block found.
    *
    * @return   The stack level of the nearest enclosing top level block or
    *           -1 if there is no block marked as a top level block.
    */
   public static int findNearestTopLevel()
   {
      return findNearestTopLevel(workArea.obtain());
   }
   
   /**
    * Search the stack of scopes and return the type of first top level block found.
    *
    * @return   The nearest enclosing top level block or {@link BlockType#UNKNOWN} if
    *           there is no block marked as a top level block.
    */
   public static BlockType getNearestTopLevelType()
   {
      return getNearestTopLevelType(workArea.obtain());
   }
   
   /**
    * Worker for reporting if this context should be treated as headless. A headless session is
    * one that has no user interaction.
    *
    * <p>
    * To determine if the current context is headless the method uses the following algorithm:
    * <ul>
    *    <li>check the {@link SecurityManager#isServerAccount()}. Server sessions are implicit
    *          headless;
    *    <li>check the context token named "headless". If it exists, its value tells whether the
    *          context is for a headless session or not; otherwise the directory is checked;
    *    <li>check the directory. If there is a {@code boolean} value set to {@code true} for a
    *          given context's account in one of the following paths, then the context is
    *          headless. This allows the user interface client to be an optional part of any
    *          account. In other words, process accounts can be run in a "headless" mode with no
    *          requirement for any user interface. These are the paths (in order in which they
    *          are searched):
    *          <ol>
    *             <li>{@code /server/&lt;serverID&gt;/runtime/&lt;account_or_group&gt;/headless};
    *             <li>{@code /server/&lt;serverID&gt;/runtime/default/headless};
    *             <li>{@code /server/default/runtime/&lt;account_or_group&gt;/headless};
    *             <li>{@code /server/default/runtime/default/headless};
    *          </ol>
    *    <li>otherwise the context is not headless.
    * </ul>
    *
    * @return   <code>true</code> if this session is headless.
    */
   public static boolean _isHeadless()
   {
      SecurityManager sm = SecurityManager.getInstance();
      if (sm != null && sm.isServerAccount())
      {
         // in server context we are also in headless mode
         return true;
      }
      
      SessionManager sessMgr = SessionManager.get();
      
      if (sessMgr != null && sessMgr.isVirtualSession())
      {
         return true;
      }
      
      Boolean headless = (sm != null) ? (Boolean) sm.contextSm.getToken(ContextKey.HEADLESS_KEY) : null;
      
      // second: check directly in the context
      if (headless != null && headless)
      {
         return true;
      }
      // third: if headless not set in context, check in the directory
      else if (headless == null && Utils.getDirectoryNodeBoolean(null, "headless", false))
      {
         return true;
      }
      
      return false;
   }
   
   /**
    * Register the specified targets as undoables.  This will mark the object as undoable and save
    * the current tx level, regardless if a tx is active or not.
    * 
    * @param    undoTargets
    *           List of object references to be registered.  This cannot be
    *           <code>null</code>.
    */
   public static void registerCurrent(LazyUndoable... undoTargets)
   {
      registerCurrent(workArea.obtain(), undoTargets);
   }
   
   /**
    * Worker that notifies all objects in the commit list that they must
    * commit (persist any state).
    * <p>
    * On the last iteration of a loop or block which ends in success (in
    * which the flow of control reaches the natural end of the block), both
    * will be called in succession.  However, other forms of exit which are
    * still considered successful (i.e. return and break which are not the
    * result of condition processing) will only execute <code>popScope</code>
    * and <code>iterate</code> will be bypassed.
    * <p>
    * It is important to note that the list of commitables in blk is
    * iterated in reverse order from that in which they are registered with
    * the <code>TransactionManager</code>.  This is done to ensure that
    * database transactions (which are registered before their corresponding
    * buffers) are always committed after all buffers are committed.
    *
    * @throws   ErrorConditionException
    *           If validation fails.
    */
   static void processValidate()
   throws ErrorConditionException
   {
      workArea.obtain().processValidate();
   }

   /**
    * Get the BlockDefinition instance of the nearest block of one of the
    * following types:
    * <ul>
    *    <li>{@link BlockType#EXTERNAL_PROC external procedure}</li>
    *    <li>{@link BlockType#INTERNAL_PROC internal procedure}</li>
    *    <li>{@link BlockType#FUNCTION user-defined function}</li>
    *    <li>{@link BlockType#DATABASETRIGGER trigger}</li>
    *    <li>{@link BlockType#TRIGGER trigger}</li>
    *    <li>{@link BlockType#EDITING editing}</li>
    * </ul> 
    * 
    * @return   See above.
    */
   static BlockDefinition nearestExecutingBlock()
   {
      return nearestExecutingBlock(workArea.obtain());
   }

   /**
    * Get the executing block at the specified level.
    * 
    * @param    levels
    *           Zero-based depth of the target block, starting from the innermost block.
    * 
    * @return   The found block, or <code>null</code> if the index is outside of the block depth.
    */
   static BlockDefinition getBlock(int levels)
   {
      return getBlock(null, levels);
   }
   
   /**
    * Get the top {@link BlockDefinition}.
    * 
    * @return   See above.
    */
   static BlockDefinition getBlock()
   {
      return workArea.obtain().blocks.peek();
   }

   /**
    * Get the executing block at the specified level.
    * 
    * @param    wa
    *           The {@link WorkArea} instance.
    * @param    levels
    *           Zero-based depth of the target block, starting from the innermost block.
    * 
    * @return   The found block, or <code>null</code> if the index is outside of the block depth.
    */
   static BlockDefinition getBlock(WorkArea wa, int levels)
   {
      if (wa == null)
      {
         wa = workArea.obtain();
      }
      
      if (levels >= wa.blocks.size())
      {
         return null;
      }
      
      return wa.blocks.get(wa.blocks.size() - 1 - levels);
   }
   
   /**
    * Get the executing block at the specified level.
    * 
    * @param    blockDepth
    *           Zero-based depth of the target block, starting from the outermost block; 0 is the
    *           global scope.
    * 
    * @return   The found block, or <code>null</code> if the index is outside of the block depth.
    */
   static BlockDefinition getBlockAtDepth(int blockDepth)
   {
      return getBlockAtDepth(null, blockDepth);
   }

   /**
    * Get the executing block at the specified level.
    * 
    * @param    wa
    *           The {@link WorkArea} instance.
    * @param    blockDepth
    *           Zero-based depth of the target block, starting from the outermost block; 0 is the
    *           global scope.
    * 
    * @return   The found block, or <code>null</code> if the index is outside of the block depth.
    */
   static BlockDefinition getBlockAtDepth(WorkArea wa, int blockDepth)
   {
      if (wa == null)
      {
         wa = workArea.obtain();
      }
      
      if (blockDepth == 0)
      {
         return wa.globalBlock;
      }
      
      if (blockDepth >= wa.blocks.size())
      {
         return null;
      }
      
      return wa.blocks.get(blockDepth);
   }
   
   /*
    * Deregister the output parameter assigner for the current block, if any.
    * It is expected that only the {@link BlockManager} will invoke this
    * method.
    * 
    * @return  Output parameter assigner for this block, or <code>null</code>
    *          if none was registered.
    */
   public static OutputParameterAssigner deregisterOutputParameterAssigner()
   {
      return deregisterOutputParameterAssigner(workArea.obtain());
   }
   
   /**
    * Adds the given object into the list of objects which require undo
    * notifications for the next block that is opened.
    * <p>
    * This form of registration is useful for objects whose natural lifetime
    * is scoped to the top-level block.  In particular, it is necessary for
    * objects that are constructed during a class' constructor/initializer
    * (before the external procedure block has opened).  Such objects cannot
    * simply register since their construction is not actually enclosed by the
    * block.
    * <p>
    * When the deferred registration occurs, it will NOT be global and will
    * NOT be added to registered Commitables.
    * <p>
    * This notification will occur whether or not a transaction is active.
    * <p>
    * These registrations naturally are removed when the associated scope
    * is removed.
    * <p>
    * The given object will only be added if it is not already in the list.
    *
    * @param    target
    *           The object references to be registered.  This cannot be
    *           <code>null</code>.
    */
   static void registerNext(LazyUndoable... target)
   {
      registerAt(ScopeLevel.NEXT, target);
   }
   
   /**
    * Adds the given object into the list of objects which require undo
    * notifications for the next block that is opened.
    * <p>
    * This form of registration is useful for objects whose natural lifetime
    * is scoped to the top-level block.  In particular, it is necessary for
    * objects that are constructed during a class' constructor/initializer
    * (before the external procedure block has opened).  Such objects cannot
    * simply register since their construction is not actually enclosed by the
    * block.
    * <p>
    * When the deferred registration occurs, it will NOT be global and will
    * NOT be added to registered Commitables.
    * <p>
    * This notification will occur whether or not a transaction is active.
    * <p>
    * These registrations naturally are removed when the associated scope
    * is removed.
    * <p>
    * The given object will only be added if it is not already in the list.
    *
    * @param    scope
    *           The scope where to register the undoable targets.
    * @param    target
    *           The object references to be registered.  This cannot be
    *           <code>null</code>.
    */
   static void registerAt(ScopeLevel scope, LazyUndoable... target)
   {
      registerAt(workArea.obtain(), scope, target);
   }
   
   /**
    * Adds the object into a scoped dictionary of objects requiring UNDO support. This registry
    * is used to backup the objects at the beginning of transactions or sub-transactions.
    * <p>
    * These registrations naturally are removed when the associated scope is removed. However,
    * the global scope is long-lived. Special care is necessary to prevent the same instance of
    * global shared variables being added multiple times.
    *
    * @param    transLevel
    *           The transaction level to set for this undoable.
    * @param    persistent
    *           Flag indicating if the undoable is defined in a persistent procedure
    * @param    undoTargets
    *           The object references to be rolled back on failure.  This
    *           cannot be <code>null</code>.
    */
   static void registerAt(int transLevel, boolean persistent, LazyUndoable... undoTargets)
   {
      registerAt(workArea.obtain(), transLevel, persistent, undoTargets);
   }
   
   /**
    * Deregister the specified undoables from all tx blocks.
    * 
    * @param    wa
    *           The {@link WorkArea} instance; if <code>null</code>, it will be resolved.
    * @param    undoables
    *           The undoables to be deregistered.
    */
   static void deregister(WorkArea wa, IdentityHashMap<LazyUndoable, Object> undoables)
   {
      if (wa == null)
      {
         wa = workArea.obtain();
      }
      
      if (wa.transLevel == -1)
      {
         return;
      }

      Stack<BlockDefinition> blocks = wa.blocks;
      for (int i = 0; i < blocks.size(); i++)
      {
         BlockDefinition block = blocks.get(i);
         if (block.undoables != null)
         {
            Iterator<UndoablePair> iter = block.undoables.iterator();
            while (iter.hasNext())
            {
               UndoablePair pair = iter.next();
               
               if (undoables.containsKey(pair.src))
               {
                  iter.remove();
               }
            }
         }
         
         if (block.full)
         {
            break;
         }
      }
   }

   /**
    * Get the current transaction depth.
    * 
    * @return   The {@link WorkArea#txNestingLevel} field.
    */
   static int getTransDepth()
   {
      return workArea.obtain().txNestingLevel;
   }
   
   /**
    * Enable tracking of all the undaobles defined in the next instantiating external program.
    */
   static void trackUndoables()
   {
      WorkArea wa = workArea.obtain();

      wa.trackUndoables = true;
      wa.trackedUndoables = new IdentityHashMap<>();
   }
  
   /**
    * Untrack (by removing from all the tx blocks) all the undoables defined and registered for
    * the specified external program.
    * 
    * @param    referent
    *           The external program instance.
    */
   static void untrackUndoables(Object referent)
   {
      untrackUndoables(workArea.obtain(), referent);
   }
   
   /**
    * Rollback the transaction, at the {@link #APPSERVER_ROOT_BLOCK_INDEX root FWD block}.
    * 
    * @param    chained
    *           Flag indicating if a new transaction will be started.
    */
   static void rollbackTx(boolean chained)
   {
      rollbackTx(workArea.obtain(), chained);
   }
   
   /**
    * Commit the transaction, at the {@link #APPSERVER_ROOT_BLOCK_INDEX root FWD block}.
    * 
    * @param    chained
    *           Flag indicating if a new transaction will be started.
    */
   static void commitTx(boolean chained)
   {
      commitTx(workArea.obtain(), chained);
   }
   
   /**
    * Begin an explicit transaction, at the {@link #APPSERVER_ROOT_BLOCK_INDEX root FWD block}.
    * <p>
    * This explicitly marks this tx as being open by the 
    * {@link TransactionResource#getTxInitProcedure() TRANS-INIT-PROCEDURE}.
    */
   static void beginTx()
   {
      beginTx(null, null);
   }
   
   /**
    * End an explicit transaction, at the {@link #APPSERVER_ROOT_BLOCK_INDEX root FWD block}.
    */
   static void endTx()
   {
      endTx(null, null);
   }
   
   /**
    * Logic required when the tx initiated by TRANSACTION-MODE AUTO gets committed or rolled back.
    */
   static void notifyMasterFinish()
   {
      WorkArea wa = workArea.get();
      
      wa.notifyMasterFinish(false);
      wa.autoRemoveMasterCommit();
      wa.autoRemoveMasterFinish(true);
   }
   
   /**
    * Remove the specified target from the list of finalizables.
    * 
    * @param    finishers
    *           The list of finalizables.
    * @param    target
    *           The target to remove.
    *           
    * @return   <code>true</code> if the target was removed.
    */
   private static boolean removeFinalizable(Set<Finalizable>[] finishers, Finalizable target)
   {
      if (finishers == null)
      {
         return false;
      }
      
      Set<Finalizable> l = finishers[target.weight().getIndex()];

      return l != null && l.remove(target);
   }
   
   /**
    * Add the given target to the list of finalizables.
    * 
    * @param    block
    *           The target block.
    * @param    target
    *           The target to add.
    */
   private static void addFinalizable(BlockDefinition block, Finalizable target)
   {
      int idx = target.weight().getIndex();
      Set<Finalizable> finishers = block.finalizables[idx];
      if (finishers == null)
      {
         finishers = Collections.newSetFromMap(new IdentityHashMap<>(8));
         block.finalizables[idx] = finishers;
      }

      // add the object to the list of finalizable targets if it isn't
      // already in the list (avoids being called twice for the same event)
      if (finishers.add(target) && target instanceof ConditionListener)
      {
         if (block.conditionListeners == null)
         {
            block.conditionListeners = new ArrayList<>(2);
         }
         
         block.conditionListeners.add((ConditionListener) target);
      }
   }
   
   /**
    * Register StopAfterTimer to a ContextLocal WorkArea.
    * 
    * @param    wa
    *           WorkArea
    * @param    stopAfterTimer
    *           StopAfterTimer to register.
    */
   private static void registerStopAfterTimer(WorkArea wa, StopAfterTimer stopAfterTimer)
   {
      wa.stopAfterTimer = stopAfterTimer;
   }
   
   /**
    * Adds the given object into the list of objects which require commit notification for the
    * block at the specified depth  This form of registration is useful for objects whose natural
    * lifetime are scoped to a different block than the block in which they must be committed.
    * For example, if a global or shared resource needs to be committed in a specific scope,
    * then this can be used in cooperation with the normal {@link #registerCurrent} processing to 
    * control the commit points.
    * <p>
    * This notification will ONLY occur when there is an active transaction.
    * <p>
    * Notifications occur in LIFO order;  thus, commitables which depend upon
    * others being committed first must be registered before the objects upon
    * which they are dependent.
    * <p>
    * These registrations naturally are removed when the associated scope
    * is removed.
    *
    * @param    wa
    *           Context local work area.  Must not be {@code null}.
    * @param    blockDepth
    *           Zero-based depth of the target block, starting from the outermost block.
    * @param    target
    *           The object reference to be committed on success.  This cannot be
    *           <code>null</code>.
    */
   private static void registerCommitAt(WorkArea wa, int blockDepth, Commitable target)
   {
      BlockDefinition blk = wa.blocks.get(blockDepth);

      // prevent ConcurrentModificationException
      if (blk.commitables != null && wa.nowIterating == blk.commitables)
      {
         if (LOG.isLoggable(Level.WARNING))
         {
            log(Level.WARNING,
                "registerCommit",
                "Illegal attempt to register Commitable",
                new Throwable());
         }
         
         return;
      }
      
      if (blk.commitables == null)
      {
         blk.commitables = new ArrayList<>();
      }
      
      // add the object to the list of commit targets
      blk.commitables.add(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    wa
    *           The {@link WorkArea} instance.
    * @param    target
    *           The scopeable instance.
    */
   private static void registerBlockScopeable(WorkArea wa, Scopeable target)
   {
      BlockDefinition block = wa.blocks.peek();
      registerScopeable(wa, 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)
   {
      if (wa.nowIterating != null && wa.nowIterating == wa.blocks.peek().scopeList)
      {
         if (LOG.isLoggable(Level.WARNING))
         {
            log(Level.WARNING,
                "registerPendingScopeable",
                "Illegal attempt to register Scopeable",
                new Throwable());
         }
         
         return;
      }

      if (wa.pendingScopeables == null)
      {
         wa.pendingScopeables = new Scopeable[ScopeId.values().length];
      }
      wa.pendingScopeables[target.getScopeId().ordinal()] = target;
   }

   /**
    * Registers the instance for scope notifications at the current block.
    * 
    * @param    wa
    *           The {@link WorkArea} instance.
    * @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(WorkArea wa, BlockDefinition block, Scopeable target, boolean start)
   {
      int id = target.getScopeId().ordinal();

      if (wa.nowIterating != null && wa.nowIterating == block.scopeList && block.scopeList[id] == null)
      {
         if (LOG.isLoggable(Level.WARNING))
         {
            log(Level.WARNING,
                "registerScopeable",
                "Illegal attempt to register Scopeable",
                new Throwable());
         }
         
         return;
      }
      
      if (block.scopeList == null)
      {
         block.scopeList = new Scopeable[ScopeId.values().length];
      }
      
      if (block.scopeList[id] == null)
      {
         block.scopeList[id] = target;
         if (start)
         {
            // process the scope start
            target.scopeStart(block);
         }
      }
   }
   
   /**
    * Remove the given finalizable from the global list of finalizables.
    * 
    * @param    wa
    *           Context local work area.  Must not be {@code null}.
    * @param    target
    *           Finalizable to be removed from the global list.
    */
   private static void deregisterGlobalFinalizable(WorkArea wa, Finalizable target)
   {
      // prevent ConcurrentModificationException
      if (wa.nowIterating == wa.globalBlock.finalizables)
      {
         if (LOG.isLoggable(Level.WARNING))
         {
            log(Level.WARNING,
                "deregisterGlobalFinalizable",
                "Illegal attempt to deregister Finalizable",
                new Throwable());
         }
         
         return;
      }
      
      removeFinalizable(wa.globalBlock.finalizables, target);
   }

   /**
    * Adds the given object into the list of objects for the specified block
    * which require iterate/retry/finish notification.  This form of
    * registration is useful for objects whose natural lifetime is scoped to
    * a block and which need a hook for cleaning up resources or handling
    * other block termination processing.
    * <p>
    * This notification will occur whether or not a transaction is active.
    * <p>
    * These registrations naturally are removed when the associated scope
    * is removed (after the {@link Finalizable#finished} method is called)
    * in the case of a non-global scope OR at the moment that the last
    * scope has been removed in the case of the global scope.
    * <p>
    * The given object will only be added if it is not already in the list.
    * <p>
    * Finalizables for a scope are processed in the same order as they were
    * registered.
    *
    * @param    wa
    *           Context local work area.  Must not be {@code null}.
    * @param    blockDepth
    *           zero-based depth of target block, starting from the outermost
    *           block. <code>0</code> indicates the global block.
    * @param    target
    *           The object reference to be notified on any exit from the
    *           block specified.  This cannot be <code>null</code>.
    */
   private static void registerFinalizableAt(WorkArea wa, int blockDepth, Finalizable target)
   {
      BlockDefinition targetBlock;
      
      if (blockDepth == 0)
      {
         // 0 indicates globalBlock rather than block with the index 0 (startup block).
         targetBlock = wa.globalBlock;
      }
      else
      {
         BlockDefinition blk = wa.blocks.get(blockDepth);
         targetBlock = blk;
      }
      
      // prevent ConcurrentModificationException
      if (wa.nowIterating == targetBlock.finalizables)
      {
         if (LOG.isLoggable(Level.WARNING))
         {
            log(Level.WARNING,
                "registerFinalizable",
                "Illegal attempt to register Finalizable",
                new Throwable());
         }
         
         return;
      }
      
      addFinalizable(targetBlock, target);
   }
   
   /**
    * Adds the given object into the list of objects which require
    * iterate/retry/finish notifications for the next block that is opened
    * which is a top-level block that corresponds to a Progress external 
    * procedure.
    * <p>
    * This form of registration is useful for objects whose natural lifetime
    * is scoped to the top-level block and which need a hook for cleaning up
    * resources or handling other block termination processing.  In 
    * particular, it is necessary for objects that are constructed during 
    * a class' constructor/initializer (before the external procedure block
    * has opened).  Such objects cannot simply register since their 
    * construction is not actually enclosed by the block.
    * <p>
    * This notification will occur whether or not a transaction is active.
    * <p>
    * These registrations naturally are removed when the associated scope
    * is removed (after the {@link Finalizable#finished} method is called).
    * <p>
    * The given object will only be added if it is not already in the list.
    *
    * @param    wa
    *           Context local work area.  Must not be {@code null}.
    * @param    target
    *           The object reference to be notified on any exit from the
    *           top-level external block.  This cannot be <code>null</code>.
    *
    * @throws   IllegalStateException
    *           if the top-level block cannot be identified.
    */
   private static void registerNextExternal(WorkArea wa, Finalizable target)
   {
      if (wa.deferredExtFinalizables == null)
      {
         wa.deferredExtFinalizables = new ArrayList<>();
      }
      
      // prevent ConcurrentModificationException
      if (wa.nowIterating == wa.deferredExtFinalizables)
      {
         if (LOG.isLoggable(Level.WARNING))
         {
            log(Level.WARNING,
                "registerNextExternal",
                "Illegal attempt to register Finalizable",
                new Throwable());
         }
         
         return;
      }
      
      // add the object to the list of finalizable targets if it isn't
      // already in the list (avoids being called twice for the same event)
      if (!wa.deferredExtFinalizables.contains(target))
      {
         wa.deferredExtFinalizables.add(target);
      }
   }
   
   /**
    * Allow finalizables to do their cleanup ({@link Finalizable#initFailure()}) now since the
    * scoped they were designed was aborted and there won't be other chance to call their
    * {@link Finalizable#finished()}.
    * <p>
    * After iteration the list is deleted.
    * <p>
    * Also, cleanup all other pending state being collected while the external program was 
    * instantiating.
    *
    * @param    wa
    *           Context local work area.  Must not be {@code null}.
    */
   private static void cleanupPending(WorkArea wa)
   {
      if (wa.deferredExtFinalizables != null)
      {
         wa.deferredExtFinalizables.forEach(Finalizable::initFailure);
         wa.deferredExtFinalizables = null;
      }
      
      wa.deferredUndoables = null;
      wa.trackedUndoables = null;
      wa.trackUndoables = false;
      wa.deferredDynExt = null;
      wa.pendingScopeables = null;
   }
   
   /**
    * Adds the given object into the list of objects which require
    * iterate/retry/finish notifications for the nearest enclosing 
    * top-level block.  A top-level block is generated for Progress external 
    * procedures, internal procedures, triggers and functions.  An additional
    * parameter allows registration to be specified for the specific block
    * which corresponds with an external procedure block scope in Progress.
    * <p>
    * This form of registration is useful for objects whose natural lifetime
    * is scoped to the top-level block and which need a hook for cleaning up
    * resources or handling other block termination processing.
    * <p>
    * This notification will occur whether or not a transaction is active.
    * <p>
    * These registrations naturally are removed when the associated scope
    * is removed (after the {@link Finalizable#finished} method is called).
    * <p>
    * The given object will only be added if it is not already in the list.
    * <p>
    * Finalizables for a scope are processed in the same order as they were
    * registered.
    *
    * @param    wa
    *           Context local work area.  Must not be {@code null}.
    * @param    target
    *           The object reference to be notified on any exit from the
    *           top-level block.  This cannot be <code>null</code>.
    * @param    external
    *           If <code>true</code>, register with the nearest enclosing
    *           external block (external procedure), otherwise register with
    *           the nearest enclosing top-level block (which may be but is
    *           not guaranteed to be the external block).
    *
    * @throws   IllegalStateException
    *           if the top-level block cannot be identified.
    */
   private static void registerTopLevelFinalizable(WorkArea    wa,
                                                   Finalizable target,
                                                   boolean     external)
   {
      BlockDefinition               targetBlock = null;
      Stack<BlockDefinition>        blocks      = wa.blocks;
      ListIterator<BlockDefinition> iter        = blocks.listIterator(blocks.size());
      
      // walk backwards up the stack of block definitions 
      while (iter.hasPrevious())
      {
         BlockDefinition blk = iter.previous();
         
         // if we are looking for an external block and found one OR 
         // if we are NOT looking for an external block and found any top
         // level block
         if ((external && blk.external) || (!external && blk.topLevel))
         {
            // done!
            targetBlock = blk;
            break;
         }
      }
      
      if (targetBlock == null)
      {
         String errmsg = (external ? "external" : "top-level");
         throw new IllegalStateException("Unable to find " + errmsg + " block");
      }
      
      // prevent ConcurrentModificationException
      if (wa.nowIterating == targetBlock.finalizables)
      {
         if (LOG.isLoggable(Level.WARNING))
         {
            log(Level.WARNING,
                "registerTopLevelFinalizable",
                "Illegal attempt to register Finalizable",
                new Throwable());
         }
         
         return;
      }
      
      addFinalizable(targetBlock, target);
   }
   
   /**
    * Adds the given object into the list of objects which require
    * iterate/retry/finish notification for the nearest enclosing, looping 
    * block.
    * <p>
    * This form of registration is useful for objects whose natural lifetime
    * is scoped to the nearest enclosing, looping block and which need a hook
    * for cleaning up resources or handling other block termination
    * processing.
    * <p>
    * This notification will occur whether or not a transaction is active.
    * <p>
    * These registrations naturally are removed when the associated scope
    * is removed (after the {@link Finalizable#finished} method is called).
    * <p>
    * The given object will only be added if it is not already in the list.
    *
    * @param    wa
    *           Context local work area.  Must not be {@code null}.
    * @param    target
    *           The object reference to be notified on any exit from or
    *           iteration of the target block.  This cannot be <code>null</code>.
    *
    * @throws   IllegalStateException
    *           if no enclosing, looping block can be identified.
    */
   private static void registerNearestLoopFinalizable(WorkArea wa, Finalizable target)
   {
      // walk backwards up the stack of block definitions 
      Stack<BlockDefinition> blocks = wa.blocks;
      ListIterator<BlockDefinition> iter = blocks.listIterator(blocks.size());
      while (iter.hasPrevious())
      {
         BlockDefinition blk = iter.previous();
         if (blk.loop)
         {
            // prevent ConcurrentModificationException
            if (wa.nowIterating == blk.finalizables)
            {
               if (LOG.isLoggable(Level.WARNING))
               {
                  log(Level.WARNING,
                      "registerNearestLoopFinalizable",
                      "Illegal attempt to register Finalizable",
                      new Throwable());
               }
               
               return;
            }
            
            addFinalizable(blk, target);
            return;
         }
      }
      
      throw new IllegalStateException("Unable to find nearest enclosing, looping block");
   }
   
   /**
    * Removes the given object from the list of objects which require
    * iterate/retry/finish notifications.  This will find the first instance
    * of the given reference by walking up each block from the current to the  
    * global block. The first instance found will be removed and the method
    * will then return. If the same instance is registered in multiple scopes,
    * it would take multiple calls to remove all instances.
    * <p>
    * After this call, no notifications will be received.
    *
    * @param    wa
    *           Context local work area.  Must not be {@code null}.
    * @param    target
    *           The object reference to be removed. Must not be {@code null}.
    *
    * @return   <code>true</code> if the target instance was found and
    *           removed.
    */
   private static boolean removeFinalizable(WorkArea wa, Finalizable target)
   {
      Stack<BlockDefinition>        blocks = wa.blocks;
      ListIterator<BlockDefinition> iter   = blocks.listIterator(blocks.size());
      boolean                       found  = false;
      
      // walk the stack of block definitions from the current back "up" 
      while (iter.hasPrevious() && !found)
      {
         BlockDefinition blk = iter.previous();
         if (wa.nowIterating != blk.finalizables)
         {
            found = removeFinalizable(blk.finalizables, target);
         }
         else
         {
            // prevent ConcurrentModificationException
            if (LOG.isLoggable(Level.WARNING))
            {
               log(Level.WARNING,
                   "removeFinalizable",
                   "Illegal attempt to deregister Finalizable",
                   new Throwable());
            }
            
            return false;
         }
      }
      
      // if still not found, try the global block
      if (!found)
      {
         if (wa.nowIterating != wa.globalBlock.finalizables)
         {
            found = removeFinalizable(wa.globalBlock.finalizables, target);
         }
         else
         {
            // prevent ConcurrentModificationException
            if (LOG.isLoggable(Level.WARNING))
            {
               log(Level.WARNING,
                   "removeFinalizable",
                   "Illegal attempt to deregister global Finalizable",
                   new Throwable());
            }
            
            return false;
         }
      }
      
      return found;
   }
   
   /**
    * Reports whether the current block was just rolled back or a containing
    * block has a pending rollback. If there is no current transaction open,
    * then there cannot be a pending or current rollback.
    *
    * @param    wa
    *           Context local work area.  Must not be {@code null}.
    *
    * @return   <code>true</code> if there was or will be a rollback.
    */
   private static boolean hadRollback(WorkArea wa)
   {
      // if there is no transaction, there can't be a pending rollback or a
      // current rollback
      if (wa.transLevel != -1)
      {
         // check if a containing block has a pending rollback
         if (wa.rollbackPending)
         {
            return true;
         }
         
         BlockDefinition blk = wa.blocks.peek();
         
         // check if the current block was rolled back
         if (blk.rolledBack)
         {
            return true;
         }
      }
      
      return false;
   }
   
   /**
    * Trigger a rollback for all registered <code>Undoable</code> objects 
    * inside the current active transaction at the scope defined
    * explicitly by the given label or implicitly if the label is
    * <code>null</code>.  All objects that are rolled back will have their
    * values assigned based on the values backed up at the start of the
    * specified scope.  If the current scope is not the target scope, the
    * given exception will be stored, the rollback will be deferred and 
    * the rollback will be processed as the scopes naturally unwind.  This
    * unwinding must be generated by a change in flow of control by the
    * calling code.  This flow control change MUST always immediately follow 
    * this method otherwise undefined results can occur.
    * <p>
    * This caller-driven control flow is required to properly duplicate the
    * semantics of Progress 4GL condition processing which always generates 
    * an implicit or explicit flow control action after every UNDO. Since the
    * flow of control change can reference a different scope than that of
    * the rollback, this can't be handled by re-throwing the exception but
    * must instead be separately driven by a deferred rollback and a caller
    * driven flow control statement such as break, continue or return. The
    * key limitation that makes this possible is the fact that the UNDO
    * (rollback) scope MUST ALWAYS BE equivalent to or more specific than
    * the scope to which flow of control will be changed.  This means that
    * one can rollback at a more deeply nested scope but change flow of
    * control to resume processing at a different (less deeply nested) scope.
    * The reverse is not possible.
    *
    * @param    wa
    *           Context local work area.  Must not be {@code null}.
    * @param    label
    *           Specifies the name of the scope at which to rollback or
    *           <code>null</code> if the scope should be implicitly 
    *           determined.
    * @param    cause
    *           The exceptional cause, if any, of the rollback.
    */
   private static void rollback(WorkArea wa, String label, Throwable cause)
   {
      if (LOG.isLoggable(Level.FINE))
      {
         log(Level.FINE, "rollback", "rollback_label = " + label);
      }
      
      // find the target block and call our worker
      int level = findStackLevel(wa, label);
      rollbackWorker(wa, level, cause);
   }
   
   /**
    * Trigger a rollback for all registered <code>Undoable</code> objects 
    * inside the current active transaction at the nearest top level scope.
    * All objects that are rolled back will have their values assigned based
    * on the values backed up at the start of the specified scope.  If the
    * current scope is not the target scope, the given exception will be 
    * stored, the rollback will be deferred and the rollback will be processed
    * as the scopes naturally unwind.  This unwinding must be generated by a
    * change in flow of control by the calling code.  This flow control change 
    * MUST always immediately follow this method otherwise undefined results
    * can occur.
    * <p>
    * This caller-driven control flow is required to properly duplicate the
    * semantics of Progress 4GL condition processing which always generates 
    * an implicit or explicit flow control action after every UNDO. Since the
    * flow of control change can reference a different scope than that of
    * the rollback, this can't be handled by re-throwing the exception but
    * must instead be separately driven by a deferred rollback and a caller
    * driven flow control statement such as break, continue or return. The
    * key limitation that makes this possible is the fact that the UNDO
    * (rollback) scope MUST ALWAYS BE equivalent to or more specific than
    * the scope to which flow of control will be changed.  This means that
    * one can rollback at a more deeply nested scope but change flow of
    * control to resume processing at a different (less deeply nested) scope.
    * The reverse is not possible.
    *
    * @param    wa
    *           Context local work area.  Must not be {@code null}.
    */
   private static void rollbackTopLevel(WorkArea wa)
   {
      if (LOG.isLoggable(Level.FINE))
      {
         log(Level.FINE, "rollback nearest top level block", null);
      }
      
      // find the target block and call the worker
      int level = findNearestTopLevel(wa);
      rollbackWorker(wa, level, null);
   }
   
   /**
    * Trigger a rollback for all registered <code>Undoable</code> objects 
    * inside the current active transaction at the scope defined by the
    * specified stack level.  All objects that are rolled back will have their
    * values assigned based on the values backed up at the start of the
    * specified scope.  If the current scope is not the target scope, the
    * given exception will be stored, the rollback will be deferred and 
    * the rollback will be processed as the scopes naturally unwind.  This
    * unwinding must be generated by a change in flow of control by the
    * calling code.  This flow control change MUST always immediately follow 
    * this method otherwise undefined results can occur.
    * <p>
    * This caller-driven control flow is required to properly duplicate the
    * semantics of Progress 4GL condition processing which always generates 
    * an implicit or explicit flow control action after every UNDO. Since the
    * flow of control change can reference a different scope than that of
    * the rollback, this can't be handled by re-throwing the exception but
    * must instead be separately driven by a deferred rollback and a caller
    * driven flow control statement such as break, continue or return. The
    * key limitation that makes this possible is the fact that the UNDO
    * (rollback) scope MUST ALWAYS BE equivalent to or more specific than
    * the scope to which flow of control will be changed.  This means that
    * one can rollback at a more deeply nested scope but change flow of
    * control to resume processing at a different (less deeply nested) scope.
    * The reverse is not possible.
    *
    * @param    wa
    *           Context local work area.  Must not be {@code null}.
    * @param    level
    *           Specifies the stack level of the scope at which to rollback or
    *           -1 if the current scope should be the target.
    * @param    cause
    *           The exceptional cause, if any, of the rollback.
    */
   private static void rollbackWorker(WorkArea wa, int level, Throwable cause)
   {
      BlockDefinition blk = null;
      
      if (level == -1)
      {
         blk = wa.blocks.peek();
      }
      else
      {
         blk = wa.blocks.elementAt(level);
      }
      
      // mark that block as being the UNDO target
      blk.isUndoLevel = true;
      
      // rollback an only occur inside an active transaction
      if (wa.isTransaction())
      {
         // is there a pending rollback?
         if (wa.isRollbackPending())
         {
            // this should not happen!
            
            String errmsg = "There is already a pending rollback.";
            
            if (wa.rollbackPendingCause != null)
            {
               LOG.log(Level.SEVERE, errmsg + " Pending cause:", wa.rollbackPendingCause);
            }
            else
            {
               LOG.severe(errmsg + " Pending cause unavailable.");
            }
            
            if (cause != null)
            {
               LOG.log(Level.SEVERE, errmsg + " Current cause:", cause);
            }
            else
            {
               LOG.severe(errmsg + " Current cause unavailable.");
            }
            
            throw new IllegalStateException(errmsg);
         }
         else
         {
            // determine if an explicit label refers to this scope OR if
            // a lack of a label implicitly refers to this scope
            if (rollbackCurrentScope(wa, level))
            {
               // peek at the current block def on the top of the stack
               if (level != -1)
               {
                  blk = wa.blocks.peek();
               }
               
               // remember that we rolled back so on exit there is no commit
               blk.rolledBack = true;
               blk.undoThrow = true;
               
               // we will immediately roll back, which allows other subsequent
               // processing like retry to work properly
               processRollback(wa, blk);
            }
            else
            {
               // the undo is destined for an enclosing scope, defer it; this
               // must be done even if the enclosing target scope is outside
               // of the transaction since otherwise, when popScope() exits
               // the transaction it will not know that there is a pending
               // rollback and the full transaction will be committed... that
               // would be a bad thing
               wa.rollbackScope   = level;
               wa.rollbackPending = true;
            }
            
            processResettables(wa, true);
         }
      }
      else
      {
         // no transaction but we still need to remember that this occurred
         // so that any following retry will know the target block for an
         // implicit retry (a null label in triggerRetry/forceRetry) - note
         // that we may be setting the target scope to null right here if the
         // label parm to this method is null, that is OK
         wa.retryScope = level;
         
         processResettables(wa, false);
      }
   }
   
   /**
    * Initializes a new scope, the associated block definition data
    * structures.  A backup of objects that can be rolled back is deferred
    * until after the block opens (see {@link #blockSetup} and 
    * {@link #makeBackup}).
    * <p>
    * In the case of a block which is defined as <code>NO_TRANSACTION</code>,
    * the core data structures are initialized but no rollback support will
    * be provided.
    * <p>
    * This method will honor any pending thread interruption by throwing
    * a {@link StopConditionException} using {@link #honorStopCondition}
    *
    * @param    wa
    *           Context local work area.  Must not be {@code null}.
    * @param    label
    *           The text label associated with the block.  This usually
    *           should correspond with the label used in the source file
    *           to identify the block for purposes of the break and continue
    *           statements.
    * @param    level
    *           One of {@link #NO_TRANSACTION}, {@link #SUB_TRANSACTION} or
    *           {@link #TRANSACTION}.  This code defines the level of
    *           rollback and transaction support which should be provided.
    * @param    props
    *           A bitfield storing block properties which will be a
    *           combination of {@link #PROP_NONE}, {@link #PROP_ERROR},
    *           {@link #PROP_ENDKEY}, {@link #PROP_STOP} or
    *           {@link #PROP_QUIT}.
    * @param    external
    *           <code>true</code> if this block is associated with the
    *           external procedure (in Progress terms). This controls the
    *           cleanup of certain resources such as temp-tables, frames
    *           and streams.
    * @param    topLevel
    *           <code>true</code> if this block is a method.
    * @param    loop
    *           Defines if this block is a loop or not.
    * @param    next
    *           <code>true</code> if this infinite loop protection should
    *           convert a retry into a next (continue), otherwise the retry
    *           will be converted into a leave (break).
    * @param    databaseTrigger
    *           <code>true</code> if this scope should define a
    *           database trigger block
    * @param    blockType
    *           Specifies this block's type.
    *
    * @throws   StopConditionException
    *           If the current thread's was previously interrupted.
    */
   private static void pushScope(WorkArea  wa,
                                 String    label,
                                 int       level,
                                 int       props,
                                 boolean   external,
                                 boolean   topLevel,
                                 boolean   loop,
                                 boolean   next,
                                 boolean   databaseTrigger,
                                 BlockType blockType)
   {
      if (LOG.isLoggable(Level.FINE))
      {
         String spec = "new block: label = %s; type = %s; level = %s; " +
                       "external = %b; top_level = %b; loop = %b; "     +
                       "next_or_leave = %s; properties = '%s';";
                       
         Object[] parms = new Object[]
         {
            label,
            blockType.toString(),
            decodeLevel(level),
            external,
            topLevel,
            loop,
            (next ? "next" : "leave"),
            decodeProperties(props)
         };
         
         log(Level.FINE, "pushScope", String.format(spec, parms));
      }
      
      try 
      {
         // check the interrupted status and throw the stop condition if needed
         wa.honorStopCondition();         
      }
      catch (RuntimeException exe)
      {
         ControlFlowOps.cleanupPending();
         throw exe;
      }
      
      // remember if this scope starts a new transaction rather than a
      // sub-transaction or no transaction
      boolean full = false;
      
      // start a new transaction if needed and one is not already active
      if (isTransactionLevel(level) && !wa.isTransaction())
      {
         full = true;
      }
      
      // determine if we have to mark this block for FOR block aggressive buffer flushing: if the
      // new block is any type of FOR block, turn on the forFlushing property
      
      boolean forFlushing = false;
      
      if (!external)
      {
         // any FOR block enables forFlushing state
         switch (blockType)
         {
            case FOR_LOOP:
            case FOR_LOOP_WHILE:
            case FOR_LOOP_TO:
            case FOR_LOOP_TO_WHILE:
            case FOR_BLOCK:
            case FOR_BLOCK_WHILE:
            case FOR_BLOCK_TO:
            case FOR_BLOCK_TO_WHILE:
               forFlushing = true;
            default:
               break;
         }
      }
      
      // create the block definition
      BlockDefinition blk = new BlockDefinition(label,
                                                level,
                                                props,
                                                full,
                                                external,
                                                topLevel,
                                                loop,
                                                next,
                                                forFlushing,
                                                databaseTrigger,
                                                blockType);
      
      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(wa, blk, wa.lt, false);
      }
      
      // add our block
      wa.blocks.push(blk);
      
      // add a new query off-end listeners set to the stack
      wa.offEndListeners.push(Collections.emptySet());
      
      // mark our global state as having an active transaction if needed
      if (full)
      {
         // this must be after we have added the latest block to our stack
         // in order to properly have a 0-based index
         wa.setTransactionLevel(wa.blocks.size() - 1);
      }
      
      if (wa.processUndoables())
      {
         wa.txNestingLevel++;
         blk.txNestingLevel = wa.txNestingLevel;
      }
      
      // the error manager needs a special notification (it can't be a
      // scopeable because it is used on both the client and the server)
      if (topLevel)
      {
         // remember if silent mode was enabled or not
         blk.suppressError = wa.em.isSilent();
         
         // disable silent mode if it was enabled because silent mode on a
         // RUN statement or for a function call is something that only
         // suppresses errors during the dispatching itself and NOT during the
         // execution of the called code
         if (blk.suppressError)
         {
            wa.em.setSilent(false);
         }
      }
      
      if (wa.bm != null)
      {
         if (wa.bm.isImportantBlockTransition()) 
         {
            BufferManager.registerTxScopeable(wa.bm);
         }
         
         if (isGlobalBlock(wa) || blk.external)
         {
            // the global block must be added, for the scoped dictionary's global block.
            // all external blocks must register also
            BufferManager.registerPendingScopeable(wa.bm);
         }
      }
      
      if (blk.external)
      {
         ArrayAssigner.registerPendingScopeable();
      }
      
      // notify Scopeables that scope has opened
      processScopeNotifications(wa, blk, true);
      
      // process deferred external procedure block registration requests
      if (external && wa.deferredExtFinalizables != null)
      {
         try
         {
            wa.nowIterating = wa.deferredExtFinalizables;
            ArrayList<Finalizable> deferredExtFinalizables = wa.deferredExtFinalizables;
            for (int i = 0; i < deferredExtFinalizables.size(); i++)
            {
               Finalizable target = deferredExtFinalizables.get(i);
               registerTopLevelFinalizable(target, true);
            }
            
            // clear the list
            wa.deferredExtFinalizables = null;
         }
         finally
         {
            wa.nowIterating = null;
         }
      }
      
      // process deferred block registration requests
      if (wa.deferredUndoables != null)
      {
         try
         {
            boolean persistent = (blk.external && wa.trackUndoables);
            wa.nowIterating = wa.deferredUndoables;
            for (LazyUndoable target : wa.deferredUndoables.keySet())
            {
               registerUndo(target, wa.txNestingLevel, persistent);
            }
            
            // clear the list
            wa.deferredUndoables = null;
         }
         finally
         {
            wa.nowIterating = null;
         }
      }
      
      if (wa.deferredDynExt != null)
      {
         try
         {
            // uninitialized dynamic extent vars
            ArrayAssigner.registerDynamicUndoableArray(wa.deferredDynExt, wa.txNestingLevel);
         }
         finally
         {
            wa.deferredDynExt = null;
         }
      }
      
      if (blk.external && wa.trackUndoables)
      {
         // if this is the scope for an external program and undoables are being tracked 
         // (i.e. the program is ran persistent), then save all the defined undoables, so that
         // they can be removed from all tx blocks, when the persistent proc gets deleted.
         Object referent = wa.pm._thisProcedure();
         if (wa.trackedUndoables != null && wa.trackedUndoables.isEmpty())
         {
            wa.procUndoables.put(referent, wa.trackedUndoables);
         }

         wa.trackUndoables = false;
         wa.trackedUndoables = null;
      }
   }
   
   /**
    * Remove the current block from the stack of scopes, process any pending
    * rollbacks (if the current block is the proper target), notify any
    * objects needing commits (if the block finished successfully) and handle
    * finish processing for the block.  This method must be called from a
    * <code>finally</code> block such that this is guaranteed to be called
    * no matter how the flow of control exits the associated block (exception,
    * break, return or the flow of control naturally passing through the 
    * end of the block).
    * <p>
    * Rollback and commit processing only occur when there is an active
    * transaction.  Otherwise this processing is bypassed.  Finish processing
    * is always called.
    * <p>
    * This method implements global finish processing (as the last action
    * before returning) when the last scope is popped from the stack of
    * scopes.  For this reason, the current implementation assumes that the
    * session is over at the moment that the top-most user-defined scope
    * exits.
    * <p>
    * This method will honor any pending thread interruption by throwing
    * a {@link StopConditionException} using {@link #honorStopCondition}
    *
    * @param    wa
    *           Context local work area.  Must not be {@code null}.
    *
    * @throws   StopConditionException
    *           If the current thread's was previously interrupted.
    */
   private static void popScope(WorkArea wa)
   throws StopConditionException
   {
      if (LOG.isLoggable(Level.FINE))
      {
         log(Level.FINE, "popScope", null);
      }
      
      // get the top block def on the stack
      BlockDefinition blk = wa.blocks.peek();
      
      if (blk.topLevel                                                                       &&
          (blk.scopeList == null || blk.scopeList[ScopeId.BUFFER_MANAGER.ordinal()] == null) &&
          wa.pm._isDelayedDelete(wa.pm._thisProcedure()))
      {
          BufferManager.registerScopeable(wa.bm);
      }
      
      try
      {
         if (wa.processUndoables())
         {
            wa.txNestingLevel--;
         }
         
         // if we have no more scopes in the stack of scopes, set flag for global termination
         // this global termination is the same for both appserver agents and normal clients - the state kept
         // in the global block needs to be finalized only when the actual client gets terminated.
         boolean globalTerm = (wa.blocks.size() == 1);
         
         // transaction related processing
         if (wa.isTransaction())
         {
            // is there a pending rollback?
            if (wa.isRollbackPending())
            {
               // determine if a pending rollback refers to this scope
               if (rollbackCurrentScope(wa, wa.getRollbackScope()))
               {
                  processRollback(wa, blk);
                  // temporary mark block as rolled back to allow locks downgrade 
                  blk.rolledBack = true;
               }
               
               // previously, if 'rollbackCurrentScope' was false, a 'processRollbackPending' method was being
               // called, which was executing the 'rollbackPending' for each Commitable.  But as there was
               // no real implementation of this method anywhere in FWD, this was removed.
            }
            else
            {
               // we only commit if we haven't rolled back in this scope
               if (!blk.rolledBack)
               {
                  // OK, this is a case where we need to COMMIT!
                  processCommit(wa, blk);
               }
            }
         }
         
         // prevent downstream code from retrying
         if (wa.retryScope != -1 && wa.retryScope == (wa.blocks.size() - 1))
         {
            wa.retryScope = -1;
         }
         
         // handle cleanup notifications
         processFinalizables(wa, blk, false);
         
         try
         {
            try
            {
               try
               {
                  if (blk.possibleCatch != null)
                  {
                     try
                     {
                        executeBlockTermination(wa, blk, 
                                                () -> processTerminationBlock(wa, blk, blk.possibleCatch), false);
                     }
                     finally
                     {
                        blk.possibleCatch = null;
                     }
                  }
               }
               finally
               {
                  if (blk.finallyBlock != null)
                  {
                     try
                     {                        
                        // FINALLY block support (this MUST be AFTER commit/rollback and finalizables are 
                        // processed but BEFORE the scope notifications)
                        executeBlockTermination(wa, blk, 
                                 () -> processTerminationBlock(wa, blk, blk.finallyBlock), true);
                     }
                     finally
                     {
                        blk.finallyBlock = null;
                     }
                  }
                  
                  wa.honorStopAfterCondition(blk);
               }
            }
            finally
            {
               if (!wa.isRemote() && isGlobalBlock(wa))
               {
                  // this will be called after the termination of the root top-level block for standalone 
                  // clients or appserver agents (the root top-level block is always a child of the global
                  // block, so this termination hook can be considered a FINALLY block for the global block).
                  
                  // FWD ensures that this code is executed after all normal application logic has executed
                  // (including any FINALLY block processing for the root top-level block).  As this is 
                  // considered a FINALLY code for the global block, we need to execute in the same terms as
                  // normal FINALLY blocks (AFTER commit/rollback and finalizables are processed
                  // but BEFORE the scope notifications)
                  processTerminationHook(wa);
               }
            }
         }
         finally
         {
            // now we are done with the scope
            processScopeNotifications(wa, blk, false);
            
            // the error manager needs a special notification (it can't be a
            // scopeable because it is used on both the client and the server)
            if (blk.topLevel && blk.suppressError)
            {
               // restore silent mode
               wa.em.setSilent(true);
            }
            
            if (blk.undoables != null)
            {
               ArrayList<UndoablePair> undoables = blk.undoables;
               for (int i = 0; i < undoables.size(); i++)
               {
                  UndoablePair target = undoables.get(i);
                  target.src.popBlock();
               }
            }
            
            // top level blocks must clear our transaction flag
            if (wa.isTransactionBlock())
            {
               wa.setTransactionLevel(-1);
            }
            
            // global termination occurs as the last thing after the last scope is removed
            if (globalTerm)
            {
               processFinalizables(wa, wa.globalBlock, false);
            }
            
            // auto-remove processing for master commitables and finalizables
            if (wa.isFullTransaction())
            {
               wa.autoRemoveMasterCommit();
               wa.autoRemoveMasterFinish();
            }
         }
      }
      finally
      {
         // pop the top block definition
         wa.blocks.pop();
         
         // pop the query off-end listeners stack
         wa.offEndListeners.pop();
         
         if (blk != null)
         {
            // propagate any disabling of loop protection in the inner block back to any
            // enclosing inner block BUT NOT past the top level block 
            if (!blk.topLevel && wa.blocks.size() > 0)
            {
               // save off the exiting block's loop protection values
               boolean prot   = blk.loopProtection;
               boolean pause  = blk.hadPause;
               boolean endkey = blk.endkeyRetry;
               
               BlockDefinition enclosing = null;
               
               // did the inner block have loop protection disabled
               if (!prot)
               {
                  enclosing = wa.blocks.peek();
                  enclosing.loopProtection = false;
               }
               
               // did the inner block have a pause occur
               if (pause)
               {
                  if (enclosing == null)
                  {
                     enclosing = wa.blocks.peek();
                  }
                  
                  enclosing.hadPause = true;
               }
               
               if (endkey)
               {
                  if (enclosing == null)
                  {
                     enclosing = wa.blocks.peek();
                  }
                  
                  enclosing.endkeyRetry = true;
               }
               
               if (blk.interactions > 0)
               {
                  if (enclosing == null)
                  {
                     enclosing = wa.blocks.peek();
                  }
                  
                  if (enclosing.rollup)
                  {
                     enclosing.interactions += blk.interactions;
                  }
               }
            }
         }
      }

      wa.handleDeferredError();
   }
   
   /**
    * Execute legacy block termination blocks like CATCH and FINALLY.
    * <p>
    * This will save and restore the {@link WorkArea#masterCommit} and {@link WorkArea#masterFinish} maps.
    * 
    * @param    wa
    *           The {@link WorkArea} instance.
    * @param    blk
    *           The parent block definition.
    * @param    code
    *           The code to execute.
    * @param    isFinally
    *           Flag indicating a FINALLY block is executing.
    */
   private static void executeBlockTermination(WorkArea wa, 
                                               BlockDefinition blk, 
                                               Runnable code, 
                                               boolean isFinally)
   {
      Map<Object, Boolean> savedMasterCommit = null;
      Map<Object, Boolean> savedMasterFinish = null;
      
      // temporarily suspend/hide master commitables and finalizables while the code is being executed
      if (wa.masterCommit != null && !wa.masterCommit.isEmpty())
      {
         savedMasterCommit = wa.masterCommit;
         wa.masterCommit = null;
      }
      
      if (wa.masterFinish != null && !wa.masterFinish.isEmpty())
      {
         savedMasterFinish = wa.masterFinish;
         wa.masterFinish = null;
      }
      
      boolean nestedSilent = (isFinally && blk.wasReturnError);
      try
      {
         if (nestedSilent)
         {
            ErrorManager.nestedSilent(code);
         }
         else
         {
            code.run();
         }
      }
      catch (ReturnUnwindException rue)
      {
         switch (blk.type)
         {
            case FUNCTION:
            case EXTERNAL_PROC:
            case INTERNAL_PROC:
            case DATABASETRIGGER:
               break;      // ignore it, returning anyway
            default:
               throw rue;  // rethrow in order to return from the top-level routine (function/procedure)
         }
      }
      finally
      {
         // restore the master commitables and finalizables, if they were stacked and hidden
         if (savedMasterCommit != null)
         {
            wa.masterCommit = savedMasterCommit;
            savedMasterCommit = null;
         }
         if (savedMasterFinish != null)
         {
            wa.masterFinish = savedMasterFinish;
            savedMasterFinish = null;
         }
      }
      
   }

   /**
    * When the {@link #isGlobalBlock() the root top-level block} terminates, invoke any hook specified by
    * the {@link CommonSession#isClientDisconnected() SESSION:CLIENT-DISCONNECTED attribute}.
    * 
    * @param    wa
    *           The context local state.
    */
   private static void processTerminationHook(WorkArea wa)
   {
      character hook = SessionUtils.getTerminationHook();
      if (hook.isUnknown() || hook.toJavaType().trim().isEmpty())
      {
         return;
      }
      
      ErrorManager.silent(() -> 
      {
         String target = hook.toJavaType();
         try
         {
            int colon = target.indexOf(':');
            if (colon > 0 && target.lastIndexOf(':') == colon)
            {
               // the com.goldencode.Foo:cleanup syntax, a static method call
               String cls = target.substring(0, colon);
               String mthd = target.substring(colon + 1);
               ObjectOps.invokeStandalone(cls, mthd);
            }
            else
            {
               // an external program
               ControlFlowOps.invokeExternalProcedure(hook, false, null, false, null, null);
            }
         }
         catch (Throwable t)
         {
            // ignore any error, but log it
            if (LOG.isLoggable(Level.SEVERE))
            {
               LOG.log(Level.SEVERE, "Abnormal end when processing termination hook '" + target + "':", t);
            }
         }
      });
   }
   
   /**
    * Execute the FINALLY block associated with the block definition, if any.
    * <p>
    * The FINALLY block will always be executed for the associated block, even if the stack unwinds because
    * of an abend.  WARNING: it is important to not change this behavior, FWD must allow the FINALLY code to
    * execute even if in cases of abends.  It is the application code's responsibility to ensure that the
    * code being executed is server-side only, maybe protected via the 
    * {@link CommonSession#isClientDisconnected() SESSION:CLIENT-DISCONNECTED attribute}.
    * 
    * @param    wa
    *           The context local state.
    * @param    blk
    *           The block definition to process.
    */
   private static void processTerminationBlock(WorkArea wa, BlockDefinition blk, Runnable code)
   {
      int transLevel         = -1;
      int workTxNestingLevel = -1;
      int blkTxNestingLevel  = -1;
      
      if (code != null)
      {
         // clear them, DeferredDeletablesManager can get registered again in the FINALLY block; but restore
         // them once that finishes.
         Set<Finalizable>[] prev = blk.finalizables;
         blk.finalizables = new Set[WeightFactor.WEIGHTS_ARRAY_SIZE];

         try
         {
            if (blk.full)
            {
               transLevel         = wa.transLevel;
               workTxNestingLevel = wa.txNestingLevel;
               blkTxNestingLevel  = blk.txNestingLevel;
               
               wa.transLevel      = -1;
               wa.txNestingLevel  = -1;
               blk.txNestingLevel = -1;
            }
            
            code.run();
         }
         catch (ReturnUnwindException rue)
         {
            switch (blk.type)
            {
               case FUNCTION:
               case EXTERNAL_PROC:
               case INTERNAL_PROC:
               case DATABASETRIGGER:
                  break;      // ignore it, returning anyway
               default:
                  throw rue;  // rethrow in order to return from the top-level routine (function/procedure)
            }
         }
         finally
         {
            if (blk.full)
            {
               wa.transLevel      = transLevel;
               wa.txNestingLevel  = workTxNestingLevel;
               blk.txNestingLevel = blkTxNestingLevel;
            }

            // execute again block's finalizables, if new ones were registered
            processFinalizables(wa, blk, false);
            
            // add back the previous ones
            blk.finalizables = prev;
         }
      }
   }
   
   /**
    * Notify {@link ConditionListener} instances about exception condition.
    * 
    * @param    wa
    *           Context local work area.  Must not be {@code null}.
    * @param   condition
    *          The instance of {@link ConditionException}.
    */
   private static void notifyCondition(WorkArea wa, ConditionException condition)
   {
      notifyCondition(wa, BlockManager.exceptionToCondition(condition));
   }
   
   /**
    * Notify {@link ConditionListener} instances about condition.
    * 
    * @param    wa
    *           Context local work area.  Must not be {@code null}.
    * @param    condition
    *           The condition the listeners will be notified.
    */
   private static void notifyCondition(WorkArea wa, BlockManager.Condition condition)
   {
      BlockDefinition blk = wa.blocks.peek();
      
      if (blk.conditionListeners != null)
      {
         ArrayList<ConditionListener> conditionListeners = blk.conditionListeners;
         for (int i = 0; i < conditionListeners.size(); i++)
         {
            ConditionListener listener = conditionListeners.get(i);
            try
            {
               listener.condition(condition);
            }
            catch (Throwable thr)
            {
               deferError(wa, "notifyCondition", thr, false); 
            }
         }
      }
   }
   
   /**
    * Provides a callback entry point for the top of the loop.  This location 
    * is required to handle the backup of undo variables via 
    * {@link #makeBackup}.  On the second and subsequent executions of this
    * method for a given block, this method handles <code>iterate</code> 
    * notifications.
    * <p>
    * It is important to note that <code>continue</code> can be called from
    * nested blocks at arbitrary depths.  This method can handle such nesting
    * at any level. 
    * <p>
    * This method will honor any pending thread interruption by throwing
    * a {@link StopConditionException} using {@link #honorStopCondition}
    *
    * @param    wa
    *           Context local work area.  Must not be {@code null}.
    *
    * @throws   StopConditionException
    *           If the current thread's was previously interrupted.
    */
   private static void blockSetup(WorkArea wa) 
   throws StopConditionException
   {
      if (LOG.isLoggable(Level.FINE))
      {
         log(Level.FINE, "blockSetup", null);
      }
      
      // read the block def at the top of the stack
      BlockDefinition blk = wa.blocks.peek();
      
      if (blk.init)
      {
         // second or subsequent time through
         iterateWorker(wa);
         blk.iterated = true;

         processResettables(wa, true);
      }
      else
      {
         // first time through
         processEntry(wa, blk);
         
         blk.init = true;
      }
      
      // now trigger the undoable backups
      backupWorker(wa, blk);
   }
   
   /**
    * Reports if the current iteration of this block or loop is a retry.
    *
    * @param    wa
    *           Context local work area.  Must not be {@code null}.
    *
    * @return    <code>true</code> if this is a retry of this block or loop
    *            (as opposed to the first time through this iteration).
    */
   private static boolean _isRetry(WorkArea wa)
   {
      // peek at the current block def on the top of the stack
      BlockDefinition blk = wa.blocks.peek();
      
      disableLoopProtection(blk);
      
      return blk.wasRetried;
   }
   
   /**
    * Records the fact that the pending RETRY is being caused by an ENDKEY
    * condition. This enables "downstream" retry processing to implement the
    * "stuttering" quirk and some behavior in honoring PAUSE as a way to
    * disable infinite loop protection.
    *
    * @param    wa
    *           Context local work area.  Must not be {@code null}.
    */
   private static void markEndkeyRetry(WorkArea wa)
   {
      // peek at the current block def on the top of the stack
      BlockDefinition blk = wa.blocks.peek();
      blk.endkeyRetry = true;
   }
   
   /**
    * This checks if any of the enclosing loops has iterated at least once.
    * 
    * @param    wa
    *           Context local work area.  Must not be {@code null}.
    *
    * @return   <code>true</code> if any of the enclosing loops has iterated
    *           at least once.
    */
   private static boolean hasParentIteratedBlock(WorkArea wa)
   {
      for (BlockDefinition blk : wa.blocks)
      {
         if (blk.loop && blk.iterated)
         {
            return true;
         }
      }
      
      return false;
   }
   
   /**
    * Determines if a NEXT operation with the given target block should
    * be honored or if the NEXT should be converted to a LEAVE. This is
    * a special form of infinite loop protection.
    * <p>
    * A NEXT is converted into a LEAVE when all of the following conditions
    * are true:
    * <p>
    * <ul>
    *   <li> The NEXT is associated with the UNDO, NEXT language statement or
    *        with an ON phrase that has UNDO, NEXT as the result of the given
    *        condition (e.g. ON ENDKEY UNDO, NEXT). It is important to note
    *        that Progress does not provide infinite loop protection for the
    *        NEXT language statement, so there is no conversion to LEAVE in
    *        that case.
    *   <li> The target block (whether implicitly determined by the lack of a
    *        label or explicitly determined by the presence of a label) must
    *        also have been the target block for the UNDO operation. It is
    *        possible for the NEXT to be targetted at a more enclosing block
    *        than the UNDO, in which case there is no conversion to LEAVE.
    *   <li> The target block type must be REPEAT, REPEAT WHILE or DO WHILE.
    *        These are the only loop types that normally convert RETRY to
    *        LEAVE instead of NEXT.  Likewise, these are the only loop types
    *        which honor infinite loop protection for NEXT by converting it
    *        to a LEAVE.
    *   <li> Infinite loop protection must not have been disabled for the
    *        target block.  In this case, any form of PAUSE is honored as
    *        one of the mechanisms to disable infinite loop protection.
    * </ul>
    * <p>
    * Since NEXT is only possible on a looping block, if the target block
    * is not a looping block then this method will return <code>false</code>.
    *
    * @param    wa
    *           Context local work area.  Must not be {@code null}.
    * @param    label
    *           The label of the target block for the NEXT action.
    *
    * @return   <code>true</code> for NEXT, <code>false</code> for LEAVE.
    */
   private static boolean honorNext(WorkArea wa, String label)
   {
      BlockDefinition blk = null;
      int             lvl = -1;
      
      // check if the NEXT is targetted at the current block
      if (label == null || label.length() == 0)
      {
         // get the current block
         lvl = wa.blocks.size() - 1;
         blk = wa.blocks.peek();
      }
      else
      {
         // lookup the block target by its label
         lvl = findStackLevel(wa, label);
         
         if (lvl >= 0)
            blk = wa.blocks.elementAt(lvl);
      }
      
      // protect from NPE, but that should not happen unless a bogus label is passed
      if (blk != null)
      {
         // any non-looping block type always converts NEXT to LEAVE
         if (blk.type == DO                  ||
             blk.type == FINALLY             ||
             blk.type == FOR_BLOCK           ||
             blk.type == FOR_BLOCK_WHILE     ||
             blk.type == FOR_BLOCK_TO        ||
             blk.type == FOR_BLOCK_TO_WHILE  ||
             blk.type == EXTERNAL_PROC       ||
             blk.type == INTERNAL_PROC       ||
             blk.type == FUNCTION            ||
             blk.type == TRIGGER             ||
             blk.type == DATABASETRIGGER)
         {
            return false;
         }
         
         // make sure the target block is one of the looping blocks that can
         // cause NEXT to convert to LEAVE
         if (blk.type == DO_WHILE ||
             blk.type == REPEAT   ||
             blk.type == REPEAT_WHILE)
         {
            // conversion only happens if there is an active UNDO operation
            // targetted at the same block
            if (blk.isUndoLevel)
            {
               boolean disable = false;
               
               // this next algorithm is needed because normally, the popping
               // of each block off the stack handles the "rollup" of the
               // ILP state, but this method is being called before that
               // rollup can occur so we simulate it here to allow us to come
               // to the same conclusion
               
               // walk up the stack to find if ILP or PAUSE has occurred
               // anywhere between the current block and the target block,
               // since disabling infinite loop protection or any PAUSE
               // causes the conversion to be avoided
               for (int i = wa.blocks.size() - 1; i >= lvl; i--)
               {
                  BlockDefinition next = wa.blocks.elementAt(i);
                                         
                  if (!next.loopProtection || next.hadPause)
                  {
                     disable = true;
                     break;
                  }
               }
               
               // if ILP is not disabled
               if (!disable)
               {
                  // convert to LEAVE
                  return false;
               }
            }
         }
      }
      
      // honor the NEXT, don't convert to LEAVE
      return true;
   }
   
   /**
    * Determines if the currently executing block should be retried.  This
    * is where infinite loop protection is implemented if it is enabled.
    *
    * @param    wa
    *           Context local work area.  Must not be {@code null}.
    *
    * @return   <code>true</code> if the block (whether or not it is a loop)
    *           should be retried.
    */
   private static boolean needsRetry(WorkArea wa)
   {  
      // peek at the current block def on the top of the stack
      BlockDefinition blk = wa.blocks.peek();
      
      boolean noPause = true;
      
      // a PAUSE sometimes acts to disable infinite loop protection
      if (blk.hadPause)
      {
         // depending solely on the current block type
         if (blk.type == DO_WHILE ||
             blk.type == REPEAT   ||
             blk.type == REPEAT_WHILE)
         {
            noPause = false;
         }
         
         // also can happen in other block types IF this is a retry caused
         // by the raising of an ENDKEY condition
         if (blk.endkeyRetry)
         {
            if (blk.type == DO                  ||
                blk.type == FINALLY             ||
                blk.type == DO_TO               ||
                blk.type == REPEAT_TO           ||
                blk.type == FOR_BLOCK           ||
                blk.type == FOR_BLOCK_WHILE     ||
                blk.type == FOR_BLOCK_TO        ||
                blk.type == FOR_BLOCK_TO_WHILE  ||
                blk.type == FOR_LOOP            ||
                blk.type == FOR_LOOP_WHILE      ||
                blk.type == FOR_LOOP_TO         ||
                blk.type == FOR_LOOP_TO_WHILE)
            {
               noPause = false;
            }
         }
      }
      
      boolean bypass = false;
      
      // in certain block types when a RETRY is caused by an ENDKEY, the
      // normal infinite loop protection mechanism is bypassed depending on
      // an internal counter; this causes a kind of "stutter" effect where
      // a RETRY on some iterations will be a real RETRY and other iterations
      // it will be converted to a NEXT
      if (blk.endkeyRetry && blk.loopProtection && noPause)
      {
         // looping blocks that modify state on each pass are the only
         // blocks that get this stuttering behavior
         if (blk.type == DO_TO               ||
             blk.type == REPEAT_TO           ||
             blk.type == FOR_LOOP            ||
             blk.type == FOR_LOOP_WHILE      ||
             blk.type == FOR_LOOP_TO         ||
             blk.type == FOR_LOOP_TO_WHILE)
         {
            // the ilpCount will start at -1 and then approach 1 (so the
            // first 2 passes through here will convert to NEXT), then on
            // the 3rd pass the ilpCount will back down to 0 (allowing the
            // RETRY to occur normally), then the 4th pass will increment
            // the ilpCount to 1 (converts to NEXT), then 5th pass will
            // back down to 0 (RETRY), 6th pass (NEXT), 7th pass (RETRY)...
            // causing a "stuttering" effect
            
            // maintain the "stutter" counter
            if (blk.ilpCount < 1)
            {
               // allow the retry to be converted to NEXT (all these block
               // types should have blk.next == true)
               blk.ilpCount++;
            }
            else
            {
               // cause a "stutter" (allow the RETRY to proceed as if ILP
               // was disabled)
               blk.ilpCount--;
               bypass = true;
            }
         }
      }
                                             
      // honor infinite loop protection (noPause is the way that PAUSE
      // disables infinite loop protection)
      if (blk.retry && blk.loopProtection && noPause && !bypass)
      {
         // set flag to convert RETRY into LEAVE
         if (!blk.next)
         {
            blk.pendingBreak = true;
         }
                                                         
         // unless the special flag is set above, this is equivalent to NEXT
         return false;
      }
      
      boolean result = blk.retry;
      
      // is this a retry?
      if (result)
      {
         // reset our retry state as if this was a fresh pass at the block 
         // except the wasRetried will be true this time
         blk.retry          = false;
         blk.loopProtection = true;
         blk.hadPause       = false;
         blk.endkeyRetry    = false;
         blk.pendingBreak   = false;
         blk.wasRetried     = true;
         blk.rolledBack     = false;
         blk.isUndoLevel    = false;
         
         // this is the last point before the retry pass of the loop actually
         // occurs, notify the finalizables that a retry is occurring
         processRetry(wa, blk);
      }
      
      return result;
   }
   
   /**
    * Accesses the state of whether there is a pending break that must be 
    * honored.  This would have been generated by the infinite loop
    * protection.
    *
    * @param    wa
    *           Context local work area.  Must not be {@code null}.
    *
    * @return   <code>true</code> if a break is pending for this block.
    */
   private static boolean isBreakPending(WorkArea wa)
   {
      // peek at the current block def on the top of the stack
      BlockDefinition blk = wa.blocks.peek();
      
      return blk.pendingBreak; 
   }
   
   /**
    * Force an error to be generated such that it will be caught in the
    * caller of this method.  The stack will be unwound and normal error 
    * processing bypassed until this method returns.  This unwinding is 
    * dependent upon catch blocks being implemented with a corresponding call
    * to {@link #honorError}.
    * <p>
    * In the case where silent error mode is enabled in the calling method,
    * the "normal" stack unwinding approach cannot be used because the 
    * exception cannot be thrown. Instead, the error manager will be updated 
    * with the error data and this method will silently return.  It is
    * critical that in such cases the calling code must immediately execute a
    * <code>return</code> statement which will maintain the proper Progress
    * behavior.  The caller can check the error manager to determine that an 
    * error occurred.
    * <p>
    * Detecting the silent error mode in the caller requires that the top
    * level block be found.  The silent error flag of the caller is cached 
    * there.
    *
    * @param    wa
    *           Context local work area.  Must not be {@code null}.
    * @param    errNum
    *           The identifier of the error.  When not-null, the error message will be registered. 
    * @param    text
    *           The text to use as the error message in the exception.
    *           
    * @throws   ErrorConditionException 
    */
   private static void triggerErrorInCaller(WorkArea wa, Integer errNum, String text)
   throws ErrorConditionException
   {
      triggerErrorInCaller(wa, errNum, text, true, true);
   }
   
   /**
    * Force an error to be generated such that it will be caught in the
    * caller of this method.  The stack will be unwound and normal error 
    * processing bypassed until this method returns.  This unwinding is 
    * dependent upon catch blocks being implemented with a corresponding call
    * to {@link #honorError}.
    * <p>
    * In the case where silent error mode is enabled in the calling method,
    * the "normal" stack unwinding approach cannot be used because the 
    * exception cannot be thrown. Instead, the error manager will be updated 
    * with the error data and this method will silently return.  It is
    * critical that in such cases the calling code must immediately execute a
    * <code>return</code> statement which will maintain the proper Progress
    * behavior.  The caller can check the error manager to determine that an 
    * error occurred.
    * <p>
    * Detecting the silent error mode in the caller requires that the top
    * level block be found.  The silent error flag of the caller is cached 
    * there.
    *
    * @param    wa
    *           Context local work area.  Must not be {@code null}.
    * @param    errNum
    *           The identifier of the error.  When not-null, the error message will be registered. 
    * @param    text
    *           The text to use as the error message in the exception.
    * @param    errFlag
    *           Flag indicating the ERROR-ERROR:ERROR is set. 
    * @param    prefix
    *           Prefix or not with two asterics when building the error message text.
    *           
    * @throws   ErrorConditionException 
    */
   private static void triggerErrorInCaller(WorkArea wa, 
                                            Integer  errNum, 
                                            String   text, 
                                            boolean  errFlag, 
                                            boolean  prefix)
   throws ErrorConditionException
   {
      BlockDefinition blk = nearestTopLevel(wa);
      
      if (blk != null && blk.suppressError)
      {
         if (errNum != null)
         {
            wa.em.addError(errNum, text, errFlag, prefix);
         }

         wa.em.setPending(true);
      }
      else
      {
         // turn on the ignore flag
         wa.ignoreError = true;

         // throw the error
          if (errNum != null)
          {
              throw new ErrorConditionException(errNum, text, true);
          }
          else
          {
              throw new ErrorConditionException(-1, text, true);
          }
      }
   }
   
   /**
    * Force an error to be generated such that it will be caught in the
    * caller of this method.  The stack will be unwound and normal error 
    * processing bypassed until this method returns.  This unwinding is 
    * dependent upon catch blocks being implemented with a corresponding call
    * to {@link #honorError}.
    * <p>
    * In the case where silent error mode is enabled in the calling method,
    * the "normal" stack unwinding approach cannot be used because the 
    * exception cannot be thrown. Instead, the error manager will be updated 
    * with the error data and this method will silently return.  It is
    * critical that in such cases the calling code must immediately execute a
    * <code>return</code> statement which will maintain the proper Progress
    * behavior.  The caller can check the error manager to determine that an 
    * error occurred.
    * <p>
    * Detecting the silent error mode in the caller requires that the top
    * level block be found.  The silent error flag of the caller is cached 
    * there.
    * 
    * @param    lex
    *           The legacy OO-like error to throw.
    *           
    * @throws   ErrorConditionException 
    */
   private static void triggerErrorInCaller(WorkArea wa, LegacyErrorException lex)
   throws ErrorConditionException
   {
      BlockDefinition blk = nearestTopLevel(wa);
      
      if (blk != null && blk.suppressError)
      {
         wa.em.setPending(true);
      }

      // throw the error
      throw lex;
   }

   /**
    * Check if this block's caller requires errors to be suppressed.
    * 
    * @param    wa
    *           Context local work area.  Must not be {@code null}.
    *
    * @return   {@link BlockDefinition#suppressError} for {@link #nearestTopLevel} block.
    *
    */
   private static boolean isSuppressError(WorkArea wa)
   {
      BlockDefinition blk = nearestTopLevel(wa);
      
      return (blk != null && blk.suppressError);
   }
   
   /**
    * Force a retry of the current block or the scope named by the given
    * label, ensuring that the flow of control will not naturally continue.
    * A {@link RetryUnwindException} will be thrown to unwind the stack
    * to the given scope, even if the scope is the current block.  This
    * unwinding is dependent upon catch blocks being implemented with a 
    * corresponding call to {@link #honorRetry}.
    * <p>
    * <b>This method will NEVER naturally return since an exception is ALWAYS
    * thrown.</b>  This method should generally not be called from within a
    * catch block if the intention is to retry the current block since it
    * would be outside of the block's associated <code>try</code>.  In that 
    * case, only an enclosing block could be retried.
    *
    * @param    wa
    *           Context local work area.  Must not be {@code null}.
    * @param    label
    *           The name of the scope to retry. Specify <code>null</code> to
    *           retry the scope that has a pending rollback OR to retry
    *           the current scope if there is no pending rollback.
    *
    * @throws   RetryUnwindException
    *           This method always generates this exception to ensure that
    *           natural processing will not continue.
    */
   private static void forceRetry(WorkArea wa, String label)
   throws RetryUnwindException
   {
      int level = findStackLevel(wa, label);
      
      if (level == -1)
      {
         if (wa.rollbackPending)
         {
            // force retry at the same level as the pending rollback
            level = wa.rollbackScope;
         }
         else
         {
            if (!wa.isTransaction())
            {
               // in a non-transaction block we still may have an implicit
               // target specified during rollback but it would have been
               // stored in the retryScope var (it may have been set to -1
               // too, that is OK)
               level = wa.retryScope;
            }
         }
      }
      
      // is the current block the target?
      if (level == -1)
      {
         // pretend that we are doing a "normal" unwind
         level = wa.blocks.size() - 1;
      }
      
      // record the scope for the pending retry
      wa.retryScope = level;
         
      // abort current processing and jump to end of current block OR unwind
      // the stack to the specified block
      throw new RetryUnwindException("Unwinding to stack level " + level);
   }
   
   /**
    * Force a retry of the current block or the scope named by the given
    * label.  If a containing scope is the target of the retry, then
    * a {@link RetryUnwindException} will be thrown to unwind the stack
    * to the given scope.  This unwinding is dependent upon catch blocks 
    * being implemented with a corresponding call to {@link #honorRetry}.
    * <p>
    * If the current block is not the target, the retry is deferred by
    * marking the target block and then unwinding the stack.  Otherwise, 
    * this method will return naturally.  This method is only appropriate 
    * to be called from within <code>catch</code> blocks since a natural
    * return within a <code>catch</code> block will fall through to allow
    * the retry loop to engage.
    *
    * @param    wa
    *           Context local work area.  Must not be {@code null}.
    * @param    label
    *           The name of the scope to retry. Specify <code>null</code> to
    *           retry the scope of that has a pending rollback OR to retry
    *           the current scope if there is no pending rollback.
    *           
    * @throws   RetryUnwindException 
    */
   private static void triggerRetry(WorkArea wa, String label)
   throws RetryUnwindException
   {
      // TODO: if this is a routine block, then check if this retry needs to convert to ERROR
      // TODO: exclude destructors and UI triggers
      
      int level = findStackLevel(wa, label);
      
      if (level == -1)
      {
         if (wa.rollbackPending)
         {
            // force retry at the same level as the pending rollback
            level = wa.rollbackScope;
         }
         else
         {
            if (!wa.isTransaction())
            {
               // in a non-transaction block we still may have an implicit
               // target specified during rollback but it would have been
               // stored in the retryScope var (it may have been set to -1
               // too, that is OK)
               level = wa.retryScope;
            }
         }
      }
      
      // peek at the current block def on the top of the stack
      BlockDefinition blk = wa.blocks.peek();
      
      if (level == -1 || level == (wa.blocks.size() - 1))
      {
         blk.retry = true;
      }
      else
      {
         // record the scope for the pending retry
         wa.retryScope = level;
         
         // unwind the stack to that point
         throw new RetryUnwindException("Unwinding to stack level " + level);
      }
   }
   

   /**
    * Detect if the nearest top level block is a database trigger block
    * @param wa
    *        Context local work area. Must not be {@code null}.
    * @return
    *        True if the nearest top level block is a database trigger, false otherwise
    */
   public static boolean isTopLevelDatabaseTrigger(WorkArea wa)
   {
      BlockDefinition blk = nearestTopLevel(wa);
      return blk.databaseTrigger;
   }
   
   /**
    * Dispatch the given exception to all registered stop condition handlers
    * to see if any requires that the STOP condition be vetoed at the
    * current scope.  If any of the registered handlers indicate that the STOP
    * condition should not be honored at this level, the exception is
    * rethrown, presumably to be honored at a higher scope, or not at all.  In
    * the latter case, the program running in the current context will abort.
    * 
    * @param    wa
    *           Context local work area.  Must not be {@code null}.
    * @param   exc
    *          The exception to be checked.
    * 
    * @throws  StopConditionException
    *          if the STOP condition is not honored at this scope.
    */
   private static void honorStop(WorkArea wa, StopConditionException exc)
   throws StopConditionException
   {
      for (StopConditionVetoHandler handler : wa.stopHandlers)
      {
         if (handler.vetoStop(exc))
         {
            // re-throw the exception but first it will maintain the rollback
            // state/processing
            abnormalEnd(wa, exc, false);
         }
      }
      
      // No handlers want to veto the STOP at this scope - allow the STOP
      // to be executed
   }
   
   /**
    * Check if there is a pending retry of the current block and if so then
    * enable retry.  This is the "last half" of the processing to unwind the
    * stack up to a containing scope that is the target of a retry.  The
    * "first half" of the unwinding is implemented by throwing a
    * {@link RetryUnwindException}.  This unwinding is dependent upon catch
    * blocks being implemented to call this method.
    * <p>
    * If the current block is not the target for the pending retry, the
    * exception is re-thrown.
    *
    * @param    wa
    *           Context local work area.  Must not be {@code null}.
    * @param    rue
    *           The exception to honor or ignore.
    *           
    * @throws   RetryUnwindException 
    */
   private static void honorRetry(WorkArea wa, RetryUnwindException rue)
   throws RetryUnwindException
   {
      if (wa.retryScope == (wa.blocks.size() - 1))
      {
         // peek at the current block def on the top of the stack
         BlockDefinition blk = wa.blocks.peek();
         
         blk.retry = true;
         wa.retryScope = -1;
      }
      else
      {
         // we cannot propagate the exception past the full transaction
         // level without causing a rollback(), unless there is a rollback
         // already pending in which case it will be naturally be handled
         // by popScope() when the full transaction exits, so don't force a
         // rollback again (calling this more than once must not be done
         // while there is a pending rollback); note that this is only safety
         // code since "well formed" converted 4GL code should never cause
         // this to occur since the UNDO and RETRY must always be targeted
         // at the same block AND the UNDO (a call to rollback()) should
         // always be made before the RETRY is triggered
         if (wa.isFullTransaction() && !hadRollback())
         {
            rollback(null, rue);
         }
         
         // we aren't there yet, re-throw the exception
         throw rue;
      }
   }
   
   /**
    * Check if the current error must unwind all the way to the caller of
    * the top-level block in this method (if the ignore flag is set).  If
    * this is the case then the error is re-thrown.  Before this occurs, if
    * the current block is the top-level block of this method, then the
    * ignore flag will be cleared.  If the ignore flag is <code>false</code> 
    * then this method returns silently.
    * <p>
    * This code also honors any nested error that was generated while another
    * error was being processed AND while nested error mode was enabled.  Any
    * such stored error will be re-thrown here.
    *
    * @param    wa
    *           Context local work area.  Must not be {@code null}.
    * @param    err
    *           The exception to honor or ignore.
    *           
    * @throws   ErrorConditionException 
    */
   private static void honorError(WorkArea wa, ErrorConditionException err)
   throws ErrorConditionException
   {
      
      if (wa.ignoreError || 
          isTopLevelDatabaseTrigger(wa))
      {
         // peek at the current block def on the top of the stack
         BlockDefinition blk = wa.blocks.peek();
         
         if (blk.topLevel)
         {
            // the next catch block will handle this error (it will be in the caller)
            wa.ignoreError = false;
            
            // this bypasses the commit
            blk.rolledBack = true;
         }
         
         // TODO: should this call abnormalEnd() instead so that the rollback
         // state/processing is maintained/handled?
         
         // continue to unwind
         throw err;
      }
      
      // nested error support
      if (wa.nestedError != null)
      {
         ConditionException ece = wa.nestedError; 
         
         // reset our nested error
         wa.nestedError = null;
         
         // TODO: should this call abnormalEnd() instead so that the rollback
         // state/processing is maintained/handled?
         throw ece;
      }
   }
   
   /**
    * Notifies the transaction manager that an unexpected exception or error
    * has occurred and that any transaction must be aborted. This will
    * maintain the state of the system properly, including rolling back any
    * open transaction. Once the state is properly maintained, the triggering
    * <code>Throwable</code> will be re-thrown as a 
    * <code>RuntimeException</code>.
    * <p>
    * If the exception is of type <code>ConditionException</code> or
    * <code>RetryUnwindException</code>, this method will do nothing since
    * these are used for normal flow of control purposes and are used in
    * close coordination with the transaction manager's state.  They need
    * no other special behavior to be safe.  This method is designed to
    * handle exceptions (like <code>NullPointerException</code>) which are
    * not valid in a converted 4GL program.
    *
    * @param    wa
    *           Context local work area.  Must not be {@code null}.
    * @param    thr
    *           The unexpected exception or error.
    * @param    needsNotify
    *           <code>true</code> if condition notification must be executed
    *           (only happens if the <code>thr</code> is a condition).
    *           
    * @throws   RuntimeException 
    */
   private static void abnormalEnd(WorkArea wa, Throwable thr, boolean needsNotify)
   throws RuntimeException
   {
      if (thr instanceof ConditionException)
      {
         if (needsNotify)
         {
            notifyCondition(wa, (ConditionException) thr);
         }
         
         // we cannot propagate the exception past the full transaction
         // level without causing a rollback() except for QUIT which has
         // the unusual behavior of committing the transaction UNLESS one
         // specifies an ON QUIT phrase inside the transaction block or a
         // nested block which encloses the raised condition (note that in
         // that case this method will never be called so it will work fine)
         if (wa.isFullTransaction() && !(thr instanceof QuitConditionException))
         {
            // if a rollback is already pending, then it will be naturally
            // handled by popScope() when the full transaction exits, so
            // don't force a rollback again (calling this more than once
            // must not be done while there is a pending rollback)
            if (!hadRollback())
            {
               rollback(wa, null, thr);
            }
         }
         
         ConditionException ce = (ConditionException) thr;
         throw ce;
      }
      else if (thr instanceof RetryUnwindException)
      {
         // this should never really get called since there should always
         // be an explicit catch block for this BEFORE any Throwable catch
         // block, but for safety purposes...
         RetryUnwindException rue = (RetryUnwindException) thr;
         honorRetry(wa, rue);
      }
      else
      {
         SessionUtils.clientDisconnected();
         
         if (!isCausedBy(thr, ApplicationRequestedStop.class))
         {
            LOG.severe("Abnormal end; original error:", thr);
         }

         boolean storeDeferred = (wa.deferredError == null);
         try
         {
            if (storeDeferred)
            {
               // if there is no existing deferred error, store the current
               // error, ensuring errors thrown in this try block do not mask
               // the cause of the abnormal end, but are still logged by
               // deferError()
               wa.deferredError = thr;
            }
            notifyCondition(wa, BlockManager.Condition.UNKNOWN);
            rollback(wa, null, thr);
         }
         finally
         {
            if (storeDeferred)
            {
               // reset deferred error to its previous state
               wa.deferredError = null;
            }
         }
         
         if (thr instanceof RuntimeException)
         {
            // the exception is already the right type so we don't have to
            // wrap it again (this avoids unnecessarily long chained log
            // entries
            throw (RuntimeException) thr;
         }
         else
         {
            // wrap the exception in a type that will propagate all the
            // way "out" as an abend
            throw new RuntimeException(thr);
         }
      }
   }

   /**
    * Returns if a Throwable is caused by a Throwable of the class to check against.
    *
    * @param    thr
    *           The Throwable to check.
    * @param    clazz
    *           The Throwable class to check against.
    *
    * @return   <code>true</code> if the Throwable is caused a Throwable of the provided class, 
    *           <code>false</code> otherwise.
    */
   private static <T extends Throwable> boolean isCausedBy(Throwable thr, Class<T> clazz)
   {
      if (thr == null)
      {
         return false;
      }
      if (thr.getClass().equals(clazz))
      {
         return true;
      }
      return isCausedBy(thr.getCause(), clazz);
   }

   /**
    * Reports if the current block is a top-level block (this corresponds
    * with a method-level block).
    *
    * @param    wa
    *           Context local work area.
    *
    * @return  <code>true</code> if top-level block, else <code>false</code>.
    */
   private static boolean isTopLevelBlock(WorkArea wa)
   {
      BlockDefinition blk = wa.blocks.peek();
      return blk.topLevel;
   }
   
   /**
    * Reports if the current block is a global scoped block. For normal contexts:
    * <code>true</code> if the current block is the only block on the stack of block scopes; for
    * appserver agent contexts: <code>true</code> if the current block is one of two outermost
    * blocks ("appserver-agent" and "startup") on the stack of block scopes.
    * 
    * @param    wa
    *           Context local work area.
    *
    * @return   See above.
    */
   private static boolean isGlobalBlock(WorkArea wa)
   {
      int size = wa.blocks.size();
      return size == 1 || (size == 2 && wa.isRemote());
   }
   
   /**
    * Search the stack of scopes and return the level of the first top level block found.
    *
    * @param    wa
    *           Context local work area.
    *
    * @return   The stack level of the nearest enclosing top level block or
    *           -1 if there is no block marked as a top level block.
    */
   private static int findNearestTopLevel(WorkArea wa)
   {
      // walk backwards up the stack
      for (int i = wa.blocks.size() - 1; i >= 0; i--)
      {
         BlockDefinition blk = wa.blocks.elementAt(i);
         
         if (blk.topLevel)
         {
            return i;
         }
      }
      
      return -1;
   }
   
   /**
    * Get the rollup value for the enclosing block. If <code>true</code>,
    * then the current block's exit will cause the enclosing (containing)
    * block's interactions counter to be increased by the current block's
    * interactions counter. Otherwise, no aggregation will occur.
    *
    * @param    wa
    *           Context local work area.  Must not be {@code null}.
    *
    * @return   The current block's rollup value.
    */
   private static boolean isEnclosingRollup(WorkArea wa)
   {
      int parent = wa.blocks.size() - 2;
      
      // by default, the rollup value is true
      if (parent < 0)
         return true;
      
      BlockDefinition blk = wa.blocks.elementAt(parent);
      
      return blk.rollup;
   }
   
   /**
    * Resets all registered <code>Resettable</code> objects or makes them
    * clear their lists of changes that should be reset.
    *
    * @param    wa
    *           The {@link WorkArea} instance.
    * @param    clearOnly
    *           If <code>true</code> then clear the lists of changes that should
    *           be reset, otherwise perform reset of these changes.
    */
   private static void processResettables(WorkArea wa, boolean clearOnly)
   {
      for (int i = 0; i < wa.resettables.size(); i++)
      {
         Resettable resettable = wa.resettables.get(i);
         
         resettable.resetState(clearOnly);
      }
   }
   
   /**
    * Adds the object into a scoped dictionary of objects requiring UNDO support. This registry
    * is used to backup the objects at the beginning of transactions or sub-transactions.
    * <p>
    * These registrations naturally are removed when the associated scope is removed. However,
    * the global scope is long-lived. Special care is necessary to prevent the same instance of
    * global shared variables being added multiple times.
    *
    * @param    undoTarget
    *           The object reference to be rolled back on failure.  This
    *           cannot be <code>null</code>.
    * @param    transLevel
    *           The transaction level to set for this undoable.
    * @param    persistent
    *           Flag indicating if the undoable is defined in a persistent procedure
    */
   private static void registerUndo(LazyUndoable undoTarget, int transLevel, boolean persistent)
   {
      if (undoTarget == null)
      {
         return;
      }

      // if we are starting a persistent procedure, then the undoables must register all
      // the way up the stack, and not just until the defining scope is reached: this is
      // because an undo can be trigger by a previous block, outside of the persistent
      // procedure, after the persistent procedure has finished its external scope
      
      // the transLevel is subtracted 1 because we register in the current tx block
      undoTarget.markUndoable(persistent ? -1 : transLevel - 1);
   }
   
   /**
    * Search the stack of scopes and return the type of first top level block found.
    *
    * @param    wa
    *           Context local work area.
    *
    * @return   The nearest enclosing top level block or {@link BlockType#UNKNOWN} if
    *           there is no block marked as a top level block.
    */
   private static BlockType getNearestTopLevelType(WorkArea wa)
   {
      BlockDefinition top = nearestTopLevel(wa);
      return (top == null) ? UNKNOWN : top.type;
   }
   
   /**
    * Search the stack of scopes and return the first top level block found.
    *
    * @param    wa
    *           Context local work area.
    *
    * @return   The nearest enclosing top level block or <code>null</code> if
    *           there is no block marked as a top level block.
    */
   private static BlockDefinition nearestTopLevel(WorkArea wa)
   {
      Stack<BlockDefinition>        blocks = wa.blocks;
      
      for (int idx = blocks.size() - 1; idx >= 0; idx--) 
      {
         BlockDefinition blk = blocks.get(idx);
         
         if (blk.topLevel)
         {
            // done!
            return blk;
         }
      }
      return null;
   }
   
   /**
    * Returns the index of the full transaction block, or -1 if none exists.
    *
    * @param    wa
    *           Context local work area. Must not be {@code null}.
    *
    * @return   The index of the full transaction block, or -1 if none exists.
    */
   private static int getFullTransactionBlock(WorkArea wa)
   {
      Stack<BlockDefinition> blocks = wa.blocks;
      
      for (int i = 0; i < blocks.size(); i++)
      {
         if (blocks.get(i).full)
         {
            return i;
         }
      }
      
      return -1;
   }
   
   /**
    * Search the stack of scopes and return the level of the first external
    * block found.
    *
    * @param    wa
    *           Context local work area. Must not be {@code null}.
    *
    * @return   The stack level of the nearest enclosing external block or
    *           -1 if there is no block marked as an external block. Level
    *           number is 1-based starting from the outermost scope.
    */
   private static int findNearestExternal(WorkArea wa)
   {
      ListIterator<BlockDefinition> iter =
         wa.blocks.listIterator(wa.blocks.size());

      int level = wa.blocks.size() + 1;

      // walk backwards up the stack of block definitions
      while (iter.hasPrevious())
      {
         BlockDefinition blk = iter.previous();
         level--;

         if (blk.external)
         {
            return level;
         }
      }

      return -1;
   }
   
   /**
    * This method is called after the initialization of a FOR or FOR EACH
    * block is finished. It will save all the {@link QueryOffEndListener}s
    * associated with the registered {@link WorkArea#offEndQueries queries}.
    *
    * @param    wa
    *           Context local work area. Must not be {@code null}.
    */
   private static void stopOffEndRegistration(WorkArea wa)
   {
      Set<QueryOffEndListener> listeners = wa.offEndListeners.peek();

      Set<P2JQuery> queries = wa.offEndQueries.pop();
      if (!queries.isEmpty())
      {
         if (listeners == Collections.EMPTY_SET)
         {
            listeners = Collections.newSetFromMap(new IdentityHashMap<>());
            wa.offEndListeners.pop();
            wa.offEndListeners.push(listeners);
         }
         
         for (P2JQuery query : queries)
         {
            ArrayList<QueryOffEndListener> ql = query.getOffEndListeners();
            for (int i = 0; i < ql.size(); i++)
            {
               listeners.add(ql.get(i));
            }
         }
      }

      wa.offEndRegistration = false;
   }
   
   /**
    * Register the passed query for query off-end events. The passed queries
    * are all the queries created in the init method for a FOR or FOR EACH
    * block.
    * <p>
    * These queries will be able to end the loop when the last record is 
    * reached, by overriding any user-defined END-KEY action. The action 
    * wich is executed is the default UNDO, LEAVE action for END-KEY.
    * 
    * @param    wa
    *           Context local work area. Must not be {@code null}.
    * @param    query
    *           The query which needs to be registered.
    */
   private static void registerOffEndQuery(WorkArea wa, P2JQuery query)
   {
      if (wa.offEndRegistration)
      {
         wa.offEndQueries.peek().add(query);
      }
   }
   
   /**
    * Check if the given listener is registered for query off-end events and
    * can override the default END-KEY action (which is UNDO, LEAVE), when
    * the current block is FOR or FOR EACH.
    * 
    * @param    wa
    *           Context local work area. Must not be {@code null}.
    * @param    listener
    *           A listener to be checked if is registered for off-end events.
    * 
    * @return  <code>true</code> if the listener is registered for query 
    *          off-end event.
    */
   private static boolean isOffEndListener(WorkArea wa, QueryOffEndListener listener)
   {
      Set<QueryOffEndListener> listeners = wa.offEndListeners.peek();
      
      return listeners.contains(listener);         
   }
   
   /**
    * Iterate all registered off-end listeners and call the 
    * {@link QueryOffEndListener#initialize() initialize} or 
    * {@link QueryOffEndListener#finish() finish} method, depending on the 
    * flag.
    * 
    * @param    wa
    *           Context local work area. Must not be {@code null}.
    * @param    init
    *           <code>true</code> if the listeners are initialized; else, the
    *           {@link QueryOffEndListener#finish()} method is called. 
    */
   private static void processOffEndListeners(WorkArea wa, boolean init)
   {
      Set<QueryOffEndListener> listeners = wa.offEndListeners.peek();
      
      for (QueryOffEndListener qoel : listeners)
      {
         if (init)
         {
            qoel.initialize();
         }
         else
         {
            qoel.finish();
         }
      }
   }
   
   /**
    * Get the topmost set of off-end {@link QueryOffEndListener listeners}.
    * 
    * @param    wa
    *           Context local work area. Must not be {@code null}.
    * @param    clear
    *           <code>true</code> if the top-most off-end listeners set
    *           should be cleared.
    */
   private static Set<QueryOffEndListener> getOffEndListeners(WorkArea wa, boolean clear)
   {
      Set<QueryOffEndListener> top = wa.offEndListeners.peek();
      try
      {
         if (!top.isEmpty())
         {
            Set<QueryOffEndListener> res = Collections.newSetFromMap(new IdentityHashMap<>());
            res.addAll(top);
            return res;
         }
         else
         {
            return Collections.emptySet();
         }
      }
      finally
      {
         if (clear)
         {
            top.clear();
         }
      }
   }
                                     
   /**
    * Register the single output parameter assigner for the current block.
    * This object will be deregistered by the {@link BlockManager} when it is
    * time to process assign-backs from function/procedure output parameters
    * to the database record fields with which they are associated.
    * 
    * @param    wa
    *           Context local work area. Must not be {@code null}.
    * @param    opa
    *           An output parameter assigner.
    * 
    * @throws  IllegalStateException
    *          if a second output parameter assigner is registered for a block
    *          before the current one is deregistered.  There can be only one.
    */
   private static void registerOutputParameterAssigner(WorkArea wa, OutputParameterAssigner opa)
   {
      // peek at the current block def on the top of the stack
      BlockDefinition blk = wa.blocks.peek();
      
      if (blk.outputParmAssigner != null)
      {
         throw new IllegalStateException(
            "Illegal attempt to register output parameter assigner");
      }
      
      blk.outputParmAssigner = opa;
   }
   
   /**
    * Defer handling of an exception thrown by a commit/rollback notification
    * callback on a registered {@link Commitable}, so that iteration or pop
    * scope processing can be continued without interruption.  This ensures
    * that all <code>Commitable</code>s registered after the errant one still
    * receive their commit/rollback notifications, and that (in the case of
    * popping a scope), registered {@link Scopeable}s still receive their
    * scope finished notifications.
    * <p>
    * If no error is already pending, <code>exc</code> is stored, to be thrown
    * upon conclusion of processing the current block scope or iteration.  If
    * an error is already pending, <code>exc</code> is discarded.  In either
    * case, <code>exc</code> is logged if error-level logging is enabled.
    *
    * @param    wa
    *           Context local work area. Must not be {@code null}.
    * @param   action
    *          A brief description of the action occurring at the time the
    *          error was encountered, such as "commit" or "master rollback".
    * @param   thr
    *          The error caught during processing of a commit or rollback
    *          notification.
    * @param   silent
    *          If <code>true</code> then exception is not logged.
    *
    * @see     WorkArea#handleDeferredError
    */
   private static void deferError(WorkArea wa, String action, Throwable thr, boolean silent)
   {
      // when intermixing Progress-specific and non-Progress aware runtime
      // layers, it is possible for ConditionException causes to be hidden
      // within other exception types; this processing follows the causal
      // chain and un-hides any ConditionException that was obscured
      Throwable cause       = thr;
      Throwable condition   = thr;
      boolean   interrupted = false;
      boolean   silentUnwind = false;

      while (cause != null)
      {
         if (cause instanceof ConditionException)
         {
            condition = cause;
         }

         if (cause instanceof InterruptedException)
         {
            interrupted = true;
         }

         if (cause instanceof SilentUnwindException)
         {
            silentUnwind = true;
         }

         cause = cause.getCause();
      }

      if (!silent)
      {
         // if the condition is a STOP condition generated by a thread
         // interruption via CTRL+C, then we deferr the error, but change the
         // log level from SEVERE to FINE
         Level logLvl = silentUnwind ||
               (condition instanceof StopConditionException &&
                     interrupted) ? Level.FINER
               : Level.SEVERE;

         if (LOG.isLoggable(logLvl))
         {
            String msg = "Error processing " + action + "; ";
            if (wa.deferredError == null)
            {
               msg += "deferring throw until block/iteration completes";
            }
            else
            {
               msg += "deferred error already pending; new error discarded";
            }

            log(logLvl, "deferError", msg, thr);
         }
      }

      if (wa.deferredError == null)
      {
         // save the error
         wa.deferredError = condition;
      }
   }

   /**
    * Notify all registered {@link BatchListener} objects that a batch is 
    * either starting or stopping.
    *
    * @param    wa
    *           Context local work area. Must not be {@code null}.
    * @param    start
    *           <code>true</code> if this is the start of a batch, otherwise
    *           <code>false</code>.
    */
   private static void processBatchNotifications(WorkArea wa, boolean start)
   {
      if (LOG.isLoggable(Level.FINE))
      {
         log(Level.FINE, 
             "processBatchNotifications",
             "start_batch = " + Boolean.toString(start));
      }
      
      for (BatchListener listener : wa.batchListeners)
      {
         listener.batchNotify(start);
      }
   }
   
   /**
    * Allows a loop iteration event to occur in the transaction manager,
    * which triggers commit processing before the next iteration of the
    * loop begins.
    * <p>
    * This hook is called at the top of the loop, just after the loop test
    * has been passed.  If the loop test fails, then this will not be called.
    * This code will be called inside the block itself, as the first thing
    * that is done.
    * <p>
    * Note that this is only called on the second and subsequent iterations
    * of the block, no matter how the iteration occurred (via natural control
    * flow through the bottom of an iterating block OR via a Java
    * <code>continue</code> statement that caused a jump to the top of the
    * loop).  Clearing the retry logic and rollback logic is important 
    * whether or not this is a loop since non-loops can be retried (via
    * condition processing) or iterated via <code>continue</code>.
    * <p>
    * All registered {@link Finalizable Finalizables} are notified of the
    * iteration via their {@link Finalizable#iterate} callback method.
    * <p>
    * If the block is not a loop, commit/rollback processing and 
    * <code>Finalizable</code> processing will NOT be executed.
    * <p>
    * This must never be called outside of a bracketed pair of 
    * {@link #pushScope} and {@link #popScope} calls.
    * <p>
    * This method will honor any pending thread interruption by throwing
    * a {@link StopConditionException} using {@link #honorStopCondition}
    *
    * @param    wa
    *           The {@link WorkArea} instance.
    *
    * @throws   StopConditionException
    *           If the current thread's was previously interrupted.
    */
   private static void iterateWorker(WorkArea wa)
   throws StopConditionException
   {
      if (LOG.isLoggable(Level.FINE))
      {
         log(Level.FINE, "iterateWorker", null);
      }
      
      // peek at the block def on the top of the stack
      BlockDefinition blk = wa.blocks.peek();
      
      // transaction related processing
      if (blk.loop && wa.isTransaction())
      {
         // is there a pending rollback or have we rolled back in this scope?
         if (wa.isRollbackPending())
         {
            // determine if a pending rollback refers to this scope
            if (rollbackCurrentScope(wa, wa.getRollbackScope()))
            {
               processRollback(wa, blk);
            }
         }
         else if (!blk.rolledBack)
         {
            // OK, this is a case where we need to COMMIT!
            processCommit(wa, blk);
         }
      }
      
      // reset our retry/commit logic
      blk.committed      = false;
      blk.retry          = false;
      blk.loopProtection = true;
      blk.hadPause       = false;
      blk.endkeyRetry    = false;
      blk.pendingBreak   = false;
      blk.wasRetried     = false;
      blk.rolledBack     = false;
      blk.isUndoLevel    = false;
      blk.ilpCount       = 0;
      
      // notify listeners of the iteration
      if (blk.loop)
      {
         processFinalizables(wa, blk, true);
      }
      
      try
      {
         // FINALLY block support (this MUST be AFTER commit/rollback and finalizables are processed); at
         // this location a full transaction still appears to be open in FWD but in the 4GL will report
         // TRANSACTION == false for the full-transaction level (sub-transaction levels still show being
         // inside the transaction); this means we must force a save/clear/restore of the transaction state
         // for the duration of the FINALLY block execution
         try
         {            
            processTerminationBlock(wa, blk, blk.finallyBlock);
         }
         finally
         {
            blk.finallyBlock = null;
         }
      }
      finally
      {
         wa.handleDeferredError();
      }
   }
   
   /**
    * Creates or updates the backup set for all objects needing rollback 
    * support in the current block, if a transaction is active.  This is
    * split up from {@link #pushScope} and {@link #blockSetup} due to the
    * semantics of Progress in which the backup set is created or refreshed
    * after the loop control variables have been modified.
    *
    * @param    wa
    *           The {@link WorkArea} instance.
    * @param    blk
    *           The block for which a backup must be made or <code>null</code>
    *           to use the current block on top of the stack.
    */
   private static void backupWorker(WorkArea wa, BlockDefinition blk) 
   {
      // read the block def at the top of the stack
      if (blk == null)
      {
         blk = wa.blocks.peek();
      }
      
      // transactions and sub-transactions trigger backup processing (blk.undoables will be
      // null when a transaction is not active OR when in a NO_TRANSACTION block OR when in
      // a transaction and no changes have been registered)
      if (blk.undoables != null)
      {
         // this is an iteration, update the backup set for the next pass
         updateBackupSet(blk.undoables);
      }
   }
   
   /**
    * Determine if the current scope represents a transaction or 
    * sub-transaction and if so, whether the given level explicitly refers
    * to this scope OR if the given level is -1, then this implicitly refers
    * to this scope.
    * <p>
    * Even if the level is not -1 (it must refer to an enclosing block), if
    * the given block is a full transaction block (the block at which the
    * transaction is scoped), then the rollback must occur at this point
    * instead of deferring the rollback until the transaction is no longer
    * active.
    *
    * @param    wa
    *           The {@link WorkArea} instance.
    * @param    level
    *           The stack level of the scope to be rolled back.
    *
    * @return   <code>true</code> if the above conditions hold.
    */
   private static boolean rollbackCurrentScope(WorkArea wa, int level)
   {
      // peek at the current block def on the top of the stack
      BlockDefinition blk = wa.blocks.peek();
      
      return !isNoTransactionLevel(blk.level) &&
             (level == -1 || level == (wa.blocks.size() - 1) || blk.full);
   }
   
   /**
    * Find the nearest enclosing block associated with the given label.
    *
    * @param    label
    *           The block label to search for.
    *
    * @return   The block found or <code>null</code> if there is no enclosing
    *           block with that label.
    */
   @SuppressWarnings("unused")
   private static BlockDefinition findBlock(String label)
   {
      if (label != null)
      {
         WorkArea wa = workArea.obtain();
         
         int sz = wa.blocks.size(); 
         
         // walk backwards up the stack
         for (int i = sz - 1; i >= 0; i--)
         {
            BlockDefinition blk = wa.blocks.elementAt(i);
            
            if (blk.label.equals(label))
            {
               return blk;
            }
         }
      }
      
      return null;
   }
   
   /**
    * Find the stack level of the nearest enclosing block associated with the
    * given label.
    *
    * @param    wa
    *           Context local work area.
    * @param    label
    *           The block label to search for.
    *
    * @return   The 0-based stack level for the block that was found or -1 if
    *           there is no enclosing block with that label.
    */
   private static int findStackLevel(WorkArea wa, String label)
   {
      if (label != null)
      {
         int sz = wa.blocks.size(); 
         
         // walk backwards up the stack
         for (int i = sz - 1; i >= 0; i--)
         {
            BlockDefinition blk = wa.blocks.elementAt(i);
            
            if (blk.label.equals(label))
            {
               return i;
            }
         }
      }
      
      return -1;
   }

   /**
    * Worker that actually implements rollback by copying the backed up
    * values into the original reference.  Once all externally driven
    * rollback processing (the {@link Undoable} interface) is done,
    * notifications are provided to all registered {@link Commitable}
    * instances in the current block's commitable list.
    * <p>
    * If the block is also the full transaction block, the last thing
    * done is a single master rollback call to the instance registered
    * for the {@link #registerTransactionCommit}.
    *
    * @param    wa
    *           The {@link WorkArea} instance.
    * @param    blk
    *           The block to be rolled back.
    */
   private static void processRollback(WorkArea wa, BlockDefinition blk)
   {
      if (blk.commitables == null)
      {
         return;
      }

      if (FwdServerJMX.JMX_DEBUG)
      {
         ROLLBACK.timer(() -> processRollbackImpl(wa, blk));
      }
      else
      {
         processRollbackImpl(wa, blk);
      }
   }

   /**
    * Worker that actually implements rollback by copying the backed up
    * values into the original reference.  Once all externally driven
    * rollback processing (the {@link Undoable} interface) is done,
    * notifications are provided to all registered {@link Commitable}
    * instances in the current block's commitable list.
    * <p>
    * If the block is also the full transaction block, the last thing
    * done is a single master rollback call to the instance registered
    * for the {@link #registerTransactionCommit}.
    *
    * @param    wa
    *           The {@link WorkArea} instance.
    * @param    blk
    *           The block to be rolled back.
    */
   private static void processRollbackImpl(WorkArea wa, BlockDefinition blk)
   {
      if (LOG.isLoggable(Level.FINE))
      {
         log(Level.FINE, "processRollback", null);
      }

      // mark the start of the critical section
      CriticalSectionManager.beginSection();

      try
      {
         try
         {
            wa.nowIterating = blk.commitables;

            // notify commitables
            ArrayList<Commitable> commitables = blk.commitables;
            for (int i = 0; i < commitables.size(); i++)
            {
               Commitable target = commitables.get(i);
               try
               {
                  target.rollback(blk.full);
               }
               catch (Throwable thr)
               {
                  deferError(wa, "rollback", thr, false);
               }
            }
         }
         finally
         {
            wa.nowIterating = null;
         }

         // process the master rollback
         if (blk.full)
         {
            wa.notifyMasterCommit(true);
         }

         // mark the work area to note that rollback has been completed, which
         // clears the deferred rollback state;  subsequent calls to the
         // isRollbackPending() method will return false.
         wa.rollbackScope   = -1;
         wa.rollbackPending = false;
         wa.rollbackPendingCause = null;

         // process the externally driven undo
         if (blk.undoables != null)
         {
            ArrayList<UndoablePair> undoables = blk.undoables;
            for (int i = 0; i < undoables.size(); i++)
            {
               UndoablePair next = undoables.get(i);
               next.src.rollback(next.copy);
            }
         }
      }
      finally
      {
         // stop the critical section
         CriticalSectionManager.endSection();
      }
   }

   /**
    * Worker that notifies all objects in the commit list that they must
    * commit (persist any state).
    * <p>
    * On the last iteration of a loop or block which ends in success (in
    * which the flow of control reaches the natural end of the block), both
    * will be called in succession.  However, other forms of exit which are
    * still considered successful (i.e. return and break which are not the
    * result of condition processing) will only execute <code>popScope</code>
    * and <code>iterate</code> will be bypassed.
    * <p>
    * It is important to note that the list of commitables in blk is
    * iterated in reverse order from that in which they are registered with
    * the <code>TransactionManager</code>.  This is done to ensure that
    * database transactions (which are registered before their corresponding
    * buffers) are always committed after all buffers are committed.
    *
    * @param    wa
    *           The {@link WorkArea} instance.
    * @param    blk
    *           The scope to be committed.
    */
   private static void processCommit(WorkArea wa, BlockDefinition blk)
   {
      if (blk.commitables == null)
      {
         return;
      }

      if (FwdServerJMX.JMX_DEBUG)
      {
         COMMIT.timer(() -> processCommitImpl(wa, blk));
      }
      else
      {
         processCommitImpl(wa, blk);
      }
   }

   /**
    * Worker that notifies all objects in the commit list that they must
    * commit (persist any state).
    * <p>
    * On the last iteration of a loop or block which ends in success (in
    * which the flow of control reaches the natural end of the block), both
    * will be called in succession.  However, other forms of exit which are
    * still considered successful (i.e. return and break which are not the
    * result of condition processing) will only execute <code>popScope</code>
    * and <code>iterate</code> will be bypassed.
    * <p>
    * It is important to note that the list of commitables in blk is
    * iterated in reverse order from that in which they are registered with
    * the <code>TransactionManager</code>.  This is done to ensure that
    * database transactions (which are registered before their corresponding
    * buffers) are always committed after all buffers are committed.
    *
    * @param    wa
    *           The {@link WorkArea} instance.
    * @param    blk
    *           The scope to be committed.
    */
   private static void processCommitImpl(WorkArea wa, BlockDefinition blk)
   {
      if (LOG.isLoggable(Level.FINE))
      {
         log(Level.FINE, "processCommit", null);
      }

      if (blk.committed || !wa.processUndoables(blk))
      {
         // already done or nothing to do, just return
         return;
      }

      try
      {
         wa.nowIterating = blk.commitables;

         ArrayList<Commitable> commitables = blk.commitables;
         for (int i = 0; i < commitables.size(); i++)
         {
            Commitable target = commitables.get(i);
            try
            {
               target.commit(blk.full);
            }
            catch (Throwable thr)
            {
               deferError(wa, "commit", thr, false);
            }
         }
      }
      finally
      {
         wa.nowIterating = null;
      }

      // process the master commit
      if (blk.full)
      {
         wa.notifyMasterCommit(false);
      }

      // remember that we have already handled the commit
      blk.committed = true;
   }
   
   /**
    * Worker that notifies all objects in the finalize list that the block
    * is being exited or iterated.
    *
    * @param    wa
    *           Context-local work area.
    * @param    blk
    *           The scope to be finalized.
    * @param    iterate
    *           <code>true</code> to call each target's <code>iterate</code>
    *           method or <code>false</code> to call <code>finished</code>.
    */
   private static void processFinalizables(WorkArea        wa,
                                           BlockDefinition blk,
                                           boolean         iterate)
   {
      if (LOG.isLoggable(Level.FINE))
      {
         log(Level.FINE,
             "processFinalizables",
             "iterate = " + Boolean.toString(iterate));
      }

      Object prevReferent = wa.pm.getProcessedProcedure();
      Set<Finalizable>[] fini = blk.finalizables;
      
      if (fini != null)
      {
         Object referent = null;
         boolean delayed = false;
         // can not call for start, as the procedure is not yet initialized
         if (!iterate && isTopLevelBlock(wa) && !isGlobalBlock(wa))
         {
            referent = wa.pm._thisProcedure();
            wa.pm.setProcessedProcedure(referent);

            if (getNearestTopLevelType(wa) == EXTERNAL_PROC)
            {
               delayed = wa.pm.isThisProcedurePersistent() && !wa.pm._isDelayedDelete(referent);
            }
         }

         try
         {
            wa.nowIterating = fini;
            wa.op = iterate ? OP_ITERATE : OP_FINISHED;
            
            for (int i = 0; i < fini.length; i++)
            {
               Set<Finalizable> l = fini[i];
               if (l == null)
               {
                  continue;
               }
               
               for (Finalizable target : l)
               {
                  try
                  {
                     if (iterate)
                     {
                        target.iterate();
                     }
                     else
                     {
                        target.finished();
                        
                        if (delayed)
                        {
                           wa.pm.addFinalizable(referent, target);
                        }
                        else
                        {
                           target.deleted();
                        }
                     }
                  }
                  catch (Throwable thr)
                  {
                     deferError(wa, (iterate ? "iterate" : "finished"), thr, false);
                  }
               }
            }
         }
         
         finally
         {
            wa.nowIterating = null;
            wa.pm.setProcessedProcedure(prevReferent);
            wa.op = OP_NONE;
         }
      }
      
      // process the master finish/iterate
      if (blk.full)
      {
         wa.notifyMasterFinish(iterate);
      }
   }
   
   /**
    * Worker that notifies all objects in the finalize list that the block is being entered
    * (the {@code Finalizable.entry()} method will be called.  No persistent changes to the
    * transaction or block state will occur.
    *
    * @param    wa
    *           The {@link WorkArea} instance.
    * @param    blk
    *           The scope to be retried.
    */
   private static void processEntry(WorkArea wa, BlockDefinition blk)
   {
      if (LOG.isLoggable(Level.FINE))
      {
         log(Level.FINE, "processEntry", null);
      }

      Set<Finalizable>[] fini = blk.finalizables;
      if (fini != null)
      {
         try
         {
            wa.nowIterating = fini;
            wa.op = OP_ENTRY;
            
            for (int i = 0; i < fini.length; i++)
            {
               Set<Finalizable> l = fini[i];
               
               if (l == null)
               {
                  continue;
               }
               
               for (Finalizable target : l)
               {
                  try
                  {
                     target.entry();
                  }
                  catch (Throwable thr)
                  {
                     deferError(wa, "entry", thr, false);
                  }
               }
            }
         }
         
         finally
         {
            wa.nowIterating = null;
            wa.op = OP_NONE;
         }
      }
   }
      
   /**
    * Worker that notifies all objects in the finalize list that the block
    * is being retried.  In addition, any pending rollback for this block
    * is honored here.
    *
    * @param    wa
    *           The {@link WorkArea} instance.
    * @param    blk
    *           The scope to be retried.
    */
   private static void processRetry(WorkArea wa, BlockDefinition blk)
   {
      if (LOG.isLoggable(Level.FINE))
      {
         log(Level.FINE, "processRetry", null);
      }

      // transaction related processing
      if (wa.isTransaction())
      {
         // is there a pending rollback or have we rolled back in this scope?
         if (wa.isRollbackPending())
         {
            // determine if a pending rollback refers to this scope
            if (rollbackCurrentScope(wa, wa.getRollbackScope()))
            {
               // note that it is good that this method does NOT set the
               // blk.rolledBack flag as otherwise a commit could not occur
               // at the end of this retry iteration
               processRollback(wa, blk);
            }
         }
      }
      
      Set<Finalizable>[] fini = blk.finalizables;
      if (fini != null)
      {
         try
         {
            wa.nowIterating = fini;
            wa.op = OP_RETRY;
            
            for (int i = 0; i < fini.length; i++)
            {
               Set<Finalizable> l = fini[i];

               if (l == null)
               {
                  continue;
               }
               
               for (Finalizable target : l)
               {
                  try
                  {
                     target.retry();
                  }
                  catch (Throwable thr)
                  {
                     deferError(wa, "retry", thr, false);
                  }
               }
            }
         }
         
         finally
         {
            wa.nowIterating = null;
            wa.op = OP_NONE;
         }
      }
      
      // process the master retry
      if (blk.full)
      {
         wa.notifyMasterRetry();
      }
      
      try
      {
         // FINALLY block support (this MUST be AFTER commit/rollback and finalizables are processed); at
         // this location a full transaction still appears to be open in FWD but in the 4GL will report
         // TRANSACTION == false for the full-transaction level (sub-transaction levels still show being
         // inside the transaction); this means we must force a save/clear/restore of the transaction state
         // for the duration of the FINALLY block execution
         try
         {            
            processTerminationBlock(wa, blk, blk.finallyBlock);
         }
         finally
         {
            blk.finallyBlock = null;
         }
      }
      finally
      {
         wa.handleDeferredError();
      }
   }

   /**
    * Get the BlockDefinition instance of the nearest block of one of the
    * following types:
    * <ul>
    *    <li>{@link BlockType#EXTERNAL_PROC external procedure}</li>
    *    <li>{@link BlockType#INTERNAL_PROC internal procedure}</li>
    *    <li>{@link BlockType#FUNCTION user-defined function}</li>
    *    <li>{@link BlockType#TRIGGER trigger}</li>
    *    <li>{@link BlockType#DATABASETRIGGER trigger}</li>
    *    <li>{@link BlockType#EDITING editing}</li>
    * </ul> 
    * 
    * @param    wa
    *           Context local work area. Must not be {@code null}.
    *
    * @return   See above.
    */
   private static BlockDefinition nearestExecutingBlock(WorkArea wa)
   {
      Stack<BlockDefinition>        blocks = wa.blocks;
      ListIterator<BlockDefinition> iter   = blocks.listIterator(blocks.size());
      
      // walk backwards up the stack of block definitions 
      while (iter.hasPrevious())
      {
         BlockDefinition blk = iter.previous();
         
         if (blk.type == EXTERNAL_PROC || 
             blk.type == INTERNAL_PROC || 
             blk.type == FUNCTION      ||
             blk.type == TRIGGER       ||
             blk.type == EDITING       ||
             blk.type == DATABASETRIGGER)
         {
            // done!
            return blk;
         }
      }
      
      return null;
   }

   /**
    * Deregister the output parameter assigner for the current block, if any.
    * It is expected that only the {@link BlockManager} will invoke this
    * method.
    * 
    * @param    wa
    *           Context local work area. Must not be {@code null}.
    *
    * @return  Output parameter assigner for this block, or <code>null</code>
    *          if none was registered.
    */
   private static OutputParameterAssigner deregisterOutputParameterAssigner(WorkArea wa)
   {
      // peek at the current block def on the top of the stack
      BlockDefinition blk = wa.blocks.peek();
      
      OutputParameterAssigner opa = blk.outputParmAssigner;
      
      blk.outputParmAssigner = null;
      
      return opa;
   }
   
   /**
    * Adds the given object into the list of objects which require undo
    * notifications for the next block that is opened.
    * <p>
    * This form of registration is useful for objects whose natural lifetime
    * is scoped to the top-level block.  In particular, it is necessary for
    * objects that are constructed during a class' constructor/initializer
    * (before the external procedure block has opened).  Such objects cannot
    * simply register since their construction is not actually enclosed by the
    * block.
    * <p>
    * When the deferred registration occurs, it will NOT be global and will
    * NOT be added to registered Commitables.
    * <p>
    * This notification will occur whether or not a transaction is active.
    * <p>
    * These registrations naturally are removed when the associated scope
    * is removed.
    * <p>
    * The given object will only be added if it is not already in the list.
    *
    * @param    wa
    *           Context local work area. Must not be {@code null}.
    * @param    target
    *           The object references to be registered.  This cannot be
    *           <code>null</code>.
    */
   private static void registerNext(WorkArea wa, LazyUndoable... target)
   {
      registerAt(wa, ScopeLevel.NEXT, target);
   }
   
   /**
    * Adds the given object into the list of objects which require undo
    * notifications for the next block that is opened.
    * <p>
    * This form of registration is useful for objects whose natural lifetime
    * is scoped to the top-level block.  In particular, it is necessary for
    * objects that are constructed during a class' constructor/initializer
    * (before the external procedure block has opened).  Such objects cannot
    * simply register since their construction is not actually enclosed by the
    * block.
    * <p>
    * When the deferred registration occurs, it will NOT be global and will
    * NOT be added to registered Commitables.
    * <p>
    * This notification will occur whether or not a transaction is active.
    * <p>
    * These registrations naturally are removed when the associated scope
    * is removed.
    * <p>
    * The given object will only be added if it is not already in the list.
    *
    * @param    wa
    *           Context local work area. Must not be {@code null}.
    * @param    scope
    *           The scope where to register the undoable targets.
    * @param    target
    *           The object references to be registered.  This cannot be
    *           <code>null</code>.
    */
   private static void registerAt(WorkArea wa, ScopeLevel scope, LazyUndoable... target)
   {
      if (wa.deferredUndoables == null)
      {
         wa.deferredUndoables = new IdentityHashMap<>();
      }
      
      // prevent ConcurrentModificationException
      if (wa.nowIterating == wa.deferredUndoables)
      {
         if (LOG.isLoggable(Level.WARNING))
         {
            log(Level.WARNING,
                "registerNext",
                "Illegal attempt to register Undoable",
                new Throwable());
         }
         
         return;
      }
      
      if (target.length == 0)
      {
         if (wa.deferredDynExt == null)
         {
            wa.deferredDynExt = Collections.newSetFromMap(new IdentityHashMap<>());
         }
         
         wa.deferredDynExt.add((BaseDataType[]) target);
      }
      else
      {
         boolean global = (scope == ScopeLevel.GLOBAL);
         
         for (LazyUndoable u : target)
         {
            wa.deferredUndoables.put(u, null);
   
            if (wa.trackUndoables)
            {
               if (global)
               {
                  u.setGlobal(true);
               }
               else
               {
                  wa.trackedUndoables.put(u, null);
               }
            }
         }
      }
   }
   
   /**
    * Register the specified targets as undoables.  This will mark the object as undoable and save
    * the current tx level, regardless if a tx is active or not.
    * 
    * @param    wa
    *           Context local work area. Must not be {@code null}.
    * @param    undoTargets
    *           List of object references to be registered.  This cannot be
    *           <code>null</code>.
    */
   private static void registerCurrent(WorkArea wa, LazyUndoable... undoTargets)
   {
      boolean persistent = wa.blocks.peek().external && wa.pm.isThisProcedurePersistent();
      
      registerAt(wa, wa.txNestingLevel, persistent, undoTargets);
   }
   
   /**
    * Adds the object into a scoped dictionary of objects requiring UNDO support. This registry
    * is used to backup the objects at the beginning of transactions or sub-transactions.
    * <p>
    * These registrations naturally are removed when the associated scope is removed. However,
    * the global scope is long-lived. Special care is necessary to prevent the same instance of
    * global shared variables being added multiple times.
    *
    * @param    wa
    *           Context local work area. Must not be {@code null}.
    * @param    transLevel
    *           The transaction level to set for this undoable.
    * @param    persistent
    *           Flag indicating if the undoable is defined in a persistent procedure
    * @param    undoTargets
    *           The object references to be rolled back on failure.  This
    *           cannot be <code>null</code>.
    */
   private static void registerAt(WorkArea        wa,
                                  int             transLevel, 
                                  boolean         persistent,
                                  LazyUndoable... undoTargets)
   {
      registerAt(wa, transLevel, persistent, null, undoTargets);
   }
   
   /**
    * Adds the object into a scoped dictionary of objects requiring UNDO support. This registry
    * is used to backup the objects at the beginning of transactions or sub-transactions.
    * <p>
    * These registrations naturally are removed when the associated scope is removed. However,
    * the global scope is long-lived. Special care is necessary to prevent the same instance of
    * global shared variables being added multiple times.
    *
    * @param    wa
    *           Context local work area. Must not be {@code null}.
    * @param    transLevel
    *           The transaction level to set for this undoable.
    * @param    persistent
    *           Flag indicating if the undoable is defined in a persistent procedure
    * @param    When not-null, persistent must be true and the referent represents the instance where to look 
    *           for undoables.  Otherwise, THIS-PROCEDURE will be used.
    * @param    undoTargets
    *           The object references to be rolled back on failure.  This
    *           cannot be <code>null</code>.
    */
   private static void registerAt(WorkArea        wa,
                                  int             transLevel, 
                                  boolean         persistent,
                                  Object          referent,
                                  LazyUndoable... undoTargets)
   {
      for (LazyUndoable undoTarget : undoTargets)
      {
         registerUndo(undoTarget, transLevel, persistent);
      }
      
      if (persistent)
      {
         if (referent == null)
         {
            referent = wa.pm._thisProcedure();
         }
         IdentityHashMap<LazyUndoable, Object> pundoables = wa.procUndoables.get(referent);
         if (pundoables == null)
         {
            wa.procUndoables.put(referent, pundoables = new IdentityHashMap<>());
         }

         // if this is an external block and this is a persistent procedure, then collect these
         for (LazyUndoable target : undoTargets)
         {
            pundoables.put(target, null);
         }
      }
   }
   
   /**
    * Enable tracking of all the undaobles defined in the next instantiating external program.
    *
    * @param    wa
    *           Context local work area. Must not be {@code null}.
    */
   private static void trackUndoables(WorkArea wa)
   {
      wa.trackUndoables = true;
      wa.trackedUndoables = new IdentityHashMap<>();
   }
  
   /**
    * Untrack (by removing from all the tx blocks) all the undoables defined and registered for
    * the specified external program.
    * 
    * @param    wa
    *           Context local work area. Must not be {@code null}.
    * @param    referent
    *           The external program instance.
    */
   private static void untrackUndoables(WorkArea wa, Object referent)
   {
      IdentityHashMap<LazyUndoable, Object> pundoables = wa.procUndoables.remove(referent);
      
      if (pundoables == null)
      {
         return;
      }
      
      // this is called when a persistent procedure gets deleted - once that happens, there is nothing left
      // in the procedure's state which can be undoable

      deregister(wa, pundoables);
   }
   
   /**
    * Rollback the transaction, at the {@link #APPSERVER_ROOT_BLOCK_INDEX root FWD block}.
    * 
    * @param    wa
    *           Context local work area. Must not be {@code null}.
    * @param    chained
    *           Flag indicating if a new transaction will be started.
    */
   private static void rollbackTx(WorkArea wa, boolean chained)
   {
      BlockDefinition blk = wa.blocks.get(APPSERVER_ROOT_BLOCK_INDEX);

      try
      {
         processRollback(wa, blk);
      }
      finally
      {
         endTx(wa, blk);

         if (chained)
         {
            beginTx(wa, blk);
         }
      }
   }
   
   /**
    * Commit the transaction, at the {@link #APPSERVER_ROOT_BLOCK_INDEX root FWD block}.
    * 
    * @param    wa
    *           Context local work area. Must not be {@code null}.
    * @param    chained
    *           Flag indicating if a new transaction will be started.
    */
   private static void commitTx(WorkArea wa, boolean chained)
   {
      BlockDefinition blk = wa.blocks.get(APPSERVER_ROOT_BLOCK_INDEX);

      try
      {
         processCommit(wa, blk);
      }
      finally
      {
         endTx(wa, blk);
         
         if (chained)
         {
            beginTx(wa, blk);
         }
      }
   }
   
   /**
    * Begin an explicit transaction, at the {@link #APPSERVER_ROOT_BLOCK_INDEX root FWD block}.
    * <p>
    * This explicitly marks this tx as being open by the 
    * {@link TransactionResource#getTxInitProcedure() TRANS-INIT-PROCEDURE}.
    * 
    * @param    wa
    *           The current {@link WorkArea} instance.  When <code>null</code>, resolve it.
    * @param    blk
    *           The {@link #APPSERVER_ROOT_BLOCK_INDEX root block}.  When <code>null</code>, 
    *           resolve it.
    */
   private static void beginTx(WorkArea wa, BlockDefinition blk)
   {
      if (wa == null)
      {
         wa = workArea.obtain();
      }
      if (blk == null)
      {
         blk = wa.blocks.get(APPSERVER_ROOT_BLOCK_INDEX);
      }

      TransactionImpl.setOpenByTxInitProc(true);
      
      blk.committed = false;
      blk.rolledBack = false;
      blk.full = true;
      blk.level = TRANSACTION;
      
      // there is only one level (the root one)
      wa.setTransactionLevel(0);
      
      // this is set only for the appserver target's block scope, but nothing else is done
      wa.txNestingLevel = wa.blocks.peek().level > 0 ? 1 : 0;
      blk.txNestingLevel = wa.txNestingLevel;
      
      // we need to notify the BufferManager that a tx is active
      BufferManager.get().beginTx(true, true, APPSERVER_ROOT_BLOCK_INDEX + 1);
   }
   
   /**
    * End an explicit transaction, at the {@link #APPSERVER_ROOT_BLOCK_INDEX root FWD block}.
    * 
    * @param    wa
    *           The current {@link WorkArea} instance.  When <code>null</code>, resolve it.
    * @param    blk
    *           The {@link #APPSERVER_ROOT_BLOCK_INDEX root block}.  When <code>null</code>, 
    *           resolve it.
    */
   private static void endTx(WorkArea wa, BlockDefinition blk)
   {
      if (wa == null)
      {
         wa = workArea.obtain();
      }
      if (blk == null)
      {
         blk = wa.blocks.get(APPSERVER_ROOT_BLOCK_INDEX);
      }
      
      try
      {
         BufferManager bufMan = BufferManager.get();
         bufMan.endTx(true, true);
         bufMan.endTxPost();
      }
      finally
      {
         blk.full = false;
         blk.level = NO_TRANSACTION;
         blk.txNestingLevel = -1;
         
         wa.setTransactionLevel(-1);
         wa.txNestingLevel = -1;
         
         TransactionImpl.setOpenByTxInitProc(false);
      }
   }
   
   /**
    * Worker that notifies all objects in the scopeable list that a block
    * scope is being started or ended.
    *
    * @param    wa
    *           Context-local work area.
    * @param    blk
    *           The scope to be finalized.
    * @param    start
    *           If <code>true</code> generate a start scope notification,
    *           otherwise generate an end scope notification.
    */
   private static void processScopeNotifications(WorkArea wa, BlockDefinition blk, boolean start)
   {
      if (LOG.isLoggable(Level.FINE))
      {
         log(Level.FINE, "processScopeNotifications", "start = " + start);
      }
      
      if (wa.pendingScopeables != null)
      {
         for (int i = 0; i < wa.pendingScopeables.length; i++)
         {
            Scopeable target = wa.pendingScopeables[i];
            if (target != null)
            {
               registerScopeable(wa, blk, target, false);
            }
         }
         wa.pendingScopeables = null;
      }
      
      // check scopeables inherited from previous block
      BlockDefinition prev = getBlock(wa, 1);
      int accumMgrId = ScopeId.ACCUMULATOR_MANAGER.ordinal();
      if (!blk.topLevel                                               && 
          prev != null                                                && 
          prev.scopeList != null                                      && 
          prev.scopeList[accumMgrId] != null                          &&
          (blk.scopeList == null || blk.scopeList[accumMgrId] == null))
      {
         registerScopeable(wa, blk, AccumulatorManager.getInstance(), false);
      }
      
      Object prevReferent = wa.pm.getProcessedProcedure();

      try
      {
         if (!(blk.topLevel || blk.scopeList != null))
         {
            // nothing to notify
            return;
         }
         
         Object referent;
         boolean delayed;
         // can not call for start, as the procedure is not yet initialized
         if (!start && blk.topLevel && !isGlobalBlock(wa))
         {
            referent = wa.pm._thisProcedure();
            wa.pm.setProcessedProcedure(referent);
            
            delayed = getNearestTopLevelType(wa) == EXTERNAL_PROC && 
                      wa.pm.isThisProcedurePersistent()           && 
                      !wa.pm._isDelayedDelete(referent);
         }
         else
         {
            delayed = false;
            referent = null;
         }
         
         if (start && blk.topLevel)
         {
            // procedure scopes need to be opened first
            wa.procScopeable.scopeStart(blk);
         }
         
         if (blk.scopeList != null)
         {
            wa.nowIterating = blk.scopeList;
            for (int i = 0; i < blk.scopeList.length; i++)
            {
               Scopeable target = blk.scopeList[i];
               if (target == null)
               {
                  continue;
               }
               
               try
               {
                  if (start)
                  {
                     target.scopeStart(blk);
                  }
                  else
                  {
                     target.scopeFinished();
                     
                     if (delayed)
                     {
                        wa.pm.addScopeable(referent, target);
                     }
                     else
                     {
                        target.scopeDeleted();
                     }
                  }
               }
               
               catch (Throwable thr)
               {
                  deferError(wa, (start ? "scopeStart" : "scopeFinished"), thr, false);
               }
            }
         }
         
         if (!start && blk.topLevel)
         {
            // procedure scopes need to be finished last
            wa.procScopeable.scopeFinished();
         }
      }
      
      finally
      {
         wa.nowIterating = null;
         wa.pm.setProcessedProcedure(prevReferent);
      }
   }
   
   /**
    * Walk through the current backup set and refresh the stored value for
    * each one.  This processing corresponds to the end of a sub-transaction
    * (see {@link #blockSetup}). 
    */
   private static void updateBackupSet(List<UndoablePair> backup)
   {
      for (UndoablePair undoPair : backup)
      {
         if (undoPair.src.changed())
         {
            undoPair.copy = (LazyUndoable) undoPair.src.deepCopy();
         }
      }
   }
   
   /**
    * Reports if the current block has the given property.
    *
    * @param    wa
    *           Context local work area.  Must not be {@code null}.
    * @param    prop
    *           The property bit flag to test.  Should be one of the constants
    *           <code>PROP_ERROR, PROP_ENDKEY, PROP_STOP or PROP_QUIT</code>.
    *
    * @return   <code>true</code> if the current block has the given 
    *           property bit set in its properties bitmap.
    */
   private static boolean hasProperty(WorkArea wa, int prop)
   {
      BlockDefinition blk = wa.blocks.peek();
      return (blk.props & prop) != 0;
   }
   
   /**
    * Write a formatted log entry with the error text and some state based 
    * on the current block.
    *
    * @param    lvl
    *           The logging level.
    * @param    location
    *           The event or call site from which the log entry was
    *           generated.
    * @param    txt
    *           The text to insert into the log.  May be <code>null</code>.
    */
   private static void log(Level           lvl,
                           String          location,
                           String          txt)
   {
      log(lvl, location, txt, null);
   }
      
   /**
    * Write a formatted log entry with the error text and some state based 
    * on the current block.
    *
    * @param    lvl
    *           The logging level.
    * @param    location
    *           The event or call site from which the log entry was
    *           generated.
    * @param    txt
    *           The text to insert into the log.  May be <code>null</code>.
    * @param    trw
    *           The throwable that caused this logging to occur or
    *           <code>null</code> if no exception is present.
    */
   private static void log(Level     lvl,
                           String    location,
                           String    txt,
                           Throwable trw)
   {
      if (LOG.isLoggable(lvl))
      {
         WorkArea        wa  = workArea.obtain();
         BlockDefinition blk = null;
         
         if (wa.depth() > 0)
         {
            blk = wa.blocks.peek();
         }
         
         String err = CentralLogger.generate("%s %s %s",
                                             wa.toString(),
                                             (blk == null ? "" : blk.toString()),
                                             (txt == null ? "" : txt));
                                         
         if (trw == null)
         {
            LOG.logp(lvl, "TransactionManager." + location, "", err);
         }
         else
         {
            LOG.logp(lvl, "TransactionManager." + location, "", err, trw);
         }
      }
   }
   
   /**
    * Helper to expose transaction state in a way that avoids context local lookups.
    */
   public static class TransactionHelper
   {
      /** Quick access to context-local error helper. */
      public final ErrorManager.ErrorHelper errHlp;
      
      /** The {@link WorkArea} instance. */
      private final WorkArea wa;
      
      /**
       * Create a new instance and associate the given WorkArea instance.
       * 
       * @param    wa
       *           The {@link WorkArea} instance.
       */
      public TransactionHelper(WorkArea wa)
      {
         this.wa     = wa;
         this.errHlp = wa.em;
      }
      
      /**
       * Get the current {@link WorkArea#nowIterating} instance.
       * 
       * @return   See above.
       */
      public Object getNowIterating()
      {
         return wa.nowIterating;
      }
      
      /**
       * Set the current {@link WorkArea#nowIterating} instance.
       * 
       * @param    nowIterating
       *           The instance (saved via {@link #getNowIterating()} or <code>null</code>) to restore.
       */
      public void setNowIterating(Object nowIterating)
      {
         wa.nowIterating = nowIterating;
      }
      
      /**
       * Get the {@link WorkArea#pendingScopeables} list.
       * 
       * @return   See above.
       */
      public Scopeable[] getPendingScopeables()
      {
         return wa.pendingScopeables == null 
                  ? null 
                  : Arrays.copyOf(wa.pendingScopeables, wa.pendingScopeables.length);
      }
      
      /**
       * Set the {@link WorkArea#pendingScopeables}.
       * 
       * @param    scopeables
       *           The pending scopeables.
       */
      public void setPendingScopeables(Scopeable[] scopeables)
      {
         if (scopeables == null)
         {
            wa.pendingScopeables = null;
            return;
         }
         
         if (wa.pendingScopeables == null)
         {
            wa.pendingScopeables = new Scopeable[ScopeId.values().length];
         }
         for (int i = 0; i < scopeables.length; i++)
         {
            wa.pendingScopeables[i] = scopeables[i];
         }
      }
      
      /**
       * Check whether the transaction id should be set for a database.
       * 
       * @param   db
       *          the logical name of the database (The {@code getId()} of the {@code Database}).
       */
      public void checkTransaction(String db)
      {
         if (!wa.isTransaction())
         {
            // no transaction active? return unknown value
            return;
         }
         
         SecurityManager sm = SecurityManager.getInstance();
         if (sm == null)
         {
            if (LOG.isLoggable(Level.WARNING))
            {
               LOG.log(Level.WARNING, "Failed to obtain the session Id");
            }
            return;
         }
         Integer userId = sm.sessionSm.getSessionId();
         if (userId == null)
         {
            if (LOG.isLoggable(Level.WARNING))
            {
               LOG.log(Level.WARNING, "Failed to obtain the session Id");
            }
            return;
         }

         DbTransactions dbData;
         synchronized (transactionData)
         {
            dbData = transactionData.get(db);
            if (dbData == null)
            {
               if (LOG.isLoggable(Level.WARNING))
               {
                  LOG.log(Level.WARNING,
                          "Database " + db +
                          " was not found in the list of active databases. Adding it now.");
               }
               
               // first access to this database?
               dbData = new DbTransactions();
               transactionData.put(db, dbData);
               if (TransactionTableUpdater.isEnabled())
               {
                  updaters.put(db, new TransactionTableUpdater());
               }
            }
         }
         
         synchronized (dbData)
         {
            // if transaction for this used has not been set, increment and add now
            if (!dbData.transactions.containsKey(userId))
            {
               int tId = ++dbData.lastTransactionId;
               if (tId < 0) // wrapping around from 1 , not Integer.MIN_VALUE
               {
                  tId = 1;
               }
               dbData.transactions.put(userId, tId);
               dbData.isDirty = true;
            }
         }
      }

      /**
       * Reports on the current <code>Finalizable</code> operation being
       * executed.
       *
       * @return   One of the following:
       *           <ul>
       *              <li> <code>OP_NONE</code>
       *              <li> <code>OP_ITERATE</code>
       *              <li> <code>OP_RETRY</code>
       *              <li> <code>OP_FINISHED</code>
       *           </ul>
       */
      public int currentOperation()
      {
         return wa.op;
      }
      
      /**
       * Register StopAfterTimer to a ContextLocal WorkArea.
       * 
       * @param    stopAfterTimer
       *           StopAfterTimer to register.
       */
      public void registerStopAfterTimer(StopAfterTimer stopAfterTimer)
      {
         TransactionManager.registerStopAfterTimer(wa, stopAfterTimer);
      }
      
      /**
       * Adds the given object into the list of objects for the current block
       * which require commit notification.  This form of registration is
       * useful for objects whose natural lifetime are scoped to a different
       * block than the block in which they must be committed.  For example,
       * if a global or shared resource needs to be committed in a specific 
       * scope, then this can be used in cooperation with the normal
       * {@link #registerCurrent} processing to control the commit points.
       * <p>
       * This notification will ONLY occur when there is an active transaction.
       * <p>
       * Notifications occur in LIFO order;  thus, commitables which depend upon
       * others being committed first must be registered before the objects upon
       * which they are dependent.
       * <p>
       * These registrations naturally are removed when the associated scope
       * is removed.
       *
       * @param    target
       *           The object reference to be committed on success.  This
       *           cannot be <code>null</code>.
       */
      public void registerCommit(Commitable target)
      {
         TransactionManager.registerCommitAt(wa, wa.blocks.size() - 1, target);
      }
   
      /**
       * Adds the given object into the list of objects which require commit notification for the
       * block at the specified depth  This form of registration is useful for objects whose natural
       * lifetime are scoped to a different block than the block in which they must be committed.
       * For example, if a global or shared resource needs to be committed in a specific scope,
       * then this can be used in cooperation with the normal {@link #registerCurrent} processing to 
       * control the commit points.
       * <p>
       * This notification will ONLY occur when there is an active transaction.
       * <p>
       * Notifications occur in LIFO order;  thus, commitables which depend upon
       * others being committed first must be registered before the objects upon
       * which they are dependent.
       * <p>
       * These registrations naturally are removed when the associated scope
       * is removed.
       *
       * @param    blockDepth
       *           Zero-based depth of the target block, starting from the outermost block.
       * @param    target
       *           The object reference to be committed on success.  This cannot be
       *           <code>null</code>.
       */
      public void registerCommitAt(int blockDepth, Commitable target)
      {
         TransactionManager.registerCommitAt(wa, blockDepth, target);
      }
      
      /**
       * Adds the given object as the transaction level reference which will get 
       * a single, "master" commit call after all other commits have been
       * processed at the full transaction level.  This will never be called
       * for sub-transaction commits, nor will it be called if the transaction
       * is abnormally exited.
       * <p>
       * This notification will ONLY occur when there is an active transaction
       * and it only will be called at the exit of the block that corresponds
       * to the full transaction.
       * <p>
       * If the full transaction block is also an iterating block, this
       * notification will occur for each iteration of the block.
       *
       * @param    target
       *           The object reference to be committed on success.  This
       *           can be <code>null</code> if one intends to clear the current
       *           reference.
       * @param    remove
       *           If <code>true</code> automatically remove this registration
       *           after the full transaction scope has ended (whether or not
       *           the commit occurs).  If <code>false</code>, this reference
       *           will remain globally until replaced.
       */
      public void registerTransactionCommit(Commitable target, boolean remove)
      {
         if (wa.masterCommit == null)
         {
            wa.masterCommit = new LinkedHashMap<>();
         }
         wa.masterCommit.put(target, remove ? Boolean.TRUE : Boolean.FALSE);
      }
      
      /**
       * Register the given object as a handler which can veto the processing of
       * STOP condition processing at the current scope.  Handlers are invoked
       * in FIFO order.  Any handler can issue a veto, which forces the STOP
       * condition to be handled at a higher scope, if at all.
       * 
       * @param   h
       *          The stop condition veto handler.
       */
      public void registerStopVetoHandler(StopConditionVetoHandler h)
      {
         wa.stopHandlers.add(h);
      }
   
      /**
       * 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 = TransactionManager.nearestTopLevel(wa);
         registerScopeable(wa, block, target, true);
      }
   
      /**
       * Search the stack of scopes and return the first top level block found.
       *
       * @return   The nearest enclosing top level block or <code>null</code> if there is no block marked as a 
       *           top level block.
       */
      public BlockDefinition nearestTopLevel()
      {
         return TransactionManager.nearestTopLevel(wa);
      }
      
      /**
       * Search the stack of scopes and return the previous top level block.
       *
       * @return   The previous enclosing top level block or <code>null</code> if there is no block marked as  
       *           a top level block after the nearest top-level block.
       */
      public BlockDefinition prevTopLevel()
      {
         Stack<BlockDefinition> blocks = wa.blocks;
         boolean found = false;
         for (int idx = blocks.size() - 1; idx >= 0; idx--) 
         {
            BlockDefinition blk = blocks.get(idx);
            
            if (blk.topLevel)
            {
               if (found)
               {
                  return blk;
               }
               
               found = true;
            }
         }
         
         return null;
      
      }
      
      /**
       * Register the given <code>Resettable</code> object in order to reset it
       * at specific points of block processing. 
       *
       * @param resettable
       *        <code>Resettable</code> to be registered.
       */
      public void registerResettable(Resettable resettable)
      {
         wa.resettables.add(resettable);
      }
      
      /**
       * Adds the given object into the list of objects for the specified block
       * which require iterate/retry/finish notification.  This form of
       * registration is useful for objects whose natural lifetime is scoped to
       * a block and which need a hook for cleaning up resources or handling
       * other block termination processing.
       * <p>
       * This notification will occur whether or not a transaction is active.
       * <p>
       * These registrations naturally are removed when the associated scope
       * is removed (after the {@link Finalizable#finished} method is called)
       * in the case of a non-global scope OR at the moment that the last
       * scope has been removed in the case of the global scope.
       * <p>
       * The given object will only be added if it is not already in the list.
       * <p>
       * Finalizables for a scope are processed in the same order as they were
       * registered.
       *
       * @param    target
       *           The object reference to be notified on any exit from the
       *           block specified.  This cannot be <code>null</code>.
       * @param    global 
       *           Specifies the target scope. <code>false</code> specifies the
       *           current scope, and <code>true</code> specifies the global
       *           scope.
       */
      public void registerFinalizable(Finalizable target, boolean global)
      {
         int blockDepth = global ? 0 : wa.blocks.size() - 1;
         TransactionManager.registerFinalizableAt(wa, blockDepth, target);
      }
   
      /**
       * Remove the given finalizable from the global list of finalizables.
       * 
       * @param    target
       *           Finalizable to be removed from the global list.
       */
      public void deregisterGlobalFinalizable(Finalizable target)
      {
         TransactionManager.deregisterGlobalFinalizable(wa, target);
      }
   
      /**
       * Adds the given object into the list of objects for the specified block
       * which require iterate/retry/finish notification.  This form of
       * registration is useful for objects whose natural lifetime is scoped to
       * a block and which need a hook for cleaning up resources or handling
       * other block termination processing.
       * <p>
       * This notification will occur whether or not a transaction is active.
       * <p>
       * These registrations naturally are removed when the associated scope
       * is removed (after the {@link Finalizable#finished} method is called)
       * in the case of a non-global scope OR at the moment that the last
       * scope has been removed in the case of the global scope.
       * <p>
       * The given object will only be added if it is not already in the list.
       * <p>
       * Finalizables for a scope are processed in the same order as they were
       * registered.
       *
       * @param    blockDepth
       *           zero-based depth of target block, starting from the outermost
       *           block. <code>0</code> indicates the global block.
       * @param    target
       *           The object reference to be notified on any exit from the
       *           block specified.  This cannot be <code>null</code>.
       */
      public void registerFinalizableAt(int blockDepth, Finalizable target)
      {
         TransactionManager.registerFinalizableAt(wa, blockDepth, target);
      }
      
      /**
       * Adds the given object into the list of objects which require
       * iterate/retry/finish notifications for the next block that is opened
       * which is a top-level block that corresponds to a Progress external 
       * procedure.
       * <p>
       * This form of registration is useful for objects whose natural lifetime
       * is scoped to the top-level block and which need a hook for cleaning up
       * resources or handling other block termination processing.  In 
       * particular, it is necessary for objects that are constructed during 
       * a class' constructor/initializer (before the external procedure block
       * has opened).  Such objects cannot simply register since their 
       * construction is not actually enclosed by the block.
       * <p>
       * This notification will occur whether or not a transaction is active.
       * <p>
       * These registrations naturally are removed when the associated scope
       * is removed (after the {@link Finalizable#finished} method is called).
       * <p>
       * The given object will only be added if it is not already in the list.
       *
       * @param    target
       *           The object reference to be notified on any exit from the
       *           top-level external block.  This cannot be <code>null</code>.
       *
       * @throws   IllegalStateException
       *           if the top-level block cannot be identified.
       */
      public void registerNextExternal(Finalizable target)
      {
         TransactionManager.registerNextExternal(wa, target);
      }
      
      /**
       * Allow finalizables to do their cleanup ({@link Finalizable#initFailure()}) now since the
       * scoped they were designed was aborted and there won't be other chance to call their
       * {@link Finalizable#finished()}.
       * <p>
       * After iteration the list is deleted.
       * <p>
       * Also, cleanup all other pending state being collected while the external program was 
       * instantiating.
       */
      public void cleanupPending()
      {
         TransactionManager.cleanupPending(wa);
      }
      
      /**
       * Adds the given object into the list of objects which require
       * iterate/retry/finish notifications for the nearest enclosing 
       * top-level block.  A top-level block is generated for Progress external 
       * procedures, internal procedures, triggers and functions.  An additional
       * parameter allows registration to be specified for the specific block
       * which corresponds with an external procedure block scope in Progress.
       * <p>
       * This form of registration is useful for objects whose natural lifetime
       * is scoped to the top-level block and which need a hook for cleaning up
       * resources or handling other block termination processing.
       * <p>
       * This notification will occur whether or not a transaction is active.
       * <p>
       * These registrations naturally are removed when the associated scope
       * is removed (after the {@link Finalizable#finished} method is called).
       * <p>
       * The given object will only be added if it is not already in the list.
       * <p>
       * Finalizables for a scope are processed in the same order as they were
       * registered.
       *
       * @param    target
       *           The object reference to be notified on any exit from the
       *           top-level block.  This cannot be <code>null</code>.
       * @param    external
       *           If <code>true</code>, register with the nearest enclosing
       *           external block (external procedure), otherwise register with
       *           the nearest enclosing top-level block (which may be but is
       *           not guaranteed to be the external block).
       *
       * @throws   IllegalStateException
       *           if the top-level block cannot be identified.
       */
      public void registerTopLevelFinalizable(Finalizable target, boolean external)
      {
         TransactionManager.registerTopLevelFinalizable(wa, target, external);
      }
      
      /**
       * Adds the given object into the list of objects which require
       * iterate/retry/finish notification for the nearest enclosing, looping 
       * block.
       * <p>
       * This form of registration is useful for objects whose natural lifetime
       * is scoped to the nearest enclosing, looping block and which need a hook
       * for cleaning up resources or handling other block termination
       * processing.
       * <p>
       * This notification will occur whether or not a transaction is active.
       * <p>
       * These registrations naturally are removed when the associated scope
       * is removed (after the {@link Finalizable#finished} method is called).
       * <p>
       * The given object will only be added if it is not already in the list.
       *
       * @param    target
       *           The object reference to be notified on any exit from or
       *           iteration of the target block.  This cannot be
       *           <code>null</code>.
       *
       * @throws   IllegalStateException
       *           if no enclosing, looping block can be identified.
       */
      public void registerNearestLoopFinalizable(Finalizable target)
      {
         TransactionManager.registerNearestLoopFinalizable(wa, target);
      }
      
      /**
       * Removes the given object from the list of objects which require
       * iterate/retry/finish notifications.  This will find the first instance
       * of the given reference by walking up each block from the current to the  
       * global block. The first instance found will be removed and the method
       * will then return. If the same instance is registered in multiple scopes,
       * it would take multiple calls to remove all instances.
       * <p>
       * After this call, no notifications will be received.
       *
       * @param    target
       *           The object reference to be removed.
       *
       * @return   <code>true</code> if the target instance was found and
       *           removed.
       */
      public boolean removeFinalizable(Finalizable target)
      {
         if (target == null)
         {
            // nothing to do
            return false;
         }
         
         return TransactionManager.removeFinalizable(wa, target);
      }
      
      /**
       * Adds the given object as the transaction level reference which will get 
       * a "master" finish call after all other finish notifications have been
       * processed at the full transaction level.  This will never be called for
       * sub-transaction finish notification.
       * <p>
       * This notification will ONLY occur when there is an active transaction
       * and it only will be called at the exit of the block that corresponds
       * to the full transaction.
       * <p>
       * Multiple targets may be registered by invoking this method multiple
       * times.  However, the same object registered multiple times will receive
       * the notification only once per full transaction block exit.  If multple
       * targets are registered, they will be called in the order in which they
       * were registered.
       *
       * @param    target
       *           The object reference to be finished at full transaction block
       *           end.
       * @param    remove
       *           If <code>true</code> automatically remove this registration
       *           after the full transaction scope has ended.  If
       *           <code>false</code>, this reference will remain globally until
       *           replaced.
       */
      public void registerTransactionFinish(Finalizable target, boolean remove)
      {
         if (wa.masterFinish == null)
         {
            wa.masterFinish = new LinkedHashMap<>();
         }
         wa.masterFinish.put(target, remove);
      }
      
      /**
       * Adds the given object into the batch notifier list.  This is a global 
       * list (it isn't associated with any block) that is triggered externally
       * by a call to {@link #batchStart} and {@link #batchStop}.  Every object
       * in the list will be notified in the order in which they were added to
       * the list.
       *
       * @param    target
       *           The object reference to be notified of any batch start and 
       *           stop.
       */
      public void registerBatchListener(BatchListener target)
      {
         wa.batchListeners.add(target);
      }
      
      /**
       * Reports whether the current block was just rolled back or a containing
       * block has a pending rollback. If there is no current transaction open,
       * then there cannot be a pending or current rollback.
       *
       * @return   <code>true</code> if there was or will be a rollback.
       */
      public boolean hadRollback()
      {
         return TransactionManager.hadRollback(wa);
      }
      
      /**
       * Trigger a rollback for all registered <code>Undoable</code> objects 
       * inside the current active transaction at the scope defined by the
       * current block.  All objects that are rolled back will have their
       * values assigned based on the values backed up at the start of the
       * specified scope.  A flow control change MUST always immediately follow 
       * this method otherwise undefined results can occur.
       * <p>
       * This caller-driven control flow is required to properly duplicate the
       * semantics of Progress 4GL condition processing which always generates 
       * an implicit or explicit flow control action after every UNDO. Since the
       * flow of control change can reference a different scope than that of
       * the rollback, this can't be handled by re-throwing the exception but
       * must instead be separately driven by a deferred rollback and a caller
       * driven flow control statement such as break, continue or return. The
       * key limitation that makes this possible is the fact that the UNDO
       * (rollback) scope MUST ALWAYS BE equivalent to or more specific than
       * the scope to which flow of control will be changed.  This means that
       * one can rollback at a more deeply nested scope but change flow of
       * control to resume processing at a different (less deeply nested) scope.
       * The reverse is not possible.
       */
      public void rollback()
      {
         rollback(null);
      }
      
      /**
       * Trigger a rollback for all registered <code>Undoable</code> objects 
       * inside the current active transaction at the scope defined
       * explicitly by the given label or implicitly if the label is
       * <code>null</code>.  All objects that are rolled back will have their
       * values assigned based on the values backed up at the start of the
       * specified scope.  If the current scope is not the target scope, the
       * given exception will be stored, the rollback will be deferred and 
       * the rollback will be processed as the scopes naturally unwind.  This
       * unwinding must be generated by a change in flow of control by the
       * calling code.  This flow control change MUST always immediately follow 
       * this method otherwise undefined results can occur.
       * <p>
       * This caller-driven control flow is required to properly duplicate the
       * semantics of Progress 4GL condition processing which always generates 
       * an implicit or explicit flow control action after every UNDO. Since the
       * flow of control change can reference a different scope than that of
       * the rollback, this can't be handled by re-throwing the exception but
       * must instead be separately driven by a deferred rollback and a caller
       * driven flow control statement such as break, continue or return. The
       * key limitation that makes this possible is the fact that the UNDO
       * (rollback) scope MUST ALWAYS BE equivalent to or more specific than
       * the scope to which flow of control will be changed.  This means that
       * one can rollback at a more deeply nested scope but change flow of
       * control to resume processing at a different (less deeply nested) scope.
       * The reverse is not possible.
       *
       * @param    label
       *           Specifies the name of the scope at which to rollback or
       *           <code>null</code> if the scope should be implicitly 
       *           determined.
       */
      public void rollback(String label)
      {
         rollback(label, null);
      }
      
      /**
       * Trigger a rollback for all registered <code>Undoable</code> objects 
       * inside the current active transaction at the scope defined
       * explicitly by the given label or implicitly if the label is
       * <code>null</code>.  All objects that are rolled back will have their
       * values assigned based on the values backed up at the start of the
       * specified scope.  If the current scope is not the target scope, the
       * given exception will be stored, the rollback will be deferred and 
       * the rollback will be processed as the scopes naturally unwind.  This
       * unwinding must be generated by a change in flow of control by the
       * calling code.  This flow control change MUST always immediately follow 
       * this method otherwise undefined results can occur.
       * <p>
       * This caller-driven control flow is required to properly duplicate the
       * semantics of Progress 4GL condition processing which always generates 
       * an implicit or explicit flow control action after every UNDO. Since the
       * flow of control change can reference a different scope than that of
       * the rollback, this can't be handled by re-throwing the exception but
       * must instead be separately driven by a deferred rollback and a caller
       * driven flow control statement such as break, continue or return. The
       * key limitation that makes this possible is the fact that the UNDO
       * (rollback) scope MUST ALWAYS BE equivalent to or more specific than
       * the scope to which flow of control will be changed.  This means that
       * one can rollback at a more deeply nested scope but change flow of
       * control to resume processing at a different (less deeply nested) scope.
       * The reverse is not possible.
       *
       * @param    label
       *           Specifies the name of the scope at which to rollback or
       *           <code>null</code> if the scope should be implicitly 
       *           determined.
       * @param    cause
       *           The exceptional cause, if any, of the rollback.
       */
      public void rollback(String label, Throwable cause)
      {
         TransactionManager.rollback(wa, label, cause);
      }
      
      /**
       * Trigger a rollback for all registered <code>Undoable</code> objects 
       * inside the current active transaction at the nearest top level scope.
       * All objects that are rolled back will have their values assigned based
       * on the values backed up at the start of the specified scope.  If the
       * current scope is not the target scope, the given exception will be 
       * stored, the rollback will be deferred and the rollback will be processed
       * as the scopes naturally unwind.  This unwinding must be generated by a
       * change in flow of control by the calling code.  This flow control change 
       * MUST always immediately follow this method otherwise undefined results
       * can occur.
       * <p>
       * This caller-driven control flow is required to properly duplicate the
       * semantics of Progress 4GL condition processing which always generates 
       * an implicit or explicit flow control action after every UNDO. Since the
       * flow of control change can reference a different scope than that of
       * the rollback, this can't be handled by re-throwing the exception but
       * must instead be separately driven by a deferred rollback and a caller
       * driven flow control statement such as break, continue or return. The
       * key limitation that makes this possible is the fact that the UNDO
       * (rollback) scope MUST ALWAYS BE equivalent to or more specific than
       * the scope to which flow of control will be changed.  This means that
       * one can rollback at a more deeply nested scope but change flow of
       * control to resume processing at a different (less deeply nested) scope.
       * The reverse is not possible.
       */
      public void rollbackTopLevel()
      {
         TransactionManager.rollbackTopLevel(wa);
      }
      
      /**
       * Initializes a new scope, the associated block definition data
       * structures.  A backup of objects that can be rolled back is deferred
       * until after the block opens (see {@link #blockSetup} and 
       * {@link #makeBackup}).
       * <p>
       * In the case of a block which is defined as <code>NO_TRANSACTION</code>,
       * the core data structures are initialized but no rollback support will
       * be provided.
       * <p>
       * This method will honor any pending thread interruption by throwing
       * a {@link StopConditionException} using {@link #honorStopCondition}.
       * <p>
       * This will create a block of <code>BlockType.UNKNOWN</code> and block
       * properties <code>PROP_NONE</code>.
       *
       * @param    label
       *           The text label associated with the block.  This usually
       *           should correspond with the label used in the source file
       *           to identify the block for purposes of the break and continue
       *           statements.
       * @param    level
       *           One of {@link #NO_TRANSACTION}, {@link #SUB_TRANSACTION} or
       *           {@link #TRANSACTION}.  This code defines the level of
       *           rollback and transaction support which should be provided.
       * @param    external
       *           <code>true</code> if this block is associated with the
       *           external procedure (in Progress terms). This controls the
       *           cleanup of certain resources such as temp-tables, frames
       *           and streams.
       * @param    topLevel
       *           <code>true</code> if this block is a method.
       * @param    loop
       *           Defines if this block is a loop or not.
       * @param    next
       *           <code>true</code> if this infinite loop protection should
       *           convert a retry into a next (continue), otherwise the retry
       *           will be converted into a leave (break).
       *
       * @throws   StopConditionException
       *           If the current thread's was previously interrupted.
       */
      public void pushScope(String    label,
                            int       level,
                            boolean   external,
                            boolean   topLevel,
                            boolean   loop,
                            boolean   next)
      {
         pushScope(label, level, PROP_NONE, external, topLevel, loop, next, UNKNOWN);
      }
      
      /**
       * Initializes a new scope, the associated block definition data
       * structures.  A backup of objects that can be rolled back is deferred
       * until after the block opens (see {@link #blockSetup} and 
       * {@link #makeBackup}).
       * <p>
       * In the case of a block which is defined as <code>NO_TRANSACTION</code>,
       * the core data structures are initialized but no rollback support will
       * be provided.
       * <p>
       * This method will honor any pending thread interruption by throwing
       * a {@link StopConditionException} using {@link #honorStopCondition}.
       * <p>
       * This will create a block with block properties <code>PROP_NONE</code>.
       *
       * @param    label
       *           The text label associated with the block.  This usually
       *           should correspond with the label used in the source file
       *           to identify the block for purposes of the break and continue
       *           statements.
       * @param    level
       *           One of {@link #NO_TRANSACTION}, {@link #SUB_TRANSACTION} or
       *           {@link #TRANSACTION}.  This code defines the level of
       *           rollback and transaction support which should be provided.
       * @param    external
       *           <code>true</code> if this block is associated with the
       *           external procedure (in Progress terms). This controls the
       *           cleanup of certain resources such as temp-tables, frames
       *           and streams.
       * @param    topLevel
       *           <code>true</code> if this block is a method.
       * @param    loop
       *           Defines if this block is a loop or not.
       * @param    next
       *           <code>true</code> if this infinite loop protection should
       *           convert a retry into a next (continue), otherwise the retry
       *           will be converted into a leave (break).
       * @param    blockType
       *           Specifies this block's type.
       *
       * @throws   StopConditionException
       *           If the current thread's was previously interrupted.
       */
      public void pushScope(String    label,
                            int       level,
                            boolean   external,
                            boolean   topLevel,
                            boolean   loop,
                            boolean   next,
                            BlockType blockType)
      {
         pushScope(label, level, PROP_NONE, external, topLevel, loop, next, UNKNOWN);
      }
      
      /**
       * Initializes a new scope, the associated block definition data
       * structures.  A backup of objects that can be rolled back is deferred
       * until after the block opens (see {@link #blockSetup} and 
       * {@link #makeBackup}).
       * <p>
       * In the case of a block which is defined as <code>NO_TRANSACTION</code>,
       * the core data structures are initialized but no rollback support will
       * be provided.
       * <p>
       * This method will honor any pending thread interruption by throwing
       * a {@link StopConditionException} using {@link #honorStopCondition}
       *
       * @param    label
       *           The text label associated with the block.  This usually
       *           should correspond with the label used in the source file
       *           to identify the block for purposes of the break and continue
       *           statements.
       * @param    level
       *           One of {@link #NO_TRANSACTION}, {@link #SUB_TRANSACTION} or
       *           {@link #TRANSACTION}.  This code defines the level of
       *           rollback and transaction support which should be provided.
       * @param    props
       *           A bitfield storing block properties which will be a
       *           combination of {@link #PROP_NONE}, {@link #PROP_ERROR},
       *           {@link #PROP_ENDKEY}, {@link #PROP_STOP} or
       *           {@link #PROP_QUIT}.
       * @param    external
       *           <code>true</code> if this block is associated with the
       *           external procedure (in Progress terms). This controls the
       *           cleanup of certain resources such as temp-tables, frames
       *           and streams.
       * @param    topLevel
       *           <code>true</code> if this block is a method.
       * @param    loop
       *           Defines if this block is a loop or not.
       * @param    next
       *           <code>true</code> if this infinite loop protection should
       *           convert a retry into a next (continue), otherwise the retry
       *           will be converted into a leave (break).
       * @param    blockType
       *           Specifies this block's type.
       *
       * @throws   StopConditionException
       *           If the current thread's was previously interrupted.
       */
      public void pushScope(String    label,
                            int       level,
                            int       props,
                            boolean   external,
                            boolean   topLevel,
                            boolean   loop,
                            boolean   next,
                            BlockType blockType)
      {
         pushScope(label, level, props, external, topLevel, loop, next, false, blockType);
      }
      
      /**
       * Initializes a new scope, the associated block definition data
       * structures.  A backup of objects that can be rolled back is deferred
       * until after the block opens (see {@link #blockSetup} and 
       * {@link #makeBackup}).
       * <p>
       * In the case of a block which is defined as <code>NO_TRANSACTION</code>,
       * the core data structures are initialized but no rollback support will
       * be provided.
       * <p>
       * This method will honor any pending thread interruption by throwing
       * a {@link StopConditionException} using {@link #honorStopCondition}
       *
       * @param    label
       *           The text label associated with the block.  This usually
       *           should correspond with the label used in the source file
       *           to identify the block for purposes of the break and continue
       *           statements.
       * @param    level
       *           One of {@link #NO_TRANSACTION}, {@link #SUB_TRANSACTION} or
       *           {@link #TRANSACTION}.  This code defines the level of
       *           rollback and transaction support which should be provided.
       * @param    props
       *           A bitfield storing block properties which will be a
       *           combination of {@link #PROP_NONE}, {@link #PROP_ERROR},
       *           {@link #PROP_ENDKEY}, {@link #PROP_STOP} or
       *           {@link #PROP_QUIT}.
       * @param    external
       *           <code>true</code> if this block is associated with the
       *           external procedure (in Progress terms). This controls the
       *           cleanup of certain resources such as temp-tables, frames
       *           and streams.
       * @param    topLevel
       *           <code>true</code> if this block is a method.
       * @param    loop
       *           Defines if this block is a loop or not.
       * @param    next
       *           <code>true</code> if this infinite loop protection should
       *           convert a retry into a next (continue), otherwise the retry
       *           will be converted into a leave (break).
       * @param    databaseTrigger
       *           <code>true</code> if this scope should create a definition
       *           of a database trigger block.
       * @param    blockType
       *           Specifies this block's type.
       *
       * @throws   StopConditionException
       *           If the current thread's was previously interrupted.
       */
      public void pushScope(String    label,
                            int       level,
                            int       props,
                            boolean   external,
                            boolean   topLevel,
                            boolean   loop,
                            boolean   next,
                            boolean   databaseTrigger,
                            BlockType blockType)
      {
         TransactionManager.pushScope(wa,
                                      label,
                                      level,
                                      props, 
                                      external,
                                      topLevel,
                                      loop,
                                      next,
                                      databaseTrigger,
                                      blockType);
      }
      
      /**
       * Remove the current block from the stack of scopes, process any pending
       * rollbacks (if the current block is the proper target), notify any
       * objects needing commits (if the block finished successfully) and handle
       * finish processing for the block.  This method must be called from a
       * <code>finally</code> block such that this is guaranteed to be called
       * no matter how the flow of control exits the associated block (exception,
       * break, return or the flow of control naturally passing through the 
       * end of the block).
       * <p>
       * Rollback and commit processing only occur when there is an active
       * transaction.  Otherwise this processing is bypassed.  Finish processing
       * is always called.
       * <p>
       * This method implements global finish processing (as the last action
       * before returning) when the last scope is popped from the stack of
       * scopes.  For this reason, the current implementation assumes that the
       * session is over at the moment that the top-most user-defined scope
       * exits.
       * <p>
       * This method will honor any pending thread interruption by throwing
       * a {@link StopConditionException} using {@link #honorStopCondition}
       *
       * @throws   StopConditionException
       *           If the current thread's was previously interrupted.
       */
      public void popScope()
      throws StopConditionException
      {
         TransactionManager.popScope(wa);
      }
      
      /**
       * Notify {@link ConditionListener} instances about condition.
       * 
       * @param   condition
       *          The condition the listeners will be notified.
       */
      public void notifyCondition(BlockManager.Condition condition)
      {
         // do not pass null down
         if (condition == null)
         {
            return;
         }
         
         TransactionManager.notifyCondition(wa, condition);
      }
   
      /**
       * Notify {@link ConditionListener} instances about exception condition.
       * 
       * @param   condition
       *          The instance of {@link ConditionException}.
       */
      public void notifyCondition(ConditionException condition)
      {
         TransactionManager.notifyCondition(wa, BlockManager.exceptionToCondition(condition));
      }
      
      /**
       * Check if the latest block has initialized.
       * 
       * @return   See above.
       */
      public boolean blockInitialized()
      {
         BlockDefinition blk = wa.blocks.peek();
   
         return blk.init;
      }
      
      /**
       * Provides a callback entry point for the top of the loop.  This location 
       * is required to handle the backup of undo variables via 
       * {@link #makeBackup}.  On the second and subsequent executions of this
       * method for a given block, this method handles <code>iterate</code> 
       * notifications.
       * <p>
       * It is important to note that <code>continue</code> can be called from
       * nested blocks at arbitrary depths.  This method can handle such nesting
       * at any level. 
       * <p>
       * This method will honor any pending thread interruption by throwing
       * a {@link StopConditionException} using {@link #honorStopCondition}
       *
       * @throws   StopConditionException
       *           If the current thread's was previously interrupted.
       */
      public void blockSetup() 
      throws StopConditionException
      {
         TransactionManager.blockSetup(wa);
      }
      
      /**
       * Save the given runnable as the FINALLY block for the next iterate, retry or finished. This
       * will be cleared after it is executed, so it must be setup each iteration of the block.
       * 
       * @param    fini
       *           The FINALLY block code to execute.
       */
      public void setupFinallyBlock(Runnable fini)
      {
         BlockDefinition blk = wa.blocks.peek();
         
         blk.finallyBlock = fini;
      }
      
      /**
       * Save the given runnable as the CATCH block for the next iterate, retry or finished. This will be
       * be cleared after it is executed, so it must be setup each iteration of the block.
       * 
       * @param    possibleCatch
       *           The CATCH block code to execute.
       */
      public void setupCatchProcessing(Runnable possibleCatch)
      {
         BlockDefinition blk = wa.blocks.peek();
         blk.possibleCatch = possibleCatch;
      }
      
      /**
       * Save the given LegacyStopException. This will be cleared after it is executed, 
       * so it must be setup each iteration of the block.
       * 
       * @param    stopCondition
       *           LegacyStopException from stop-after to propagate.
       */
      public void setupStopAfterProccessing(LegacyStopException stopCondition) 
      {
         wa.stopAfterException = stopCondition;
      }
      
      /**
       * Creates or updates the backup set for all objects needing rollback 
       * support in the current block, if a transaction is active.  This is
       * split up from {@link #pushScope} and {@link #blockSetup} due to the
       * semantics of Progress in which the backup set is created or refreshed
       * after the loop control variables have been modified.
       */
      public void makeBackup() 
      {
         TransactionManager.backupWorker(wa, null);
      }
      
      /**
       * Reports if there is a current transaction which is active.
       *
       * @return    <code>true</code> if a transaction is active.
       */
      public logical isTransactionActive()
      {
         return new logical(wa.isTransaction());
      }
      
      /**
       * Reports if the current iteration of this block or loop is a retry.
       *
       * @return    <code>true</code> if this is a retry of this block or loop
       *            (as opposed to the first time through this iteration).
       */
      public boolean _isRetry()
      {
         return TransactionManager._isRetry(wa);
      }
   
      /**
       * Reports if the current iteration of this block or loop is a retry.
       *
       * @return    <code>true</code> if this is a retry of this block or loop
       *            (as opposed to the first time through this iteration).
       */
      public logical isRetry()
      {
         return new logical(_isRetry());
      }
      
      /**
       * Records the fact that the pending RETRY is being caused by an ENDKEY
       * condition. This enables "downstream" retry processing to implement the
       * "stuttering" quirk and some behavior in honoring PAUSE as a way to
       * disable infinite loop protection.
       */
      public void markEndkeyRetry()
      {
         TransactionManager.markEndkeyRetry(wa);
      }
      
      /**
       * This checks if any of the enclosing loops has iterated at least once.
       * 
       * @return   <code>true</code> if any of the enclosing loops has iterated
       *           at least once.
       */
      public boolean hasParentIteratedBlock()
      {
         return TransactionManager.hasParentIteratedBlock(wa);
      }
      
      /**
       * Determines if a NEXT operation with the given target block should
       * be honored or if the NEXT should be converted to a LEAVE. This is
       * a special form of infinite loop protection.
       * <p>
       * A NEXT is converted into a LEAVE when all of the following conditions
       * are true:
       * <p>
       * <ul>
       *   <li> The NEXT is associated with the UNDO, NEXT language statement or
       *        with an ON phrase that has UNDO, NEXT as the result of the given
       *        condition (e.g. ON ENDKEY UNDO, NEXT). It is important to note
       *        that Progress does not provide infinite loop protection for the
       *        NEXT language statement, so there is no conversion to LEAVE in
       *        that case.
       *   <li> The target block (whether implicitly determined by the lack of a
       *        label or explicitly determined by the presence of a label) must
       *        also have been the target block for the UNDO operation. It is
       *        possible for the NEXT to be targetted at a more enclosing block
       *        than the UNDO, in which case there is no conversion to LEAVE.
       *   <li> The target block type must be REPEAT, REPEAT WHILE or DO WHILE.
       *        These are the only loop types that normally convert RETRY to
       *        LEAVE instead of NEXT.  Likewise, these are the only loop types
       *        which honor infinite loop protection for NEXT by converting it
       *        to a LEAVE.
       *   <li> Infinite loop protection must not have been disabled for the
       *        target block.  In this case, any form of PAUSE is honored as
       *        one of the mechanisms to disable infinite loop protection.
       * </ul>
       * <p>
       * Since NEXT is only possible on a looping block, if the target block
       * is not a looping block then this method will return <code>false</code>.
       *
       * @param    label
       *           The label of the target block for the NEXT action.
       *
       * @return   <code>true</code> for NEXT, <code>false</code> for LEAVE.
       */
      public boolean honorNext(String label)
      {
         return TransactionManager.honorNext(wa, label);
      }
      
      /**
       * Determines if the currently executing block should be retried.  This
       * is where infinite loop protection is implemented if it is enabled.
       *
       * @return   <code>true</code> if the block (whether or not it is a loop)
       *           should be retried.
       */
      public boolean needsRetry()
      {
         return TransactionManager.needsRetry(wa);
      }
      
      /**
       * Accesses the state of whether there is a pending break that must be 
       * honored.  This would have been generated by the infinite loop
       * protection.
       *
       * @return   <code>true</code> if a break is pending for this block.
       */
      public boolean isBreakPending()
      {
         return TransactionManager.isBreakPending(wa);
      }
      
      /**
       * Force an error to be generated such that it will be caught in the
       * caller of this method.  The stack will be unwound and normal error 
       * processing bypassed until this method returns.  This unwinding is 
       * dependent upon catch blocks being implemented with a corresponding call
       * to {@link #honorError}.
       * <p>
       * In the case where silent error mode is enabled in the calling method,
       * the "normal" stack unwinding approach cannot be used because the 
       * exception cannot be thrown. Instead, the error manager will be updated 
       * with the error data and this method will silently return.  It is
       * critical that in such cases the calling code must immediately execute a
       * <code>return</code> statement which will maintain the proper Progress
       * behavior.  The caller can check the error manager to determine that an 
       * error occurred.
       * <p>
       * Detecting the silent error mode in the caller requires that the top
       * level block be found.  The silent error flag of the caller is cached 
       * there.
       *
       * @param    text
       *           The text to use as the error message in the exception.
       *           
       * @throws   ErrorConditionException 
       */
      public void triggerErrorInCaller(String text)
      throws ErrorConditionException
      {
         TransactionManager.triggerErrorInCaller(wa, null, text);
      }
      
      /**
       * Check if this block's caller requires errors to be suppressed.
       * 
       * @return   {@link BlockDefinition#suppressError} for {@link #nearestTopLevel} block.
       */
      public boolean isSuppressError()
      {
         return TransactionManager.isSuppressError(wa);
      }
      
      /**
       * Force an error to be generated such that it will be caught in the
       * caller of this method.  The stack will be unwound and normal error 
       * processing bypassed until this method returns.  This unwinding is 
       * dependent upon catch blocks being implemented with a corresponding call
       * to {@link #honorError}.
       * <p>
       * In the case where silent error mode is enabled in the calling method,
       * the "normal" stack unwinding approach cannot be used because the 
       * exception cannot be thrown. Instead, the error manager will be updated 
       * with the error data and this method will silently return.  It is
       * critical that in such cases the calling code must immediately execute a
       * <code>return</code> statement which will maintain the proper Progress
       * behavior.  The caller can check the error manager to determine that an 
       * error occurred.
       * <p>
       * Detecting the silent error mode in the caller requires that the top
       * level block be found.  The silent error flag of the caller is cached 
       * there.
       *           
       * @throws   ErrorConditionException 
       */
      public void triggerErrorInCaller()
      throws ErrorConditionException
      {
         TransactionManager.triggerErrorInCaller(wa, null, "");
      }

      /**
       * Force an error to be generated such that it will be caught in the
       * caller of this method.  The stack will be unwound and normal error 
       * processing bypassed until this method returns.  This unwinding is 
       * dependent upon catch blocks being implemented with a corresponding call
       * to {@link #honorError}.
       * <p>
       * In the case where silent error mode is enabled in the calling method,
       * the "normal" stack unwinding approach cannot be used because the 
       * exception cannot be thrown. Instead, the error manager will be updated 
       * with the error data and this method will silently return.  It is
       * critical that in such cases the calling code must immediately execute a
       * <code>return</code> statement which will maintain the proper Progress
       * behavior.  The caller can check the error manager to determine that an 
       * error occurred.
       * <p>
       * Detecting the silent error mode in the caller requires that the top
       * level block be found.  The silent error flag of the caller is cached 
       * there.
       * 
       * @param    lex
       *           The legacy OO-like error to throw.
       *           
       * @throws   ErrorConditionException 
       */
      public void triggerErrorInCaller(LegacyErrorException lex)
      throws ErrorConditionException
      {
         TransactionManager.triggerErrorInCaller(wa, lex);
      }
      
      /**
       * Force a retry of the current block (if there is no pending rollback)
       * or retry the same block as the pending rollback, in either case
       * ensure that the flow of control will not naturally continue.
       * <p>
       * A {@link RetryUnwindException} will be thrown to unwind the stack
       * to the given scope, even if the scope is the current block.  This
       * unwinding is dependent upon catch blocks being implemented with a 
       * corresponding call to {@link #honorRetry}.
       * <p>
       * <b>This method will NEVER naturally return since an exception is ALWAYS
       * thrown.</b>  This method should generally not be called from within a
       * catch block if the intention is to retry the current block since it
       * would be outside of the block's associated <code>try</code>.  In that 
       * case, only an enclosing block could be retried.
       *
       * @throws   RetryUnwindException
       *           This method always generates this exception to ensure that
       *           natural processing will not continue.
       */
      public void forceRetry()
      throws RetryUnwindException
      {
         TransactionManager.forceRetry(wa, null);
      }
      
      /**
       * Force a retry of the current block or the scope named by the given
       * label, ensuring that the flow of control will not naturally continue.
       * A {@link RetryUnwindException} will be thrown to unwind the stack
       * to the given scope, even if the scope is the current block.  This
       * unwinding is dependent upon catch blocks being implemented with a 
       * corresponding call to {@link #honorRetry}.
       * <p>
       * <b>This method will NEVER naturally return since an exception is ALWAYS
       * thrown.</b>  This method should generally not be called from within a
       * catch block if the intention is to retry the current block since it
       * would be outside of the block's associated <code>try</code>.  In that 
       * case, only an enclosing block could be retried.
       *
       * @param    label
       *           The name of the scope to retry. Specify <code>null</code> to
       *           retry the scope that has a pending rollback OR to retry
       *           the current scope if there is no pending rollback.
       *
       * @throws   RetryUnwindException
       *           This method always generates this exception to ensure that
       *           natural processing will not continue.
       */
      public void forceRetry(String label)
      throws RetryUnwindException
      {
         TransactionManager.forceRetry(wa, label);
      }
      
      /**
       * Force a retry of the current block (if there is no pending rollback)
       * or the block targeted for a pending rollback.  If a containing scope is
       * the target of the retry, then a {@link RetryUnwindException} will be
       * thrown to unwind the stack to the given scope.  This unwinding is
       * dependent upon catch blocks being implemented with a corresponding call
       * to {@link #honorRetry}.
       * <p>
       * If the current block is not the target, the retry is deferred by
       * marking the target block and then unwinding the stack.  Otherwise, 
       * this method will return naturally.  This method is only appropriate 
       * to be called from within <code>catch</code> blocks since a natural
       * return within a <code>catch</code> block will fall through to allow
       * the retry loop to engage.                
       *           
       * @throws   RetryUnwindException 
       */
      public void triggerRetry()
      throws RetryUnwindException
      {
         TransactionManager.triggerRetry(wa, null);
      }
      
      /**
       * Force a retry of the current block or the scope named by the given
       * label.  If a containing scope is the target of the retry, then
       * a {@link RetryUnwindException} will be thrown to unwind the stack
       * to the given scope.  This unwinding is dependent upon catch blocks 
       * being implemented with a corresponding call to {@link #honorRetry}.
       * <p>
       * If the current block is not the target, the retry is deferred by
       * marking the target block and then unwinding the stack.  Otherwise, 
       * this method will return naturally.  This method is only appropriate 
       * to be called from within <code>catch</code> blocks since a natural
       * return within a <code>catch</code> block will fall through to allow
       * the retry loop to engage.
       *
       * @param    label
       *           The name of the scope to retry. Specify <code>null</code> to
       *           retry the scope of that has a pending rollback OR to retry
       *           the current scope if there is no pending rollback.
       *           
       * @throws   RetryUnwindException 
       */
      public void triggerRetry(String label)
      throws RetryUnwindException
      {
         TransactionManager.triggerRetry(wa, label);
      }
      
      /**
       * Dispatch the given exception to all registered stop condition handlers
       * to see if any requires that the STOP condition be vetoed at the
       * current scope.  If any of the registered handlers indicate that the STOP
       * condition should not be honored at this level, the exception is
       * rethrown, presumably to be honored at a higher scope, or not at all.  In
       * the latter case, the program running in the current context will abort.
       * 
       * @param   exc
       *          The exception to be checked.
       * 
       * @throws  StopConditionException
       *          if the STOP condition is not honored at this scope.
       */
      public void honorStop(StopConditionException exc)
      throws StopConditionException
      {
         TransactionManager.honorStop(wa, exc);
      }
      
      /**
       * Check if there is a pending retry of the current block and if so then
       * enable retry.  This is the "last half" of the processing to unwind the
       * stack up to a containing scope that is the target of a retry.  The
       * "first half" of the unwinding is implemented by throwing a
       * {@link RetryUnwindException}.  This unwinding is dependent upon catch
       * blocks being implemented to call this method.
       * <p>
       * If the current block is not the target for the pending retry, the
       * exception is re-thrown.
       *
       * @param    rue
       *           The exception to honor or ignore.
       *           
       * @throws   RetryUnwindException 
       */
      public void honorRetry(RetryUnwindException rue)
      throws RetryUnwindException
      {
         TransactionManager.honorRetry(wa, rue);
      }
      
      /**
       * Check if the current error must unwind all the way to the caller of
       * the top-level block in this method (if the ignore flag is set).  If
       * this is the case then the error is re-thrown.  Before this occurs, if
       * the current block is the top-level block of this method, then the
       * ignore flag will be cleared.  If the ignore flag is <code>false</code> 
       * then this method returns silently.
       * <p>
       * This code also honors any nested error that was generated while another
       * error was being processed AND while nested error mode was enabled.  Any
       * such stored error will be re-thrown here.
       *
       * @param    err
       *           The exception to honor or ignore.
       *           
       * @throws   ErrorConditionException 
       */
      public void honorError(ErrorConditionException err)
      throws ErrorConditionException
      {
         TransactionManager.honorError(wa, err);
      }
      
      /**
       * Notifies the transaction manager that an unexpected exception or error
       * has occurred and that any transaction must be aborted. This will
       * maintain the state of the system properly, including rolling back any
       * open transaction. Once the state is properly maintained, the triggering
       * <code>Throwable</code> will be re-thrown as a 
       * <code>RuntimeException</code>.
       * <p>
       * If the exception is of type <code>ConditionException</code> or
       * <code>RetryUnwindException</code>, this method will do nothing since
       * these are used for normal flow of control purposes and are used in
       * close coordination with the transaction manager's state.  They need
       * no other special behavior to be safe.  This method is designed to
       * handle exceptions (like <code>NullPointerException</code>) which are
       * not valid in a converted 4GL program.
       *
       * @param    thr
       *           The unexpected exception or error.
       *           
       * @throws   RuntimeException 
       */
      public void abnormalEnd(Throwable thr)
      throws RuntimeException
      {
         TransactionManager.abnormalEnd(wa, thr, true);
      }
      
      /**
       * Notifies the transaction manager that an unexpected exception or error
       * has occurred and that any transaction must be aborted. This will
       * maintain the state of the system properly, including rolling back any
       * open transaction. Once the state is properly maintained, the triggering
       * <code>Throwable</code> will be re-thrown as a 
       * <code>RuntimeException</code>.
       * <p>
       * If the exception is of type <code>ConditionException</code> or
       * <code>RetryUnwindException</code>, this method will do nothing since
       * these are used for normal flow of control purposes and are used in
       * close coordination with the transaction manager's state.  They need
       * no other special behavior to be safe.  This method is designed to
       * handle exceptions (like <code>NullPointerException</code>) which are
       * not valid in a converted 4GL program.
       *
       * @param    thr
       *           The unexpected exception or error.
       * @param    needsNotify
       *           <code>true</code> if condition notification must be executed
       *           (only happens if the <code>thr</code> is a condition).
       *           
       * @throws   RuntimeException 
       */
      public void abnormalEnd(Throwable thr, boolean needsNotify)
      throws RuntimeException
      {
         TransactionManager.abnormalEnd(wa, thr, needsNotify);
      }
      
      /**
       * Reports the transaction level of the current block.
       *
       * @return   The transaction level.
       */
      public int currentTransactionLevel()
      {
         BlockDefinition blk = wa.blocks.peek();
         return blk.level;
      }
      
      /**
       * Reports if the current scope defines the outermost transaction block
       * (which is the scope at which final commit processing must occur).
       *
       * @return   <code>true</code> if the current scope defines the top-level
       *           transaction.
       */
      public boolean isFullTransaction()
      {
         return wa.isFullTransaction();
      }
      
      /**
       * Reports if there is a current transaction which is active.
       *
       * @return    <code>true</code> if a transaction is active.
       */
      public boolean isTransaction()
      {
         return wa.transLevel != -1;
      }
      
      /**
       * Reports if there is a current active transaction at the specified block depth.
       *
       * @param     blockDepth
       *            Zero-based block depth starting from the outermost block at which transaction
       *            status is checked.
       *
       * @return    <code>true</code> if a transaction is active at the specified block depth.
       */
      public boolean isTransactionAt(int blockDepth)
      {
         int trans = wa.transLevel;
         return trans != -1 && trans <= blockDepth;
      }
      
      /**
       * Reports if the current block is a top-level block (this corresponds
       * with a method-level block).
       *
       * @return  <code>true</code> if top-level block, else <code>false</code>.
       */
      public boolean isTopLevelBlock()
      {
         return TransactionManager.isTopLevelBlock(wa);
      }
      
      /**
       * Reports if the current block is a external block (this corresponds
       * with a method-level block that was derived from a Progress external
       * procedure).  This will be <code>false</code> for blocks derived from
       * internal procedures, functions and triggers.
       *
       * @return   <code>true</code> if it is an external block, else 
       *           <code>false</code>.
       */
      public boolean isExternalBlock()
      {
         BlockDefinition blk = wa.blocks.peek();
         return blk.external;
      }
      
      /**
       * Reports if the current block is a global scoped block. For normal contexts:
       * <code>true</code> if the current block is the only block on the stack of block scopes; for
       * appserver agent contexts: <code>true</code> if the current block is one of two outermost
       * blocks ("appserver-agent" and "startup") on the stack of block scopes.
       *
       * @return   See above.
       */
      public boolean isGlobalBlock()
      {
         int size = wa.blocks.size();
         return size == 1 || (size == 2 && wa.isRemote());
      }
   
      /**
       * Check if this session is an appserver session or not.
       * 
       * @return   The {@link WorkArea#remote} state.
       */
      public boolean isRemote()
      {
         return wa.isRemote();
      }
      
      /**
       * Reports if the current block has the ERROR property.
       *
       * @return   <code>true</code> if the current block has the 
       *           <code>PROP_ERROR</code> bit set in its properties bitmap.
       */
      public boolean hasErrorProp()
      {
         return TransactionManager.hasProperty(wa, PROP_ERROR);
      }
      
      /**
       * Reports if the current block has the ENDKEY property.
       *
       * @return   <code>true</code> if the current block has the 
       *           <code>PROP_ENDKEY</code> bit set in its properties bitmap.
       */
      public boolean hasEndkeyProp()
      {
         return TransactionManager.hasProperty(wa, PROP_ENDKEY);
      }
      
      /**
       * Reports if the current block has the STOP property.
       *
       * @return   <code>true</code> if the current block has the 
       *           <code>PROP_STOP</code> bit set in its properties bitmap.
       */
      public boolean hasStopProp()
      {
         return TransactionManager.hasProperty(wa, PROP_STOP);
      }
      
      /**
       * Reports if the current block has the QUIT property.
       *
       * @return   <code>true</code> if the current block has the 
       *           <code>PROP_QUIT</code> bit set in its properties bitmap.
       */
      public boolean hasQuitProp()
      {
         return TransactionManager.hasProperty(wa, PROP_QUIT);
      }
      
      /**
       * During global finish processing, this reports if the exit was caused
       * by a <code>QuitConditionException</code>.
       *
       * @return   <code>true</code> if this exit is due to a quit condition.
       */
      public boolean isProcessingQuit()
      {
         return wa.processingQuit;
      }
      
      /**
       * During global finish processing, this sets the flag for an exit that
       * was caused by a <code>QuitConditionException</code>.
       *
       * @param    quit
       *           <code>true</code> if this exit is due to a quit condition.
       */
      public void setProcessingQuit(boolean quit)
      {
         wa.processingQuit = quit;
      }
      
      /**
       * Report if this context should be treated as headless. A headless
       * session is one that has no user interaction.
       *
       * @return   <code>true</code> if this session is headless.
       */
      public boolean isHeadless()
      {
         return wa.headless;
      }
   
      /**
       * Increases the number (by 1) of interactive language statements
       * (statements which took user input except for PAUSE) that have executed
       * in the current block since the most recent start scope, iterate or
       * retry.
       */
      public void incrementInteractions()
      {
         BlockDefinition blk = wa.blocks.peek();
         blk.interactions++;
      }
      
      /**
       * Resets the number of interactive language statements (statements which
       * took user input except for PAUSE) that have executed in the current
       * block to 0.
       */
      public void resetInteractions()
      {
         BlockDefinition blk = wa.blocks.peek();
         blk.interactions = 0;
      }
      
      /**
       * Gets the number of interactive language statements (statements which
       * took user input except for PAUSE) that have executed in the current
       * block  since the most recent start scope, iterate or retry.
       *
       * @return   The number of interactions in the current block.
       */
      public int getInteractions()
      {
         BlockDefinition blk = wa.blocks.peek();
         return blk.interactions;
      }
      
      /**
       * Get the rollup value for the current block. If <code>true</code>,
       * then any contained blocks that exit will cause the current (containing)
       * block's interactions counter to be increased by the contained block's
       * interactions counter. Otherwise, no aggregation will occur.
       *
       * @return   The current block's rollup value.
       */
      public boolean isRollup()
      {
         BlockDefinition blk = wa.blocks.peek();
         return blk.rollup;
      }
      
      /**
       * Get the rollup value for the enclosing block. If <code>true</code>,
       * then the current block's exit will cause the enclosing (containing)
       * block's interactions counter to be increased by the current block's
       * interactions counter. Otherwise, no aggregation will occur.
       *
       * @return   The current block's rollup value.
       */
      public boolean isEnclosingRollup()
      {
         return TransactionManager.isEnclosingRollup(wa);
      }
      
      /**
       * Set the new rollup value for the current block. If <code>true</code>,
       * then any contained blocks that exit will cause the current (containing)
       * block's interactions counter to be increased by the contained block's
       * interactions counter. Otherwise, no aggregation will occur.
       *
       * @param    rollup
       *           The new rollup value.
       */
      public void setRollup(boolean rollup)
      {
         BlockDefinition blk = wa.blocks.peek();
         blk.rollup = rollup;
      }
      
      /**
       * Notify all registered {@link BatchListener} objects that a batch is 
       * starting.
       */
      public void batchStart()
      {
         TransactionManager.processBatchNotifications(wa, true);
      }
         
      /**
       * Notify all registered {@link BatchListener} objects that a batch is 
       * stopping.
       */
      public void batchStop()
      {
         TransactionManager.processBatchNotifications(wa, false);
      }
      
      /**
       * Tests the interrupted status of the current thread using
       * <code>Thread.interrupted</code>, if <code>true</code> then a
       * {@link StopConditionException} will be thrown. The interrupted status
       * will be <code>true</code> if a call to <code>Thread.interrupt</code>
       * was previously made.
       * <p>
       * The thread's interrupted status is cleared by this method before the
       * <code>StopConditionException</code> is thrown.
       *
       * @throws   StopConditionException
       *           If the current thread's was previously interrupted.
       * @throws   SilentUnwindException
       *           If the connection ended prematurely end the current thread 
       *           needs to stop.
       */
      public void honorStopCondition()
      throws StopConditionException,
             SilentUnwindException
      {
         wa.honorStopCondition();
      }
      
      /**
       * Method called when the processing needs to be terminated. This will set a flag which will
       * indicate that the processing should be ended by raising a {@link
       * LeaveUnwindException error}, on the next {@link #honorStopCondition()} call.
       */
      public void abortProcessing()
      {
         if (workArea.get(false) == null)
         {
            // if the context does not exist, do not create it at this time
            return;
         }
         
         wa.abortProcessing = true;
      }
      
      /**
       * Enable/disable nested error mode. This mode allows for storing an error
       * which is generated during the processing of a different error, so that
       * during {@link #honorError} this can be re-thrown.  This duplicates some
       * obscure behavior seen in Progress.
       *
       * @param    mode
       *           <code>true</code> to enable, <code>false</code> to disable.
       */
      public void setNestedMode(boolean mode)
      {
         wa.nested = mode;
      }
      
      /**
       * Reports on the enable/disable state of nested error mode. This mode
       * allows for storing an error which is generated during the processing of 
       * a different error, so that during {@link #honorError} this can be
       * re-thrown.
       *
       * @return   <code>true</code> if nested error mode is enabled.
       */
      public boolean isNestedMode()
      {
         return wa.nested;
      }
      
      /**
       * Returns the current block type.
       *
       * @return   The type of the current block.
       */
      public BlockType getBlockType()
      {
         BlockDefinition blk = wa.blocks.peek();
         
         return blk.type;
      }
      
      /**
       * Get the top {@link BlockDefinition}.
       * 
       * @return   See above.
       */
      public BlockDefinition getBlock()
      {
         return wa.blocks.peek();
      }

      /**
       * Returns the index of the full transaction block, or -1 if none exists.
       *
       * @return   The index of the full transaction block, or -1 if none exists.
       */
      public int getFullTransactionBlock()
      {
         return TransactionManager.getFullTransactionBlock(wa);
      }
      
      /**
       * Returns the current nesting level.
       *
       * @return   the current nesting level.
       */
      public int getNestingLevel()
      {
         return wa.blocks.size();
      }
   
      /**
       * Check if this is block is the block associated with the root external program,
       * in the 4GL legacy stack.
       * 
       * @return   See above.
       */
      public boolean isRootNestingLevel()
      {
         int nestingLevel = getNestingLevel();
         
         return nestingLevel == (wa.isRemote() ? 3 : 2);
      }
      
      /**
       * Check if this is block is the block associated with the root external program,
       * in the 4GL legacy stack using the nearest external block.
       * 
       * @return   See above.
       */
      public boolean isRootExternal()
      {
         return TransactionManager.isRootExternal();
      }

      /**
       * Store the given exception for re-throwing during {@link #honorError} if 
       * nested error mode is enabled.  Note that only 1 error can ever be nested
       * and the last one set via this method will be the one that is honored.
       *
       * @param    err
       *           The error to store.
       */
      public void saveNestedError(ConditionException err)
      {
         if (wa.nested)
         {
            wa.nestedError = err;
         }
      }
      
      /** 
       * Notifies this class that an interactive pause has occurred. This may
       * affect the processing of infinite loop protection depending on the
       * type of block being processed.  This state is set for the remainder of 
       * the current iteration of the innermost block.
       */
      public void hadPause()
      {
         // peek at the current block def on the top of the stack
         BlockDefinition blk = wa.blocks.peek();
         blk.hadPause = true;
      }
      
      /**
       * Search the stack of scopes and return the level of the first external
       * block found.
       *
       * @return   The stack level of the nearest enclosing external block or
       *           -1 if there is no block marked as an external block. Level
       *           number is 1-based starting from the outermost scope.
       */
      public int findNearestExternal()
      {
         return TransactionManager.findNearestExternal(wa);
      }
      
      /**
       * This method is called before the initialization of a FOR or FOR EACH 
       * block. It will clear the {@link WorkArea#offEndQueries} set and will
       * set the {@link WorkArea#offEndRegistration} flag to ON. While this flag
       * is ON, all created queries will be collected in the 
       * {@link WorkArea#offEndQueries} set.
       * <p>
       * After the block initialization is finished, the 
       * {@link #stopOffEndRegistration()} will be called.
       */
      public void startOffEndRegistration()
      {
         wa.offEndRegistration = true;
         wa.offEndQueries.push(new HashSet<>());
      }
   
      /**
       * This method is called after the initialization of a FOR or FOR EACH
       * block is finished. It will save all the {@link QueryOffEndListener}s
       * associated with the registered {@link WorkArea#offEndQueries queries}.
       */
      public void stopOffEndRegistration()
      {
         TransactionManager.stopOffEndRegistration(wa);
      }
      
      /**
       * Register the passed query for query off-end events. The passed queries
       * are all the queries created in the init method for a FOR or FOR EACH
       * block.
       * <p>
       * These queries will be able to end the loop when the last record is 
       * reached, by overriding any user-defined END-KEY action. The action 
       * wich is executed is the default UNDO, LEAVE action for END-KEY.
       * 
       * @param  query
       *         The query which needs to be registered.
       */
      public void registerOffEndQuery(P2JQuery query)
      {
         TransactionManager.registerOffEndQuery(wa, query);
      }
      
      /**
       * Check if the given listener is registered for query off-end events and
       * can override the default END-KEY action (which is UNDO, LEAVE), when
       * the current block is FOR or FOR EACH.
       * 
       * @param   listener
       *          A listener to be checked if is registered for off-end events.
       * 
       * @return  <code>true</code> if the listener is registered for query 
       *          off-end event.
       */
      public boolean isOffEndListener(QueryOffEndListener listener)
      {
         return TransactionManager.isOffEndListener(wa, listener);
      }
      
      /**
       * Iterate all registered off-end listeners and call the 
       * {@link QueryOffEndListener#initialize() initialize} or 
       * {@link QueryOffEndListener#finish() finish} method, depending on the 
       * flag.
       * 
       * @param   init
       *          <code>true</code> if the listeners are initialized; else, the
       *          {@link QueryOffEndListener#finish()} method is called. 
       */
      public void processOffEndListeners(boolean init)
      {
         TransactionManager.processOffEndListeners(wa, init);
      }
      
      /**
       * Get the topmost set of off-end {@link QueryOffEndListener listeners}.
       * 
       * @param   clear
       *          <code>true</code> if the top-most off-end listeners set
       *          should be cleared.
       */
      public Set<QueryOffEndListener> getOffEndListeners(boolean clear)
      {
         return TransactionManager.getOffEndListeners(wa, clear);
      }
      
      /**
       * Register the single output parameter assigner for the current block.
       * This object will be deregistered by the {@link BlockManager} when it is
       * time to process assign-backs from function/procedure output parameters
       * to the database record fields with which they are associated.
       * 
       * @param   opa
       *          An output parameter assigner.
       * 
       * @throws  IllegalStateException
       *          if a second output parameter assigner is registered for a block
       *          before the current one is deregistered.  There can be only one.
       */
      public void registerOutputParameterAssigner(OutputParameterAssigner opa)
      {
         TransactionManager.registerOutputParameterAssigner(wa, opa);
      }
   
      /**
       * Defer handling of an exception thrown by a commit/rollback notification
       * callback on a registered {@link Commitable}, so that iteration or pop
       * scope processing can be continued without interruption.  This ensures
       * that all <code>Commitable</code>s registered after the errant one still
       * receive their commit/rollback notifications, and that (in the case of
       * popping a scope), registered {@link Scopeable}s still receive their
       * scope finished notifications.
       * <p>
       * If no error is already pending, <code>exc</code> is stored, to be thrown
       * upon conclusion of processing the current block scope or iteration.  If
       * an error is already pending, <code>exc</code> is discarded.  In either
       * case, <code>exc</code> is logged if error-level logging is enabled.
       *
       * @param   action
       *          A brief description of the action occurring at the time the
       *          error was encountered, such as "commit" or "master rollback".
       * @param   thr
       *          The error caught during processing of a commit or rollback
       *          notification.
       *
       * @see     WorkArea#handleDeferredError
       */
      public void deferError(String action, Throwable thr)
      {
         TransactionManager.deferError(wa, action, thr, false);
      }
   
      /**
       * Defer handling of an exception thrown by a commit/rollback notification
       * callback on a registered {@link Commitable}, so that iteration or pop
       * scope processing can be continued without interruption.  This ensures
       * that all <code>Commitable</code>s registered after the errant one still
       * receive their commit/rollback notifications, and that (in the case of
       * popping a scope), registered {@link Scopeable}s still receive their
       * scope finished notifications.
       * <p>
       * If no error is already pending, <code>exc</code> is stored, to be thrown
       * upon conclusion of processing the current block scope or iteration.  If
       * an error is already pending, <code>exc</code> is discarded.  In either
       * case, <code>exc</code> is logged if error-level logging is enabled.
       *
       * @param   action
       *          A brief description of the action occurring at the time the
       *          error was encountered, such as "commit" or "master rollback".
       * @param   thr
       *          The error caught during processing of a commit or rollback
       *          notification.
       * @param   silent
       *          If <code>true</code> then exception is not logged.
       *
       * @see     WorkArea#handleDeferredError
       */
      public void deferError(String action, Throwable thr, boolean silent)
      {
         TransactionManager.deferError(wa, action, thr, silent);
      }
   
      /**
       * Search the stack of scopes and return the level of the first top level
       * block found.
       *
       * @return   The stack level of the nearest enclosing top level block or
       *           -1 if there is no block marked as a top level block.
       */
      public int findNearestTopLevel()
      {
         return TransactionManager.findNearestTopLevel(wa);
      }
      
      /**
       * Search the stack of scopes and return the type of first top level block found.
       *
       * @return   The nearest enclosing top level block or {@link BlockType#UNKNOWN} if
       *           there is no block marked as a top level block.
       */
      public BlockType getNearestTopLevelType()
      {
         return TransactionManager.getNearestTopLevelType(wa);
      }
      
      /**
       * Attempt to initiate a rollback. A rollback is initiated if the transaction level
       * of the current scope is a SUB-TRANSACTION or TRANSACTION and if there is no pending
       * rollback. If a rollback is already pending a rollback will be initiated when the scope
       * is being popped. See {@link #popScope()}.
       */
      public void attemptRollback()
      {
         if (currentTransactionLevel() != NO_TRANSACTION && !wa.isRollbackPending())
         {
            rollback();
         }
      }
      
      /**
       * Worker that notifies all objects in the commit list that they must
       * commit (persist any state).
       * <p>
       * On the last iteration of a loop or block which ends in success (in
       * which the flow of control reaches the natural end of the block), both
       * will be called in succession.  However, other forms of exit which are
       * still considered successful (i.e. return and break which are not the
       * result of condition processing) will only execute <code>popScope</code>
       * and <code>iterate</code> will be bypassed.
       * <p>
       * It is important to note that the list of commitables in blk is
       * iterated in reverse order from that in which they are registered with
       * the <code>TransactionManager</code>.  This is done to ensure that
       * database transactions (which are registered before their corresponding
       * buffers) are always committed after all buffers are committed.
       *
       * @throws   ErrorConditionException
       *           If validation fails.
       */
      void processValidate()
      throws ErrorConditionException
      {
         wa.processValidate();
      }
   
      /**
       * Get the BlockDefinition instance of the nearest block of one of the
       * following types:
       * <ul>
       *    <li>{@link BlockType#EXTERNAL_PROC external procedure}</li>
       *    <li>{@link BlockType#INTERNAL_PROC internal procedure}</li>
       *    <li>{@link BlockType#FUNCTION user-defined function}</li>
       *    <li>{@link BlockType#TRIGGER trigger}</li>
       *    <li>{@link BlockType#DATABASETRIGGER trigger}</li>
       *    <li>{@link BlockType#EDITING editing}</li>
       * </ul> 
       * 
       * @return   See above.
       */
      BlockDefinition nearestExecutingBlock()
      {
         return TransactionManager.nearestExecutingBlock(wa);
      }
   
      /**
       * Get the executing block at the specified level.
       * 
       * @param    levels
       *           Zero-based depth of the target block, starting from the innermost block.
       *           
       * @return   The found block, or <code>null</code> if the index is outside of the block depth.
       */
      BlockDefinition getBlock(int levels)
      {
         return TransactionManager.getBlock(wa, levels);
      }
      
      /**
       * Deregister the output parameter assigner for the current block, if any.
       * It is expected that only the {@link BlockManager} will invoke this
       * method.
       * 
       * @return  Output parameter assigner for this block, or <code>null</code>
       *          if none was registered.
       */
      OutputParameterAssigner deregisterOutputParameterAssigner()
      {
         return TransactionManager.deregisterOutputParameterAssigner(wa);
      }
      
      /**
       * Adds the given object into the list of objects which require undo
       * notifications for the next block that is opened.
       * <p>
       * This form of registration is useful for objects whose natural lifetime
       * is scoped to the top-level block.  In particular, it is necessary for
       * objects that are constructed during a class' constructor/initializer
       * (before the external procedure block has opened).  Such objects cannot
       * simply register since their construction is not actually enclosed by the
       * block.
       * <p>
       * When the deferred registration occurs, it will NOT be global and will
       * NOT be added to registered Commitables.
       * <p>
       * This notification will occur whether or not a transaction is active.
       * <p>
       * These registrations naturally are removed when the associated scope
       * is removed.
       * <p>
       * The given object will only be added if it is not already in the list.
       *
       * @param    target
       *           The object references to be registered.  This cannot be
       *           <code>null</code>.
       */
      void registerNext(LazyUndoable... target)
      {
         TransactionManager.registerAt(wa, ScopeLevel.NEXT, target);
      }
      
      /**
       * Adds the given object into the list of objects which require undo
       * notifications for the next block that is opened.
       * <p>
       * This form of registration is useful for objects whose natural lifetime
       * is scoped to the top-level block.  In particular, it is necessary for
       * objects that are constructed during a class' constructor/initializer
       * (before the external procedure block has opened).  Such objects cannot
       * simply register since their construction is not actually enclosed by the
       * block.
       * <p>
       * When the deferred registration occurs, it will NOT be global and will
       * NOT be added to registered Commitables.
       * <p>
       * This notification will occur whether or not a transaction is active.
       * <p>
       * These registrations naturally are removed when the associated scope
       * is removed.
       * <p>
       * The given object will only be added if it is not already in the list.
       *
       * @param    scope
       *           The scope where to register the undoable targets.
       * @param    target
       *           The object references to be registered.  This cannot be
       *           <code>null</code>.
       */
      void registerAt(ScopeLevel scope, LazyUndoable... target)
      {
         TransactionManager.registerAt(wa, scope, target);
      }
      
      /**
       * Register the specified targets as undoables.  This will mark the object as undoable and save
       * the current tx level, regardless if a tx is active or not.
       * 
       * @param    undoTargets
       *           List of object references to be registered.  This cannot be
       *           <code>null</code>.
       */
      public void registerCurrent(LazyUndoable... undoTargets)
      {
         TransactionManager.registerCurrent(wa, undoTargets);
      }
      
      /**
       * Adds the object into a scoped dictionary of objects requiring UNDO support. This registry
       * is used to backup the objects at the beginning of transactions or sub-transactions.
       * <p>
       * These registrations naturally are removed when the associated scope is removed. However,
       * the global scope is long-lived. Special care is necessary to prevent the same instance of
       * global shared variables being added multiple times.
       *
       * @param    transLevel
       *           The transaction level to set for this undoable.
       * @param    persistent
       *           Flag indicating if the undoable is defined in a persistent procedure
       * @param    When not-null, persistent must be true and the referent represents the instance where to  
       *           look for undoables.  Otherwise, THIS-PROCEDURE will be used.
       * @param    undoTargets
       *           The object references to be rolled back on failure.  This
       *           cannot be <code>null</code>.
       */
      void registerAt(int transLevel, boolean persistent, Object referent, LazyUndoable... undoTargets)
      {
         TransactionManager.registerAt(wa, transLevel, persistent, referent, undoTargets);
      }

      /**
       * Adds the object into a scoped dictionary of objects requiring UNDO support. This registry
       * is used to backup the objects at the beginning of transactions or sub-transactions.
       * <p>
       * These registrations naturally are removed when the associated scope is removed. However,
       * the global scope is long-lived. Special care is necessary to prevent the same instance of
       * global shared variables being added multiple times.
       *
       * @param    transLevel
       *           The transaction level to set for this undoable.
       * @param    persistent
       *           Flag indicating if the undoable is defined in a persistent procedure
       * @param    undoTargets
       *           The object references to be rolled back on failure.  This
       *           cannot be <code>null</code>.
       */
      void registerAt(int transLevel, boolean persistent, LazyUndoable... undoTargets)
      {
         TransactionManager.registerAt(wa, transLevel, persistent, undoTargets);
      }
      
      /**
       * Enable tracking of all the undaobles defined in the next instantiating external program.
       */
      void trackUndoables()
      {
         wa.trackUndoables = true;
         wa.trackedUndoables = new IdentityHashMap<>();
      }
     
      /**
       * Untrack (by removing from all the tx blocks) all the undoables defined and registered for
       * the specified external program.
       * 
       * @param    referent
       *           The external program instance.
       */
      void untrackUndoables(Object referent)
      {
         TransactionManager.untrackUndoables(wa, referent);
      }
      
      /**
       * Rollback the transaction, at the {@link #APPSERVER_ROOT_BLOCK_INDEX root FWD block}.
       * 
       * @param    chained
       *           Flag indicating if a new transaction will be started.
       */
      void rollbackTx(boolean chained)
      {
         TransactionManager.rollbackTx(wa, chained);
      }
      
      /**
       * Commit the transaction, at the {@link #APPSERVER_ROOT_BLOCK_INDEX root FWD block}.
       * 
       * @param    chained
       *           Flag indicating if a new transaction will be started.
       */
      void commitTx(boolean chained)
      {
         TransactionManager.commitTx(wa, chained);
      }
      
      /**
       * Begin an explicit transaction, at the {@link #APPSERVER_ROOT_BLOCK_INDEX root FWD block}.
       * <p>
       * This explicitly marks this tx as being open by the 
       * {@link TransactionResource#getTxInitProcedure() TRANS-INIT-PROCEDURE}.
       */
      void beginTx()
      {
         TransactionManager.beginTx(wa, null);
      }
      
      /**
       * End an explicit transaction, at the {@link #APPSERVER_ROOT_BLOCK_INDEX root FWD block}.
       */
      void endTx()
      {
         TransactionManager.endTx(wa, null);
      }
      
      /**
       * Detect if the top-most block is a database trigger
       * @return
       *    True if the top-most block is a database trigger, false otherwise
       */
      public boolean isTopLevelDatabaseTrigger()
      {
         return TransactionManager.isTopLevelDatabaseTrigger(wa);
      }

      /**
       * Used to make a weak block stack snapshot. The stack is a copy while the content
       * should still reference the original definitions.
       * 
       * @return    A new stack containing the same block definitions.
       */
      public Stack<BlockDefinition> copyStack()
      {
         Stack<BlockDefinition> copy = new Stack<>();
         copy.addAll(wa.blocks);
         return copy;
      }
   }
   
   /**
    * Helper class to expose context-local state for undoable support.
    */
   static class UndoableHelper
   {
      /** The {@link WorkArea} instance. */
      private WorkArea wa;

      /**
       * Create a new instance and associate the given WorkArea instance.
       * 
       * @param    wa
       *           The {@link WorkArea} instance.
       */
      public UndoableHelper(WorkArea wa)
      {
         this.wa = wa;
      }
      
      /**
       * Obtain the context-local error helper.
       *
       * @return   The error helper.
       */
      public ErrorManager.ErrorHelper getErrorHelper()
      {
         return wa.em;
      }
      
      /**
       * Register the undoable with all transaction blocks (currently in the stack) started after
       *  the given transaction level.
       * 
       * @param    src
       *           The undoable instance.
       *           
       * @return   The current transaction level, as in {@link WorkArea#txNestingLevel}. 
       */
      public int registerUndoable(LazyUndoable src)
      {
         // the transaction level at which the given Undoable was last registered.
         int transLevel = src.getTransLevel();
         
         if (wa.txNestingLevel <= transLevel)
         {
            // nothing to do, no new tx blocks on the stack
            return wa.txNestingLevel;
         }

         boolean global = src.isGlobal();
         
         LazyUndoable snapshot = null;
         BlockDefinition blk = null;
         
         int idx = wa.blocks.size() - 1;
         while (idx >= 0)
         {
            blk = wa.blocks.get(idx);
            
            if (blk.level == NO_TRANSACTION)
            {
               idx = idx - 1;
               continue;
            }
            
            // global vars register all the way up the stack, until the full tx block is found
            if (!global && blk.txNestingLevel <= transLevel)
            {
               break;
            }
            
            if (blk.undoables == null)
            {
               blk.undoables = new ArrayList<>();
            }
            
            if (snapshot == null)
            {
               if (global && src instanceof BaseDataType)
               {
                  // TODO: this is an attempt to fix some quirks with NEW GLOBAL SHARED variables
                  // which are undone in a outer scope, greater than the defining one: in this 
                  // case, the var seems to undo to unknown, but the solution is not yet complete.
                  // only the first global var gets undone, the other get 'deleted'.
                  snapshot = ((BaseDataType) src).instantiateUnknown();
               }
               else
               {
                  snapshot = (LazyUndoable) src.deepCopy();
               }
            }

            blk.undoables.add(new UndoablePair(src, snapshot));
            
            idx = idx - 1;
            
            if (blk.full)
            {
               break;
            }
         }
         
         return wa.txNestingLevel;
      }
      
      /**
       * Check if undoable can be processed.
       * 
       * @param   currentBlock
       *          {@code true} to check only for the current block;
       *          {@code false} to check only if a transaction has been started.
       *          
       * @return  {@code true} if undoable can be processed.
       */
      public boolean processUndoables(boolean currentBlock)
      {
         return wa.processUndoables(currentBlock);
      }
   }
   
   /**
    * Stores global data relating to the state of the current context's
    * transactions.  Includes convenience methods for notifying listeners.
    */
   private static class WorkArea
   {  
      /** Sprintf format specification for string representation. */
      public static final String spec = "<depth = %d; trans_level = %d; "   +
                                        "trans_label = %s; "                +
                                        "rollback_scope = %d; "             +
                                        "rollback_label = %s; "             +
                                        "rollback_pending = %b; in_quit = " +
                                        "%b; retry_scope = %d; "            +
                                        "retry_label = %s; "                +
                                        "ignore_err = %b>";

      /** Helper to use the ProcedureManager without any context local lookups. */
      public ProcedureManager.ProcedureHelper pm;

      /** Helper to use the ErrorManager without any context local lookups. */
      public ErrorManager.ErrorHelper em;
      
      /** 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 Scopeable[] pendingScopeables = null;
      
      /** StopAfterTimer of the current conversation block. */
      private StopAfterTimer stopAfterTimer = null;
      
      /**
       * Map of Commitables to get a commit notification after all other commits occur in a full transaction;
       * keys are the listeners, values are flags indicating whether listeners are auto-removed.
       */
      private Map<Object, Boolean> masterCommit = null;
      
      /**
       * Map of Finalizables to get a finish notification after all other finish notifications occur in a full
       * transaction;  keys are the listeners, values are flags indicating whether listeners are auto-removed.
       */
      private Map<Object, Boolean> masterFinish = null;
      
      /** Map of BatchListeners to get batch start and stop notifications. */
      private final List<BatchListener> batchListeners = new LinkedList<>();
      
      /** Stack level of the full transaction if one is currently active. */
      // having a package-private field avoids a synthetic getter method emitted by javac
      int transLevel = -1;
      
      /** The number of blocks with full or sub-transaction level, in the current stack.*/
      private int txNestingLevel = -1;
      
      /** Defines the scope at which rollback will occur. */
      private int rollbackScope = -1;
      
      /** Flag to denote if a rollback is pending. */
      private boolean rollbackPending = false;
      
      /** Cause (if available) of pending rollback. */
      private Throwable rollbackPendingCause = null;
      
      /** Flag to denote if the exit is due to a quit condition. */
      private boolean processingQuit = false;
      
      /** Defines the scope at which a pending retry will occur. */
      private int retryScope = -1;
      
      /**
       * Defines if an error condition should be ignored until reaching the 
       * caller of a top-level block.
       */
      private boolean ignoreError = false;
      
      /** Throwable caught during commit/rollback processing but deferred. */
      private Throwable deferredError = null;
      
      /** Enables/disables nested error support at runtime. */
      private boolean nested = false;
      
      /** Exception caught during processing another error. */
      private ConditionException nestedError = null;
      
      /** Defines a single global scope which is not kept in the stack. */ 
      private BlockDefinition globalBlock = null;
      
      /**
       * Tracks block definitions with the top of the stack as the innermost
       * scope.
       */
      // having a package-private field avoids a synthetic getter method emitted by javac
      Stack<BlockDefinition> blocks = new Stack<>();
      
      /** <code>Finalizable</code>s which need deferred registration. */
      private ArrayList<Finalizable> deferredExtFinalizables = null;
      
      /** <code>Undoable</code>s which need deferred registration. */
      private IdentityHashMap<LazyUndoable, Object> deferredUndoables = null;
      
      /** Set of undoable dynamic extent vars, to be registered with the invoked procedure. */
      private Set<BaseDataType[]> deferredDynExt = null;
      
      /** Map of undoables defined in persistent procedures. */
      private Map<Object, IdentityHashMap<LazyUndoable, Object>> procUndoables = new IdentityHashMap<>();
      
      /**
       * Flag indicating if undoables need to be tracked at ext program instantiation, thus to
       * be registered with a persistent procedure.  It will be activated only when the 
       * instantiated external program is being ran persistent.
       */
      private boolean trackUndoables = false;
      
      /**
       * The map of undoables which are defined in a new instantiated external program, ran
       * persistent.
       */
      private IdentityHashMap<LazyUndoable, Object> trackedUndoables = null;
      
      /** Collection or map currently being iterated. */
      private Object nowIterating = null;
      
      /** Current operation. */
      private int op = OP_NONE;
      
      /** Registered STOP condition handlers */
      private List<StopConditionVetoHandler> stopHandlers = new LinkedList<>();

      /**
        * A stack of registered query off-end listeners, which interrupt the
        * FOR or FOR EACH block when a {@link QueryOffEndException} is 
        * caught (the user defined END-KEY action is ignored and will perform
        * a UNDO, LEAVE).
        */
      private ArrayDeque<Set<QueryOffEndListener>> offEndListeners = new ArrayDeque<>(10);

      /**
       * Registered <code>Resettable</code>s which should be reset at
       * specific points of block processing.
       */
      private ArrayList<Resettable> resettables = new ArrayList<>(1);

      /**
       * A stack of sets of queries registered during FOR or FOR EACH block initialization. Only
       * {@link QueryOffEndException} triggered by the queries from a given set can end a FOR or FOR EACH
       * block, even if the END-KEY action is overridden. The sets are organized in the stack because
       * in the {@code init()} section of a block not only off-end query registration can happen, but also
       * buffer flush and call of database triggers which may have their own FOR or FOR EACH blocks.
       */
      private ArrayDeque<Set<P2JQuery>> offEndQueries = new ArrayDeque<>();

      /**
       * Flag indicating that the block executes its {@link Block#init()} 
       * method. Only variants of FOR or FOR EACH blocks will set the flag to
       * <code>true</code>. During block initialization, all created queries
       * will be collected in the {@link #offEndQueries} set. 
       */
      private boolean offEndRegistration = false;

      /**
       * Value of {@code offEndRegistration} flag saved for the period of off-end query registration pause.
       */
      private boolean savedOffEndRegistration = false;

      /**
       * Flag indicating the processing should be terminated.
       */
      private volatile boolean abortProcessing = false;
      
      /** Flag that disables calls to the client side. */
      private boolean headless = false;
      
      /**
       * The cached {@link ProcedureManager}'s scopeable instance, to avoid resolving 
       * context-local data.
       */
      private Scopeable procScopeable;
      
      /** 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;
      
      /** Keep stop-after exception in order to re-throw it to the timeout block */
      private LegacyStopException stopAfterException = null;
      
      /**
       * Worker that notifies all objects in the commit list that they must
       * commit (persist any state).
       * <p>
       * On the last iteration of a loop or block which ends in success (in
       * which the flow of control reaches the natural end of the block), both
       * will be called in succession.  However, other forms of exit which are
       * still considered successful (i.e. return and break which are not the
       * result of condition processing) will only execute <code>popScope</code>
       * and <code>iterate</code> will be bypassed.
       * <p>
       * It is important to note that the list of commitables in blk is
       * iterated in reverse order from that in which they are registered with
       * the <code>TransactionManager</code>.  This is done to ensure that
       * database transactions (which are registered before their corresponding
       * buffers) are always committed after all buffers are committed.
       *
       * @throws   ErrorConditionException
       *           If validation fails.
       */
      private void processValidate()
      throws ErrorConditionException
      {
         if (FwdServerJMX.JMX_DEBUG)
         {
            VALIDATE.timer(this::processValidateImpl);
         }
         else
         {
            processValidateImpl();
         }
      }

      /**
       * Worker that notifies all objects in the commit list that they must
       * commit (persist any state).
       * <p>
       * On the last iteration of a loop or block which ends in success (in
       * which the flow of control reaches the natural end of the block), both
       * will be called in succession.  However, other forms of exit which are
       * still considered successful (i.e. return and break which are not the
       * result of condition processing) will only execute <code>popScope</code>
       * and <code>iterate</code> will be bypassed.
       * <p>
       * It is important to note that the list of commitables in blk is
       * iterated in reverse order from that in which they are registered with
       * the <code>TransactionManager</code>.  This is done to ensure that
       * database transactions (which are registered before their corresponding
       * buffers) are always committed after all buffers are committed.
       *
       * @throws   ErrorConditionException
       *           If validation fails.
       */
      private void processValidateImpl()
      {
         if (LOG.isLoggable(Level.FINE))
         {
            log(Level.FINE, "processValidate", null);
         }

         BlockDefinition blk = blocks.peek();
         if (blk.commitables == null)
         {
            return;
         }

         try
         {
            nowIterating = blk.commitables;
            boolean aggressiveFlush = blk.forFlushing || blk.external;

            ArrayList<Commitable> commitables = blk.commitables;
            for (int i = 0; i < commitables.size(); i++)
            {
               Commitable target = commitables.get(i);
               target.validate(blk.full, aggressiveFlush);
            }
         }

         finally
         {
            nowIterating = 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.
       * 
       * @return   The {@link #remote} state.
       */
      private boolean isRemote()
      {
         if (remote == null)
         {
            remote = AppServerManager.isRemote();
         }
         
         return remote.booleanValue();
      }
      
      /**
       * Reports if the current scope defines the outermost transaction block
       * (which is the scope at which final commit processing must occur).
       *
       * @return   <code>true</code> if the current scope defines the top-level
       *           transaction.
       */
      private boolean isFullTransaction()
      {
         BlockDefinition blk = blocks.peek();
         return blk.full;
      }
      
      /**
       * Reports if there is a current transaction which is active.
       *
       * @return    <code>true</code> if a transaction is active.
       */
      private boolean isTransaction()
      {
         return transLevel != -1;
      }

      /**
       * Reports if there is a rollback operation pending.
       *
       * @return    <code>true</code> if a rollback has been requested but not
       *            yet processed.
       */
      private boolean isRollbackPending()
      {
         return rollbackPending;
      }
      
      /**
       * Reports if there is a current transaction which is active.
       *
       * @return    <code>true</code> if a transaction is active.
       */
      private boolean isTransactionBlock()
      {
         return (transLevel != -1) && (transLevel == (blocks.size() - 1));
      }
         
      /**
       * Starts a new top-level transaction (requires that there is no current
       * transaction which is active).
       *
       * @param     level
       *            Stack level of the scope that is to be specified as the top
       *            level scope.
       */
      private void setTransactionLevel(int level)
      {
         if (transLevel != -1 && level != -1)
         {
            throw new IllegalArgumentException("Transaction is already active!");
         }
         
         transLevel = level;
         
         // 'close' all active transactions
         if (!isTransaction())
         {
            synchronized (transactionData)
            {
               SecurityManager sm = SecurityManager.getInstance();
               if (sm != null)
               {
                  Integer userId = sm.sessionSm.getSessionId();
                  if (userId != null)
                  {
                     for (Map.Entry<String, DbTransactions> entry : transactionData.entrySet())
                     {
                        DbTransactions dbData = entry.getValue();
                        // do not remove the [entry] from [transactionData] even if there are
                        // no more transactions in the database because it also keeps the 
                        // transactions counter that will be used for the upcoming transactions
                        dbData.transactions.remove(userId);
                        // mark the in-memory as dirty in order to remove the record with
                        // next flush operation 
                        dbData.isDirty = true;
                     }
                  }
               }
            }
         }
      }
      
      /**
       * Tests the interrupted status of the current thread using
       * <code>Thread.interrupted</code>, if <code>true</code> then a
       * {@link StopConditionException} will be thrown. The interrupted status
       * will be <code>true</code> if a call to <code>Thread.interrupt</code>
       * was previously made.
       * <p>
       * The thread's interrupted status is cleared by this method before the
       * <code>StopConditionException</code> is thrown.
       *
       * @throws   StopConditionException
       *           If the current thread's was previously interrupted.
       * @throws   SilentUnwindException
       *           If the connection ended prematurely end the current thread 
       *           needs to stop.
       */
      private void honorStopCondition()
      throws StopConditionException,
             SilentUnwindException
      {
         if (abortProcessing)
         {
            throw new SilentUnwindException();
         }

         if (Thread.interrupted())
         {
            if (LOG.isLoggable(Level.FINE))
            {
               log(Level.FINE,
                   "honorStopCondition",
                   "STOP (honoring deferred thread interruption)!");
            }
            
            String errmsg = "Thread was asynchronously interrupted.";
            StopConditionException exe = null;
            
            if (stopAfterTimer != null && stopAfterTimer.isStopAfterConditionRaised())
            {
               exe = new StopAfterConditionException(errmsg);
            }
            else 
            {
               exe = new StopConditionException(errmsg);
            }
            
            ErrorManager.handleStopException(exe);
         }
      }
      
      /**
       * If the LegacyStopException should be propagated up the stack
       * to the block that raised the STOP condition after a stop-after
       * timeout.
       *
       * @throws   blk
       *           BlockDefinition of the current block.
       */
      private void honorStopAfterCondition(BlockDefinition blk)
      throws LegacyStopException
      {
         LegacyStopException lex = stopAfterException;
            
         if (lex == null)
         {
            return;
         }
         ObjectVar<? extends Stop> error = lex.getError();
            
         if (stopAfterTimer != null && !stopAfterTimer.isStoppedBlock(blk))
         {
            throw lex;
         }
         else if (stopAfterTimer != null && stopAfterTimer.isStopAfterConditionRaised())
         {
            if (stopAfterTimer.isStoppedBlock(blk))
            {
               stopAfterTimer.cleanUp();
            }
          
            // we need a decrement here, as the instance is also 'hooked' by LegacyErrorException
            error.decrement();               
            // release this reference
            error.set(null);
            stopAfterException = null;
         }
      }
      
      /**
       * Returns the level specifying the rollback scope that is pending.
       *
       * @return    The level of the scope to be rolled back or -1 if the
       *            closest containing transaction or sub-transaction should
       *            be rolled back.
       */
      private int getRollbackScope()
      {
         return rollbackScope;
      }

      /**
       * Handle a pending, deferred error, represented by an exception thrown
       * by a commit or rollback notification callback on a registered {@link
       * Commitable}, and caught within {@link #processCommit} or {@link
       * #processRollback}, respectively.  If there is currently no deferred
       * error, this method returns immediately.  Otherwise, a debug message
       * is logged, the deferred error is reset to <code>null</code>, and the
       * stored exception is thrown.  It is intended that this method is called
       * at the end of iterate or pop scope processing, after all registered
       * <code>Commitable</code>s and <code>Scopeable</code>s have been notified
       * of the state change.
       *
       * @see     #deferError
       */
      private void handleDeferredError()
      throws RuntimeException
      {
         if (deferredError == null)
         {
            // nothing to do
            return;
         }
         
         if (LOG.isLoggable(Level.SEVERE))
         {
            String msg = "Throwing deferred error (" + deferredError.getMessage() + ")";
            log(Level.SEVERE, "handleDeferredError", msg);
         }
         
         // reset the error condition
         Throwable thr = deferredError;
         deferredError = null;
         
         // condition exceptions get thrown directly since they are "safe"
         if (!(thr instanceof ConditionException))
         {
            // anything else is wrapped in an unchecked exception and becomes an abnormal end
            thr = new RuntimeException("Deferred error processing.", thr);
         }
         
         // honor the error
         throw (RuntimeException) thr;
      }

      /**
       * Report scope depth.
       *
       * @return  Number of blocks currently on stack.
       */
      private int depth()
      {
         return blocks.size();
      }
      
      /**
       * Notify all master Finalizables of a retry event.  Listeners are
       * notified in the order in which they were registered.
       */
      private void notifyMasterRetry()
      {
         if (masterFinish == null)
         {
            return;
         }
         
         if (LOG.isLoggable(Level.FINE))
         {
            log(Level.FINE, "notifyMasterRetry", null);
         }
         
         try
         {
            nowIterating = masterFinish;
            op = OP_RETRY;
            
            for (Object it : masterFinish.keySet())
            {
               // Process notification, deferring any error until we are in a
               // safe state to handle it.
               Finalizable target = (Finalizable) it;
               try
               {
                  target.retry();
               }
               catch (Throwable thr)
               {
                  deferError("master retry", thr);
               }
            }
         }
         
         finally
         {
            nowIterating = null;
            op = OP_NONE;
         }
      }
      
      /**
       * Notify all master Finalizables of an iteration or finished event.
       * <p>
       * After each notification, the corresponding {@code Finalizable} is removed, if it was registered as
       * auto-removable.  Listeners are notified in the order in which they were registered.
       *
       * @param   iterate
       *          {@code true} to notify of an iterate event; {@code false} to notify of a finished event.
       */
      private void notifyMasterFinish(boolean iterate)
      {
         if (masterFinish == null)
         {
            return;
         }
         
         if (LOG.isLoggable(Level.FINE))
         {
            log(Level.FINE, "notifyMasterFinish", "iterate = " + iterate);
         }
         
         try
         {
            nowIterating = masterFinish;
            op = iterate ? OP_ITERATE : OP_FINISHED;
            
            for (Object it : masterFinish.keySet())
            {
               // process notification, deferring any error until we are in a safe state to handle it
               Finalizable target = (Finalizable) it;
               if (iterate)
               {
                  try
                  {
                     target.iterate();
                  }
                  catch (Throwable thr)
                  {
                     deferError("master iterate", thr);
                  }
               }
               else
               {
                  try
                  {
                     target.finished();
                  }
                  catch (Throwable thr)
                  {
                     deferError("master finish", thr);
                  }
               }
            }
         }
         finally
         {
            nowIterating = null;
            op = OP_NONE;
         }
      }
      
      /**
       * Notify all master Commitables of a rollback or commit event.
       * After each notification, the corresponding Commitable is removed,
       * if it was registered as auto-removable and <code>autoRemove</code>
       * is <code>true</code>.  Listeners are notified in the order in which
       * they were registered.
       *
       * @param   rollback
       *          <code>true</code> to notify of a rollback event;
       *          <code>false</code> to notify of a commit event.
       */
      private void notifyMasterCommit(boolean rollback)
      {
         if (masterCommit == null)
         {
            return;
         }
         
         if (LOG.isLoggable(Level.FINE))
         {
            log(Level.FINE, "notifyMasterCommit", "rollback = " + rollback);
         }
         
         try
         {
            nowIterating = masterCommit;
            
            for (Object it : masterCommit.keySet())
            {
               // Process notification, deferring any error until we are in a safe state to handle it.
               Commitable target = (Commitable) it;
               if (rollback)
               {
                  try
                  {
                     target.rollback(true);
                  }
                  catch (Throwable thr)
                  {
                     deferError("master rollback", thr);
                  }
               }
               else
               {
                  try
                  {
                     target.commit(true);
                  }
                  catch (Throwable thr)
                  {
                     deferError("master commit", thr);
                  }
               }
            }
         }
         finally
         {
            nowIterating = null;
         }
      }
      
      /**
       * Iterate through registered, master Finalizables and deregister those marked for auto-remove.
       */
      private void autoRemoveMasterFinish()
      {
         autoRemoveMasterFinish(false);
      }
      
      /** 
       * Iterate through registered, master Finalizables and deregister those marked for auto-remove.
       * Before deregistering, it will also call {@link Finalizable#deleted()} based on the parameter
       * value given.
       * 
       * @param   delete
       *          If the deleted() method of the Finalizable should be called.
       */
      private void autoRemoveMasterFinish(boolean delete)
      {
         if (masterFinish != null && canRemoveFromMap(masterFinish, "Finalizables"))
         {
            autoRemoveAndDelete(masterFinish, delete);
            if (masterFinish.isEmpty())
            {
               masterFinish = null;
            }
         }
      }
      
      /** Iterate through registered, master Commitables and deregister those marked for auto-remove. */
      private void autoRemoveMasterCommit()
      {
         if (masterCommit != null && canRemoveFromMap(masterCommit, "Commitables"))
         {
            autoRemoveAndDelete(masterCommit, false);
            if (masterCommit.isEmpty())
            {
               masterCommit = null;
            }
         }
      }
      
      /**
       * Test whether the {@code map} is NOT being iterated so elements stored there can be removed.
       *
       * @param   map
       *          The map to be tested.
       * @param   name
       *          The name/type of the elements contained in the map. Only for debugging.
       *
       * @return  {@code true} if the map can be altered by removing elements or {@code false} when a remove
       *          operation will cause a {@code ConcurrentModificationException}.
       */
      private boolean canRemoveFromMap(Map<Object, Boolean> map, String name)
      {
         if (nowIterating == map)
         {
            // prevent ConcurrentModificationException
            if (LOG.isLoggable(Level.WARNING))
            {
               log(Level.WARNING,
                   "autoRemoveMaster" + name,
                   "Illegal attempt to auto-remove master " + name,
                   new Throwable());
            }
            
            return false;
         }
         
         return true;
      }
      
      /**
       * Iterate through the given map entries and remove all those whose values are {@code true}.  
       * The key is a {@code Commitable} or {@code Finalizable} target, the value is its registered 
       * auto-remove setting. For a {@code Finalizable} target, it also calls the 
       * {@link Finalizable#deleted()} method.
       * 
       * @param   map
       *          Map of registered targets and their auto-remove settings.
       * @param   delete
       *          Should be {@code true} when the map contains Finalizable targets and in this case
       *          the deleted() method of the Finalizable will be called.
       */
      private void autoRemoveAndDelete(Map<Object, Boolean> map, boolean delete)
      {
         Iterator<Map.Entry<Object, Boolean>> iter = map.entrySet().iterator();
         
         while (iter.hasNext())
         {
            Map.Entry<Object, Boolean> current = iter.next();
            if (current.getValue())
            {
               if (delete)
               {
                  Object key = current.getKey();
                  if (key instanceof Finalizable)
                  {
                     ((Finalizable) key).deleted();
                  }
               }
               iter.remove();
            }
         }
      }
      
      /**
       * Render this work area's data as a human readable string.
       *
       * @return   The human readable form of the global data.
       */
      public String toString()
      {
         BlockDefinition tblk = null;
         BlockDefinition rollbackBlk = null;
         BlockDefinition retryBlk = null;
         int size = blocks.size();
         
         if (transLevel != -1 && transLevel < size)
         {
            tblk = blocks.elementAt(transLevel);
         }
         
         if (rollbackScope != -1 && rollbackScope < size)
         {
            rollbackBlk = blocks.elementAt(rollbackScope);
         }
         
         if (retryScope != -1 && retryScope < size)
         {
            retryBlk = blocks.elementAt(retryScope);
         }
         
         Object[] parms = new Object[]
         {
            depth(),
            transLevel,
            (tblk == null) ? null : tblk.label,
            rollbackScope,
            (rollbackBlk == null) ? null : rollbackBlk.label,
            rollbackPending,
            processingQuit,
            retryScope,
            (retryBlk == null) ? null : retryBlk.label,
            ignoreError
         };
         
         return String.format(spec, parms);
      }
      
      /**
       * Check if undoable can be processed.
       * 
       * @param   currentBlock
       *          {@code true} to check only for the current block;
       *          {@code false} to check only if a transaction has been started.
       *          
       * @return  {@code true} if undoable can be processed.
       */
      private boolean processUndoables(boolean currentBlock)
      {
         if (!currentBlock)
         {
            return this.transLevel != -1;
         }

         return processUndoables(this.blocks.peek());
      }
      
      /**
       * Check if undoable can be processed by the current block.
       * 
       * @return   {@code true} if a transaction has been started and the current block's
       *           transaction level is not {@link TransactionManager#NO_TRANSACTION}.
       */
      private boolean processUndoables()
      {
         return processUndoables(this.blocks.peek());
      }
      
      /**
       * Check if undoable can be processed by the current block.
       * 
       * @param    blk
       *           The target block.
       *           
       * @return   {@code true} if a transaction has been started and the current block's
       *           transaction level is not {@link TransactionManager#NO_TRANSACTION}.
       */
      private boolean processUndoables(BlockDefinition blk)
      {
         return this.transLevel != -1 && blk.level != NO_TRANSACTION;
      }
   }
   
   /**
    * Simple container that stores and returns a context-local instance of
    * the global work area.
    */
   private static class ContextContainer
   extends ContextLocal<WorkArea>
   {
      /**
       * Get the weight of this context-local var.
       * 
       * @return   Always {@link WeightFactor#LEVEL_4}.
       */
      @Override
      public WeightFactor getWeight()
      {
         return WeightFactor.LEVEL_4;
      };
      
      /**
       * 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.
       *
       * @return  The work area associated with this context.
       */
      public WorkArea obtain() 
      {
         WorkArea wa = get();
         if (wa == null)
         {
            wa = new WorkArea();
            wa.globalBlock = new BlockDefinition("globalScope",
                                                 NO_TRANSACTION,
                                                 PROP_NONE,
                                                 false,
                                                 false,
                                                 false,
                                                 false,
                                                 false,
                                                 false,
                                                 false,
                                                 UNKNOWN);
            
            // detect if headless mode is needed
            wa.headless = _isHeadless();
            
            // the following call MUST occur before creating Scopeables
            // below, in case a Scopeable initializer calls back into the
            // TransactionManager in such a way as to call this class'
            // obtain() method (prevents infinite loop and stack overflow)
            set(wa);
            
            wa.pm = ProcedureManager.getProcedureHelper();
            wa.em = ErrorManager.getErrorHelper();
            if (SessionManager.get() != null && !SessionManager.get().isLeaf())
            {
               // only on server-side
               wa.bm = BufferManager.get();
               wa.lt = (wa.headless ? null : LogicalTerminal.getInstance());
            }
            
            wa.em.setHeadless(wa.headless);

            wa.procScopeable = ProcedureManager.getScopeable();
         }
         
         return wa;
      }   
      
      /**
       * Return <code>null</code> rather than providing a true initial value;
       * initial value is instead lazily created in {@link #obtain}.
       *
       * @return  <code>null</code>.
       */
      protected final WorkArea initialValue()
      {
         return null;
      }
   }
   
   /**
    * The transaction information for a database. The object holds the transaction counter and the
    * set of transactions mapped by the connection id of the users that created them.
    */
   private static class DbTransactions
   {
      /** The id of last transaction generated for this database. */
      private int lastTransactionId = 0;
      
      /**
       * Flags whether a transaction has started or ended since the last flush to persistence.
       * This is only used to quickly decide whether there are transactions to be flushed.
       */
      private boolean isDirty = false;
      
      /** The set of transaction ids of this database mapped by the users that opened them. */
      private Map<Integer, Integer> transactions = new HashMap<>();
   }
}