ProcedureManager.java
/*
** Module : ProcedureManager.java
** Abstract : Implements access to procedure system handles and procedure related attributes and methods.
**
** Copyright (c) 2013-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- --------------------------------------Description---------------------------------------
** 001 CA 20130112 Created initial version.
** 002 CA 20130130 Added stubbed methods to implement the PUBLISH, SUBSCRIBE and UNSUBSCRIBE
** language statements.
** 003 CA 20130131 Fixed problem determining THIS-PROCEDURE when the trigger invoked is defined
** as ON ... PERSISTENT RUN.
** 004 CA 20130202 Update from character to TextOps.
** 005 CA 20130529 Some refactoring related to procedure deletion and added appserver support.
** 006 CA 20130918 Added runtime support for widget-pools: the pools need scope notifications,
** and this must be done by the ProcedureManager, to ensure proper access to the
** THIS-PROCEDURE handle.
** 007 CA 20130925 Added support for named events.
** 008 CA 20131013 Implemented implicit deletion of static resources and delayed processing of
** finalizable/scopeable instances. Added support for the INSTANTIATING-PROCEDURE
** attribute.
** 009 CA 20131208 Emit SEARCH-TARGET and SEARCH-SELF literals as integer. Fixed runtime for
** RUN SUPER and SUPER() calls, in search-target and search-self modes.
** 010 HC 20131222 Implemented ADM-DATA and UNIQUE-ID attribute.
** INSTANTIATING-PROCEDURE is now managed by ProcedureManager.
** 011 EVK 20131223 Made method isProcedure public.
** 012 CA 20140109 WorkArea.scopeFinished has no target-procedure to pop, when terminating an
** appserver request.
** 013 VIG 20140109 Implemented CURRENT-WINDOW attribute.
** 014 CA 20140218 Implemented PROGRAM-NAME. Fixed some SOURCE- and TARGET-PROCEDURE cases (the
** RUN PERSISTENT or RUN ext-prog cases). Also, the external program name used
** at the RUN statement must be used in error messages; the absolute name must be
** used each time SourceNameMapper APIs are invoked. Now we allow java-style
** calls for both internal and external procedures.
** 015 CA 20140221 Fixed SUPER-PROCEDURES - emit correct handle ID.
** 016 CA 20140223 Fixed deletion of persistent procedures via DELETE OBJECT <system-proc-handle>.
** 017 CA 20140301 Procedure's scope is notified first for start and last for finish.
** 018 HC 20140424 Fixed resolveClosestMethod failing to resolve extent functions.
** 019 CA 20140502 Fixed super-procedure chaining in SUPER and RUN SUPER calls.
** 020 SVL 20140414 Modified INSTANTIATING-PROCEDURE assignment logic in order to support agent
** contexts.
** 021 CA 20140513 Added a weight for the context-local var, to ensure predetermined order during
** context reset.
** 022 HC 20140629 Implemented access to the internal callee information structure.
** 023 CA 20140729 Made getRelativeName public.
** 024 VIG 20140810 Added invalid param processing for setCurrentWindow method.
** 025 CA 20140926 Added shared widget configuration support, to allow GUI/ChUI concrete
** implementation for the same widget ID. Refactored the widget configuration
** classes: all fields were made public; these classes need to be as dumb as
** possible, all logic related to setting/getting a certain field should be at
** the widget or at the caller. Refs #2254
** 026 EVL 20141024 The dynamic flag need to be set early enough for dynamic widgets to avoid NPE
** in processResource() method.
** 027 OM 20150112 Made _thisProcedure() public.
** 028 CA 20150306 When an external, non-persistent procedure, gets terminated, execute the same
** work as when deleting a persistent procedure.
** 029 CA 20150501 DELETE OBJECT THIS-PROCEDURE forces the delete of the current procedure,
** regardless of where is called (in an external proc block, inner proc, function
** or trigger).
** Track the resources created by each procedure, as dynamic resources don't get
** deleted when the creator proc gets deleted, but their INSTANTIATING-PROCEDURE
** attr needs to be unknown.
** 030 ECF 20150810 Removed redundant Throwable.fillInStackTrace call from resolveClosestMethod
** as a performance improvement.
** 031 ECF 20150907 Made resolveClosestMethod public. Re-organized methods by access modifier.
** 032 CA 20151027 When running an internal entry (procedure, function) of a persistent procedure,
** all its shared data (variables, tables, etc) needs to be pushed and popped,
** so that the internal entry (and any other external program ran from it) has
** access to it.
** When a procedure gets deleted (persistent or not), then all created triggers
** must be destroyed.
** resolveInstantiatingProcedure must not use the handle's c'tor, as this will
** not perform the assignment if a NO-ERROR clause is in effect.
** 033 ECF 20160105 Fixed concurrent modification errors in publish method.
** CA 20151219 Track the name of the external program being instantiated - required for
** ERROR conditions raised during instantiation (i.e. by shared vars).
** OM 20161026 Added setDefaultWindow() method.
** 034 EVL 20160224 Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
** 035 OM 20161026 Added cleanupPending() method.
** 036 IAS 20160314 DEBUG-ALERT support.
** 037 CA 20160418 Added infrastructure for message passing from converted code to a
** customer-specific application or calls from the customer-specific application
** into the converted code (i.e. invoke external programs, procedures or
** functions). Can be used only when the P2J client runs embedded in a
** customer-specific application.
** 038 GES 20160630 Some ControlFlowOps cases call removeProcedureImpl() with a null handle, we
** treat that as a no-op.
** 039 HC 20160718 PUBLISH/SUBSCRIBE runtime support modified so that events are fired in the
** order of subscriptions. Procedure cleanup will also remove the procedure's
** subscriptions.
** 040 CA 20160728 Fixed undoable support for data in external programs ran persistent.
** 041 HC 20160817 Fixed PUBLISH to properly invoke all procedures even in case there is a
** failure due to procedure not found in the procedure file.
** Fixed PUBLISH to resolve the target internal procedure in the subscribed
** procedure as well as in its SUPER procedures or SESSION procedures.
** Fixed UNSUBSCRIBE to only unsubscribe the procedure passed in the statement.
** 042 CA 20160812 Performance improvements for H040: use IdentityHashMap when using
** ext prog instances as keys.
** 043 CA 20160824 Fixed a regression with delete-on-exit support: the external program must be
** deleted only when the last of its internal entry on the stack is finished.
** 044 CA 20160902 Fixed H043 - only this-procedure stack is checked for delete-on-exit postpone.
** Fixed a possible leak when deleting ext prog at scope finish. Fixed order of
** created static resources in an ext prog.
** 045 CA 20160907 For the duration of a PUBLISH call, the SOURCE-PROCEDURE must be set as the
** publisher's procedure handle.
** 046 OM 20160901 Replaced ArrayDeque with custom Deque implementation for persistentProcedures.
** Small optimizations.
** CA 20160923 Reworked H045 to explicitly set the PUBLISH's SOURCE-PROCEURE without
** 'hacking' the WorkArea.thisProcedures stack, as this is used in conjunction
** with the WorkArea.internalEntryStack, when determining PROGRAM-NAME or
** 4GL-style stacktraces.
** 047 HC 20161110 Fixed an abend in publish() when subscriptions are removed as a result of the
** published event processing.
** Fixed several cases of referencing deleted system handles TARGET-PROCEDURE,
** SOURCE-PROCEDURE and THIS-PROCEDURE.
** 048 CA 20170228 Added PUBLISH/SUBSCRIBE/UNSUBSCRIBE extensions for global support and for
** external applications.
** 049 GES 20171206 Switch to ErrorManager.silent().
** 050 CA 20171202 Added searchInStack.
** 051 ECF 20171228 Refactored to remove redundant context-local lookups where possible.
** 052 CA 20181112 Added support for the TRANSACTION-MODE AUTOMATIC [CHAINED] language statement.
** Added support for the TRANSACTION resource.
** 053 EVL 20190219 Making hasReferent() method to be public to call outside the class.
** ECF 20190306 Replaced stream filter loop with simple for loop for better performance in
** deleteResources.
** 054 CA 20190123 Definitions like FUNCTION ... IN hvar will not convert via a HandleExpr inner
** class, but will receive the handle variable instead and the runtime will wrap
** it into a Resolvable.
** Infrastructure for class event registration.
** CA 20190219 Added runtime for class events.
** Added runtime for BLOCK-LEVEL UNDO, THROW and ROUTINE-LEVEL UNDO, THROW.
** CA 20190325 Refactored to expose heavy-duty APIs via a instance which provides direct
** access to the context-local state.
** CA 20190326 Implemented TransactionHelper instead of direct usage of the static API.
** This allows the elimination of context local usage in TM.
** WidgetPool's scope support is now exposed via a helper.
** 055 CA 20190628 Added SINGLE-RUN and SINGLETON support.
** 056 OM 20190718 Avoid NPE on hand-written code.
** CA 20190719 Allow searchInStack via ProcedureHelper.
** CA 20190720 Load implicit default databases - these databases are for non-default schemas
** in p2j.cfg.xml, so that they will not be loaded during normal conversion
** unless a hint is specified. We emulate this in the directory, in the
** 'default-databases' section, where the databases as specified for each Java
** package prefix.
** HC 20190727 Dynamic function lookup must be case insensitive.
** CA 20190816 Static temp-tables are now created when their default buffer is defined.
** CA 20190903 Added GLOBAL support for SUBSCRIBE/UNSUBSCRIBE (cross-session messaging).
** 057 AIL 20191120 Made the scope methods aware of DATABASE TRIGGER DataType
** CA 20191119 Track the executing legacy class, to be able to register any buffer to its
** source definition class.
** CA 20200110 Fixed for resource deleted, in cases of OE class hierarchy and REFERENCE-ONLY
** parameters.
** 058 CA 20200304 A 4GL object instance must delay its delete if is still on stack.
** 059 CA 20200427 For a remote appserver request, the INSTANTIATING-PROCEDURE may be null for
** a persistently run program.
** 060 CA 20200720 Moved some APIs to ProcedureHelper.
** 061 AIL 20200616 Added the persistentProcStacks, which should store snapshots of block stacks
** and are used to properly delete persistent procedures (defer if needed).
** 20200617 Added support for stale persistent procedures handling.
** 20200622 Changed the signature of procedure delete in order to exploit the stale
** persistent procedure by default.
** Impovements and javadoc update.
** GES 20200804 Removed context-local lookups from some ErrorManager usage.
** CA 20200927 Avoid context-local lookups by relying on the helper instance.
** CA 20200930 More context-local lookup improvements.
** RFB 20201002 Added a new thisProcedure, which allows for a referent to be passed in. Ref #4861
** CA 20201003 Use an identity HashSet where possible.
** CA 20201015 Replaced java.util.Stack with a non-synchronized custom implementation.
** RFB 20201015 New thisProcedure should handle null returned from locateProcedure. Ref. 4936.
** CA 20201119 The parameters for a PUBLISH call must not be altered by the ControlFlowOps invoke (in case
** of OUTPUT or INPUT-OUTPUT, it ensures to use a different instance, to not alter the
** caller's reference). For this reason, PUBLISH ensures that after a call, all parameter
** references are re-established and the values updated.
** CA 20201201 Performance improvement - keep the subscribers in a 'per event' map, so that PUBLISH will
** avoid iterating all subscriptions, and work only with the required event.
** CA 20210104 Fixed a regression after adding BaseObject.equals/hashCode - replaced plain set with
** identity set and a bug in resource delete.
** CA 20210310 Performance improvement for management of super-procedures (keep track of where each
** referent was added as a super-proc, so it knows which super-procedures to clean when it
** gets deleted, instead of walking the entire procedure chain).
** 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.
** CA 20210910 Fixed a NPE when a legacy OO instance is deleted from within a method invocation on its
** own instance.
** CA 20210917 Javadoc fixes.
** HC 20211001 Implementation of i18n support.
** OM 20211005 Added procedure-cleanup handler support. Object can register with a piece of code to be
** executed on procedure return (like table parameter unbinding).
** AL2 20211015 Support interation over the stack of resolved destinations of calls.
** ECF 20211124 Added isExecuted method to report whether an external procedure has been executed.
** CA 20211222 Legacy OO does not have 'stale procedure' behavior - this is specific only for external
** programs, and not legacy classes.
** CA 20220104 CA/20211222 needs to include the 'static class' referent, too, not just legacy instances.
** CA 20220106 PUBLISH arguments must be evaluated only if the subscription is found.
** CA 20220107 Fixed a NPE for remote PUBLISH (from JS client).
** CA 20220120 Performance improvement for pushing a new calee scope, keep the already resolved internal
** entry legacy name.
** CA 20220203 Added 'currentInternalEntry', to get the name of the currently executing internal entry.
** CA 20220208 Added SESSION:ERROR-STACK-TRACE runtime support.
** TW 20220803 Error-status:error must be set for return error in method, while return error
** in old-style ABL function must remain the same. Refs: #6567.
** TW 20220806 Error-status:error must be set for return error in method. Javadoc fix.
** TW 20220817 Error-status:error must be set for return error in static method.
** CA 20220930 Refactored the callback invocation to be performed via a call-site and InvokeConfig, to
** allow caching of the resolved target. ProcedureManager is responsible for invalidating
** the cache when the SUPER-PROCEDURES change or an external program gets deleted.
** 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 20221006 Improved performance of pending resources. Refs #6824
** UNIQUE-ID is kept as Java type instead of BDT. Refs #6827
** CA 20220427 Fixed isProxy when being called outside of a FWD context.
** CA 20220526 If there are subscriptions for a legacy OO instance, do not delete (implicitly).
** CA 20220526 Track and report any errors when deleting a persistent program (i.e. from buffer
** validation).
** CA 20220613 Improvement for the 'execute on return' handlers - process them only for top-level blocks.
** CA 20220918 Use a weak reference for the INSTANTIATING-PROCEDURE, to allow the garbage collector to
** remove the references once this was deleted.
** 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 Moved more APIs to ProcedureHelper, to reduce ContextLocal.get calls.
** UNIQUE-ID is generated only when is read by the application.
** CA 20230116 Avoid using handle.unwrap, handle.getReference or other BDT usage from within FWD runtime.
** Improvement from context-local state resolution.
** 062 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
** 063 CA 20230605 Made setInstantingExternalProgram public.
** 064 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.
** 065 AL2 20230613 Make executeOnReturn, disablePendingResources, enablePendingResources available in helper
** 066 CA 20230703 Allow deregistration of the 'executeOnReturn' cleaners.
** 067 CA 20230712 Added '_rootProcedure', to retrieve the root procedure instance.
** 068 CA 20230724 Further reduce context-local usage.
** 069 SVL 20230810 Do not include persistent trigger procedure in the legacy stack trace.
** 070 CA 20230818 Fixed a regression in H068 - when obtain() is used, we need to explicitly execute the
** 'WorkArea.init' code.
** 071 CA 20231009 Fixed the 'executing' type for direct Java calls.
** 072 CA 20231026 A persistent procedure with a pending delete is no longer persistent, so scopeDeleted
** needs to be executed.
** CA 20231031 Added 'BlockDefinition' parameter to 'scopeStart'.
** 073 CA 20231031 The constructor and destructor 'internal entry name' is the exact legacy name for this
** entry, as it is reported by PROGRAM-NAME and the error callstack.
** 074 AI 20231027 Added executeInThisProcedure() to execute a block inside the given procedure as the
** new THIS-PROCEDURE.
** AI 20231031 Pushed into internalEntryStack from executeInThisProcedure() to mark the newly added
** THIS-PROCEDURE to be excluded from the stack-trace. Added THIS_PROCEDURE_OVERRIDE
** constant to be pushed into internalEntryStack().
** 075 CA 20231129 Improvements related to the management of various dequeues and collections (use empty
** set/map instead of creating a new instance, where possible).
** CA 20231221 Refactored the WidgetPool scopeable support to register for processing as needed.
** Refactored the this/source/target-procedure stacks, to remain only one for each case.
** 076 CA 20240105 Fixed regression in H075 - running an internal entry in a persistent procedure which
** has an unnamed pool must push WidgetPool scope for this internal entry.
** 077 CA 20240206 Added 'onStackReferences'.
** 078 VVT 20240326 buildTraceLine(): adjust for changes in populateCallStack(): pass all source data as
** arguments. See #8130.
** 079 SC 20240508 Added flag and checking implementation for dynamic calls in CalleeInfoImpl
** Added method overload for pushCalleeInfo to cover the checking for dynamic calls
** SC 20240516 Refactored the body of overloaded method pushCalleeInfo
** 080 TJD 20240126 Java 17 fixes, c.g.p2j.net.Queue must be public
** 081 CA 20240809 Avoid iterator usage, for performance improvement.
** 082 VVT 20240826 SourceNameMapper.convertJavaProg() renamed. See #8613-16.
** 083 AP 20240827 Improved firstProcedure to only use the iterator instead of accessing the HashMap from
** FastSiblingDeque for each node.
** 084 AL2 20240930 Use identity hash map for FastSiblingDeque.
** 085 ICP 20250129 Used logical constant to leverage cached values, fixed javadoc.
** 086 CA 20250221 For delayed delete, save/restore the 'nowIterating' set, same as in
** ControlFlowOps.executeDestructor.
** 087 CA 20250311 Fixed virtual function support defined in 4GL classes, with static property/var as
** expression.
** 088 OM 20250131 Fine-tuned exit condition from processResource() in headless mode. Made load() public.
** 089 GBB 20250403 wa.tm can be null in multi-session appservers. deleteInstance made public.
*/
/*
** 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.lang.ref.*;
import java.lang.reflect.*;
import java.util.*;
import java.util.Queue;
import java.util.function.*;
import java.util.logging.*;
import com.goldencode.p2j.directory.*;
import com.goldencode.p2j.net.*;
import com.goldencode.p2j.oo.lang.*;
import com.goldencode.p2j.persist.*;
import com.goldencode.p2j.scheduler.*;
import com.goldencode.p2j.security.*;
import com.goldencode.p2j.ui.*;
import com.goldencode.p2j.ui.client.event.ServerEvent;
import com.goldencode.p2j.util.UniqueIdGenerator.*;
import com.goldencode.p2j.util.Utils.DirScope;
import com.goldencode.p2j.util.logging.*;
import com.goldencode.util.*;
import com.goldencode.util.Stack;
import static com.goldencode.p2j.util.ErrorManager.*;
/**
* Provides implementation for the following procedure handles:
* <table summary="">
* <tr>
* <td>THIS-PROCEDURE</td>
* <td>{@link #thisProcedure()}</td>
* <td>{@link #thisProcedure(Object)}</td>
* </tr>
* <tr>
* <td>SOURCE-PROCEDURE</td>
* <td>{@link #sourceProcedure()}</td>
* </tr>
* <tr>
* <td>TARGET-PROCEDURE</td>
* <td>{@link #targetProcedure()}</td>
* </tr>
* </table>
* The following procedure-related attributes and methods are implemented:
* <table summary="">
* <tr>
* <th>attribute or method</th>
* <th>API</th>
* </tr>
* <tr>
* <td>SESSION:SUPER-PROCEDURES</td>
* <td>{@link #superProcedures()}</td>
* </tr>
* <tr>
* <td>SESSION:ADD-SUPER-PROCEDURE</td>
* <td>
* <ul>
* <li>{@link #addSuperProcedure(handle)}</li>
* <li>{@link #addSuperProcedure(handle, SearchMode)}</li>
* <li>{@link #addSuperProcedure(handle, int)}</li>
* </ul>
* </td>
* </tr>
* <tr>
* <td>SESSION:REMOVE-SUPER-PROCEDURE</td>
* <td>
* <ul>
* <li>{@link #removeSuperProcedure(handle)}</li>
* </ul>
* </td>
* </tr>
* <tr>
* <td>SESSION:FIRST-PROCEDURE</td>
* <td>{@link #firstProcedure()}</td>
* </tr>
* <tr>
* <td>SESSION:LAST-PROCEDURE</td>
* <td>{@link #lastProcedure()}</td>
* </tr>
* </table>
* <p>
* The {@link #registerFunctionHandle(String, Resolvable)} is emitted at the
* beginning of an external procedure, for any function defined
* <code>IN handle</code>.
*/
public class ProcedureManager
{
/** Constant defining search mode: SEARCH-SELF */
public static final int SEARCH_SELF = 1;
/** Constant for socket search mode: SEARCH_TARGET */
public static final int SEARCH_TARGET = 2;
/** Marks the external program's root procedure - this is a P2J reserved name. */
public static final String EXTERNAL_PROGRAM = "__P2J__EXTERNAL__PROGRAM__";
/** Marks the trigger block - this is what 4GL reports for triggers. */
public static final String TRIGGER_PROCEDURE = "USER-INTERFACE-TRIGGER";
/**
* Marks the persistent trigger block (ON ... PERSISTENT RUN) - FWD-only, doesn't exist in 4GL.
*/
public static final String PERSISTENT_TRIGGER_PROCEDURE = "PERSISTENT_TRIGGER_PROCEDURE";
/** Marks a temporary THIS-PROCEDURE - FWD-only, does not exust in 4GL. */
public static final String THIS_PROCEDURE_OVERRIDE = "THIS_PROCEDURE_OVERRIDE";
/** Logger */
private static final CentralLogger LOG = CentralLogger.get(ProcedureManager.class.getName());
/** Worker for managing global subscriptions. */
private static GlobalEventSubscriptions globalSubscriptions;
/**
* Defines search modes for the ADD-SUPER-PROCEDURE statement. Needed to
* be able to identify the correct target in RUN SUPER and SUPER() cases.
*/
public static enum SearchMode
{
SEARCH_SELF
{
@Override
public String toString()
{
return "SEARCH-SELF";
}
},
SEARCH_TARGET
{
@Override
public String toString()
{
return "SEARCH-TARGET-PROCEDURE";
}
}
}
/** Stores context-local state variables. */
private static ContextContainer work = new ContextContainer();
/**
* API executed on client startup, to initialize this class.
*/
public static void initialize()
{
// WARNING: do not initialize static fields in the default class c'tor, as the
// ProcedureManager currently gets resolved on client-side, too, because of a client-side
// dependency on TM.setNestedMode
// initialize the static fields.
globalSubscriptions = new GlobalEventSubscriptions();
}
/**
* Register the handle variable as a resolvable for the given function.
*
* @param pname
* The function's legacy 4GL name.
* @param hvar
* The handle variable which determins the function's target.
*/
public static void registerFunctionHandle(String pname, handle hvar)
{
registerFunctionHandle(pname, new Resolvable()
{
@Override
public BaseDataType resolve()
{
return hvar;
}
@Override
public Class getType()
{
return handle.class;
}
});
}
/**
* Register the handle expression for the given function.
*
* @param pname
* The function's legacy 4GL name.
* @param handleExpr
* The {@link Resolvable} instance used to determine its IN
* handle target.
*/
public static void registerFunctionHandle(String pname, Resolvable handleExpr)
{
// always work with thisProcedure
handle h = thisProcedure();
Object referent = h.get();
ProcedureData pp = locateProcedure(referent);
if (pp.inHandleFunctions == Collections.EMPTY_MAP)
{
pp.inHandleFunctions = new HashMap<>();
}
pp.inHandleFunctions.put(pname.toLowerCase(), handleExpr);
}
/**
* Implementation of the TRANSACTION-MODE AUTOMATIC language statement.
*/
public static void transactionModeAuto()
{
transactionModeAuto(false);
}
/**
* Implementation of the TRANSACTION-MODE AUTOMATIC [CHAINED] language statement.
*
* @param chained
* Flag indicating that the CHAINED option is set.
*/
public static void transactionModeAuto(boolean chained)
{
WorkArea wa = work.obtain();
// we must be remote and the first accessed procedure - otherwise, fail
if (!(AppServerManager.isRemote() && wa.tm.isRootNestingLevel()))
{
String pname = ProcedureManager.getAbsoluteName(_thisProcedure(wa));
ErrorManager.recordOrThrowError(7335,
"AUTOMATIC MODE TRANSACTION procedure " + pname +
" may only be run remotely as a top-level procedure");
return;
}
if (!TransactionImpl.get().getTxInitProcedure().isUnknown() || wa.tm.isTransaction())
{
ErrorManager.recordOrThrowError(7336,
"A .p with TRANSACTION-MODE AUTOMATIC may not be run " +
"inside a transaction or while another AUTOMATIC .p " +
"is running in the same process",
false);
return;
}
BufferManager.registerScopeable(BufferManager.get());
Object referent = _thisProcedure(wa);
ProcedureData pp = locateProcedure(wa, referent);
pp.transactionModeAutoChained = chained;
// set TRANS-INIT-PROCEDURE to THIS-PROCEDURE
TransactionImpl.setTxInitProcedure(referent);
// start the transaction
wa.tm.beginTx();
}
/**
* Get the THIS-PROCEDURE system handle.
*
* @return See above.
*/
public static handle thisProcedure()
{
return thisProcedure(work.obtain());
}
/**
* Get the THIS-PROCEDURE system handle.
*
* @param ref
* Reference handle
*
* @return See above.
*/
public static handle thisProcedure(Object ref)
{
ProcedureData referent = locateProcedure(ref);
return (referent != null) ? new handle(referent.phandle) : new handle();
}
/**
* Get the TARGET-PROCEDURE system handle.
*
* @return See above.
*/
public static handle targetProcedure()
{
return targetProcedure(work.obtain());
}
/**
* Get the SOURCE-PROCEDURE system handle.
*
* @return See above.
*/
public static handle sourceProcedure()
{
WorkArea wa = work.obtain();
ProcedureData pd = wa.sourceProceduresData.peek();
// Do not use locateProcedure() here, the system handle may have been deleted
// with DELETE PROCEDURE. Note that when a procedure is deleted it can be
// referenced and queried through the system procedure handles.
return pd == null ? new handle() : new handle(pd.phandle);
}
/**
* Get the SESSION:LAST-PROCEDURE handle (unknown if no persistent
* procedures are loaded).
*
* @return See above.
*/
public static handle lastProcedure()
{
WorkArea wa = work.obtain();
if (wa.persistentProcedures.isEmpty())
{
return new handle();
}
Object referent = wa.persistentProcedures.peekLast();
while (ObjectOps.isLegacyObject(referent))
{
referent = wa.persistentProcedures.getPrevious(referent);
}
if (referent == null)
{
return new handle();
}
return new handle(locateProcedure(wa, referent).phandle);
}
/**
* Get the SESSION:FIRST-PROCEDURE handle (unknown if no persistent
* procedures are loaded).
*
* @return See above.
*/
public static handle firstProcedure()
{
WorkArea wa = work.obtain();
if (wa.persistentProcedures.isEmpty())
{
return new handle();
}
// find the first persistentProcedure which is not a LegacyObject
for (Object referent : wa.persistentProcedures)
{
if (!ObjectOps.isLegacyObject(referent))
{
return new handle(locateProcedure(wa, referent).phandle);
}
}
return new handle();
}
/**
* Get the SESSION:SUPER-PROCEDURES attribute.
*
* @return See above.
*/
public static character superProcedures()
{
WorkArea wa = work.obtain();
return superProceduresImpl(wa.getSessionSuperProcedures().values());
}
/**
* Add the given handle as a super-procedure for the SESSION handle.
*
* @param h
* The handle to be added as a super-procedure.
*
* @return <code>true</code> if <code>h</code> is a valid procedure
* handle.
*/
public static logical addSuperProcedure(handle h)
{
return addSuperProcedure(h, (SearchMode) null);
}
/**
* Add the given handle as a super-procedure for the SESSION handle, using
* the explicit {@link SearchMode mode}.
*
* @param h
* The handle to be added as a super-procedure.
* @param searchMode
* The {@link SearchMode} to be set for the <code>h</code>
* handle.
*
* @return <code>true</code> if <code>h</code> is a valid procedure
* handle.
*/
public static logical addSuperProcedure(handle h, int searchMode)
{
return addSuperProcedure(h, getSearchMode(searchMode));
}
/**
* Add the given handle as a super-procedure for the SESSION handle, using
* the explicit {@link SearchMode mode}.
*
* @param h
* The handle to be added as a super-procedure.
* @param searchMode
* The {@link SearchMode} to be set for the <code>h</code>
* handle.
*
* @return <code>true</code> if <code>h</code> is a valid procedure
* handle.
*/
public static logical addSuperProcedure(handle h, integer searchMode)
{
if (searchMode.isUnknown())
{
return addSuperProcedure(h, (SearchMode) null);
}
else
{
return addSuperProcedure(h, searchMode.intValue());
}
}
/**
* Add the given handle as a super-procedure for the SESSION handle, using
* the search-mode set as a string (one of the <code>SEARCH-TARGET</code>
* or <code>SEARCH-SELF</code> string literals).
*
* @param h
* The handle to be added as a super-procedure.
* @param mode
* The search-mode to be set for the <code>h</code> handle.
*
* @return <code>true</code> if <code>h</code> is a valid procedure
* handle.
*/
public static logical addSuperProcedure(handle h, SearchMode mode)
{
return addSuperProcedureImpl(work.obtain(), h, mode, null);
}
/**
* Remove the given handle from the SESSION's list of super-procedures.
*
* @param h
* The procedure handle to be removed.
*
* @return <code>true</code> if <code>h</code> is a valid procedure
* handle.
*/
public static logical removeSuperProcedure(handle h)
{
if (!h._isValid() || !isProcedure(h))
{
return new logical(false);
}
ControlFlowOps.invalidateCallSiteCache();
WorkArea wa = work.obtain();
removeProcedureImpl(wa, h, wa.getSessionSuperProcedures().keySet());
ProcedureData pData = locateProcedure(wa, h.get());
pData.isSessionSuperProcedure = false;
return new logical(true);
}
/**
* Process the given resource by either setting its INSTANTIATING-PROCEDURE attribute
* immediately (in case of non-static resources) or add the static resource to the
* {@link WorkArea#pendingResources} set, to be processed when the next scope is entered.
*
* @param res
* The resource to be processed.
* @param dynamic
* Flag indicating if this is a dynamic or static resource.
*/
public static void processResource(WrappedResource res, boolean dynamic)
{
processResource(work.obtain(), res, dynamic);
}
/**
* Process the given resource by either setting its INSTANTIATING-PROCEDURE attribute
* immediately (in case of non-static resources) or add the static resource to the
* {@link WorkArea#pendingResources} set, to be processed when the next scope is entered.
*
* @param wa
* The context-local state.
* @param res
* The resource to be processed.
* @param dynamic
* Flag indicating if this is a dynamic or static resource.
*/
public static void processResource(WorkArea wa, WrappedResource res, boolean dynamic)
{
// be a no-op in case of headless mode
if (wa.tm == null || wa.tm.errHlp != null && wa.tm.errHlp.isHeadless())
{
return;
}
if (!wa.registerPendingResources)
{
return;
}
if (res instanceof StaticTempTable || (res instanceof DynamicResource && !dynamic))
{
// all static resources are created as instance fields; thus, the actual processing is
// delayed, as the THIS-PROCEDURE is not available at the time of the call (i.e. we are
// in an instance field initialization code, at the business logic class)
wa.pendingResources.add(res);
}
else if (res instanceof HandleResource)
{
wa.pendingResources.remove(res);
// all resources which don't have dynamic counterparts are processed immediately
Object referent = _thisProcedure(wa);
ProcedureData pdata = locateProcedure(wa, referent);
// need to init dynamic flag here to avoid NPE
if (dynamic && res instanceof GenericWidget)
{
((GenericWidget)res)._setDynamic(dynamic);
}
// [pdata] should always be not-null in normal usage, except when the resource is
// created outside the FWD environment, like handle-written code.
if (pdata != null)
{
((HandleResource) res).setInstantiatingProcedure(pdata.phandle);
}
}
}
/**
* The {@code ProcedureManager} should know the DEFAULT-WINDOW in order to prevent releasing /
* deleting it when the top-level scope is popped out.
* <p>
* The DEFAULT-WINDOW was eventually added in {@link #processResource(WrappedResource,
* boolean)} but, at that time, we were not able to distinct it. The {@link LogicalTerminal}
* must call this as soon as the internal variable has been set.
*
* @param defaultWindow
* The default window for this session.
*/
public static void deregisterPendingResource(WindowWidget defaultWindow)
{
WorkArea wa = work.obtain();
// a no-op in case of headless mode
if (wa.tm.errHlp.isHeadless())
{
return;
}
wa.pendingResources.remove(defaultWindow);
}
/**
* Get the current external program (an application logic instance) being processed for
* scopeable or finalizable notifications.
*
* @return See above
*/
public static Object getProcessedProcedure()
{
WorkArea wa = work.obtain();
return wa.processedProcedure;
}
/**
* Check if the given referent is for a persistent procedure. This implements the PERSISTENT
* attribute for a procedure handle.
*
* @param referent
* An instance of a converted external procedure class.
*
* @return <code>true</code> if the referent is a persistent procedure.
*/
public static boolean _isPersistent(Object referent)
{
return _isPersistent(work.obtain(), referent);
}
/**
* Deregister an external program which was ran persistent by a remote side.
*
* @param referent
* The external program instance.
*/
public static void deregisterRemotePersistentProcedure(Object referent)
{
work.obtain().remotePersistentProcedures.remove(referent);
}
/**
* Check if an external program was ran persistent by a remote side.
*
* @param referent
* The external program instance.
*
* @return <code>true</code> if the referent is part of the
* {@link WorkArea#remotePersistentProcedures} map.
*/
public static boolean isRemotePersistentProcedure(Object referent)
{
return work.obtain().isRemotePersistentProcedure(referent);
}
/**
* Check if the handle's referent is a procedure or not. This means that the value must be set
* and must be an instance of {@link ExternalProgramWrapper}. The enclosed referent may or may
* not be registered with the {@link ProcedureManager}.
*
* @param h
* handle object to check.
*
* @return See above.
*/
public static boolean isProcedure(handle h)
{
return h != null && !h.isUnknown() && (h.getResource() instanceof ExternalProgramWrapper);
}
/**
* Gets the value of CURRENT-WINDOW attribute of specified procedure
* @param referent
* Procedure wrapper
* @return handle.
*/
public static handle getCurrentWindow(Object referent)
{
ProcedureData pd = locateProcedure(referent);
return pd.currentWindow == null ? new handle(): new handle(pd.currentWindow);
}
/**
* Sets the value of CURRENT-WINDOW attribute of specified procedure
* @param referent
* Procedure wrapper
* @param winHandle
* New value for the attribute.
*/
public static void setCurrentWindow(Object referent, handle winHandle)
{
if (winHandle == null || !winHandle._isValid() || winHandle.isUnknown())
{
ErrorManager.recordOrShowError(
4083,
"Unable to assign UNKNOWN value to attribute CURRENT-WINDOW on PROCEDURE widget",
false, false, false);
return;
}
if (!(winHandle.getResource() instanceof WindowWidget))
{
String ms = "Unable to set attribute CURRENT-WINDOW of procedure widget";
ErrorManager.recordOrShowError(4078, ms, false, false, false);
return;
}
ProcedureData pd = locateProcedure(referent);
pd.currentWindow = winHandle.getResource();
}
/**
* Get the relative name used to create the given external program instance.
*
* @param instance
* The external program instance.
*
* @return See above.
*/
public static String getRelativeName(Object instance)
{
return getRelativeName(work.obtain(), instance);
}
/**
* Resolve the closest business logic method in the stack trace.
*
* @param referent
* The external program to which the method must belong.
* @param function
* Flag indicating if a function or internal procedure will be searched.
* @param externalProc
* Flag indicating if we are looking for an external procedure.
*
* @return The legacy method name.
*/
public static String resolveClosestMethod(Object referent,
boolean function,
boolean externalProc)
{
// get a helper to process the stack
Throwable stack = new Throwable();
// obtain the stack trace
StackTraceElement[] trace = stack.getStackTrace();
String rClsName = referent.getClass().getName();
String methodName = null;
String legacyName = null;
String bmClass = BlockManager.class.getName();
String procApi = (externalProc ? "internalProcedure" : "externalProcedure");
String funcApi = "function";
// walk the stack trace until we find a method defined by the referent's class
boolean foundBlockManager = false;
for (int i = 1; i < trace.length; i++)
{
String cname = trace[i - 1].getClassName();
String mname = trace[i - 1].getMethodName();
if (cname.equals(bmClass) &&
((function && mname.toLowerCase().endsWith(funcApi)) ||
(!function && mname.equals(procApi))))
{
foundBlockManager = true;
}
else if (foundBlockManager)
{
methodName = mname;
break;
}
}
if (methodName != null)
{
if ("execute".equals(methodName))
{
// this is a special 4GL string.
legacyName = EXTERNAL_PROGRAM;
}
else
{
// resolve the legacy name of this method
legacyName = SourceNameMapper.convertJavaMethod(rClsName, methodName, function);
if (legacyName == null)
{
// assume the java name as legacy name
legacyName = methodName;
}
}
}
if (legacyName == null)
{
final String msg = "Could not resolve closest legacy method for class %s";
throw new RuntimeException(String.format(msg, rClsName) , stack);
}
return legacyName;
}
/**
* Get the name of the currently instantiated external program.
*
* @return The 4GL external program name being instantiated, or <code>null</code> if this is
* not the case.
*/
public static String getInstantiatingExternalProgram()
{
WorkArea wa = work.obtain();
return wa.initiatingExtProg;
}
/**
* Get the source class of the currently instantiating external program or OE class.
*
* @return The Java class associated with the source code for the current instantiating OE
* class.
*/
public static Class<?> getInstantiatingExternalProgramClass()
{
WorkArea wa = work.obtain();
return wa.initiatingExtProgClass;
}
/**
* Set the Java class of the currently instantiating external program or OE class.
*
* @param cls
* The Java class for the currently executing code.
*/
public static void setInstantingExternalProgramClass(Class<?> cls)
{
work.obtain().setInstantingExternalProgramClass(cls);
}
/**
* Get an instance of {@link ProcedureHelper}, which has the context-local instance saved,
* so API access can be done without a context-local query.
*
* @return See above.
*/
public static ProcedureHelper getProcedureHelper()
{
return new ProcedureHelper(work.obtain());
}
/**
* Get the default databases visible in the current stack.
*
* @return The nearest non-null {@link ProcedureData#connectedDbs} in the stack.
*/
public static Set<String> getDefaultDatabases()
{
WorkArea wa = work.obtain();
if (wa.defaultDbs == null)
{
return null;
}
Set<String> dbs = (Set<String>) wa.searchInStack((referent) ->
{
ProcedureData pdata = locateProcedure(wa, referent);
return pdata.connectedDbs;
});
return dbs;
}
/**
* Get the singleton instance of the specified class. This can be a 4GL external program or
* legacy class.
*
* @param cls
* The converted Java class for which the singleton instance is required.
*
* @return See above.
*/
static Object getSingleton(Class<?> cls)
{
WorkArea wa = work.obtain();
return wa.singletonMap.get(cls);
}
/**
* Save a singleton instance for the given class.
*
* @param cls
* The class.
* @param instance
* The singleton instance.
*/
static void addSingleton(Class<?> cls, Object instance)
{
WorkArea wa = work.obtain();
wa.singletonMap.put(cls, instance);
}
/**
* Delete the specified instance.
*
* @param hprog
* A handle referencing either a external program or {@link _BaseObject_} instance.
* @param isClass
* Flag indicating if the handle is referencing a class.
*/
public static void deleteInstance(handle hprog, boolean isClass)
{
if (hprog._isValid())
{
if (isClass)
{
ObjectOps.delete((_BaseObject_) hprog.get());
}
else
{
HandleOps.delete(hprog);
}
}
}
/**
* Get the {@link Scopeable} instance for this context.
*
* @return See above.
*/
static Scopeable getScopeable()
{
return work.obtain();
}
/**
* Set the external procedure beeing processed for scopeable or finalizable notifications.
*
* @param referent
* The external procedure instance.
*/
static void setProcessedProcedure(Object referent)
{
WorkArea wa = work.obtain();
wa.processedProcedure = referent;
}
/**
* Notify all subscribers (to the given event) that the event has occurred. This method is
* executed in a {@link ErrorManager#silent} bracket, as the <code>PUBLISH</code>
* statement is executed with a default NO-ERROR clause.
* <p>
* All processing is done with silent error mode implicitly enabled.
* <p>
* The subscriptions are fired in the order they were subscribed. Although 4GL
* documentation specifically states, that "If a named event has multiple subscribers,
* the order in which the AVM notifies subscribers is undefined.", the actual
* 4GL implementation (OpenEdge 10.2B) does fire the events in the order the subscriptions
* get subscribed.
*
* @param classEvent
* Flag indicating if we are publishing a class event.
* @param event
* The name of the event which is being published.
* @param publisher
* The procedure handle representing the program that is the source of the event.
* Must not be <code>null</code>.
* @param modes
* The mode for each parameter, encoded in a string. When <code>null</code>,
* the parameter modes will not be checked against the procedure definition.
* @param parameters
* The parameters passed to the subscriber. May be <code>null</code> to represent no
* parameters. When <code>null</code>, it means that no parameters were specified for
* this call.
*/
static void publish(boolean classEvent,
character event,
handle publisher,
String modes,
Supplier<Object[]> parameters)
{
publish(classEvent, false, event, publisher, modes, parameters);
}
/**
* Notify all subscribers (to the given event) that the event has occurred. This method is
* executed in a {@link ErrorManager#silent} bracket, as the <code>PUBLISH</code>
* statement is executed with a default NO-ERROR clause.
* <p>
* All processing is done with silent error mode implicitly enabled.
* <p>
* The subscriptions are fired in the order they were subscribed. Although 4GL
* documentation specifically states, that "If a named event has multiple subscribers,
* the order in which the AVM notifies subscribers is undefined.", the actual
* 4GL implementation (OpenEdge 10.2B) does fire the events in the order the subscriptions
* get subscribed.
*
* @param classEvent
* Flag indicating if we are publishing a class event.
* @param global
* Flag indicating if the publish is for global (cross-session) subscriptions, too.
* @param event
* The name of the event which is being published.
* @param publisher
* The procedure handle representing the program that is the source of the event.
* Must not be <code>null</code>.
* @param modes
* The mode for each parameter, encoded in a string. When <code>null</code>,
* the parameter modes will not be checked against the procedure definition.
* @param parameters
* The parameters passed to the subscriber. May be <code>null</code> to represent no
* parameters. When <code>null</code>, it means that no parameters were specified for
* this call.
*/
static void publish(boolean classEvent,
boolean global,
character event,
handle publisher,
String modes,
Supplier<Object[]> parameters)
{
Runnable ptask = () ->
{
if (!publisher.isExternalResource())
{
if (publisher.isUnknown())
{
final String msg = "Could not evaluate publisher handle expression while trying " +
"to execute PUBLISH statement";
ErrorManager.recordOrThrowError(6472, msg, false);
return;
}
if (!isProcedure(publisher) || !publisher._isValid())
{
final String msg =
"Invalid or inappropriate publisher handle value given to PUBLISH statement";
ErrorManager.recordOrThrowError(9072, msg, false);
return;
}
}
// Progress does not validate the event
Object rpub = publisher.get();
boolean externalPublisher = publisher.isExternalResource();
WorkArea wa = work.obtain();
List<String> errors = new LinkedList<>();
// determine the subscriptions
// business logic can unsubscribe during this loop, so we have to use a copy of
// the subscription list to avoid concurrent modification
List<Subscription> evtSubs = wa.subscriptionsByEvent.get(event);
ArrayList<Subscription> subCopy = evtSubs == null ? new ArrayList<>() : new ArrayList<>(evtSubs);
subCopy.addAll(globalSubscriptions.getAllSubscriptions());
Session currentSession = SessionManager.get().getSession();
for (int j = 0; j < subCopy.size(); j++)
{
Subscription<?> sub = subCopy.get(j);
// if the events match and the subscription is ANYWHERE or the publishers match,
// then invoke the procedure
if (sub.event.equals(event) &&
(sub.publisher == null || sub.publisher.equals(rpub)))
{
// the parameters are resolved only when a subscription is found
Object[] param = parameters == null ? null : parameters.get();
if (param == null)
{
param = new Object[0];
}
if (currentSession == sub.session)
{
sub.publish((externalPublisher ? null : rpub), errors, modes, param);
}
else if (global)
{
Object[] params = new Object[param.length];
// create a copy of the params - as the OUTPUT/INPUT-OUTPUT params must not
// affect this session, and following are not supported:
// - extent parameters
// - field parameters
// - memptr
// - handle
// - buffers
// - blob
// - object
for (int i = 0; i < param.length; i++)
{
if (param[i] instanceof BaseDataType)
{
params[i] = ((BaseDataType) param[i]).duplicate();
}
else if (param[i] instanceof AbstractParameter ||
param[i] instanceof object ||
param[i] instanceof blob ||
param[i] instanceof memptr ||
param[i] instanceof handle ||
param[i] instanceof WrappedResource ||
param[i] instanceof BufferReference)
{
throw new QuitConditionException("Parameter of type " +
param[i].getClass().toString() +
" is not supported!");
}
else if (param[i] != null && param[i].getClass().isArray())
{
throw new QuitConditionException("Array parameters are not supported!");
}
else
{
params[i] = param[i];
}
}
Runnable task = () -> sub.publishRemote(modes, params);
globalSubscriptions.post(new PublishTask(sub, task));
}
}
}
if (!errors.isEmpty())
{
int[] nums = new int[errors.size()];
Arrays.setAll(nums, operand -> 6480);
ErrorManager.recordOrThrowError(nums, errors.toArray(new String[0]), false);
}
};
// errors are not ignored if we are publishing a class event
if (classEvent)
{
ptask.run();
}
else
{
silent(ptask);
}
}
/**
* Create a new subscription to the given event for a specified program. This program will
* receive a call-back when the event is published.
*
* @param subscriber
* The program to notify when the specified event is published. Must not be
* <code>null</code>. The specified program must have a method that corresponds to
* the given Progress 4GL internal procedure name (<code>method</code>).
* @param event
* The name of the event which is being published.
* @param publisher
* The procedure handle representing the program that is the source of the event.
* If specified, the named event will only cause a notification if the event source
* matches this handle. <code>null</code> represents the <code>ANYWHERE</code> option
* which means that the events will not be filtered by source.
* @param method
* The Progress 4GL internal procedure name which will be translated into a method
* name and invoked on the <code>subscriber</code> instance. When <code>null</code>,
* it means that no method was specified for this call and the event name will be
* used as the method name.
* @param signature
* When specified, it represents the target to be called (part of the subscriber).
*
* @return <code>true</code> if the subscription was registered.
*/
static boolean subscribe(handle subscriber,
character event,
handle publisher,
character method,
InternalEntry signature)
{
return subscribe(false, subscriber, event, publisher, method, signature);
}
/**
* Create a new subscription to the given event for a specified program. This program will
* receive a call-back when the event is published.
*
* @param global
* Flag indicating if the subscription is for global (cross-session) events.
* @param subscriber
* The program to notify when the specified event is published. Must not be
* <code>null</code>. The specified program must have a method that corresponds to
* the given Progress 4GL internal procedure name (<code>method</code>).
* @param event
* The name of the event which is being published.
* @param publisher
* The procedure handle representing the program that is the source of the event.
* If specified, the named event will only cause a notification if the event source
* matches this handle. <code>null</code> represents the <code>ANYWHERE</code> option
* which means that the events will not be filtered by source.
* @param method
* The Progress 4GL internal procedure name which will be translated into a method
* name and invoked on the <code>subscriber</code> instance. When <code>null</code>,
* it means that no method was specified for this call and the event name will be
* used as the method name.
* @param signature
* When specified, it represents the target to be called (part of the subscriber).
*
* @return <code>true</code> if the subscription was registered.
*/
static boolean subscribe(boolean global,
handle subscriber,
character event,
handle publisher,
character method,
InternalEntry signature)
{
// validate subscriber
if (!subscriber.isExternalResource())
{
if (subscriber.isUnknown())
{
final String msg = "Could not evaluate subscriber handle expression while trying to " +
"execute SUBSCRIBE statement";
ErrorManager.recordOrThrowError(6463, msg, false);
return false;
}
if (!isProcedure(subscriber) || !subscriber._isValid())
{
final String msg = "Invalid or inappropriate subscriber handle value given to " +
"SUBSCRIBE statement";
ErrorManager.recordOrThrowError(6466, msg, false);
return false;
}
}
// validate publisher; if null, is OK, as it means ANYWHERE option
if (publisher != null && !publisher.isExternalResource())
{
if (publisher.isUnknown())
{
final String msg = "Could not evaluate publisher handle expression while trying " +
"to execute SUBSCRIBE statement";
ErrorManager.recordOrThrowError(6464, msg, false);
return false;
}
if (!isProcedure(publisher) || !publisher._isValid())
{
final String msg = "Invalid or inappropriate publisher handle value given to " +
"SUBSCRIBE statement";
ErrorManager.recordOrThrowError(6467, msg, false);
return false;
}
}
// Progress does not validate event and method name
Object rsub = subscriber.get();
Object rpub = (publisher == null ? null : publisher.get());
WorkArea wa = null;
List<Subscription> subs;
if (global)
{
subs = globalSubscriptions.getSubscriptions();
}
else
{
if (wa == null)
{
wa = work.obtain();
}
subs = wa.subscriptionsByEvent.computeIfAbsent(event, (k) -> new LinkedList<Subscription>());
}
if (subs == null)
{
return false;
}
// 4GL doesn't raise an error if a subscription already exists, but doesn't replace it
// either
boolean found = false;
for (Subscription<?> sub : subs)
{
if (sub.readOnly)
{
// read-only subscriptions are not checked.
continue;
}
// get an exact match
if (sub.subscriber.equals(rsub) &&
event.equals(sub.event) &&
((rpub == null && sub.publisher == null) ||
(rpub != null && sub.publisher != null && rpub.equals(sub.publisher))))
{
found = true;
break;
}
}
if (found)
{
return false;
}
// create only if there isn't an existing one;
// the subscription is added into a "global" list of subscriptions, the list
// also holds the order when the subscriptions got subscribed so that they
// can be fired in this order
Subscription<?> sub = (rsub instanceof ExternalResource)
? new ExternalSubscription((ExternalResource) rsub, event, rpub)
: new LegacySubscription(rsub, event, rpub, method);
sub.signature = signature;
if (global)
{
globalSubscriptions.subscribe(sub);
}
else
{
if (wa == null)
{
wa = work.obtain();
}
wa.subscriptions.add(sub);
List<Subscription> evtSubs =
wa.subscriptionsByEvent.computeIfAbsent(event, (k) -> new LinkedList<Subscription>());
evtSubs.add(sub);
}
return true;
}
/**
* Create a new subscription to the given event for any program. This program will receive a
* call-back when the event is published.
*
* @param global
* Flag indicating if the subscription is for global (cross-session) events.
* @param subscriber
* The program to notify when the specified event is published. Must not be
* <code>null</code>. The specified program must have a method that corresponds to
* the given Progress 4GL internal procedure name (<code>method</code>).
* @param event
* The name of the event which is being published.
* @param method
* The Progress 4GL internal procedure name which will be translated into a method
* name and invoked on the <code>subscriber</code> instance. When <code>null</code>,
* it means that no method was specified for this call and the event name will be
* used as the method name.
*/
static void subscribeAnywhere(boolean global,
handle subscriber,
character event,
character method)
{
subscribe(global, subscriber, event, null, method, null);
}
/**
* Create a new subscription to the given event for a specified external resource.
* This resource will receive a notification when the event is published.
*
* @param global
* Flag indicating if the subscription is for global (cross-session) events.
* @param subscriber
* The resource to notify when the specified event is published. If <code>null</code>,
* a {@link ExternalResource} will be built and its ID return.
* @param event
* The name of the event which is being published.
* @param publisher
* The handle representing the program or resource that is the source of the event.
* If specified, the named event will only cause a notification if the event source
* matches this handle. <code>null</code> represents the <code>ANYWHERE</code> option
* which means that the events will not be filtered by source.
*
* @return The subscriber's ID.
*/
static handle subscribeExternal(boolean global,
handle subscriber,
String event,
handle publisher)
{
if (subscriber == null)
{
subscriber = handle.buildExternalResource();
}
subscribe(global, subscriber, new character(event), publisher, null, null);
return subscriber;
}
/**
* Delete a subscription to the given event for the specified external resource. This method is
* executed in a {@link ErrorManager#silent} bracket, as the <code>UNSUBSCRIBE</code>
* statement is executed with a default NO-ERROR clause.
*
* @param global
* Flag indicating if the search is done in the global (cross-session) event register.
* @param subscriber
* The resource whose subscription is to be deleted. Must not be <code>null</code>.
* @param event
* The name of the event which is being published. If this is <code>null</code>, then
* subscriptions to all events will be deleted.
* @param publisher
* The procedure or resource handle representing the source of the event.
* If specified, the subscription will only be deleted if the event source matches
* this handle. <code>null</code> represents the <code>ALL</code> option which means
* that the events will not be filtered by source. When <code>null</code>, it means
* that the publisher was not set.
*/
static void unsubscribeExternal(boolean global,
handle subscriber,
String event,
handle publisher)
{
unsubscribe(global, subscriber, event == null ? null : new character(event), publisher, null);
}
/**
* Notify all subscribers (to the given event) that the event has occurred. This method is
* executed in a {@link ErrorManager#silent} bracket, as the <code>PUBLISH</code>
* statement is executed with a default NO-ERROR clause.
* <p>
* All processing is done with silent error mode implicitly enabled.
* <p>
* The subscriptions are fired in the order they were subscribed. Although 4GL
* documentation specifically states, that "If a named event has multiple subscribers,
* the order in which the AVM notifies subscribers is undefined.", the actual
* 4GL implementation (OpenEdge 10.2B) does fire the events in the order the subscriptions
* get subscribed.
*
* @param event
* The name of the event which is being published.
* @param publisher
* The procedure or resource handle representing the program that is the source of
* the event. Must not be <code>null</code>.
* @param args
* The parameters passed to the subscriber. May be <code>null</code> to represent no
* parameters. When <code>null</code>, it means that no parameters were specified for
* this call.
*/
static void publishExternal(String event, handle publisher, String[] args)
{
// convert the arguments from String to an Object element array
Object[] param = new Object[args.length];
System.arraycopy(args, 0, param, 0, args.length);
publish(false, new character(event), publisher, null, () -> param);
}
/**
* Remove all subscriptions registered by the given resource. This may be a program handle
* or a {@link ExternalResource}.
*
* @param resource
* All subscriptions for this resource will be removed.
*/
static void cleanupSubscriptions(Object resource)
{
WorkArea wa = work.obtain();
Set<Subscription> removed = Collections.newSetFromMap(new IdentityHashMap<>());
Iterator<Subscription> iter = wa.subscriptions.iterator();
while (iter.hasNext())
{
Subscription sub = iter.next();
if (sub.subscriber.equals(resource))
{
iter.remove();
removed.add(sub);
}
}
if (removed.isEmpty())
{
return;
}
Iterator<Map.Entry<character, LinkedList<Subscription>>> iter2 = wa.subscriptionsByEvent.entrySet().iterator();
while (iter2.hasNext())
{
Map.Entry<character, LinkedList<Subscription>> entry = iter2.next();
entry.getValue().removeAll(removed);
if (entry.getValue().isEmpty())
{
iter2.remove();
}
}
globalSubscriptions.removeAll(resource);
}
/**
* Delete a subscription to the given event for the specified program. This method is executed
* executed in a {@link ErrorManager#silent} bracket, as the <code>UNSUBSCRIBE</code>
* statement is executed with a default NO-ERROR clause.
*
* @param global
* Flag indicating if the search is done in the global (cross-session) event register.
* @param subscriber
* The program whose subscription is to be deleted. Must not be <code>null</code>.
* @param event
* The name of the event which is being published. If this is <code>null</code>, then
* subscriptions to all events will be deleted.
* @param publisher
* The procedure handle representing the program that is the source of the event.
* If specified, the subscription will only be deleted if the event source matches
* this handle. <code>null</code> represents the <code>ALL</code> option which means
* that the events will not be filtered by source. When <code>null</code>, it means
* that the publisher was not set.
* @param method
* The subscriber's method which needs to be removed. Must be not-null for class
* events.
*/
static void unsubscribe(boolean global,
handle subscriber,
character event,
handle publisher,
character method)
{
// TODO: method name must be specified for CLASS EVENTS
silent(() ->
{
if (!subscriber.isExternalResource())
{
// validate subscriber
if (subscriber.isUnknown())
{
final String msg = "Could not evaluate subscriber handle expression while trying " +
"to execute UNSUBSCRIBE statement";
ErrorManager.recordOrThrowError(6475, msg, false);
return;
}
if (!isProcedure(subscriber) || !subscriber._isValid())
{
final String msg = "Invalid or inappropriate subscriber handle value given to " +
"UNSUBSCRIBE statement";
ErrorManager.recordOrThrowError(6477, msg, false);
return;
}
}
// validate publisher; if null, is OK, as it means ALL option
if (publisher != null && !publisher.isExternalResource())
{
if (publisher.isUnknown())
{
final String msg = "Could not evaluate publisher handle expression while " +
"trying to execute UNSUBSCRIBE statement";
ErrorManager.recordOrThrowError(6476, msg, false);
return;
}
if (!isProcedure(publisher) || !publisher._isValid())
{
final String msg = "Invalid or inappropriate publisher handle value given to " +
"UNSUBSCRIBE statement";
ErrorManager.recordOrThrowError(6478, msg, false);
return;
}
}
// Progress does not validate the event
Object rsub = subscriber.get();
Object rpub = (publisher == null ? null : publisher.get());
boolean found = false;
WorkArea wa = work.obtain();
List<Subscription> subs;
if (global)
{
subs = globalSubscriptions.getSubscriptions();
}
else
{
subs = event == null ? work.obtain().subscriptions : wa.subscriptionsByEvent.get(event);
if (subs == null)
{
subs = Collections.emptyList();
}
}
Set<Subscription> removed = Collections.newSetFromMap(new IdentityHashMap<>());
Iterator<Subscription> itr = subs.iterator();
while (itr.hasNext())
{
Subscription sub = itr.next();
// a special case: if a publisher is specified and ALL clause is used, than all
// ANYWERE subscriptions are marked read-only, for the event.
if (rpub != null && event == null && sub.publisher == null)
{
sub.readOnly = true;
}
if (sub.readOnly)
{
// read-only subscriptions can't be removed
continue;
}
// if removes all subscriptions considering:
// - if the passed event is null, then ALL clause is in effect, else event names must
// match
// - the publisher is not matched as stated in the documentation. the correct approach
// is that if the publisher is missing, then ANY publisher is matched
if (sub.subscriber.equals(rsub) &&
(event == null || event.equals(sub.event)) &&
((rpub == null || rpub.equals(sub.publisher))))
{
// TODO: in case of class events, the METHOD must match, too
if (global)
{
globalSubscriptions.removeAll(sub);
}
else
{
// remove this subscription
itr.remove();
removed.add(sub);
}
found = true;
}
}
if (!removed.isEmpty())
{
if (event == null)
{
Iterator<Map.Entry<character, LinkedList<Subscription>>> iter =
wa.subscriptionsByEvent.entrySet().iterator();
while (iter.hasNext())
{
Map.Entry<character, LinkedList<Subscription>> entry = iter.next();
entry.getValue().removeAll(removed);
if (entry.getValue().isEmpty())
{
iter.remove();
}
}
}
else
{
if (subs.isEmpty())
{
wa.subscriptionsByEvent.remove(event);
}
wa.subscriptions.removeAll(removed);
}
}
// if a specific event is used and no subscriptions found, then error
if (!found && event != null)
{
final String msg = "UNSUBSCRIBE failed. No subscriptions for %s";
String sevt = (event.isUnknown() ? "" : event.toStringMessage());
ErrorManager.recordOrThrowError(6479, String.format(msg, sevt), false);
return;
}
});
}
/**
* Delete a subscription to ALL events for the specified program. This method is
* executed in a {@link ErrorManager#silent} bracket, as the <code>UNSUBSCRIBE</code>
* statement is executed with a default NO-ERROR clause.
*
* @param global
* Flag indicating if the subscription is for global (cross-session) events.
* @param subscriber
* The program whose subscription is to be deleted. Must not be <code>null</code>.
* @param publisher
* The procedure handle representing the program that is the source of the event.
* If specified, the subscription will only be deleted if the event source matches
* this handle. <code>null</code> represents the <code>ALL</code> option which means
* that the events will not be filtered by source. When <code>null</code>, it means
* that the publisher was not set.
*/
static void unsubscribeAll(boolean global, handle subscriber, handle publisher)
{
unsubscribe(global, subscriber, null, publisher, null);
}
/**
* Ensure all the context-local data has initialized (to be able to listen
* for scope notifications).
*/
public static void load()
{
// needed to ensure the variable is loaded, so it listens for scope notifications
work.obtain();
}
/**
* Remove the given handle from the list of persistent procedures. Used by
* {@link ControlFlowOps#invokePersistent(character, Object...)} and
* {@link ControlFlowOps#invokePersistentSet(String, handle, Object...)} in
* cases when errors were encountered while invoking the procedure.
*
* @param h
* The procedure handle to be added to the list of persistent
* procedures.
*/
static void removePersistentProcedure(handle h)
{
WorkArea wa = work.obtain();
removeProcedureImpl(wa, h, wa.persistentProcedures);
locateProcedure(wa, h.get()).persistent = false;
wa.removeStaleProcedure(h.get());
}
/**
* Implementation for the PROGRAM-NAME functions. Resolves the legacy top level block (external
* program, function, internal procedure or trigger), at the given depth. The index starts with
* 0 for the currently executing top-level block.
*
* @param idx
* The stack entry index.
*
* @return The name of the stack entry or {@code null} if outside of stack.
*/
static String getStackEntry(int idx)
{
return work.obtain().getStackEntry(idx);
}
/**
* Get the legacy stack trace
*
* @return the legacy stack trace
*/
public static List<String> getStackTrace()
{
return getStackTrace(work.obtain());
}
/**
* Get the legacy stack trace
*
* @param wa
* The context-local state.
*
* @return the legacy stack trace
*/
public static List<String> getStackTrace(WorkArea wa)
{
List<String> stack = new ArrayList<>();
Iterator<ProcedureData> procItr = wa.thisProceduresData.iterator();
Iterator<String> ieItr = wa.internalEntryStack.iterator();
while (procItr.hasNext())
{
Object referent = procItr.next().getReferent();
String iename = ieItr.next();
if (PERSISTENT_TRIGGER_PROCEDURE.equals(iename) || THIS_PROCEDURE_OVERRIDE.equals(iename))
{
continue;
}
String name = wa.relativeNames.get(referent);
stack.add(EXTERNAL_PROGRAM.equals(iename) ? name : (iename + " " + name));
}
return stack;
}
/**
* Build a trace line including both Java and legacy names.
*
* @param className
* Java class name
* @param methodName
* Java method name
* @param fileName
* Java source file name
* @param lineNumber
* Java source line number
*
* @return the trace line.
*/
public static String buildTraceLine(
final String className,
final String methodName,
final String fileName,
final int lineNumber)
{
boolean externalProgram = methodName.equals("execute");
boolean ooInitializer = methodName.startsWith("__") && methodName.endsWith("_execute__");
Class<?> cls = null;
try
{
cls = Class.forName(className);
}
catch (Throwable t)
{
// Ignore
}
final String pname = SourceNameMapper.getLegacySourceName(className);
String iename;
if (cls != null && _BaseObject_.class.isAssignableFrom(cls))
{
// legacy OO class
iename = SourceNameMapper.findLegacyEntryName(cls, methodName);
}
else
{
// external program
iename = null;
if (pname != null)
{
iename = SourceNameMapper.convertJavaMethod(className, methodName, false);
if (iename == null)
{
iename = SourceNameMapper.convertJavaMethod(className, methodName, true);
}
}
}
String ptrace = null;
if (pname != null)
{
if (externalProgram || ooInitializer)
{
ptrace = pname;
}
else if (iename != null)
{
ptrace = pname + " " + iename;
}
}
return className + "." + methodName
+ (ptrace == null ? "" : "[" + ptrace + "]")
+ " at line " + lineNumber
+ " (" + fileName + ")";
}
/**
* Get the absolute name of the external program associated with the given java instance.
* <p>
* This is used internally by P2J, as we need absolute names when resolving a program's
* signature, events, etc.
* <p>
* For the name associated with the procedure's NAME and FILE-NAME attributes, and returned by
* the PROGRAM-NAME function or used in 4GL error messages, see {@link #getRelativeName}, which
* returns the real name used when the external procedure was instantiated first.
*
* @param referent
* The referent for which the name is needed.
*
* @return The absolute name.
*/
static String getAbsoluteName(Object referent)
{
String jname = null;
if (referent.getClass() == Object.class)
{
jname = ObjectOps.getStaticClass(referent).getName();
}
else
{
jname = referent.getClass().getName();
}
String pname = SourceNameMapper.getLegacySourceName(jname);
return pname;
}
/**
* Check if the given referent has been (or is still) registered with the
* {@link ProcedureManager} in the {@link WorkArea#pMap procedure} map.
*
* @param referent
* The referent to be checked if is still loaded.
*
* @return <code>true</code> if the referent was found.
*/
public static boolean hasReferent(Object referent)
{
return hasReferent(work.obtain(), referent);
}
/**
* Delete the given procedure handle (i.e. execute the DELETE OBJECT or DELETE PROCEDURE
* handle).
*
* @param referent
* The persistent procedure instance to be deleted.
* @param letStaleProcedure
* Flag to indicate if the procedure should go in an stale state.
*
* @return <code>true</code> if this procedure handle can be deleted.
*/
static boolean delete(Object referent, boolean letStaleProcedure)
{
return delete(null, referent, letStaleProcedure);
}
/**
* Delete the given procedure handle (i.e. execute the DELETE OBJECT or DELETE PROCEDURE
* handle).
*
* @param wa
* The context-local state.
* @param referent
* The persistent procedure instance to be deleted.
* @param letStaleProcedure
* Flag to indicate if the procedure should go in an stale state.
*
* @return <code>true</code> if this procedure handle can be deleted.
*/
static boolean delete(WorkArea wa, Object referent, boolean letStaleProcedure)
{
if (wa == null)
{
wa = work.obtain();
}
boolean result = true;
if (referent instanceof _BaseObject_)
{
ObjectResource res = ObjectOps.asResource((_BaseObject_) referent);
if (!res.isPendingDelete())
{
result = ObjectOps.delete((_BaseObject_) referent);
}
}
// the referent may already have been deleted by ObjectOps.delete
result = result && (!wa.pMap.containsKey(referent) || delete(wa, referent));
if (!result || ObjectOps.isLegacyObject(referent) || !wa.hasPersistentProcStack(referent))
{
// legacy OO does not have the 'stale procedure' behavior.
return result;
}
if (letStaleProcedure)
{
StaleProcedureManager.addStaleReferent(referent);
}
else
{
StaleProcedureManager.notifyResourceDelete(referent);
}
return true;
}
/**
* Check if this referent can be deleted or not.
* <p>
* This checks if the instance is on stack, and if the run is SINGLE-RUN or SINGLETON.
*
* @param wa
* The {@link WorkArea} instance.
* @param referent
* The instance to check.
*
* @return <code>true</code> if the delete must be postponed.
*/
static boolean isDelayedDelete(WorkArea wa, Object referent)
{
ProcedureData pdata = locateProcedure(wa, referent);
// do not postpone the delete when the referent is a TARGET-PROCEDURE or
// a SOURCE-PROCEDURE
if (pdata != null && pdata.persistent && pdata.thisCount > 0)
{
if (pdata.singleRun)
{
return true;
}
if (pdata.singleton)
{
// raise an ERROR condition
wa.tm.triggerErrorInCaller();
return true;
}
// if this is a persistent system handle, the actual delete is postponed until we
// are finished.
wa.deleteOnExit.put(referent, true);
if (LOG.isLoggable(Level.FINE))
{
LOG.log(Level.FINE, "Postponing deletion of procedure " + getAbsoluteName(referent));
}
return true;
}
return false;
}
/**
* Delete the given procedure handle (i.e. execute the DELETE OBJECT or DELETE PROCEDURE
* handle).
*
* @param wa
* The context-local state in {@link WorkArea}.
* @param referent
* The persistent procedure instance to be deleted.
*
* @return <code>true</code> if this procedure handle can be deleted.
*/
private static boolean delete(WorkArea wa, Object referent)
{
// handle is always valid
if (isDelayedDelete(wa, referent))
{
return false;
}
ProcedureData pdata = locateProcedure(wa, referent);
if (TransactionImpl.isTxInitProcedure(referent))
{
if (TransactionImpl.isOpenByTxInitProc())
{
TransactionImpl.requestFinished(false, false);
}
// if this is the TRANS-INIT-PROCEDURE, then remove the reference and clear the pending
// flags
TransactionImpl.setTxInitProcedure(null);
}
boolean invalidateCallSiteCache = false;
wa.persistentProcedures.remove(referent);
if (pdata.isSessionSuperProcedure)
{
invalidateCallSiteCache = true;
wa.sessionSuperProcs.remove(referent);
}
wa.singletonMap.remove(referent.getClass());
if (!pdata.superProcedures.isEmpty())
{
for (ProcedureData pp : pdata.superProcedures.values())
{
// clean the super-procedure list from the reverse-tracking of the referent.
boolean removed = pp.addedToSuperProcedures.remove(referent);
invalidateCallSiteCache = invalidateCallSiteCache || removed;
}
}
if (!pdata.addedToSuperProcedures.isEmpty())
{
for (Object ref : pdata.addedToSuperProcedures)
{
invalidateCallSiteCache = true;
ProcedureData pp = locateProcedure(wa, ref);
if (pp != null)
{
// the procedure to which this was added may have already been deleted.
pp.superProcedures.remove(referent);
}
}
}
// force the cleanup here (do not use cleanup()), we don't want to cause any inconsistency
// here, when this method proceeds with clean up do the cleanup completely
wa.forceCleanup(referent);
Object prevReferent = wa.processedProcedure;
List<String> errors = new ArrayList<>();
List<Integer> nums = new ArrayList<>();
try
{
wa.processedProcedure = referent;
BiConsumer<String, Integer> processor = (msg, num) ->
{
errors.add(msg);
nums.add(num);
};
// cleanup after the procedure
if (!pdata.finalizables.isEmpty())
{
for (Finalizable target : pdata.finalizables)
{
wa.errHelper.nestedSilent(target::deleted, processor);
}
}
if (!pdata.scopeables.isEmpty())
{
for (Scopeable target : pdata.scopeables)
{
wa.errHelper.nestedSilent(target::scopeDeleted, processor);
}
}
// as the procedure no longer exists, delete the static resources
// this can have multiple scopes, one for each class in the OE hierarchy.
while (!pdata.staticResources.isEmpty())
{
deleteResources(wa, pdata.staticResources.pop());
}
}
finally
{
wa.processedProcedure = prevReferent;
}
handle unknown = new handle();
// remove it from the instantiating procedure
for (HandleResource res : pdata.createdResources.keySet())
{
res.setInstantiatingProcedure(unknown);
}
// delete the widget pools for this external program
wa.wp.deleteWidgetPools(referent);
// get rid of references...
pdata.createdResources.clear();
pdata.scopeables.clear();
pdata.finalizables.clear();
// remove any triggers created in this-procedure
LogicalTerminal.cleanupTriggers(referent);
handle.removeResource(pdata.phandle.getResource());
// clear all undoable variables created by this procedure (they might have been tracked
// in parent tx blocks).
wa.tm.untrackUndoables(referent);
if (invalidateCallSiteCache)
{
ControlFlowOps.invalidateCallSiteCache();
}
else
{
ControlFlowOps.invalidateCallSiteCache(referent);
}
if (!errors.isEmpty())
{
// force an ERROR condition to be raised and displayed - it will ignore NO-ERROR mode
ErrorManager.displayAndRaiseError(errors);
}
return true;
}
/**
* Check if the given handle is a proxy procedure.
*
* @param h
* The handle to check.
*
* @return <code>true</code> if the handle is a proxy procedure.
*/
static boolean isProxy(handle h)
{
return h != null && (h.getResource() instanceof ProxyProcedureWrapper) && h._isValid();
}
/**
* Check if the given referent is for a system handle (i.e. for one of the
* THIS-PROCEDURE, SOURCE-PROCEDURE or TARGET-PROCEDURE handles).
*
* @param referent
* An instance of an converted external procedure class.
*
* @return See above.
*/
static boolean systemHandle(Object referent)
{
WorkArea wa = work.obtain();
ProcedureData pd = wa.pMap.get(referent);
return pd != null && (pd.thisCount > 0 || pd.sourceCount > 0 || pd.targetCount > 0);
}
/**
* Find the root enclosing instance of the specified inner class instance.
* It gets computed using the <code>this$</code> fields inserted by the
* compiler; it returns the instance refered by the <code>this$0</code>
* fields, when reached.
*
* @param instance
* The inner class instance.
*
* @return The root instance.
*/
static Object findRootEnclosingInstance(Object instance)
{
Field[] fields = instance.getClass().getDeclaredFields();
for (Field f : fields)
{
String fname = f.getName();
if (fname.startsWith("this$"))
{
try
{
f.setAccessible(true);
Object outer = f.get(instance);
return fname.equals("this$0") ? outer : findRootEnclosingInstance(outer);
}
catch (Exception e)
{
LOG.severe("", e);
return null;
}
}
}
return null;
}
/**
* Set the name of the 4GL program currently being instantiated, into the
* {@link WorkArea#initiatingExtProg} field.
*
* @param name
* The 4GL external program name.
*/
public static void setInstantingExternalProgram(String name)
{
work.obtain().setInstantingExternalProgram(name);
}
/**
* Walk the {@link WorkArea#thisProceduresData THIS-PROCEDURE} stack and apply the search function;
* return the first value given by the search function, once it returns a non-null value.
*
* @param search
* The search function.
*
* @return The first non-null value returned by the search function.
*/
static Object searchInStack(Function<Object, Object> search)
{
return work.obtain().searchInStack(search);
}
/**
* Get the THIS-PROCEDURE system handle.
*
* @param wa
* Context-local work area.
*
* @return See above.
*/
private static handle thisProcedure(WorkArea wa)
{
ProcedureData pdata = wa.thisProceduresData.peek();
return (pdata != null) ? new handle(pdata.phandle) : new handle();
}
/**
* Get the THIS-PROCEDURE referent.
*
* @param wa
* Context-local work area.
*
* @return See {@link #thisProcedure()}.
*/
private static Object _thisProcedure(WorkArea wa)
{
ProcedureData pd = wa.thisProceduresData.peek();
return pd == null ? null : pd.getReferent();
}
/**
* Get the TARGET-PROCEDURE system handle.
*
* @param wa
* Context-local work area.
*
* @return See above.
*/
private static handle targetProcedure(WorkArea wa)
{
// Do not use locateProcedure() here, the system handle may have been deleted
// with DELETE PROCEDURE. Note that when a procedure is deleted it can be
// referenced and queried through the system procedure handles.
return new handle(wa.targetProceduresData.peek().phandle);
}
/**
* Check if the given referent is for a persistent procedure. This implements the PERSISTENT
* attribute for a procedure handle.
*
* @param wa
* Context-local work area.
* @param referent
* An instance of a converted external procedure class.
*
* @return <code>true</code> if the referent is a persistent procedure.
*/
private static boolean _isPersistent(WorkArea wa, Object referent)
{
ProcedureData pdata = locateProcedure(wa, referent);
return pdata != null && pdata.persistent;
}
/**
* Check if the given referent is for a single-run procedure. This implements the SINGLE-RUN
* attribute for a procedure handle.
*
* @param wa
* Context-local work area.
* @param referent
* An instance of a converted external procedure class.
*
* @return <code>true</code> if the referent is a single-run procedure.
*/
private static boolean _isSingleRun(WorkArea wa, Object referent)
{
ProcedureData pdata = locateProcedure(wa, referent);
return pdata != null && pdata.singleRun;
}
/**
* Check if the given referent is for a singleton procedure. This implements the SINGLETON
* attribute for a procedure handle.
*
* @param wa
* Context-local work area.
* @param referent
* An instance of a converted external procedure class.
*
* @return <code>true</code> if the referent is a singleton procedure.
*/
private static boolean _isSingleton(WorkArea wa, Object referent)
{
ProcedureData pdata = locateProcedure(wa, referent);
return pdata != null && pdata.singleton;
}
/**
* Push an internal entry to the stack. The passed internal entry name is resolved and is
* pushed as it was defined, not as it is being called.
* <p>
* Use {@link #EXTERNAL_PROGRAM} for the name of the root external procedure block and
* {@link #TRIGGER_PROCEDURE} for the root trigger block.
*
* @param wa
* Context-local work area.
* @param instance
* The external program instance.
* @param iename
* The legacy internal-entry name.
* @param function
* Flag indicating if the internal entry is a user-defined function.
* @param legacyName
* The already known legacy name for the internal entry.
*/
private static void pushInternalEntry(WorkArea wa,
Object instance,
String iename,
boolean function,
String legacyName)
{
if (!(iename.equals(EXTERNAL_PROGRAM) ||
iename.equals(TRIGGER_PROCEDURE) ||
iename.equals(PERSISTENT_TRIGGER_PROCEDURE) ||
iename.equals(THIS_PROCEDURE_OVERRIDE)))
{
// the internal entry names are reported as they are defined, not as they are called.
// thus, we need to determine the definition and get it from there
if (legacyName == null)
{
// if not already resolved, resolve it now
String pname = getAbsoluteName(instance);
InternalEntry ie = SourceNameMapper.getInternalEntry(pname, iename, function);
iename = (ie == null ? iename : ie.pname);
}
else
{
iename = legacyName;
}
}
wa.internalEntryStack.push(iename);
}
/**
* Add the <code>h</code> handle as a persistent procedure.
*
* @param wa
* Context-local work area.
* @param h
* The procedure handle to be added to the list of persistent
* procedures.
* @param name
* The name of this procedure, used at the RUN statement.
* @param persistent
* Flag indicating if this is a persistent procedure.
* @param singleton
* Flag indicating if this is a singleton procedure.
* @param singleRun
* Flag indicating if this is a single-run procedure.
* @param legacyClass
* Set to {@code true} if the method is called as a result of
* legacy class initialization, {@code false} otherwise.
*/
private static void addProcedure(WorkArea wa,
handle h,
String name,
boolean persistent,
boolean singleton,
boolean singleRun,
boolean legacyClass)
{
Object referent = h.get();
ProcedureData pp = new ProcedureData(h, null, name);
pp.singleton = singleton;
pp.singleRun = singleRun;
pp.persistent = persistent;
ProcedureData prev = wa.pMap.put(referent, pp);
if (prev == null && !legacyClass)
{
TranslationManager tm = TranslationManager.getInstance();
tm.applyLanguage(((TransparentWrapper) h.getResource()).get().getClass());
}
if (persistent)
{
wa.persistentProcedures.add(referent);
}
if (singleton)
{
wa.singletonMap.put(referent.getClass(), referent);
}
wa.relativeNames.put(referent, name);
if (wa.defaultDbs != null)
{
String rpkg = null;
if (referent.getClass() == Object.class)
{
// a static OO class placeholder
Class<?> cls = ObjectOps.getStaticClass(referent);
rpkg = cls.getPackage().getName();
}
else
{
rpkg = referent.getClass().getPackage().getName();
}
rpkg = rpkg + ".";
for (String pkg : wa.defaultDbs.keySet())
{
if (rpkg.startsWith(pkg))
{
if (pp.connectedDbs == null)
{
pp.connectedDbs = new HashSet<>();
}
pp.connectedDbs.addAll(wa.defaultDbs.get(pkg));
}
}
}
}
/**
* Implementation for the SUPER-PROCEDURES attribute.
*
* @param superProcs
* A list of {@link ProcedureData} objects, in the order in
* which they were added.
*
* @return The string representation of each super-procedure, separated
* by a comma. The last added super-procedure must be first in
* this list, the second-to-last must be the second in the list,
* etc.
*/
private static character superProceduresImpl(Collection<ProcedureData> superProcs)
{
character sp = new character("");
boolean first = true;
ArrayDeque<ProcedureData> queue = new ArrayDeque<>();
queue.addAll(superProcs);
// need to emit them in a LIFO order
while (!queue.isEmpty())
{
ProcedureData pp = queue.removeLast();
long hashCode = handle.resourceId(pp.phandle.getResource());
sp.assign(TextOps.concat(sp, (!first ? "," : ""), hashCode));
first = false;
}
return sp;
}
/**
* The implementation of the ADD-SUPER-PROCEDURE method. When the
* <code>referent</code> is null, this call is for the SESSION handle.
*
* @param wa
* Context-local work area.
* @param h
* A handle to be added as super-procedure.
* @param mode
* The {@link SearchMode} for the passed handle.
* @param referent
* When <code>null</code> add to the SESSION:SUPER-PROCEDURES
* list. Else, add to the procedure associated with this
* referent.
*
* @return <code>true</code> if <code>h</code> is a valid procedure
* handle.
*/
private static logical addSuperProcedureImpl(WorkArea wa, handle h, SearchMode mode, Object referent)
{
if (!h._isValid() || !isProcedure(h))
{
return new logical(false);
}
ControlFlowOps.invalidateCallSiteCache();
Map<Object, ProcedureData> superProcs = wa.getSuperProcedures(referent, false);
ProcedureData pData = locateProcedure(wa, h.get());
if (referent == null)
{
pData.isSessionSuperProcedure = false;
}
else
{
pData.addedToSuperProcedures.remove(referent);
}
// the super-procedure to be added to the chain will always exist (either as the current
// this-procedure or as a persistent procedure). the remove call is needed just to eliminate
// it from the referent's super procedure chain, if was previously added
removeProcedureImpl(wa, h, superProcs.keySet());
if (!(mode == null || pData.searchMode == null || pData.searchMode.equals(mode)))
{
// if the mode is changed, show warning
int errNum = 8914;
String msg = "**Changing proc-search for procedure %s from '%s' to '%s'";
String progName = getRelativeName(wa, referent);
String oldMode = pData.searchMode.toString();
String newMode = mode.toString();
msg = String.format(msg, progName, oldMode, newMode);
// this is just a warning, the mode will be changed
ErrorManager.recordOrShowError(errNum, msg, false, false, false);
}
pData.searchMode = mode;
superProcs.put(h.get(), pData);
if (referent == null)
{
pData.isSessionSuperProcedure = true;
}
else
{
if (pData.addedToSuperProcedures == Collections.EMPTY_SET)
{
pData.addedToSuperProcedures = Collections.newSetFromMap(new IdentityHashMap<>());
}
pData.addedToSuperProcedures.add(referent);
}
return logical.TRUE;
}
/**
* Removes the given procedure handle from the given procedure instances list.
*
* @param wa
* Context-local work area.
* @param h
* The procedure handle to be removed.
* @param ppList
* The collection from which the procedure must be removed.
*/
private static void removeProcedureImpl(WorkArea wa, handle h, Collection<Object> ppList)
{
// if the handle is null or the list is empty, there is nothing to do
if (h == null || ppList.isEmpty())
{
return;
}
Object hreferent = h.get();
Iterator<Object> iter = ppList.iterator();
while (iter.hasNext())
{
Object referent = iter.next();
if (hreferent == referent)
{
iter.remove();
}
}
wa.cleanup(hreferent);
}
/**
* Get the {@link SearchMode} constant associated with the given mode, used
* by ADD-SUPER-PROCEDURE calls. If the mode is not one of the SEARCH-SELF
* or SEARCH-TARGET values, or an {@link IllegalArgumentException} will be
* thrown.
* <p>
* The given <code>mode</code> is not compatible with the mode returned by
* {@link SearchMode#toString()}.
*
* @param mode
* The string representation of the search-mode.
*
* @return For SEARCH-TARGET value, {@link SearchMode#SEARCH_TARGET}
* will be returned. For SEARCH-SELF value,
* {@link SearchMode#SEARCH_SELF} will be returned.
*
* @throws IllegalArgumentException
* If the mode is not of a known value.
*/
private static SearchMode getSearchMode(int mode)
{
if (mode == SEARCH_SELF)
return SearchMode.SEARCH_SELF;
if (mode == SEARCH_TARGET)
return SearchMode.SEARCH_TARGET;
// if not a known mode, default to "search mode not specified"
return null;
}
/**
* Get the relative name used to create the given external program instance.
*
* @param instance
* The external program instance.
*
* @return See above.
*/
private static String getRelativeName(WorkArea wa, Object instance)
{
return wa.relativeNames.get(instance);
}
/**
* Check if the given referent has been (or is still) registered with the
* {@link ProcedureManager} in the {@link WorkArea#pMap procedure} map.
*
* @param wa
* Context-local work area.
* @param referent
* The referent to be checked if is still loaded.
*
* @return <code>true</code> if the referent was found.
*/
private static boolean hasReferent(WorkArea wa, Object referent)
{
return wa.pMap.containsKey(referent);
}
/**
* Check if the specified referent is stale or not.
*
* @param wa
* The work area for which the query is done.
* @param referent
* The referent which is checked for staleness
*
* @return {@code true} if the given referent is stale.
*/
private static boolean hasStaleReferent(WorkArea wa, Object referent)
{
return wa.staleProcedures.contains(referent);
}
/**
* Check whether the specified referent (procedure) has been executed or not.
*
* @param wa
* The work area for which the query is done.
* @param referent
* The referent which is checked for having been executed. Must not be {@code null} and must
* represent a valid procedure referent.
*
* @return {@code true} if the procedure has been executed or was not found (i.e., is assumed to have
* executed and been deleted/discarded); else {@code false}.
*
* @throws IllegalStateException
* if the procedure data for the given referent cannot be found.
*/
public static boolean isExecuted(WorkArea wa, Object referent)
{
ProcedureData pdata = locateProcedure(wa, referent);
if (pdata == null)
{
throw new IllegalStateException("Unable to find procedure for referent: " + referent);
}
return pdata.executed;
}
/**
* Locate the {@link ProcedureData} instance associated with the given external program
* instance.
* <p>
* This will find any persistent procedure or any procedure active on the stack.
*
* @param referent
* The external program instance.
*
* @return See above.
*/
private static ProcedureData locateProcedure(Object referent)
{
return locateProcedure(work.obtain(), referent);
}
/**
* Locate the {@link ProcedureData} instance associated with the given external program
* instance.
* <p>
* This will find any persistent procedure or any procedure active on the stack.
*
* @param wa
* Context-local work area.
* @param referent
* The external program instance.
*
* @return See above.
*/
private static ProcedureData locateProcedure(WorkArea wa, Object referent)
{
return wa.pMap.get(referent);
}
/**
* Delete the given resources, as long as they are still valid.
*
* @param wa
* The {@link WorkArea} instance.
* @param resources
* The resources to be deleted.
*/
private static void deleteResources(WorkArea wa, Set<WrappedResource> resources)
{
if (resources.isEmpty())
{
return;
}
for (WrappedResource target : resources)
{
if (target.valid())
{
wa.pendingResourceDelete = target;
try
{
((Deletable) target).delete();
}
finally
{
wa.pendingResourceDelete = null;
}
}
}
}
/**
* Remove elements from the end of each queue until the specified referent is found.
*
* @param queues
* The queues which need to be prepared.
* @param ref
* The referent on which removing will stop. This will be also removed, if found.
*/
private static void prepareSearches(Collection<?>[] queues, Object ref)
{
l1: for (Collection<?> queue_i : queues)
{
Deque<Object> queue = new ArrayDeque<>(queue_i);
while (!queue.isEmpty())
{
Object referent = queue.removeLast();
queue_i.remove(referent);
if (referent == ref)
{
break l1;
}
}
}
}
/**
* Helper to expose APIs which have direct access to the context-local state.
*/
public static class ProcedureHelper
{
/** Cache and expose the {@link WidgetPool.WidgetPoolHelper}. */
final WidgetPool.WidgetPoolHelper wp;
/** Cache and expose the {@link handle.Helper}. */
final handle.Helper h;
/** Cache and expose the {@link ObjectOps.ObjectHelper}. */
final ObjectOps.ObjectHelper oh;
/** The {@link WorkArea} instance. */
private final WorkArea wa;
/**
* Create a new instance and associate the given WorkArea instance.
*
* @param wa
* The {@link WorkArea} instance.
*/
private ProcedureHelper(WorkArea wa)
{
this.wa = wa;
this.wp = wa.wp;
this.h = wa.h;
this.oh = wa.oh;
}
/**
* Allow static resources to be registered to {@link WorkArea#pendingResources}.
*/
public void enablePendingResources()
{
wa.registerPendingResources = true;
}
/**
* Disable static resources to be registered to {@link WorkArea#pendingResources}.
*/
public void disablePendingResources()
{
wa.registerPendingResources = false;
}
/**
* Process the given resource by either setting its INSTANTIATING-PROCEDURE attribute
* immediately (in case of non-static resources) or add the static resource to the
* {@link WorkArea#pendingResources} set, to be processed when the next scope is entered.
*
* @param res
* The resource to be processed.
* @param dynamic
* Flag indicating if this is a dynamic or static resource.
*/
public void processResource(WrappedResource res, boolean dynamic)
{
ProcedureManager.processResource(wa, res, dynamic);
}
/**
* Check if this referent can be deleted or not.
* <p>
* This checks if the instance is on stack, and if the run is SINGLE-RUN or SINGLETON.
*
* @param referent
* The instance to check.
*
* @return <code>true</code> if the delete must be postponed.
*/
public boolean isDelayedDelete(Object referent)
{
return ProcedureManager.isDelayedDelete(wa, referent);
}
/**
* Check if the given instance has associated subscriptions. If yes, it can't be deleted implicitly.
*
* @param resource
* The instance to check.
*
* @return If there are any subscriptions registered.
*/
public boolean hasSubscriptions(_BaseObject_ resource)
{
Function<List<Subscription>, Boolean> check = (subs) ->
{
Iterator<Subscription> iter = subs.iterator();
while (iter.hasNext())
{
Subscription sub = iter.next();
if (sub.subscriber.equals(resource))
{
return true;
}
}
return false;
};
if (check.apply(wa.subscriptions))
{
return true;
}
Session session = SessionManager.get().getSession();
List<Subscription> subs = globalSubscriptions.subscriptions.get(session);
if (subs != null && check.apply(subs))
{
return true;
}
return false;
}
/**
* Get the <code>SUPER-PROCEDURES</code> attribute of the specified
* external procedure instance.
*
* @param referent
* An instance of an converted external procedure class.
*
* @return See above.
*/
public character superProcedures(Object referent)
{
return superProceduresImpl(wa.getSuperProcedures(referent, true).values());
}
/**
* Remove the given handle from the specified referent's list of
* super-procedures.
*
* @param referent
* An instance of an converted external procedure class.
* @param h
* The procedure handle to be removed.
*
* @return <code>true</code> if <code>h</code> is a valid procedure
* handle.
*/
public logical removeSuperProcedure(Object referent, handle h)
{
if (!h._isValid() || !isProcedure(h))
{
return new logical(false);
}
ControlFlowOps.invalidateCallSiteCache();
ProcedureData pData = locateProcedure(wa, h.get());
pData.addedToSuperProcedures.remove(referent);
removeProcedureImpl(wa, h, wa.getSuperProcedures(referent, true).keySet());
return new logical(true);
}
/**
* Add the given handle as a super-procedure for the specified referent.
*
* @param referent
* An instance of an converted external procedure class.
* @param h
* The handle to be added as a super-procedure.
*
* @return <code>true</code> if <code>h</code> is a valid procedure
* handle.
*/
public logical addSuperProcedure(Object referent, handle h)
{
return addSuperProcedure(referent, h, (SearchMode) null);
}
/**
* Add the given handle as a super-procedure for the specified referent,
* using the explicit {@link SearchMode mode}.
*
* @param referent
* An instance of an converted external procedure class.
* @param h
* The handle to be added as a super-procedure.
* @param mode
* The {@link SearchMode} to be set for the <code>h</code>
* handle.
*
* @return <code>true</code> if <code>h</code> is a valid procedure
* handle.
*/
public logical addSuperProcedure(Object referent, handle h,SearchMode mode)
{
return ProcedureManager.addSuperProcedureImpl(wa, h, mode, referent);
}
/**
* Add the given handle as a super-procedure for the specified referent,
* using the search-mode set as a string (one of the
* <code>SEARCH-TARGET</code> or <code>SEARCH-SELF</code> string literals).
*
* @param referent
* An instance of an converted external procedure class.
* @param h
* The handle to be added as a super-procedure.
* @param mode
* The search-mode to be set for the <code>h</code> handle.
*
* @return <code>true</code> if <code>h</code> is a valid procedure
* handle.
*/
public logical addSuperProcedure(Object referent, handle h, int mode)
{
return addSuperProcedure(referent, h, getSearchMode(mode));
}
/**
* Resolve the instantiating procedure from the given handle: if the handle contains a valid
* procedure, return a copy of it; else, return an unknown handle.
*
* @param instProc
* The instance representing an instantiating procedure.
*
* @return See above.
*/
public handle resolveInstantiatingProcedure(Object instProc)
{
handle res = new handle();
if (instProc != null && wa.pMap.containsKey(instProc))
{
res.assign(new ExternalProgramWrapper(this, instProc));
}
return res;
}
/**
* Check if the given referent is the top-level procedure (the startup procedure).
*
* @param referent
* The instance to be verified.
*
* @return <code>true</code> if the referent is the startup procedure.
*/
public boolean isTopLevel(Object referent)
{
return wa.thisProceduresData.getLast().getReferent() == referent;
}
/**
* Set the <code>PRIVATE-DATA</code> attribute of the specified referent.
*
* @param referent
* An instance of an converted external procedure class.
* @param data
* the PRIVATE-DATA.
*/
public void setPrivateData(Object referent, character data)
{
ProcedureData pp = locateProcedure(wa, referent);
pp.privateData = data.getValue();
}
/**
* Get the <code>PRIVATE-DATA</code> attribute of the specified referent.
*
* @param referent
* An instance of an converted external procedure class.
*
* @return See above.
*/
public character getPrivateData(Object referent)
{
ProcedureData pp = locateProcedure(wa, referent);
return pp.privateData == null ? new character() : new character(pp.privateData);
}
/**
* Gets the the unique ID number associated to the referent. Not the
* same as the handle value itself.
*
* @param referent
* An instance of an converted external procedure class.
*
* @return The integer number of the children.
*/
public integer getUniqueID(Object referent)
{
ProcedureData pp = locateProcedure(wa, referent);
if (pp.uniqueID == null)
{
pp.uniqueID = UniqueIdGenerator.getUniqueId(IdKind.PROCEDURE);
}
return new integer(pp.uniqueID);
}
/**
* Check if this resource is currently being implicitly deleted.
*
* @param res
* The resource to check.
*
* @return <code>true</code> if the resource is the same as
* {@link WorkArea#pendingResourceDelete}.
*/
public boolean pendingResourceDelete(WrappedResource res)
{
return wa.pendingResourceDelete == res;
}
/**
* Register the given resource as being created by the specified procedure.
*
* @param referent
* The procedure which creates the resource.
* @param resource
* The resource being created.
*/
public void registerResource(Object referent, HandleResource resource)
{
ProcedureData pdata = locateProcedure(wa, referent);
if (pdata != null)
{
pdata.createdResources.put(resource, null);
}
else if (LOG.isLoggable(Level.FINE))
{
Long id = handle.resourceId(resource);
LOG.log(Level.FINE, "Could not register resource " + resource.type() +
" with id " + id + " - the data for procedure " +
referent.getClass().getName() + " could not be found!");
}
}
/**
* Deregister the given resource from the specified creator procedure.
*
* @param referent
* The procedure which creates the resource.
* @param resource
* The resource being created.
*/
public void deregisterResource(Object referent, HandleResource resource)
{
ProcedureData pdata = locateProcedure(wa, referent);
if (pdata != null)
{
pdata.createdResources.remove(resource);
}
else if (LOG.isLoggable(Level.FINE))
{
Long id = handle.resourceId(resource);
LOG.log(Level.FINE, "Could not deregister resource " + resource.type() +
" with id " + id + " - the data for procedure " +
referent.getClass().getName() + " could not be found!");
}
}
/**
* Check if we are executing finalizable or scopeable notifications.
*
* @return See above.
*/
public boolean inNotifications()
{
return wa.processedProcedure != null;
}
/**
* Check if the given referent is a valid procedure.
*
* @param referent
* The instance to check.
*/
public boolean isValid(Object referent)
{
return referent != null && wa.pMap.containsKey(referent);
}
/**
* Get the legacy stack trace
*
* @return the legacy stack trace
*/
public List<String> getStackTrace()
{
return ProcedureManager.getStackTrace(wa);
}
/**
* Delete the specified resource - this allows a static resource to be deleted.
*
* @param res
* The resource to delete.
*/
public void deleteResource(WrappedResource res)
{
WrappedResource old = wa.pendingResourceDelete;
wa.pendingResourceDelete = res;
try
{
((Deletable) res).delete();
}
finally
{
wa.pendingResourceDelete = old;
}
}
/**
* Gets the INSTANTIATING-PROCEDURE associated to the referent.
*
* @param referent
* The external program.
*
* @return See above.
*/
public Object getInstantiatingProcedure(Object referent)
{
ProcedureData pdata = locateProcedure(wa, referent);
return pdata.instProc == null ? null : pdata.instProc.get();
}
/**
* Get the {@code NEXT-SIBLING} attribute of the specified referent.
*
* @param referent
* An instance of an converted external procedure class.
*
* @return See above.
*/
public handle getNextSibling(Object referent)
{
Object next = wa.persistentProcedures.getNext(referent);
// exclude surrogate referents for OO instances, and any single-run/singleton proxy objects
while (ObjectOps.isLegacyObject(next) || (next instanceof DeferredProgram))
{
next = wa.persistentProcedures.getNext(next);
}
if (next == null)
{
return new handle();
}
return locateProcedure(wa, next).phandle;
}
/**
* Get the {@code PREV-SIBLING} attribute of the specified referent.
*
* @param referent
* An instance of an converted external procedure class.
*
* @return See above.
*/
public handle getPrevSibling(Object referent)
{
Object prev = wa.persistentProcedures.getPrevious(referent);
// exclude surrogate referents for OO instances, and any single-run/singleton proxy objects
while (ObjectOps.isLegacyObject(prev) || (prev instanceof DeferredProgram))
{
prev = wa.persistentProcedures.getPrevious(prev);
}
if (prev == null)
{
return new handle();
}
return locateProcedure(wa, prev).phandle;
}
/**
* Check if the given referent is for a single-run procedure. This
* implements the SINGLE-RUN attribute for a procedure handle.
*
* @param referent
* An instance of an converted external procedure class.
*
* @return <code>true</code> if the referent is a single-run procedure.
*/
public logical isSingleRun(Object referent)
{
return new logical(_isSingleRun(wa, referent));
}
/**
* Check if the given referent is for a singleton procedure. This
* implements the SINGLETON attribute for a procedure handle.
*
* @param referent
* An instance of an converted external procedure class.
*
* @return <code>true</code> if the referent is a singleton procedure.
*/
public logical isSingleton(Object referent)
{
return new logical(_isSingleton(wa, referent));
}
/**
* Check if the given referent is for a persistent procedure. This
* implements the PERSISTENT attribute for a procedure handle.
*
* @param referent
* An instance of an converted external procedure class.
*
* @return <code>true</code> if the referent is a persistent procedure.
*/
public logical isPersistent(Object referent)
{
ProcedureData pdata = locateProcedure(wa, referent);
boolean persistent = pdata != null && pdata.persistent && !(pdata.singleRun || pdata.singleton);
return logical.of(persistent);
}
/**
* Get the value of the ADM-DATA attribute of the specified referent.
*
* @param referent
* An instance of an converted external procedure class.
*
* @return See above.
*/
public character getADMData(Object referent)
{
ProcedureData pp = locateProcedure(wa, referent);
return new character(pp.admData);
}
/**
* Set the value of the ADM-DATA attribute for the specified referent.
*
* @param referent
* An instance of an converted external procedure class.
*
* @param value
* The new value.
*/
public void setADMData(Object referent, String value)
{
ProcedureData pp = locateProcedure(wa, referent);
pp.admData = value;
}
/**
* Get an iterator over the stack of currently executing code.
*
* @return An iterator of Java classes associated with the code executing on the stack.
*/
public Iterator<Class<?>> executingIterator()
{
return wa.executingIterator();
}
/**
* Get an iterator over the stack of currently destination resolved code.
*
* @return An iterator of Java classes associated with the code on the stack.
*/
public Iterator<Object> destinationIterator()
{
return wa.destinationIterator();
}
/**
* For procedures or functions defined IN SUPER, this computes the target procedure handle
* where the procedure's or function's body resides.
* <p>
* In case when no super procedure is found, it will throw proper error messages and return
* <code>null</code>.
*
* @param iename
* Internal-entry's name (this may be a procedure or function).
* @param function
* <code>true</code> if this is a function.
*
* @return A procedure handle where to search for the internal-entry's body or
* <code>null</code> of none found.
*/
handle getSuperHandle(String iename, boolean function)
{
handle hthis = ProcedureManager.thisProcedure(wa);
handle htarget = targetProcedure(wa);
Object refThis = hthis.get();
Object refTarget = htarget.get();
ProcedureData ppThis = locateProcedure(wa, refThis);
ProcedureData ppTarget = locateProcedure(wa, refTarget);
boolean searchSelf = ppThis.isSearchSelf();
handle psuper = null;
@SuppressWarnings("unchecked")
Collection<Object>[] search = new Collection[2];
if (searchSelf)
{
search[0] = ppThis.superProcedures.keySet();
search[1] = wa.sessionSuperProcs.keySet();
}
else
{
Collection<Object> targetProcs = ppTarget.superProcedures.keySet();
search[0] = new ArrayDeque<>(targetProcs);
search[1] = new ArrayDeque<>(wa.sessionSuperProcs.keySet());
boolean inTarget = search[0].contains(refThis);
boolean inSess = search[1].contains(refThis);
if (inTarget || inSess)
{
// remove referents until this-procedure is found in the list
prepareSearches(search, refThis);
}
if (!(inTarget || inSess) || (inSess && search[1].isEmpty() && !targetProcs.isEmpty()))
{
if (!function)
{
// in some cases when invoking internal procedures, it can end up in an infinite
// recursion
search[0] = new ArrayDeque<>(targetProcs);
search[1] = new ArrayDeque<>(wa.sessionSuperProcs.keySet());
// step 1. find the first already-invoked proc part of target's super-procs
Object ref = null;
for (CalleeInfoImpl info : wa.calleeInfo)
{
if (targetProcs.contains(info.destination))
{
ref = info.destination;
break;
}
if (!info.superCall && refTarget != info.destination)
{
break;
}
}
if (ref != null)
{
// step 2. remove all until ref is found, from all searches
prepareSearches(search, ref);
}
else
{
search[0] = null;
search[1] = null;
}
}
else
{
// not found, clear the lists
search[0] = null;
search[1] = null;
}
}
}
for (int i = 0; i < search.length && psuper == null; i++)
{
if (search[i] == null || search[i].isEmpty())
{
continue;
}
// search is done backwards (last set super-procedure is searched first)
Deque<Object> queue = new ArrayDeque<>(search[i]);
while (!queue.isEmpty())
{
Object referent = queue.removeLast();
ProcedureData pp = locateProcedure(wa, referent);
if (pp == null || !pp.phandle._isValid())
{
// deleted or invalid procedures are ignored
continue;
}
String spname = getAbsoluteName(pp.phandle.get());
String jname = SourceNameMapper.getMethodName(spname, iename, function);
if (jname != null)
{
// found it
psuper = pp.phandle;
break;
}
}
}
if (psuper == null)
{
String err = null;
int errId = -1;
if (function)
{
final String errMsg =
"SUPER version of user-defined function '%s' invoked but could not be found";
errId = 6454;
err = String.format(errMsg, iename);
ErrorManager.recordOrShowError(errId, err, false, false, false);
}
else
{
String pname = getRelativeName(searchSelf ? refThis : refTarget);
errId = 6439;
if (searchSelf)
{
final String errMsg =
"Procedure %s %s has no SUPER procedure with internal procedure %s";
err = String.format(errMsg, iename, pname, iename);
}
else
{
final String errMsg =
"Procedure %s has no SUPER procedure with internal procedure %s";
err = String.format(errMsg, pname, iename);
}
ErrorManager.recordOrThrowError(errId, err, false);
}
}
return psuper;
}
/**
* Get the Java class associated with the currently executing code.
*
* @return See above.
*/
public Class<?> getExecuting()
{
return wa.getExecuting();
}
/**
* Calculate the number of occurences of the specified referent, on current stack.
*
* @param referent
* The referent to check.
*
* @return The number of occurrences of the referent on the current stack.
*/
public long onStackReferences(Object referent)
{
return wa.thisProceduresData.stream().filter(pd -> pd.referent == referent).count();
}
/**
* Walk the {@link WorkArea#thisProceduresData THIS-PROCEDURE} stack and apply the search function;
* return the first value given by the search function, once it returns a non-null value.
*
* @param search
* The search function.
*
* @return The first non-null value returned by the search function.
*/
public Object searchInStack(Function<Object, Object> search)
{
return wa.searchInStack(search);
}
/**
* Check if an external program was ran persistent by a remote side.
*
* @param referent
* The external program instance.
*
* @return <code>true</code> if the referent is part of the
* {@link WorkArea#remotePersistentProcedures} map.
*/
public boolean isRemotePersistentProcedure(Object referent)
{
return wa.isRemotePersistentProcedure(referent);
}
/**
* Register an external program which was ran persistent by a remote side.
*
* @param referent
* The external program instance.
*/
public void registerRemotePersistentProcedure(Object referent)
{
wa.remotePersistentProcedures.add(referent);
}
/**
* Get the THIS-PROCEDURE system handle.
*
* @return See above.
*/
public handle thisProcedure()
{
return ProcedureManager.thisProcedure(wa);
}
/**
* Get the THIS-PROCEDURE referent.
*
* @return See {@link #thisProcedure()}.
*/
public Object _thisProcedure()
{
return ProcedureManager._thisProcedure(wa);
}
/**
* Get the root procedure instance .
*/
public Object _rootProcedure()
{
return wa.thisProceduresData.peekLast().getReferent();
}
/**
* Check if {@link #_thisProcedure()} is ran persistent.
*
* @return <code>true</code> if THIS-PROCEDURE is ran persistent.
*/
public boolean isThisProcedurePersistent()
{
return wa.thisProceduresData.peek().persistent;
}
/**
* Get the THIS-PROCEDURE data for the previous block on the stack.
*
* @return See above.
*/
public ProcedureData prevThisProcedureData()
{
ProcedureData pdata = wa.thisProceduresData.pop();
try
{
return wa.thisProceduresData.peek();
}
finally
{
wa.thisProceduresData.push(pdata);
}
}
/**
* Get the THIS-PROCEDURE data for the current block.
*
* @return See above.
*/
public ProcedureData thisProcedureData()
{
return wa.thisProceduresData.peek();
}
/**
* Get the SOURCE-PROCEDURE referent.
*
* @return See {@link #sourceProcedure()}.
*/
public Object _sourceProcedure()
{
return wa.sourceProceduresData.peek().getReferent();
}
/**
* Check if the given referent is for a persistent procedure. This implements the PERSISTENT
* attribute for a procedure handle.
*
* @param referent
* An instance of a converted external procedure class.
*
* @return <code>true</code> if the referent is a persistent procedure.
*/
public boolean _isPersistent(Object referent)
{
return ProcedureManager._isPersistent(wa, referent);
}
/**
* Check if the given referent is for a persistent procedure which has not been deleted explicitly while
* its still on stack, so a delete is pending when last call for this reference exits the stack.
*
* @param referent
* An instance of a converted external procedure class.
*
* @return <code>true</code> if the referent is a persistent procedure.
*/
public boolean _isPersistentAndNotPendingDelete(Object referent)
{
if (!ProcedureManager._isPersistent(wa, referent))
{
return false;
}
if (_isDelayedDelete(referent))
{
// check that is not still on the stack - a persistent procedure which had a
// 'DELETE PROCEDURE THIS-PROCEDURE' executed will have its delete postponed until the reference
// exits the stack
// exclude the current block - on scope notification, 'this-procedure' is popped after all other
// scopeables were notified.
Iterator<ProcedureData> iter = wa.thisProceduresData.iterator();
Object next = iter.next().getReferent(); // skip THIS-PROCEDURE
while (iter.hasNext())
{
next = iter.next().getReferent();
if (next == referent)
{
return true;
}
}
return false;
}
else
{
// this is a plain old persistent procedure
return true;
}
}
/**ProcedureHelper
* Get the name of the currently instantiated external program.
*
* @return The 4GL external program name being instantiated, or <code>null</code> if this is
* not the case.
*/
public String getInstantiatingExternalProgram()
{
return wa.initiatingExtProg;
}
/**
* Get the source class of the currently instantiating external program or OE class.
*
* @return The Java class associated with the source code for the current instantiating OE
* class.
*/
public Class<?> getInstantiatingExternalProgramClass()
{
return wa.initiatingExtProgClass;
}
/**
* Set the external procedure beeing processed for scopeable or finalizable notifications.
*
* @param referent
* The external procedure instance.
*/
public void setProcessedProcedure(Object referent)
{
wa.processedProcedure = referent;
}
/**
* Get the current external program (an application logic instance) being processed for
* scopeable or finalizable notifications.
*
* @return See above
*/
public Object getProcessedProcedure()
{
return wa.processedProcedure;
}
/**
* Cleanup of pending resources. This is called when the external procedure constructor failed
* to build a new object because of an exception thrown in a member initialization (shared
* frame/table).
*/
public void cleanupPending()
{
// a no-op in case of headless mode
if (wa.tm == null || wa.tm.errHlp == null || wa.tm.errHlp.isHeadless())
{
return;
}
wa.pendingResources.clear();
}
/**
* Check if this is a "delete-on-exit" procedure.
*
* @param referent
* An instance of a converted external procedure class.
*
* @return <code>true</code> if the referent is a "delete-on-exit" procedure.
*/
public boolean _isDelayedDelete(Object referent)
{
Boolean delete = wa.deleteOnExit.get(referent);
return delete != null && delete;
}
/**
* Save the information about the program being called, to be used on the top-level scope
* start notification: at that time, the info about the callee (this, source- and target-
* procedures and other info) will be resolved.
*
* @param def
* The currently executing OE class or external program.
* @param superCall
* Flag indicating if the destination is in a super-procedure.
* @param target
* The target procedure, which was targeted by the caller.
* @param destination
* The procedure where the internal entry was resolved.
* @param iename
* The internal entry name. In case of triggers or external programs, use the
* {@link #EXTERNAL_PROGRAM} and {@link #TRIGGER_PROCEDURE} constants.
* @param pname
* The external program name; will be non-null only when we are invoking an external
* program.
* @param function
* Flag indicating we are calling a user-defined function.
* @param persistentProc
* Flag indicating the external program was ran persistent.
*/
public void pushCalleeInfo(Class<?> def,
boolean superCall,
Object target,
Object destination,
String iename,
String pname,
boolean function,
boolean persistentProc)
{
pushCalleeInfo(def, superCall, target, destination, iename, pname, function, persistentProc, false);
}
/**
* Save the information about the program being called, to be used on the top-level scope
* start notification: at that time, the info about the callee (this, source- and target-
* procedures and other info) will be resolved.
*
* @param def
* The currently executing OE class or external program.
* @param superCall
* Flag indicating if the destination is in a super-procedure.
* @param target
* The target procedure, which was targeted by the caller.
* @param destination
* The procedure where the internal entry was resolved.
* @param iename
* The internal entry name. In case of triggers or external programs, use the
* {@link #EXTERNAL_PROGRAM} and {@link #TRIGGER_PROCEDURE} constants.
* @param pname
* The external program name; will be non-null only when we are invoking an external
* program.
* @param function
* Flag indicating we are calling a user-defined function.
* @param persistentProc
* Flag indicating the external program was ran persistent.
* @param dynamicCall
* Flag indicating a dynamic call.
*/
public void pushCalleeInfo(Class<?> def,
boolean superCall,
Object target,
Object destination,
String iename,
String pname,
boolean function,
boolean persistentProc,
boolean dynamicCall)
{
CalleeInfoImpl info = new CalleeInfoImpl(wa, def, superCall, target, destination,
iename, pname, function,
persistentProc, false, false, dynamicCall);
wa.calleeInfo.push(info);
}
/**
* Save the information about the program being called, to be used on the top-level scope
* start notification: at that time, the info about the callee (this, source- and target-
* procedures and other info) will be resolved.
*
* @param def
* The currently executing OE class or external program.
* @param superCall
* Flag indicating if the destination is in a super-procedure.
* @param target
* The target procedure, which was targeted by the caller.
* @param destination
* The procedure where the internal entry was resolved.
* @param iename
* The internal entry name. In case of triggers or external programs, use the
* {@link #EXTERNAL_PROGRAM} and {@link #TRIGGER_PROCEDURE} constants.
* @param pname
* The external program name; will be non-null only when we are invoking an external
* program.
* @param function
* Flag indicating we are calling a user-defined function.
* @param persistentProc
* Flag indicating the external program was ran persistent.
* @param singletonProc
* Flag indicating the external program was ran singleton.
* @param singleRunProc
* Flag indicating the external program was ran single-run.
*/
public void pushCalleeInfo(Class<?> def,
boolean superCall,
Object target,
Object destination,
String iename,
String pname,
boolean function,
boolean persistentProc,
boolean singletonProc,
boolean singleRunProc)
{
CalleeInfoImpl info = new CalleeInfoImpl(wa,
def, superCall, target, destination,
iename, pname, function,
persistentProc, singletonProc,
singleRunProc, false);
wa.calleeInfo.push(info);
}
/**
* Pop the last callee info from the stack.
*/
public void popCalleeInfo()
{
wa.calleeInfo.pop();
}
/**
* Return the last callee info from the stack.
*
* @return {@link CalleeInfo} instance.
*/
public CalleeInfo peekCalleeInfo()
{
return wa.calleeInfo.peek();
}
/**
* Execute the given block with THIS-PROCEDURE as the given instProc procedure.
*
* @param thisProcedureOverride
* Procedure to be treated as the new THIS-PROCEDURE.
* @param block
* The code to execute.
*/
public void executeInThisProcedure(Object thisProcedureOverride, Block block)
{
boolean pushThisProcedure = _thisProcedure() != thisProcedureOverride;
try
{
if (pushThisProcedure)
{
wa.thisProceduresData.push(wa.pMap.get(thisProcedureOverride));
wa.internalEntryStack.push(THIS_PROCEDURE_OVERRIDE);
}
block.body();
}
finally
{
if (pushThisProcedure)
{
wa.thisProceduresData.pop();
wa.internalEntryStack.pop();
}
}
}
/**
* Get the relative name used to create the given external program instance.
*
* @param instance
* The external program instance.
*
* @return See above.
*/
public String getRelativeName(Object instance)
{
return wa.relativeNames.get(instance);
}
/**
* Check if the given referent has been (or is still) registered with the
* {@link ProcedureManager} in the {@link WorkArea#pMap procedure} map.
*
* @param referent
* The referent to be checked if is still loaded.
*
* @return <code>true</code> if the referent was found.
*/
public boolean hasReferent(Object referent)
{
return ProcedureManager.hasReferent(wa, referent);
}
/**
* Check if a provided referent is stale or not.
*
* @param referent
* The referent for which the checking will be done
*
* @return {@code true} if the referent is stale.
*/
public boolean hasStaleReferent(Object referent)
{
return ProcedureManager.hasStaleReferent(wa, referent);
}
/**
* Check whether the specified referent (procedure) has been executed or not.
*
* @param referent
* The referent which is checked for staleness
*
* @return {@code true} if the procedure has been executed or was not found (i.e., is assumed to have
* executed and been deleted/discarded); else {@code false}.
*/
public boolean isExecuted(Object referent)
{
return ProcedureManager.isExecuted(wa, referent);
}
/**
* A system which notifies the ProcedureManager when a static record buffer is changed.
* This is used to keep up to date the persistent procedure stacks - which are in fact
* snapshots of the block stacks stored the first time a static record is loaded.
*
* @param recordBuffer
* The record buffer which was loaded and triggered this notification.
*/
public void notifyStaticRecordWasLoaded(RecordBuffer recordBuffer)
{
Object targetProcedure = recordBuffer.getPersistentProc();
if (targetProcedure == null ||
ObjectOps.isLegacyObject(targetProcedure) ||
wa.hasPersistentProcStack(targetProcedure))
{
// legacy OO does not have the 'stale procedure' behavior.
return;
}
Stack<BlockDefinition> currentStack = wa.tm.copyStack();
wa.addPersistentProcStack(targetProcedure, currentStack);
}
/**
* Removes the referent information from the persistent procedure stack mapping
* and stale set. This should be done only when the referent is deleted.
*
* @param referent
* The persistent procedure for which the entry should be removed.
*/
public void removeStaleProcedure(Object referent)
{
wa.removeStaleProcedure(referent);
}
/**
* Obtains the stack snapshot of one persistent procedure. The stack represent the block
* stack when the first global buffer was defined inside the referent.
*
* @param referent
* The persistent procedure for which the stack should be retrieved.
*
* @return The block stack snapshot stored for the specified referent.
*
*/
public Stack<BlockDefinition> getPersistentProcStack(Object referent)
{
return wa.getPersistentProcStack(referent);
}
/**
* Mark that the selected referent is stale.
*
* @param referent
* The referent which should be marked as stale.
*/
public boolean addStaleReferent(Object referent)
{
return wa.addStaleReferent(referent);
}
/**
* Set the name of the 4GL program currently being instantiated, into the
* {@link WorkArea#initiatingExtProg} field.
*
* @param name
* The 4GL external program name.
*/
public void setInstantingExternalProgram(String name)
{
wa.setInstantingExternalProgram(name);
}
/**
* Set the Java class of the currently instantiating external program or OE class.
*
* @param cls
* The Java class for the currently executing code.
*/
public void setInstantingExternalProgramClass(Class<?> cls)
{
wa.setInstantingExternalProgramClass(cls);
}
/**
* Registers a specific procedure-cleanup handler. The code will be executed AFTER the last bit of code of
* the CURRENT procedure was executed, including the eventual {@code fini} / {@code finally:} block, just
* before the procedure is popped out from the stack.
*
* @param code
* The code that does the cleanup. It can handle a (input-)output parameter or other resource
* whose scope is bound to the current procedure. For example, in case of a table parameter, it
* will handle the unbinding process of the table parameter from the local table.
* @param weight
* The priority of the code. The lower the weight, the higher priority.
*/
public void executeOnReturn(Runnable code, WeightFactor weight)
{
Deque<List<Runnable>[]> allHandlers = wa.cleanupHandlers;
List<Runnable>[] handlers = allHandlers.peek();
if (handlers == null)
{
LOG.log(Level.WARNING, "No procedure scope was defined at this moment");
return;
}
if (handlers == WorkArea.EMPTY_SCOPE_STACK)
{
// replace the empty scope marker with an actual container for the handlers
allHandlers.pop();
handlers = new ArrayList[WeightFactor.WEIGHTS_ARRAY_SIZE];
allHandlers.push(handlers);
}
List<Runnable> handlerSusbset = handlers[weight.getIndex()];
if (handlerSusbset == null)
{
handlerSusbset = new ArrayList<>();
handlers[weight.getIndex()] = handlerSusbset;
}
handlerSusbset.add(code);
}
/**
* Deregister the given code from the {@link WorkArea#cleanupHandlers}.
*
* @param code
* The clean code that needs to be removed.
* @param weight
* The priority of the code. The lower the weight, the higher priority.
*/
public void deregisterExecuteOnReturn(Runnable code, WeightFactor weight)
{
Deque<List<Runnable>[]> allHandlers = wa.cleanupHandlers;
List<Runnable>[] handlers = allHandlers.peek();
if (handlers == null)
{
LOG.log(Level.WARNING, "No procedure scope was defined at this moment");
return;
}
if (handlers != WorkArea.EMPTY_SCOPE_STACK)
{
List<Runnable> handlerSubset = handlers[weight.getIndex()];
if (handlerSubset != null)
{
handlerSubset.remove(code);
}
}
}
/**
* Using the name of a function defined IN handle and a procedure handle
* to which this function definition belongs, resolve the target handle,
* to be able to invoke it.
*
* @param phandle
* The procedure handle where the function was defined.
* @param fname
* The function's legacy name.
*
* @return The procedure handle where to search for the function's body.
*/
handle getHandleForFunction(handle phandle, String fname)
{
Object referent = phandle.get();
ProcedureData pp = locateProcedure(wa, referent);
Resolvable target = pp.inHandleFunctions.get(fname.toLowerCase());
if (target == null && _BaseObject_.class.isAssignableFrom(referent.getClass()))
{
// search for a registration in the legacy class
referent = ObjectOps.getStaticInstance((Class<? extends _BaseObject_>) referent.getClass());
pp = locateProcedure(wa, referent);
target = pp.inHandleFunctions.get(fname.toLowerCase());
}
handle h = target == null ? null : (handle) target.resolve();
return h == null ? new handle() : h;
}
/**
* Add the <code>h</code> handle as a persistent procedure. Used by
* {@link ControlFlowOps#invokePersistent(character, Object...)} and
* {@link ControlFlowOps#invokePersistentSet(String, handle, Object...)}.
*
* @param h
* The procedure handle to be added to the list of persistent
* procedures.
* @param name
* The name of this procedure, used at the RUN statement.
* @param persistent
* Flag indicating if this is a persistent procedure.
*/
void addProcedure(handle h, String name, boolean persistent)
{
ProcedureManager.addProcedure(wa, h, name, persistent, false, false, false);
}
/**
* Add the <code>h</code> handle as a persistent procedure. Used by
* {@link ControlFlowOps#invokePersistent(character, Object...)} and
* {@link ControlFlowOps#invokePersistentSet(String, handle, Object...)}.
*
* @param h
* The procedure handle to be added to the list of persistent
* procedures.
* @param name
* The name of this procedure, used at the RUN statement.
* @param persistent
* Flag indicating if this is a persistent procedure.
* @param singleton
* Flag indicating if this is a singleton procedure.
* @param singleRun
* Flag indicating if this is a single-run procedure.
* @param legacyClass
* Set to {@code true} if the method is called as a result of
* legacy class initialization. {@code false} otherwise.
*/
void addProcedure(handle h,
String name,
boolean persistent,
boolean singleton,
boolean singleRun,
boolean legacyClass)
{
ProcedureManager.addProcedure(wa, h, name, persistent, singleton, singleRun, legacyClass);
}
/**
* Add the <code>h</code> handle as a persistent procedure. Used by
* {@link ControlFlowOps#invokePersistent(character, Object...)} and
* {@link ControlFlowOps#invokePersistentSet(String, handle, Object...)}.
*
* @param h
* The procedure handle to be added to the list of persistent
* procedures.
* @param name
* The name of this procedure, used at the RUN statement.
* @param singleton
* Flag indicating if this is a singleton procedure.
* @param singleRun
* Flag indicating if this is a singleton procedure.
*/
void addProcedure(handle h, String name, boolean singleton, boolean singleRun)
{
ProcedureManager.addProcedure(wa, h, name, false, singleton, singleRun, false);
}
/**
* Implementation for the PROGRAM-NAME functions. Resolves the legacy top level block (external
* program, function, internal procedure or trigger), at the given depth. The index starts with
* 0 for the currently executing top-level block.
*
* @param idx
* The stack entry index.
*
* @return The name of the stack entry or {@code null} if outside of stack.
*/
String getStackEntry(int idx)
{
return wa.getStackEntry(idx);
}
/**
* Get the currently executing internal entry name.
*
* @return The top of the {@link WorkArea#internalEntryStack}.
*/
String currentInternalEntry()
{
return wa.internalEntryStack.peek();
}
/**
* Delete the given procedure handle (i.e. execute the DELETE OBJECT or DELETE PROCEDURE
* handle).
*
* @param referent
* The persistent procedure instance to be deleted.
* @param letStaleProcedure
* Flag to indicate if the procedure should go in an stale state.
*
* @return <code>true</code> if this procedure handle can be deleted.
*/
boolean delete(Object referent, boolean letStaleProcedure)
{
return ProcedureManager.delete(wa, referent, letStaleProcedure);
}
/**
* Check if current THIS-PROCEDURE is marked as ROUTINE-LEVEL UNDO, THROW or
* BLOCK-LEVEL UNDO, THROW (as BLOCK-LEVEL includes the ROUTINE-LEVEL blocks).
*
* @return See above.
*/
boolean isRoutineLevelUndoThrow()
{
Object referent = _thisProcedure();
ProcedureData pdata = wa.pMap.get(referent);
return pdata.routineLevelUndoThrow || pdata.blockLevelUndoThrow;
}
/**
* Check if current THIS-PROCEDURE is marked as BLOCK-LEVEL UNDO, THROW.
*
* @return See above.
*/
boolean isBlockLevelUndoThrow()
{
Object referent = _thisProcedure();
ProcedureData pdata = wa.pMap.get(referent);
return pdata.blockLevelUndoThrow;
}
/**
* Mark current THIS-PROCEDURE as ROUTINE-LEVEL UNDO, THROW.
*/
void markRoutineLevelUndoThrow()
{
Object referent = _thisProcedure();
ProcedureData pdata = wa.pMap.get(referent);
pdata.routineLevelUndoThrow = true;
}
/**
* Mark current THIS-PROCEDURE as BLOCK-LEVEL UNDO, THROW.
*/
void markBlockLevelUndoThrow()
{
Object referent = _thisProcedure();
ProcedureData pdata = wa.pMap.get(referent);
pdata.blockLevelUndoThrow = true;
}
/**
* Register the given {@link Scopeable} to be processed when the persistent procedure gets
* deleted.
*
* @param referent
* The external program.
* @param target
* The {@link Scopeable} instance which needs delayed notification.
*/
void addScopeable(Object referent, Scopeable target)
{
ProcedureData pdata = locateProcedure(wa, referent);
if (pdata.scopeables == Collections.EMPTY_SET)
{
pdata.scopeables = Collections.newSetFromMap(new IdentityHashMap<>());
}
pdata.scopeables.add(target);
}
/**
* Register the given {@link Finalizable} to be processed when the persistent procedure gets
* deleted.
*
* @param referent
* The external program.
* @param target
* The {@link Finalizable} instance which needs delayed notification.
*/
void addFinalizable(Object referent, Finalizable target)
{
ProcedureData pdata = locateProcedure(wa, referent);
if (pdata.finalizables == Collections.EMPTY_SET)
{
pdata.finalizables = Collections.newSetFromMap(new IdentityHashMap<>());
}
pdata.finalizables.add(target);
}
/**
* Get a handle list of super-procedures for the given referent.
*
* @param referent
* An instance of an converted external procedure class.
*
* @return See above.
*/
List<handle> getSuperProcedures(Object referent)
{
Map<Object, ProcedureData> supers = wa.getSuperProcedures(referent, true);
if (supers.isEmpty())
{
return Collections.emptyList();
}
List<handle> superProcs = new LinkedList<>();
// need to emit them in reverse order (they are kept in LIFO)
for (ProcedureData pp : supers.values())
{
superProcs.add(0, pp.phandle);
}
return superProcs;
}
/**
* Get a handle list of super-procedures for the session handle.
*
* @return See above.
*/
List<handle> getSuperProcedures()
{
return getSuperProcedures(null);
}
}
/**
* Container for a PUBLISH task to be executed in the actual subscription's context.
*/
private static class PublishTask
{
/** The subscription on which {@link Subscription#session context } the task is executed. */
private final Subscription subscription;
/** The PUBLISH task. */
private final Runnable task;
/**
* Create a new task.
*
* @param subscription
* The subscription.
* @param task
* The PUBLISH task.
*/
public PublishTask(Subscription subscription, Runnable task)
{
this.subscription = subscription;
this.task = task;
}
}
/**
* Helper class to manage the global subscriptions.
*/
private static class GlobalEventSubscriptions
{
/** The global list of subscriptions. */
private final Map<Session, List<Subscription>> subscriptions = new HashMap<>();
/** Map of session listener for each subscription. */
private final Map<Subscription, SessionListener> listeners = new HashMap<>();
/** Tasks for PUBLISH are posted here. */
private Queue<PublishTask> queue = new LinkedList<>();
/** Flag indicating the listening loop was terminated. */
private boolean terminated = false;
/**
* Create a dedicated thread, in the server's context, which will {@link #listen()} for
* PUBLISH tasks.
*/
public GlobalEventSubscriptions()
{
String jobName = "global-publisher";
Runnable launchJob = () ->
{
new AssociatedThread(new Runnable()
{
@Override
public void run()
{
listen();
}
})
{
{
this.setName("Global Publisher Worker");
this.setDaemon(true);
};
}.start();
};
Job job = new Job(jobName, launchJob, true, JobMode.NOW);
if (!Scheduler.addJob(job))
{
throw new IllegalStateException(
"A job with for " + jobName + " is already configured!");
}
}
/**
* Listen for incoming {@link PublishTask tasks}, on the {@link #queue}.
* <p>
* Once a task is received, an {@link AssociatedThread} will be created and will execute the
* task in the {@link Subscription#session subscription's session}, using
* {@link SessionManager#executeInContext}.
*/
private void listen()
{
SessionManager sm = SessionManager.get();
ArrayList<PublishTask> tasks = new ArrayList<>();
while (true)
{
tasks.clear();
synchronized (queue)
{
if (queue.isEmpty())
{
try
{
queue.wait();
}
catch (InterruptedException e)
{
LOG.log(Level.INFO, "global publish event listening loop interrupted");
// terminate the loop
terminated = true;
break;
}
}
tasks.addAll(queue);
queue.clear();
}
for (int i = 0; i < tasks.size(); i++)
{
PublishTask task = tasks.get(i);
Runnable ptask = () ->
{
Thread t = Thread.currentThread();
try
{
sm.registerAsyncThread(t);
task.task.run();
}
finally
{
sm.deregisterAsyncThread(t);
}
};
try
{
sm.executeInContext(task.subscription.session,
() -> new AssociatedThread(ptask).start());
}
catch (RestrictedUseException e)
{
String msg = "Not allowed to use SessionManager.executeInContext";
throw new IllegalStateException(msg, e);
}
}
}
}
/**
* Post the given task to the {@link #queue}.
*
* @param task
* The task for a PUBLISH event.
*/
public void post(PublishTask task)
{
synchronized (queue)
{
if (terminated)
{
LOG.log(Level.INFO,
"global publish event listening loop is terminated - could not post event");
return;
}
queue.offer(task);
queue.notify();
}
}
/**
* Get all the global subscriptions, for all contexts.
*
* @return See above.
*/
public synchronized List<Subscription> getAllSubscriptions()
{
if (subscriptions.isEmpty())
{
return Collections.EMPTY_LIST;
}
List<Subscription> subs = new LinkedList<>();
subscriptions.values().forEach(l -> subs.addAll(l));
return subs;
}
/**
* Remove all the global subscriptions for the given resource. This will remove them only
* from the current context's global subscriptions.
*
* @param resource
* The resource which will be matched against the {@link Subscription#subscriber}.
*/
public synchronized void removeAll(Object resource)
{
if (subscriptions.isEmpty())
{
return;
}
Session session = SessionManager.get().getSession();
List<Subscription> subs = subscriptions.get(session);
if (subs == null)
{
return;
}
Iterator<Subscription> iter = subs.iterator();
while (iter.hasNext())
{
Subscription sub = iter.next();
if (sub.subscriber.equals(resource))
{
session.removeSessionListener(listeners.remove(sub));
iter.remove();
}
}
if (subs.isEmpty())
{
subscriptions.remove(session);
}
}
/**
* Remove the specified subscription from the current context's global subscriptions.
*
* @param subscription
* The subscription which will removed.
*/
public synchronized void removeAll(Subscription subscription)
{
if (subscriptions.isEmpty())
{
return;
}
Session session = SessionManager.get().getSession();
List<Subscription> subs = subscriptions.get(session);
Iterator<Subscription> iter = subs.iterator();
while (iter.hasNext())
{
Subscription sub = iter.next();
if (sub == subscription)
{
session.removeSessionListener(listeners.remove(sub));
iter.remove();
}
}
if (subs.isEmpty())
{
subscriptions.remove(session);
}
}
/**
* Get all the global subscriptions, for this context.
*
* @return See above.
*/
public synchronized List<Subscription> getSubscriptions()
{
if (subscriptions.isEmpty())
{
return Collections.EMPTY_LIST;
}
Session session = SessionManager.get().getSession();
List<Subscription> subs = subscriptions.get(session);
return subs == null ? Collections.EMPTY_LIST
: new ArrayList<>(subscriptions.get(session));
}
/**
* Register the specified {@link Subscription} as global, for this context.
*
* @param subscription
* The subscription to register.
*/
public synchronized void subscribe(Subscription subscription)
{
Session session = SessionManager.get().getSession();
List<Subscription> subs = subscriptions.get(session);
if (subs == null)
{
subs = new ArrayList<>();
subscriptions.put(session, subs);
}
SessionListener listener = new SessionListener()
{
@Override
public void terminate(Session session)
{
synchronized (GlobalEventSubscriptions.this)
{
subscriptions.remove(session);
listeners.remove(subscription);
}
}
@Override
public void initialize(Session session)
{
// no-op
}
};
session.addSessionListener(listener);
listeners.put(subscription, listener);
subs.add(subscription);
}
}
/**
* Stores global data relating to the state of the current context.
*/
private static class WorkArea
implements Initializable,
Scopeable
{
/** The default databases for each java package prefix. */
private Map<String, Set<String>> defaultDbs = null;
/** Helper to use the TM without any context local lookups. */
private TransactionManager.TransactionHelper tm = null;
/** Helper to use the {@link WidgetPool} without any context local lookups. */
private WidgetPool.WidgetPoolHelper wp = null;
/** Helper to use the {@link ErrorManager} without any context local lookups. */
private ErrorManager.ErrorHelper errHelper = null;
/** Helper to use the {@link handle} without any context local lookups. */
private handle.Helper h = null;
/** Helper to use the {@link ObjectOps} without any context local lookups. */
private ObjectOps.ObjectHelper oh = null;
/** The list of all registered persistent procedures, via RUN PERSISTENT calls. */
private FastSiblingDeque<Object> persistentProcedures = new FastSiblingDeque<>(true);
/** A map containing the list of super-procedures, for the session, by each instance. */
private LinkedHashMap<Object, ProcedureData> sessionSuperProcs = new LinkedHashMap<>();
/** This map holds all the singleton instances in this context. */
private IdentityHashMap<Class<?>, Object> singletonMap = new IdentityHashMap<>();
/**
* This map holds the attributes for all registered external procedures, by instance.
* It includes all persistent procedures and all procedures active on the stack.
*/
private IdentityHashMap<Object, ProcedureData> pMap = new IdentityHashMap<>();
/**
* Stack of target-procedures' data. Must be kept in sync with targetProcedures!
* This stack holds procedure data for deleted TARGET-PROCEDURE handles.
*/
private ArrayDeque<ProcedureData> targetProceduresData = new ArrayDeque<>();
/**
* Stack of source-procedures' data. Must be kept in sync with sourceProcedures!
* This stack holds procedure data for deleted SOURCE-PROCEDURE handles.
*/
private ArrayDeque<ProcedureData> sourceProceduresData = new ArrayDeque<>();
/** Stack of this-procedures' data. Must be kept in sync with thisProcedures! */
private ArrayDeque<ProcedureData> thisProceduresData = new ArrayDeque<>();
/**
* Stack with output parameter handlers to be processed on procedure's return. Each scope is created
* using the {@code EMPTY_SCOPE_STACK} immutable object to avoid memory consumption due unused new
* objects. This will be replaced with a true stack at the moment of first usage. We use a deque/stack
* to enforce the order of execution of the cleanup handlers to be in reverse order, the LIFO order
* assuring the scope bracketing inside nested scopes.
*/
private final ArrayDeque<List<Runnable>[]> cleanupHandlers = new ArrayDeque<>();
/** An immutable marker for UNUSED stack of cleanup handlers for a scope. */
private static final LinkedList<Runnable>[] EMPTY_SCOPE_STACK = new LinkedList[0];
/** Explicit referent used as SOURCE-PROCEDURE for a PUBLISH statement. */
private Object publishSourceProcedure = null;
/**
* Stack of internal entry names. Evolves together with the {@link #thisProceduresData} stack.
*/
private ArrayDeque<String> internalEntryStack = new ArrayDeque<>();
/** Resources pending to registered with THIS-PROCEDURE when the next block is entered. */
private LinkedHashSet<WrappedResource> pendingResources = new LinkedHashSet<>();
/** Flag indicating if static resources can be added to {@link #pendingResources}. */
private boolean registerPendingResources = true;
/** Map of relative names (i.e. used at the RUN statement) for each external program. */
private IdentityHashMap<Object, String> relativeNames = new IdentityHashMap<>();
/**
* Indicates the current external program when a scope or finalizable notification occurs.
*/
private Object processedProcedure = null;
/** The resource currently being deleted. */
private WrappedResource pendingResourceDelete = null;
/** A stack containing the info about the next called program or internal entry. */
private ArrayDeque<CalleeInfoImpl> calleeInfo = new ArrayDeque<>();
/** The 4GL program name being currently initiating. */
private String initiatingExtProg = null;
/** The Java class for the current external program or OE class being executed. */
private Class<?> initiatingExtProgClass = null;
/** Map with flags indicating if this procedure should be deleted on exit. */
private IdentityHashMap<Object, Boolean> deleteOnExit = new IdentityHashMap<>();
/** A registry of persistent procedures which were instantiated by a remote side. */
private Set<Object> remotePersistentProcedures = Collections.newSetFromMap(new IdentityHashMap<>());
/** The global list of subscriptions, for this context. */
private LinkedList<Subscription> subscriptions = new LinkedList<>();
/** Per-event subscribes, to avoid iterating all subscriptions when performing a PUBLISH. */
private HashMap<character, LinkedList<Subscription>> subscriptionsByEvent = new HashMap<>();
/**
* This will store the a snapshot of the block stack when the first global buffer was
* used. This is used when a persistent procedure should be deleted: the delete will
* be postponed at a block on this stack.
*/
private IdentityHashMap<Object, Stack<BlockDefinition> > persistentProcStacks = new IdentityHashMap<>();
/**
* A set of persistent procedures which were deleted but are in a state that allow them to be deleted again
*/
public Set<Object> staleProcedures = Collections.newSetFromMap(new IdentityHashMap<>());
/**
* Initialize this instance - read the <code>default-databases</code> configuration.
*/
@Override
public void init()
{
// load the per-package default databases
DirectoryService ds = DirectoryService.getInstance();
if (ds != null && ds.bind())
{
String path = Utils.findDirectoryNodePath(ds, "default-databases", null,
DirScope.SERVER, null);
if (path != null)
{
defaultDbs = new HashMap<>();
String[] pkgs = ds.enumerateNodes(path);
for (String pkg : pkgs)
{
String[] dbs = ds.getNodeStrings(path + "/" + pkg, "values");
defaultDbs.put(pkg, new HashSet<>(Arrays.asList(dbs)));
}
}
ds.unbind();
}
}
/**
* Adds a block stack snapshot to the persistent procedure stacks. This is should be called
* when the stack shows the first time a global buffer in the referent is defined.
*
* @param referent
* The persistent procedure to which the stack is mapped.
* @param stack
* The stack which should represent the current block stack snapshot.
*/
public void addPersistentProcStack(Object referent, Stack<BlockDefinition> stack)
{
persistentProcStacks.put(referent, stack);
}
/**
* Check if a referent is persistent and has a stack attached. If it has, it means that
* it opened a global static buffer at least once.
*
* @param referent
* The persistent procedure for which the checking is done.
*
* @return {@code true} if the referent has a persistent procedure stack attached.
*/
public boolean hasPersistentProcStack(Object referent)
{
return persistentProcStacks.containsKey(referent);
}
/**
* Obtains the stack snapshot of one persistent procedure. The stack represent the block
* stack when the first global buffer was defined inside the referent.
*
* @param referent
* The persistent procedure for which the stack should be retrieved.
*
* @return The block stack snapshot stored for the specified referent.
*
*/
public Stack<BlockDefinition> getPersistentProcStack(Object referent)
{
return persistentProcStacks.get(referent);
}
/**
* Marks the specified referent as stale.
*
* @param referent
* The referent which should be marked as stale.
* @return {@code true} if the provided referent was made stale for the first time.
*/
public boolean addStaleReferent(Object referent)
{
if (persistentProcStacks.containsKey(referent) && !staleProcedures.contains(referent))
{
staleProcedures.add(referent);
return true;
}
return false;
}
/**
* Removes the referent information from the persistent procedure stack mapping.
* This should be done only when the referent is deleted (never be used again).
*
* @param referent
* The persistent procedure for which the entry should be removed.
*/
public void removeStaleProcedure(Object referent)
{
persistentProcStacks.remove(referent);
staleProcedures.remove(referent);
}
/**
* Walk the {@link WorkArea#thisProceduresData THIS-PROCEDURE} stack and apply the search function;
* return the first value given by the search function, once it returns a non-null value.
*
* @param search
* The search function.
*
* @return The first non-null value returned by the search function.
*/
public Object searchInStack(Function<Object, Object> search)
{
Map<Object, Object> searched = new IdentityHashMap<>();
Iterator<ProcedureData> iter = thisProceduresData.iterator();
while (iter.hasNext())
{
Object referent = iter.next().getReferent();
if (searched.containsKey(referent))
{
continue;
}
Object ret = search.apply(referent);
if (ret != null)
{
return ret;
}
searched.put(referent, referent);
}
return null;
}
/**
* Set the name of the 4GL program currently being instantiated, into the
* {@link WorkArea#initiatingExtProg} field.
*
* @param name
* The 4GL external program name.
*/
public void setInstantingExternalProgram(String name)
{
initiatingExtProg = name;
}
/**
* Set the Java class of the currently instantiating external program or OE class.
*
* @param cls
* The Java class for the currently executing code.
*/
public void setInstantingExternalProgramClass(Class<?> cls)
{
initiatingExtProgClass = cls;
}
/**
* Check if an external program was ran persistent by a remote side.
*
* @param referent
* The external program instance.
*
* @return <code>true</code> if the referent is part of the
* {@link WorkArea#remotePersistentProcedures} map.
*/
public boolean isRemotePersistentProcedure(Object referent)
{
return remotePersistentProcedures.contains(referent);
}
/**
* Implementation for the PROGRAM-NAME functions. Resolves the legacy top level block (external
* program, function, internal procedure or trigger), at the given depth. The index starts with
* 0 for the currently executing top-level block.
*
* @param idx
* The stack entry index.
*
* @return The name of the stack entry or {@code null} if outside of stack.
*/
public String getStackEntry(int idx)
{
if (idx < 0 || idx >= thisProceduresData.size()) // quick-out
{
return null;
}
Iterator<ProcedureData> procItr = thisProceduresData.iterator();
Iterator<String> ieItr = internalEntryStack.iterator();
Object referent = null;
String iename = null;
for (int i = 0; i <= idx;)
{
if (!procItr.hasNext())
{
return null;
}
referent = procItr.next().getReferent();
iename = ieItr.next();
// do not include PERSISTENT_TRIGGER_PROCEDURE or THIS_PROCEDURE_OVERRIDE in the legacy
// stack trace
if (!(PERSISTENT_TRIGGER_PROCEDURE.equals(iename) ||
THIS_PROCEDURE_OVERRIDE.equals(iename)))
{
i++;
}
}
String name = relativeNames.get(referent);
return EXTERNAL_PROGRAM.equals(iename) ? name : (iename + " " + name);
}
/**
* Handle scope notifications, to maintain proper source, target, and
* this-procedure references.
*
* @param block
* The explicit block definition which required this notification.
*/
@Override
public void scopeStart(BlockDefinition block)
{
// WARNING: in TransactionManager, this is called only for top-level blocks.
BlockType bt = block.type;
if (block.topLevel)
{
// to minimize object creation but still add a scope in the stack, we use a marker at this moment.
// The 'immutable' EMPTY_SCOPE_STACK marker will be replaced with a true container when the 1st
// handler will be registered
cleanupHandlers.push(EMPTY_SCOPE_STACK);
}
if (bt.equals(BlockType.EXTERNAL_PROC) ||
bt.equals(BlockType.INTERNAL_PROC) ||
bt.equals(BlockType.FUNCTION) ||
bt.equals(BlockType.TRIGGER) ||
bt.equals(BlockType.DATABASETRIGGER))
{
// always push info before pushing new THIS-PROCEDURE
CalleeInfoImpl info = calleeInfo.peek();
info.push();
ProcedureData pdata = pMap.get(info.destination);
// setting INSTANTIATING-PROCEDURE for external procedure block can be done only
// after ProcedureData is created
if (bt.equals(BlockType.EXTERNAL_PROC) && !tm.isRootNestingLevel())
{
// if this is not the entry point, set the INSTANTIATING-PROCEDURE as the current
// THIS-PROCEDURE
pdata.instProc = new WeakReference<>(_thisProcedure(this));
}
pdata.thisCount = pdata.thisCount + 1;
thisProceduresData.push(pdata);
if (pdata.persistent && bt != BlockType.EXTERNAL_PROC && wp.hasUnnamedPool(pdata.referent))
{
wp.scopeStart(block);
}
if (pendingResources.isEmpty())
{
pdata.staticResources.push(Collections.emptySet());
}
else
{
// collect the static resources in the correct scope
pdata.staticResources.push(new LinkedHashSet<>());
// process the newly created resources
for (WrappedResource res : pendingResources)
{
if (res instanceof HandleResource)
{
((HandleResource) res).setInstantiatingProcedure(pdata.phandle);
}
}
Set<WrappedResource> res = pdata.staticResources.peek();
res.addAll(pendingResources);
pendingResources.clear();
}
}
}
/**
* Handle scope notifications, to maintain proper source, target, and
* this-procedure references.
*/
@Override
public void scopeFinished()
{
// WARNING: in TransactionManager, this is called only for top-level blocks.
BlockDefinition block = tm.getBlock();
BlockType bt = block.type;
if (tm.isTopLevelBlock())
{
// execute the cleanup handlers
List<Runnable>[] handlers = cleanupHandlers.pop();
if (handlers != EMPTY_SCOPE_STACK)
{
for (int i = 0; i < handlers.length; i++)
{
List<Runnable> handlerSet = handlers[i];
if (handlerSet != null)
{
for (Runnable handler : handlerSet)
{
handler.run();
}
}
}
}
}
if (bt.equals(BlockType.EXTERNAL_PROC) ||
bt.equals(BlockType.INTERNAL_PROC) ||
bt.equals(BlockType.FUNCTION) ||
bt.equals(BlockType.TRIGGER) ||
bt.equals(BlockType.DATABASETRIGGER))
{
if (tm.isRootNestingLevel() && TransactionImpl.isOpenByTxInitProc())
{
handle txInitProc = TransactionImpl.get().getTxInitProcedure();
if (txInitProc._isValid())
{
ProcedureData pd = locateProcedure(this, txInitProc.get());
TransactionImpl.requestFinished(pd.transactionModeAutoChained, true);
}
}
// pop this-procedure from the stack and cleanup
ProcedureData pdata = thisProceduresData.pop();
pdata.thisCount = pdata.thisCount - 1;
Object referent = pdata.getReferent();
boolean extProc = bt == BlockType.EXTERNAL_PROC;
if (extProc)
{
// mark the external procedure as having finished executing
pdata.executed = true;
}
CalleeInfoImpl info = calleeInfo.peek();
info.pop();
boolean persistent = pdata.persistent;
// check if we have a postponed delete
Boolean delete = deleteOnExit.get(referent);
if (delete != null && delete)
{
// check if this is the last usage of the referent
if (pdata.thisCount > 0)
{
// still used, wait for the last proc on the stack to finish.
delete = null;
}
else
{
// this is no longer a persistent procedure being used, can be deleted
deleteOnExit.remove(referent);
persistentProcedures.remove(referent);
persistent = false;
}
}
if (block.widgetPool)
{
wp.scopeFinished(referent, extProc, persistent);
}
boolean deleted = false;
// notify the widget-pool management a procedure has finished; this needs to be done
// here to ensure that the correct THIS-PROCEDURE handle is available
if (!(extProc && persistent))
{
// bypass this if there was a postponed delete, as the resources were deleted by
// the delete(referent) call; otherwise delete this external, non-persistent,
// procedure
if (extProc && !persistent)
{
deleted = delete(this, referent, true);
// already deleted, nothing else to do
delete = null;
}
else
{
// delete the static resources created in an internal proc/function/trigger
deleteResources(this, pdata.staticResources.pop());
}
}
else
{
cleanup(referent);
}
if (delete != null && delete)
{
String name = getAbsoluteName(referent);
// force the delete of this external procedure, as some internal proc/func/trig
// has called DELETE OBJECT THIS-PROCEDURE
Object saved = tm.getNowIterating();
tm.setNowIterating(null);
try
{
// this is the same approach as in ControlFlowOps.executeDestructor
deleted = delete(this, referent, true);
}
finally
{
tm.setNowIterating(saved);
}
if (LOG.isLoggable(Level.FINE))
{
LOG.log(Level.FINE, "Processing postponed delete for procedure " + name +
" has " + (deleted ? "passed" : "failed"));
}
}
if (deleted)
{
handle.removeResource(new ExternalProgramWrapper(referent));
}
}
}
/**
* Provides a notification that an external scope is about to be deleted.
* <p>
* This implementation is a no-op.
*/
@Override
public void scopeDeleted()
{
// no-op
}
/**
* Get the {@link ScopeId} for the instance.
*
* @return <code>null</code>, as the scope notification is done explicitly via
* {@link TransactionManager}.
*/
@Override
public ScopeId getScopeId()
{
// no ID required, as this is called explicitly in TransactionManager
return null;
}
/**
* Get the map of session's super-procedures.
*/
public Map<Object, ProcedureData> getSessionSuperProcedures()
{
return getSuperProcedures(null, true);
}
/**
* Get the map of the super-procedures for the given referent.
*
* @param referent
* The external program instance for which the super-procedures
* are needed.
* @param readOnly
* Flag indicating that the result will not be altered.
*/
public Map<Object, ProcedureData> getSuperProcedures(Object referent, boolean readOnly)
{
if (referent == null)
{
return sessionSuperProcs;
}
ProcedureData pdata = pMap.get(referent);
if (!readOnly && pdata.superProcedures == Collections.EMPTY_MAP)
{
pdata.superProcedures = new LinkedHashMap<>();
}
return pdata.superProcedures;
}
/**
* Attempt cleanup of the given external program instance. If is no
* longer used as a persistent procedure, super procedure, or as a
* system handle, the referent will be removed from the {@link #pMap}
* map.
*
* @param referent
* The external program instance for which needs to be cleaned
* up.
*/
public void cleanup(Object referent)
{
ProcedureData pd = pMap.get(referent);
// if no longer used, remove from global map
if (pd.thisCount == 0 &&
pd.targetCount == 0 &&
pd.sourceCount == 0 &&
!persistentProcedures.contains(referent) &&
!sessionSuperProcs.containsKey(referent) &&
!superProcForExtProc(referent, persistentProcedures) &&
!superProcForExtProc(referent, sessionSuperProcs.keySet()))
{
forceCleanup(referent);
}
}
/**
* Cleanup of the given external program instance. The referent will be
* removed from the {@link #pMap} map.
*
* @param referent
* The external program instance for which needs to be cleaned
* up.
*/
public void forceCleanup(Object referent)
{
// remove from global map
ProcedureData pd = pMap.remove(referent);
relativeNames.remove(referent);
ProcedureManager.cleanupSubscriptions(referent);
}
/**
* Get the Java class associated with the currently executing code.
*
* @return See above.
*/
private Class<?> getExecuting()
{
Class<?> def = calleeInfo.peek().executing;
if (def == null)
{
Object thisProc = thisProceduresData.peek().getReferent();
if (thisProc.getClass() == Object.class)
{
def = ObjectOps.getStaticClass(thisProc);
}
else
{
def = thisProc.getClass();
}
}
return def;
}
/**
* Get an iterator over the stack of currently executing code.
*
* @return An iterator of Java classes associated with the code executing on the stack.
*/
private Iterator<Class<?>> executingIterator()
{
Iterator<CalleeInfoImpl> iter = calleeInfo.iterator();
return new Iterator<Class<?>>()
{
@Override
public boolean hasNext()
{
return iter.hasNext();
}
@Override
public Class<?> next()
{
return iter.next().executing;
}
};
}
/**
* Get an iterator over the stack of destination resolved code.
*
* @return An iterator of Java classes associated with the code on the stack.
*/
private Iterator<Object> destinationIterator()
{
Iterator<CalleeInfoImpl> iter = calleeInfo.iterator();
return new Iterator<Object>()
{
@Override
public boolean hasNext()
{
return iter.hasNext();
}
@Override
public Object next()
{
return iter.next().getDestination();
}
};
}
/**
* Check if the given referent is a super-procedure of one of the
* procedures in the given list.
*
* @param referent
* The external program instance.
* @param progs
* The list of external programs for which their
* super-procedures list will be searched for the referent.
*
* @return <code>true</code> if it was found as a super-procedure for
* one of the given procedures.
*/
private boolean superProcForExtProc(Object referent, Collection<Object> progs)
{
for (Object prog : progs)
{
ProcedureData pp = pMap.get(prog);
if (pp.superProcedures.isEmpty())
{
continue;
}
for (Object sreferent : pp.superProcedures.keySet())
{
if (sreferent == referent)
{
return true;
}
}
}
return false;
}
}
/**
* Contains data about a subscription.
*/
private static abstract class Subscription<T>
{
/** The client session which registered this subscription. */
private final Session session = SessionManager.get().getSession();
/** The subscriber. */
protected final T subscriber;
/** The subscribed event. */
protected final character event = new character();
/** The publisher to match. <code>null</code> represents any source. */
protected final Object publisher;
/** Flag indicating that this subscription can't be removed. */
protected boolean readOnly = false;
/** When specified, it references exactly what needs to be invoked. */
protected InternalEntry signature = null;
/**
* Build a new subscription with the specified data.
*
* @param subscriber
* The subscriber.
* @param event
* The event to subscribe.
* @param publisher
* The event's publisher. May be <code>null</code>.
*/
public Subscription(T subscriber, character event, Object publisher)
{
this.subscriber = subscriber;
this.publisher = publisher;
this.event.assign(event);
}
/**
* Execute a PUBLISH request, by posting the event to the actual {@link #session context}.
*
* @param modes
* The parameter modes.
* @param params
* The PUBLISH parameters.
*/
public abstract void publishRemote(String modes, Object[] params);
/**
* Execute a PUBLISH request, in this context.
*
* @param publisher
* The publisher which posted the PUBLISH event.
* @param errors
* A list to collect any errors during PUBLISH.
* @param modes
* The parameter modes.
* @param params
* The PUBLISH parameters.
*/
public abstract void publish(Object publisher,
List<String> errors,
String modes,
Object[] params);
}
/**
* Contains data about a subscription from an {@link ExternalResource}.
*/
private static class ExternalSubscription
extends Subscription<ExternalResource>
{
/**
* Build a new subscription with the specified data.
*
* @param subscriber
* The subscriber.
* @param event
* The event to subscribe.
* @param publisher
* The event's publisher. May be <code>null</code>.
*/
public ExternalSubscription(ExternalResource subscriber, character event, Object publisher)
{
super(subscriber, event, publisher);
}
/**
* Execute a PUBLISH request, by posting the event to the actual {@link #session context}.
*
* @param modes
* The parameter modes.
* @param params
* The PUBLISH parameters.
*/
@Override
public void publishRemote(String modes, Object[] params)
{
publishWorker(params, true);
}
/**
* Execute a PUBLISH request, in this context.
*
* @param publisher
* The publisher which posted the PUBLISH event.
* @param errors
* A list to collect any errors during PUBLISH.
* @param modes
* The parameter modes.
* @param params
* The PUBLISH parameters.
*/
@Override
public void publish(Object publisher, List<String> errors, String modes, Object[] params)
{
publishWorker(params, false);
}
/**
* Worker to perform PUBLISH to a remote side.
*
* @param params
* The PUBLISH parameters.
* @param delayed
* Flag indicating if the PUBLISH is executed immediately or to be posted to the
* client's event queue, when <code>false</code>.
*/
private void publishWorker(Object[] params, boolean delayed)
{
// stringify all params...
String[] sparams = new String[params.length];
for (int i = 0; i < params.length; i++)
{
Object p = params[i];
if (p instanceof BaseDataType)
{
sparams[i] = ((BaseDataType) p).toStringMessage();
}
else
{
sparams[i] = (p == null ? null : p.toString());
}
}
LogicalTerminal.publish(this.subscriber, this.event.toStringMessage(), sparams, delayed);
}
}
/**
* Contains data about a legacy subscription made by a certain procedure.
*/
private static class LegacySubscription
extends Subscription<Object>
{
/** The method to be invoked. May be <code>null</code>. */
private final String method;
/**
* Build a new subscription with the specified data.
*
* @param subscriber
* The subscriber procedure.
* @param event
* The event to subscribe.
* @param publisher
* The event's publisher. May be <code>null</code>.
* @param method
* The method to be invoked. When <code>null</code>, the event name will be used
* as the method name.
*/
public LegacySubscription(Object subscriber, character event, Object publisher, character method)
{
super(subscriber, event, publisher);
this.method = method == null ? event.getValue() : method.getValue();
}
/**
* Execute a PUBLISH request, by posting the event to the actual {@link #session context}.
*
* @param modes
* The parameter modes.
* @param params
* The PUBLISH parameters.
*/
@Override
public void publishRemote(String modes, Object[] params)
{
ServerEvent evt = new ServerEvent(-1,
"PUBLISH",
() -> publish(null, new ArrayList<>(), modes, params));
LogicalTerminal.postServerEvent(evt);
}
/**
* Execute a PUBLISH request, in this context.
*
* @param publisher
* The publisher which posted the PUBLISH event.
* @param errors
* A list to collect any errors during PUBLISH.
* @param modes
* The parameter modes.
* @param params
* The PUBLISH parameters.
*/
@Override
public void publish(Object publisher, List<String> errors, String modes, Object[] params)
{
WorkArea wa = work.obtain();
ProcedureData pd = wa.pMap.get(this.subscriber);
// business logic can unsubscribe during this loop
if (pd == null)
{
return;
}
String relName = getRelativeName(wa, pd.phandle.get());
// invoke the associated procedure. there are some rules here:
// - changes of INPUT-OUTPUT parameters are inherited from one call to another
// - OUTPUT parameters are always overridden
String iename = (this.method == null ? event.toStringMessage() : this.method);
if (signature != null)
{
if (ObjectOps.isLegacyObject(subscriber))
{
ControlFlowOps.invokeLegacyMethod(signature, true, subscriber,
relName, iename,
modes, params);
}
return;
}
// check if current procedure (or its SUPER) has a definition for target
handle phandle = ControlFlowOps.resolveProgramForProcedure(pd.phandle, iename);
if (phandle == null)
{
final String msg =
"PUBLISH statement failed. Procedure %s has no entry point for %s";
// postpone error reporting until all the invocations are done,
// we want to prevent setting of deferred error as this would
// produce an invalid handle in the call to handle.ctor below
errors.add(String.format(msg, relName, iename));
return;
}
// for the duration of this call, the SOURCE-PROCEDURE must be assumed as the
// publisher.
// the received publisher argument may be null, if the PUBLISH originates from a remote
// side. in this case, THIS-PROCEDURE will remain unchanged.
wa.publishSourceProcedure = publisher;
// backup the parameters
Object[] saveParams = params == null || modes == null ? null : new Object[params.length];
if (saveParams != null)
{
for (int i = 0; i < params.length; i++)
{
saveParams[i] = params[i];
}
}
try
{
// even if silent error mode is enabled by default, when the procedure is
// executed it will be disabled for the duration of the execution and re-enabled
// when the procedure ends. TransactionManager.push/popScope takes care of this.
ControlFlowOps.invokeInWithMode(iename, pd.phandle, modes, params);
}
finally
{
// ensure it remains null, in case for any reason the internal proc is not
// executed
wa.publishSourceProcedure = null;
if (saveParams != null)
{
for (int i = 0; i < saveParams.length; i++)
{
if (saveParams[i] != params[i] &&
saveParams[i] instanceof BaseDataType &&
modes.charAt(i) != 'I')
{
((BaseDataType) saveParams[i]).assign(((BaseDataType) params[i]));
params[i] = saveParams[i];
}
}
}
}
}
}
/**
* A container with various data about the next called program/internal-entry, needed by
* {@link WorkArea#scopeStart}.
*/
private static class CalleeInfoImpl
implements CalleeInfo
{
/** Flag indicating if the destination is in a super-procedure. */
private final boolean superCall;
/** The target procedure, which was targeted by the caller. */
private final Object target;
/** The procedure where the internal entry was resolved. */
private final Object destination;
/**
* The internal entry name. In case of triggers or external programs, use the
* {@link #EXTERNAL_PROGRAM} and {@link #TRIGGER_PROCEDURE} constants.
*/
private final String iename;
/** The currently executing OE class or external program. */
private final Class<?> executing;
/**
* The external program name; will be non-null only when we are invoking an external
* program.
*/
private final String pname;
/** Flag indicating we are calling a user-defined function. */
private final boolean function;
/** Flag indicating a dynamic call. */
private final boolean dynamicCall;
/** Flag indicating we are calling a method. */
private final boolean method;
/** Flag indicating the external program was ran persistent. */
private final boolean persistentProc;
/** Flag indicating the external program was ran as singleton. */
private final boolean singletonProc;
/** Flag indicating the external program was ran as single-run. */
private final boolean singleRunProc;
/** Access to context-local data. */
private final WorkArea wa;
/** The already known legacy name for the internal entry. */
private String legacyName;
/**
* Create a new instance.
*
* @param wa
* Access to context-local data.
* @param def
* The currently executing OE class or external program.
* @param superCall
* Flag indicating if the destination is in a super-procedure.
* @param target
* The target procedure, which was targeted by the caller.
* @param destination
* The procedure where the internal entry was resolved.
* @param iename
* The internal entry name. In case of triggers or external programs, use the
* {@link #EXTERNAL_PROGRAM} and {@link #TRIGGER_PROCEDURE} constants.
* @param pname
* The external program name; will be non-null only when we are invoking an external
* program.
* @param function
* Flag indicating we are calling a user-defined function.
* @param persistentProc
* Flag indicating the external program was ran persistent.
* @param singletonProc
* Flag indicating the external program was ran singleton.
* @param singleRunProc
* Flag indicating the external program was ran single-run.
* @param dynamicCall
* Flag indicating a dynamic call
*/
public CalleeInfoImpl(WorkArea wa,
Class<?> def,
boolean superCall,
Object target,
Object destination,
String iename,
String pname,
boolean function,
boolean persistentProc,
boolean singletonProc,
boolean singleRunProc,
boolean dynamicCall)
{
this.wa = wa;
this.executing = def;
this.superCall = superCall;
this.target = target;
this.destination = destination;
this.iename = iename;
this.pname = pname;
this.function = function;
this.method = (function && ObjectOps.isLegacyObject(destination));
this.persistentProc = persistentProc;
this.singletonProc = singletonProc;
this.singleRunProc = singleRunProc;
this.dynamicCall = dynamicCall;
}
/**
* Set the {@link #legacyName} so that {@link ProcedureManager#pushInternalEntry} will avoid resolve
* it via {@link SourceNameMapper}, as that is expensive. This is done only in cases where the
* internal entry's legacy name is previously resolved.
*
* @param legacyName
* The resolved internal entry's legacy name.
*/
@Override
public void setLegacyName(String legacyName)
{
this.legacyName = legacyName;
}
/**
* Called by {@link WorkArea#scopeStart} - depending on the callee's configuration, it
* populates the procedure system handles and maintains persistent procedures.
*/
public void push()
{
if (pname != null && !wa.pMap.containsKey(target))
{
// this is an external procedure invocation...
handle htarget = new handle(new ExternalProgramWrapper(target));
ProcedureManager.addProcedure(wa, htarget, pname,
persistentProc, singletonProc, singleRunProc, false);
}
if (!superCall)
{
if (!wa.thisProceduresData.isEmpty())
{
ProcedureData pd;
if (wa.publishSourceProcedure != null)
{
pd = wa.pMap.get(wa.publishSourceProcedure);
wa.publishSourceProcedure = null;
}
else
{
pd = wa.thisProceduresData.peek();
}
pd.sourceCount = pd.sourceCount + 1;
wa.sourceProceduresData.push(pd);
}
ProcedureData pd = wa.pMap.get(target);
pd.targetCount = pd.targetCount + 1;
wa.targetProceduresData.push(pd);
}
pushInternalEntry(wa, destination, iename, function, legacyName);
}
/**
* Called by {@link WorkArea#scopeFinished()} - depending on the callee's configuration, it
* backs out the data pushed by {@link #push()}, so the state will be the same as before
* the callee as invoked.
*/
public void pop()
{
wa.internalEntryStack.pop();
if (!superCall)
{
if (!wa.thisProceduresData.isEmpty())
{
ProcedureData pd = wa.sourceProceduresData.pop();
pd.sourceCount = pd.sourceCount - 1;
}
ProcedureData pd = wa.targetProceduresData.pop();
pd.targetCount = pd.targetCount - 1;
}
}
/**
* The internal entry name. In case of triggers or external programs, returns the
* {@link ProcedureManager#TRIGGER_PROCEDURE} and {@link ProcedureManager#EXTERNAL_PROGRAM}
* constants respectively.
*
* @return The internal entry name.
*/
@Override
public String getInternalEntryName()
{
return iename;
}
/**
* The procedure where the internal entry was resolved.
*
* @return A valid instance.
*/
@Override
public Object getDestination()
{
return destination;
}
/**
* The external program name; will be non-null only when we are invoking an external
* program.
*
* @return A {@link String} representing the program name.
*/
@Override
public String getExternalProgramName()
{
return pname;
}
/**
* Flag indicating a user-defined function is called.
*
* @return A boolean flag.
*/
@Override
public boolean isFunction()
{
return function;
}
/**
* Flag indicating a method is called.
*
* @return A boolean flag.
*/
@Override
public boolean isMethod()
{
return method;
}
/**
* Flag indicating the call is dynamic.
*
* @return A boolean flag.
*/
@Override
public boolean isDynamicCall()
{
return dynamicCall;
}
}
/**
* Container for various procedure data.
*/
static class ProcedureData
{
/** The set of implicitly connected databases for this procedure. */
private Set<String> connectedDbs = null;
/**
* Flag indicating that a tx needs to be automatically opened after any pending rollback
* or commit (the TRANSACTION-MODE AUTOMATIC CHAINED language statement is in effect
* for this procedure.
*/
private boolean transactionModeAutoChained = false;
/** The list of super-procedures for this procedure, by external program instance. s*/
private Map<Object, ProcedureData> superProcedures = Collections.emptyMap();
/**
* The map of name of functions defined IN handle in this external program, to their
* {@link Resolvable} instance.
*/
private Map<String, Resolvable> inHandleFunctions = Collections.emptyMap();
/**
* A handle instance for this procedure, so that other handles can be instantiated from it.
*/
private final handle phandle;
/** The actual referent for this procedure. */
private final Object referent;
/** The current search-mode for this external program. */
private SearchMode searchMode = null;
/** The private data of this procedure handle. */
private String privateData = null;
/** Implementation of ADM-DATA attribute. */
private String admData = null;
/** Implementation of UNIQUE-ID attribute. */
private Long uniqueID = null;
/** Handle referencing the instantiating procedure. */
private WeakReference<Object> instProc = null;
/**
* Set of {@link Scopeable} instances which need notification when the persistent procedure
* gets deleted.
*/
private Set<Scopeable> scopeables = Collections.emptySet();
/**
* Set of {@link Finalizable} instances which need notification when the persistent procedure
* gets deleted.
*/
private Set<Finalizable> finalizables = Collections.emptySet();
/**
* A stack holding the resources created in each top-level block.
*/
private Deque<Set<WrappedResource>> staticResources = new ArrayDeque<>();
/**
* A value of CURRENT-WINDOW attribute of THIS-PROCEDURE handle
*/
private WrappedResource currentWindow = null;
/** A set of all created (and still alive) resources, in this procedure (dynamic or not). */
private Map<HandleResource, Object> createdResources = new WeakHashMap<>();
/** Flag indicating this procedure has ROUTINE-LEVEL UNDO, THROW. */
private boolean routineLevelUndoThrow = false;
/** Flag indicating this procedure has BLOCK-LEVEL UNDO, THROW. */
private boolean blockLevelUndoThrow = false;
/** Flag indicating if the program was ran PERSISTENT. */
private boolean persistent;
/** Flag indicating if the program was ran SINGLE-RUN. */
private boolean singleRun;
/** Flag indicating if the program was ran SINGLETON. */
private boolean singleton;
/** Flag indicating that the program has finished executing. */
private boolean executed = false;
/** Flag indicating if this procedure was added as a session super-procedure. */
private boolean isSessionSuperProcedure = false;
/** The set of procedures to which it was added as a super-procedure. */
private Set<Object> addedToSuperProcedures = Collections.emptySet();
/** The name of this program or class. */
private final String name;
/** Counts the number of times this {@link #referent} appears on current stack as SOURCE-PROCEDURE. */
private int sourceCount = 0;
/** Counts the number of times this {@link #referent} appears on current stack as THIS-PROCEDURE. */
private int thisCount = 0;
/** Counts the number of times this {@link #referent} appears on current stack as TARGET-PROCEDURE. */
private int targetCount = 0;
/**
* Create a new {@link ProcedureData} instance using the given
* procedure handle and search-mode.
*
* @param phandle
* The procedure handle.
* @param searchMode
* The search-mode. May be null.
* @param name
* The name if this program or class.
*/
ProcedureData(handle phandle, SearchMode searchMode, String name)
{
this.phandle = new handle(phandle);
this.referent = this.phandle.get();
this.searchMode = searchMode;
this.name = name;
}
/**
* Get the {@link #persistent} flag.
*
* @return See above.
*/
boolean isPersistent()
{
return persistent;
}
/**
* Get the {@link #referent}.
*
* @return See above.
*/
Object getReferent()
{
return referent;
}
/**
* Check if the search-mode for this external program is SEARCH-SELF.
*
* @return See above.
*/
boolean isSearchSelf()
{
return searchMode == null || searchMode == SearchMode.SEARCH_SELF;
}
}
/**
* 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_5}.
*/
@Override
public WeightFactor getWeight()
{
return WeightFactor.LEVEL_5;
}
/**
* Retrieve the {@link WorkArea} instance. Cross-dependencies will be initialized only after the
* context-local instance is set.
*
* @return The {@link WorkArea} instance.
*/
public WorkArea obtain()
{
WorkArea wa = get(false);
if (wa == null)
{
wa = new WorkArea();
wa.init();
set(wa);
wa.tm = TransactionManager.getTransactionHelper();
wa.wp = WidgetPool.getHelper();
wa.h = handle.getHelper();
wa.oh = ObjectOps.getObjectHelper();
wa.errHelper = ErrorManager.getErrorHelper();
}
return wa;
}
/**
* Initializes the work area, the first time it is requested within a
* new context. Registers the {@link WorkArea} instance with the
* {@link TransactionManager}, for scope notifications.
*
* @return The newly instantiated work area.
*/
protected synchronized WorkArea initialValue()
{
return new WorkArea();
}
}
}