WidgetPool.java
/*
** Module : WidgetPool.java
** Abstract : widget pools management functions
**
** Copyright (c) 2010-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- ----------------------------------Description---------------------------------
** 001 SVL 20101004 Created initial version with no implementation.
** 002 CA 20130131 Moved to util package (as other non-widget resources can be pooled). Added
** DELETE WIDGET-POOL support.
** 003 CA 20130918 Added runtime support for widget-pools.
** 004 CA 20140228 Delete only valid resources. Fixed merging of unnamed persistent pools to
** previous scope.
** 005 OM 20141117 Fixed widget-pool management for persistent procedures.
** 006 CA 20150429 Fixed a mem-leak in deleteWidgetPools.
** 007 EVL 20160224 Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
** 008 ECF 20171122 Minor optimizations.
** 009 CA 20190313 The persistent program's unnamed pools must be saved and pushed on the stack
** whenever one of its internal entries get executed.
** CA 20190325 Implemented ProcedureHelper instead of direct usage of the static API.
** This allows the elimination of context local usage in ProcedureManager.
** CA 20190326 Refactored to reduce the context-local usage.
** 010 CA 20190811 Optimized deleteNonPersistentPools, when there are no IDs in the set.
** 011 CA 20200505 Set the ProcedureManager and TransactionManager helper only for runtime.
** 012 CA 20201117 Fixed a leak with unnamedProcPools, which was not removing deleted referents.
** CA 20210310 Small performance improvement when processing unnamed/named pools on scope finish (avoid
** the 'add' overhead if they are empty).
** 013 CA 20220120 Performance improvement for scope processing - don't build new collections until they are
** needed.
** CA 20220201 A small performance improvement in scopeFinished.
** CA 20220409 Fixed a bug when invoking a procedure in an external program: upon exit of that internal
** procedure, the external program's unnamed pools must survive (until it gets deleted).
** CA 20221010 Performance improvements - avoid the ProcedureData lookup, by keeping a parallel stack of
** this data for THIS-PROCEDURE. Refs #6826
** CA 20230110 Keep a single 'ids' in WorkArea which will be used in 'scopeFinished', to avoid creating
** a new instance on each scope finished call.
** CA 20230110 Moved some other APIs to WidgetPoolHelper, to reduce ContextLocal.get calls.
** 014 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
** 015 CA 20230724 Further reduce context-local usage.
** 016 CA 20230817 HandleOps.delete executing for a DELETE WIDGET-POOL is not considered an explicit DELETE
** OBJECT (which would prevent deleting a temp-table/buffer part of a dataset).
** 017 CA 20231031 Added 'BlockDefinition' parameter to 'scopeStart'.
** 018 CA 20231216 Small performance improvement.
** CA 20231221 Refactored the WidgetPool scopeable support to register for processing as needed.
** 019 CA 20231216 Fixed regression related to widget pools used in OO (the issue was with the implicit
** widget pool for a class with USE-WIDGET-POOL, for which the instance gets deleted
** implicitly when a block finishes).
** 020 CA 20240105 Fixed regression in H019 - running an internal entry in a persistent procedure which
** has an unnamed pool must push WidgetPool scope for this internal entry.
** 021 DDF 20240222 Avoid NPE since the head of the wa.unnamedPoolScopes might be null.
** 022 ES 20250109 Changed resources in WidgetPoolData from HashSet to LinkedHashSet.
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
**
** Additional terms under GNU Affero GPL version 3 section 7:
**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
**
** 0. Attribution Requirement.
**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
**
** 1. No License To Use Trademarks.
**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
**
** 2. No Misrepresentation of Affiliation.
**
** You may not represent yourself as Golden Code Development Corporation or FWD.
**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
**
** 3. No Misrepresentation of Source or Origin.
**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
** Corporation or FWD. All modified versions must contain the notices of
** attribution required in this license.
*/
package com.goldencode.p2j.util;
import java.util.*;
import java.util.logging.*;
import com.goldencode.p2j.security.*;
import com.goldencode.p2j.util.ObjectOps.ObjectHelper;
import com.goldencode.p2j.util.ProcedureManager.ProcedureData;
import com.goldencode.p2j.util.logging.*;
/**
* Widget-pool management class.
* <p>
* A widget pool is a a mechanism to explicitly define the scope at which resources get an
* "implicit" DELETE OBJECT when the control flow causes the program to exit that particular
* scope. At its most basic, a widget pool is just a list of resources that get deleted, an
* optional name, an optional persistent flag and an association with a block scope that exists at
* a "top-level" block (external proc, internal proc, user-defined function and trigger).
* A block scope will only open and close at top-level blocks (external program, procedure,
* function or trigger). Inner blocks don't cause any scoping behavior.
* <p>
* All pools are context-local, as they are private to the user's session. No session-level
* widget-pool cleanup is implemented at this time. The current implementation of all the
* poolable resources does not require any session-level cleanup processing that is driven by
* widget pools. Should the implementation of any one of the poolable resources be modified to
* be dependent upon session-level widget-pool cleanup, then this feature will have to be added.
* <p>
* A pool will contain only resources which are dynamically created (and must be removed using
* a DELETE statement), and only a sub-set of them can be pooled. From the list of CREATE
* statements, only the following statements accept a named pool:
* <ul>
* <li>CREATE BROWSE</li>
* <li>CREATE BUFFER</li>
* <li>CREATE CALL</li>
* <li>CREATE DATASET</li>
* <li>CREATE DATA-SOURCE</li>
* <li>CREATE QUERY</li>
* <li>CREATE SAX-ATTRIBUTES</li>
* <li>CREATE SAX-READER</li>
* <li>CREATE SAX-WRITER</li>
* <li>CREATE SOAP-HEADER</li>
* <li>CREATE SOAP-HEADER-ENTRYREF</li>
* <li>CREATE TEMP-TABLE handle</li>
* <li><pre>CREATE { BUTTON | COMBO-BOX | CONTROL-FRAME | DIALOG-BOX |
* EDITOR | FILL-IN | FRAME | IMAGE |
* MENU-ITEM | RADIO-SET | RECTANGLE | SELECTION-LIST |
* MENU | SLIDER | SUB-MENU | TEXT |
* TOGGLE-BOX | WINDOW | VALUE ( string-expression )
* }</pre></li>
* <li>CREATE X-DOCUMENT</li>
* <li>CREATE X-NODEREF</li>
* </ul>
* Even 4GL has a default persistent unnamed pool associated with the user's session (and always
* stands on the bottom of the stack of unnamed pools), we do not implement this. For P2J, if
* there is no explicitly created unnamed pool, it means the unnamed persistent pool is in effect,
* and we will not add the resources to it. As 4GL doesn't offer any mechanism of accessing a
* pool's resources, the pool for a resource or the pool stack, plus the resources already exist
* at most for the duration of the user's session, adding them to this pool will add unnecessary
* overhead in P2J. Thus, in P2J a resource may be:
* <ul>
* <li>unpooled (no pool will contain it), if no unnamed pool exists when the CREATE statement was
* executed, as there is no default unnamed pool. This is the equivalent of "part of the
* default unnamed persistent pool" in 4GL.</li>
* <li>part of only one pool (named if IN WIDGET-POOL clause is present or unnamed, if no
* IN WIDGET-POOL clause is present and there is an unnamed pool).</li>
* </ul>
* <p>
* The following rules describe how named and unnamed pools are implemented:
* <p>
* <h3>Unnamed Pools</h3>
* Each created unnamed pool (non-persistent) is active for the current scope. In the current
* scope, unnamed pools are "stacked". A chain of CREATE WIDGET-POOL statements will create a set
* of unnamed pools and they will be deleted in the reversed order of the creation, in LIFO order:
* <pre>
* 1: DEF VAR h1 AS HANDLE.
* 2: DEF VAR h2 AS HANDLE.
*
* 3: CREATE WIDGET-POOL. // U1
* 4: CREATE BUTTON h1.
*
* 5: CREATE WIDGET-POOL. // U2
* 6: CREATE BUTTION h2.
*
* 7: DELETE WIDGET-POOL. // U2
* 8: DELETE WIDGET-POOL. // U1
* </pre>
* At step 3 and 5, an unnamed pool is created, identified by U1 and U2. At step 4, the resource
* referenced by handle <code>h1</code> (R1) is added to unnamed pool U1 and at step 6, the
* resource referenced by handle <code>h2</code> (R2) is added to unnamed pool U2 (the last created
* one). Step 7 will delete unnamed pool U2 (and resource R2), while step 8 will delete unnamed
* pool U1 (and resource R1).
* <p>
* Once a persistent unnamed pool is created, a DELETE WIDGET-POOL statement will not be able to
* delete it, if the persistent pool is on top of the stack. For this reason, a
* CREATE WIDGET-POOL PERSISTENT statement will do nothing if the unnamed pool at the top of the
* stack is persistent too.
* <p>
* When the current scope ends, all non-persistent unnamed pools will be deleted. But, if there
* is an unnamed persistent pool on the top of the stack for scope S2, this one will be "leaked"
* to the previous scope, S1. The reason for this is that if scope S1 has an active unnamed
* non-persistent pool, then this pool must be deleted when S1 ends. There are no restriction
* for S1 to already have its own persistent unnamed pool. Thus, when S2 ends and leaks the top
* persistent unnamed pool to S1, will result in S1 to have more than one persistent pool. But
* this will not alter how persistent pools behave for S1: non-persistent unnamed pools can still
* be created on top of the persistent unnamed pool, and unnamed pool deletion will stop when the
* first persistent unnamed pool is back on the top of the stack.
* <pre>
* 1: CREATE WIDGET-POOL. // U1
*
* 2: PROCEDURE proc0.
* 3: CREATE WIDGET-POOL PERSISTENT. // U2
* 4: // create resource R1
* 5: DELETE WIDGET-POOL.
* 6: END.
*
* 7: RUN proc0.
* 8: DELETE WIDGET-POOL.
* 9: CREATE WIDGET-POOL. // U3
* 10: // create resource R2
* 11: DELETE WIDGET-POOL.
* 12: DELETE WIDGET-POOL.
*
* 13: CREATE WIDGET-POOL. // U4
* 14: // create resource R3
*
* 15: CREATE WIDGET-POOL PERSISTENT. // U5
* 16: // create resource R4
* 17: DELETE WIDGET-POOL.
* </pre>
* <ul>
* <li>step 1 creates an unnamed widget pool U1. The stack of unnamed pools for scope of the
* external program (S1) contains only U1.</li>
* <li>step 7 executes <code>proc0</code> which creates unnamed pool U2. On step 5, the active
* pool contains resource R1, but will not be deleted. After <code>proc0</code> ends, pool U2
* is placed on top of the stack for scope S1, and the pool stack looks like [ U2 -> U1 ].
* Resource R2 will not be deleted.</li>
* <li>step 8 will try to delete the top of the stack, but U2 is persistent, so will be a no-op.
* </li>
* <li>step 9 creats a new unnamed pool in scope S1, U3, and puts it on the top of the unnamed
* pool stack for scope S1. The stack for S1 will look like [ U3 -> U2 -> U1 ]. </li>
* <li>step 10 will create resource R2 and place it in unnamed pool U3 (as this is on top of the
* stack for scope S1).</li>
* <li>step 11 will delete the unnamed pool on the top of the stack for scope S1, which is U3 and
* is non-persistent. This will delete resource R2 too.</li>
* <li>step 12 will try to delete the unnamed pool on the top of the stack for scope S1, which
* now is U2. But U2 is persistent, thus will not be deleted.</li>
* <li>step 13 will create a new unnamed pool, U4, and place it on the top of stack for S1. The
* stack of unnamed pools for S1 will look like: [ U4 -> U2 -> U1 ]</li>
* <li>step 14 will create resource R3 and place it in pool U4 (top of stack for S1).</li>
* <li>step 15 will create a new persistent unnamed pool, U5, for scope S1. The stack for S1 will
* look like: [ U5 -> U4 -> U2 -> U1 ].</li>
* <li>step 16 will create resource R4 and place it in pool U5 (top of stack for S1).</li>
* <li>step 17 will try to delete the top unnamed pool for S1, U5, but this is persistent, so it
* will not be deleted. After this, pools U4, U2 and U1 remain hidden. When S1 ends, only the
* non-persistent unnamed pools (U1 and U4) will be deleted. Pools U2 and U5 will survive, and
* pool U5 will be placed on the top of the stack for the previous scope, U0.</li>
* </ul>
* <p>
* This unnamed pool property which doesn't allow a persistent unnamed pool to be deleted will
* force it to exist for the duration of the user's session.
*
* <h3>Named Pools</h3>
* At a certain time, no more than one named pool may exist with a given name. When a scope ends,
* all non-persistent named pools will be deleted. A persistent pool will survive until someone
* calls a DELETE WIDGET-POOL statement for that persistent pool; this means a persistent pool
* will survive even after the external procedure which created it is finished or gets deleted.
*
* <h3>Resource management</h3>
* When a resource (which can be pooled) is created, its IN WIDGET-POOL clause (if present) is
* validated first. If a named pool is specified and it can't be found/isn't valid, then the
* resource will not be created.
* <p>
* Adding a resource to an unnamed pool will search for the first active unnamed pool, either in
* current scope or any previous scope. Once a resource is added to a named or unnamed pool,
* it will survive until that pool gets deleted (or the resource is deleted).
* <p>
* When an external procedure is ran persistent, all resources will survive until the procedure is
* deleted; when the procedure is deleted, it will delete all the resources in the non-persistent
* pools.
*/
public class WidgetPool
{
/** Logger */
private static final CentralLogger LOG = CentralLogger.get(WidgetPool.class);
/** A marker instance for an empty dequeu. */
private static final Deque<Integer> EMPTY_DEQUE = new ArrayDeque<>();
/** The widget pools are private per each context. */
private static final ContextLocal<WorkArea> local = new ContextLocal<WorkArea>()
{
protected WorkArea initialValue()
{
return new WorkArea();
};
};
/**
* Delete an unnamed pool.
*/
public static void delete()
{
delete((character) null);
}
/**
* Delete a pool with the given name.
*
* @param name
* The name of the newly created pool. Specify <code>null</code> is the pool is
* unnamed.
*/
public static void delete(String name)
{
delete(name == null ? (character) null : new character(name));
}
/**
* Delete a pool with the given name.
*
* @param name
* The name of the newly created pool. Specify <code>null</code> is the pool is
* unnamed.
*/
public static void delete(character name)
{
if (name != null && name.isUnknown())
{
final String errMsg = "Unable to evaluate widget pool name";
ErrorManager.recordOrThrowError(3177, errMsg, false);
return;
}
WorkArea wa = locate();
// ensure the current top-level block has widget pool support
BlockDefinition block = wa.tm.nearestTopLevel();
if (!block.widgetPool)
{
wa.scopeStart(block);
}
boolean unnamed = (name == null);
String sname = (unnamed ? null : name.toStringMessage());
WidgetPoolData data = findActivePool(wa, sname, true);
if (data == null)
{
if (!unnamed)
{
final String msg = "Attempt to delete widget-pool %s, but it doesn't exist";
ErrorManager.recordOrThrowError(4117, String.format(msg, sname), false);
}
return;
}
deletePool(wa, data, false);
}
/**
* Create an unnamed pool.
*/
public static void create()
{
create((character) null);
}
/**
* Create an unnamed pool.
*
* @param persistent
* <code>true</code> if the pool is persistent.
*/
public static void create(boolean persistent)
{
create((character) null, persistent);
}
/**
* Create a pool with the given name.
*
* @param name
* The name of the newly created pool. Specify <code>null</code> is the pool is
* unnamed.
*/
public static void create(character name)
{
create(name, false);
}
/**
* Create a pool with the given name.
*
* @param name
* The name of the newly created pool. Specify <code>null</code> is the pool is
* unnamed.
*/
public static void create(String name)
{
create(name == null ? (character) null : new character(name), false);
}
/**
* Create a pool with the given name and the given "persistent" option.
*
* @param name
* The name of the newly created pool. Specify <code>null</code> is the pool is
* unnamed.
* @param persistent
* <code>true</code> if the pool is persistent.
*/
public static void create(String name, boolean persistent)
{
create(name == null ? (character) null : new character(name), persistent);
}
/**
* Create a pool with the given name and the given "persistent" option.
*
* @param name
* The name of the newly created pool. Specify <code>null</code> is the pool is
* unnamed.
* @param persistent
* <code>true</code> if the pool is persistent.
*/
public static void create(character name, boolean persistent)
{
if (name != null)
{
if (name.isUnknown())
{
final String errMsg = "Unable to evaluate widget pool name";
ErrorManager.recordOrThrowError(3177, errMsg, false);
return;
}
else if (name.toStringMessage().length() == 0)
{
final String errMsg = "Attempt to create a widget-pool with a zero-length name";
ErrorManager.recordOrThrowError(4115, errMsg, false);
return;
}
}
boolean unnamed = (name == null);
String sname = (unnamed ? null : name.toStringMessage());
WorkArea wa = locate();
// ensure the current top-level block has widget pool support
BlockDefinition block = wa.tm.nearestTopLevel();
if (!block.widgetPool)
{
wa.scopeStart(block);
}
WidgetPoolData data = findActivePool(wa, sname, true);
if (data != null && !unnamed)
{
// error is shown only in case there is already a named pool
final String msg = "Attempt to create widget-pool %s, but it already exists";
ErrorManager.recordOrThrowError(4116, String.format(msg, sname), false);
return;
}
// any existing unnamed pools will be hidden in this scope.
data = newPool(wa, sname, persistent);
}
/**
* Adds the specified resource to the pool. Although this is exposed for the public, do not
* used outside of P2J code. Is made public because access to it is needed from any P2J
* package.
*
* @param resource
* The resource to be pooled.
* @param name
* The pool name, set to <code>null</code> if the unnamed pool is to be used.
*
* @throws IllegalStateException
* The exception is throw if the call of this method targets a named pool (the
* <code>name</code> parameter is specified) and either the name is unknown or a pool
* with the specified name does not exist.
* <p>
* If the exception is thrown, there must be an error in the code which creates the
* resource. That code must validate the pool name (by calling
* {@link WidgetPool#validWidgetPool}) before any other resource-related validation
* and before the resource is created.
*/
public static void addResource(WrappedResource resource, character name)
{
addResource(null, resource, name);
}
/**
* Adds the specified resource to the pool. Although this is exposed for the public, do not
* used outside of P2J code. Is made public because access to it is needed from any P2J
* package.
*
* @param wa
* The {@link WorkArea} instance.
* @param resource
* The resource to be pooled.
* @param name
* The pool name, set to <code>null</code> if the unnamed pool is to be used.
*
* @throws IllegalStateException
* The exception is throw if the call of this method targets a named pool (the
* <code>name</code> parameter is specified) and either the name is unknown or a pool
* with the specified name does not exist.
* <p>
* If the exception is thrown, there must be an error in the code which creates the
* resource. That code must validate the pool name (by calling
* {@link WidgetPool#validWidgetPool}) before any other resource-related validation
* and before the resource is created.
*/
public static void addResource(WorkArea wa, WrappedResource resource, character name)
{
if (wa == null)
{
wa = locate();
}
BlockDefinition blk = wa.tm.nearestTopLevel();
if (!blk.widgetPool)
{
wa.scopeStart(blk);
}
boolean unnamed = (name == null);
String sname = (unnamed ? null : name.toStringMessage());
WidgetPoolData data = (name != null && name.isUnknown() ? null
: findActivePool(wa, sname, false));
if (data == null)
{
if (!unnamed)
{
final String msg =
"Could not determine named pool '%s'. You need to validate before adding the " +
"resource!";
throw new IllegalStateException(String.format(msg, sname));
}
// no pool is active, return
return;
}
data.addResource(resource);
// save the resource, to be able to determine its pool later
wa.poolForResource.put(resource, data.id);
}
/**
* Determine if the specified <code>name</code> targets a known widget pool.
* <p>
* A missing unnamed pool will be an error.
*
* @param name
* The pool name, set to <code>null</code> if the unnamed pool is to be used.
*
* @return <code>true</code> if a pool was found with the specified name.
*/
public static boolean validWidgetPool(character name)
{
// an unnamed widget-pool name is always valid.
if (name == null)
{
return true;
}
if (name.isUnknown())
{
final String errMsg = "Unable to evaluate widget pool name";
ErrorManager.recordOrThrowError(3177, errMsg, false);
return false;
}
String sname = name.toStringMessage();
WidgetPoolData data = findActivePool(locate(), sname, false);
if (data == null)
{
// error is shown only if named pool is not found
final String msg =
"Trying to create a widget in widget-pool '%s', but pool does not exist";
ErrorManager.recordOrThrowError(3178, String.format(msg, sname), false);
return false;
}
return true;
}
/**
* Get an instance of {@link WidgetPoolHelper}, which has the context-local instance saved,
* so API access can be done without a context-local query.
*
* @return See above.
*/
static WidgetPoolHelper getHelper()
{
return new WidgetPoolHelper(locate());
}
/**
* Locate the context's {@link WorkArea} instance.
*
* @return See above.
*/
private static WorkArea locate()
{
WorkArea wa = local.get();
if (wa.tm == null)
{
wa.tm = TransactionManager.getTransactionHelper();
wa.pm = ProcedureManager.getProcedureHelper();
wa.oh = ObjectOps.getObjectHelper();
}
return wa;
}
/***
* Create a new pool and register it in the appropriate maps/sets.
*
* @param wa
* The context-local state in {@link WorkArea}.
* @param name
* The name of this pool. Is <code>null</code> if the pool is unnamed.
* @param persistent
* Flag identifying if this pool is persistent.
*
* @return The created {@link WidgetPoolData definition} for the new pool.
*/
private static WidgetPoolData newPool(WorkArea wa, String name, boolean persistent)
{
boolean unnamed = (name == null);
if (unnamed && persistent)
{
Integer top = wa.unnamedPoolScopes.peek().peek();
if (top != null && wa.persistentPools.contains(top))
{
// if the top pool is persistent, then do not create another one. even if 4GL might
// create a different pool, there is no reason to do so, because we can't pop from
// the stack a persistent unnamed pool.
return wa.pools.get(top);
}
}
int id = Utils.uniqueId();
WidgetPoolData data = new WidgetPoolData(id, name);
wa.pools.put(id, data);
if (unnamed)
{
wa.unnamedPools.add(id);
}
else
{
wa.namedPools.put(data.key, id);
}
if (persistent)
{
wa.persistentPools.add(id);
}
if (unnamed)
{
Deque<Integer> ups = wa.unnamedPoolScopes.peek();
if (ups == EMPTY_DEQUE)
{
ups = new ArrayDeque<>();
wa.unnamedPoolScopes.pop();
wa.unnamedPoolScopes.push(ups);
}
ups.push(id);
}
else
{
Set<Integer> nps = wa.namedPoolScopes.peek();
if (nps == Collections.EMPTY_SET)
{
nps = new HashSet<>();
wa.namedPoolScopes.pop();
wa.namedPoolScopes.push(nps);
}
nps.add(id);
}
// collect the pools for each external program
Object oThis = wa.pm._thisProcedure();
Set<Integer> thisIds = wa.poolsPerExtProg.get(oThis);
if (thisIds == null)
{
thisIds = new HashSet<Integer>();
wa.poolsPerExtProg.put(oThis, thisIds);
}
thisIds.add(id);
// save the procedure which created this pool
wa.procForPool.put(id, oThis);
return data;
}
/**
* Filter the set of pool IDs and from it, delete only the non-persistent widget-pools.
*
* @param wa
* The context-local state in {@link WorkArea}.
* @param ids
* A set of pool IDs.
* @param fromScopeFinished
* Flag indicating that this is called from {@link WorkArea#scopeFinished}.
*/
private static void deleteNonPersistentPools(WorkArea wa, Set<Integer> ids, boolean fromScopeFinished)
{
if (ids.isEmpty())
{
return;
}
// determine the non-persistent pools
Set<Integer> nonPersistent = new HashSet<>(ids);
nonPersistent.removeAll(wa.persistentPools);
// delete the non-persistent pools; persistent pools will survive, even after a procedure
// ran non-persistent is finished (or deleted).
for (int id : nonPersistent)
{
WidgetPoolData data = getPool(wa, id);
deletePool(wa, data, fromScopeFinished);
}
}
/**
* Delete the specified pool. Note that an unnamed persistent pool will not be deleted.
* <p>
* When a pool is deleted, all its contained resources will be deleted too.
*
* @param wa
* The context-local state in {@link WorkArea}.
* @param data
* The definition of the pool to be deleted.
* @param fromScopeFinished
* Flag indicating that this is called from {@link WorkArea#scopeFinished}.
*/
private static void deletePool(WorkArea wa, WidgetPoolData data, boolean fromScopeFinished)
{
int id = data.id;
boolean unnamed = wa.unnamedPools.contains(id);
boolean named = wa.namedPools.containsKey(data.key);
boolean persistent = wa.persistentPools.contains(id);
if (unnamed && persistent)
{
// the unnamed persistent pool can't be deleted
return;
}
// ensure the current top-level block has widget pool support
boolean usePrev = wa.pm.inNotifications();
fromScopeFinished = fromScopeFinished || (wa.oh.inScopeFinished() && wa.tm.nearestTopLevel().widgetPool);
BlockDefinition block = usePrev ? wa.tm.prevTopLevel() : wa.tm.nearestTopLevel();
if (!block.widgetPool)
{
Set<Integer> namedScope = null;
Deque<Integer> unnamedScope = null;
// if we are executing current block's WidgetPool.scopeFinished, we need to pop/push the block's scope
// otherwise, even if we are in notifications, the current block's scope was either non-existent or
// already popped
if (usePrev && fromScopeFinished)
{
namedScope = wa.namedPoolScopes.pop();
unnamedScope = wa.unnamedPoolScopes.pop();
}
wa.scopeStart(block);
if (usePrev && fromScopeFinished)
{
wa.namedPoolScopes.push(namedScope);
wa.unnamedPoolScopes.push(unnamedScope);
}
}
// delete the resources; this will clean the resource-related maps
data.deleteResources();
// clean up the registry
wa.pools.remove(id);
if (unnamed)
{
wa.unnamedPools.remove(id);
}
else
{
wa.namedPools.remove(data.key);
}
wa.persistentPools.remove(id);
// remove from the external program's set too.
Object oThis = wa.procForPool.remove(id);
Set<Integer> ids = wa.poolsPerExtProg.get(oThis);
if (ids != null)
{
// ids may be null if the program which created this pool has been deleted.
ids.remove(id);
}
// the other maps/sets
if (unnamed)
{
Deque<Integer> ups = wa.unnamedPoolScopes.peek();
if (ups != null && !ups.isEmpty())
{
ups.remove(id);
}
}
else if (!wa.namedPoolScopes.isEmpty())
{
// this might be from a previous scope, so need to walk the stack
for (Set<Integer> namedPools : wa.namedPoolScopes)
{
if (namedPools.remove(id))
{
// terminate on first found
break;
}
}
}
}
/**
* Determine the active pool. If <code>name</code> is not <code>null</code>, than the active
* named pool will be used.
*
* @param wa
* The context-local state in {@link WorkArea}.
* @param name
* The pool name. May be <code>null</code> for an unnamed pool.
* @param currentScope
* <code>true</code> when search must be done only in current scope.
* @return <code>null</code> if no pool is found, else its
* {@link WidgetPoolData definition} is returned.
*/
private static WidgetPoolData findActivePool(WorkArea wa, String name, boolean currentScope)
{
Integer id = null;
boolean unnamed = (name == null);
if (!unnamed)
{
// get the named pool
String key = name.toLowerCase();
id = wa.namedPools.get(key);
}
else if (wa.unnamedPools.size() > 0)
{
// if no registered unnamed pool, then no reason to search down the stack
// get the unnamed pool for this scope; will be null if no unnamed in stack. always
// check down the stack(s) until one is found
for (Deque<Integer> stack : wa.unnamedPoolScopes)
{
if (!stack.isEmpty())
{
id = stack.peek();
break;
}
if (currentScope)
{
// exit on first scope, if needed
break;
}
}
}
return (id == null ? null : getPool(wa, id));
}
/**
* Get the {@link WidgetPoolData pool} with the given ID.
*
* @param wa
* Context-local work area.
* @param id
* The pool's ID.
*
* @return See above.
*/
private static WidgetPoolData getPool(WorkArea wa, int id)
{
return wa.pools.get(id);
}
/**
* Helper to expose APIs which have direct access to the context-local state.
*/
static class WidgetPoolHelper
{
/** The {@link WorkArea} instance. */
private final WorkArea wa;
/**
* Create a new instance and associate the given WorkArea instance.
*
* @param wa
* The {@link WorkArea} instance.
*/
public WidgetPoolHelper(WorkArea wa)
{
this.wa = wa;
}
/**
* Adds the specified resource to the pool. Although this is exposed for the public, do not
* used outside of P2J code. Is made public because access to it is needed from any P2J
* package.
*
* @param resource
* The resource to be pooled.
* @param name
* The pool name, set to <code>null</code> if the unnamed pool is to be used.
*
* @throws IllegalStateException
* The exception is throw if the call of this method targets a named pool (the
* <code>name</code> parameter is specified) and either the name is unknown or a pool
* with the specified name does not exist.
* <p>
* If the exception is thrown, there must be an error in the code which creates the
* resource. That code must validate the pool name (by calling
* {@link WidgetPool#validWidgetPool}) before any other resource-related validation
* and before the resource is created.
*/
public void addResource(WrappedResource resource, character name)
{
WidgetPool.addResource(wa, resource, name);
}
/**
* Checks if the given referent has an unnamed pool registered.
*
* @param referent
* The referent to check.
*
* @return <code>true</code> if there is at least one unnamed pool for this referent.
*/
public boolean hasUnnamedPool(Object referent)
{
Deque<Integer> up = wa.unnamedProcPools.get(referent);
return up != null && !up.isEmpty();
}
/**
* Delegates the call to {@link WorkArea#scopeStart}
*
* @param block
* The explicit block definition which required this notification.
*/
public void scopeStart(BlockDefinition block)
{
wa.scopeStart(block);
}
/**
* Delegates the call to {@link WorkArea#scopeFinished(Object, boolean, boolean)}
*/
public void scopeFinished(Object oThis, boolean external, boolean persistent)
{
wa.scopeFinished(oThis, external, persistent);
}
/**
* Remove the given resource from its pool, if it was pooled.
*
* @param resource
* The resource to be removed.
*/
public void removeResource(WrappedResource resource)
{
Integer poolId = wa.poolForResource.remove(resource);
// nothing more to do if the resource wasn't pooled
if (poolId == null)
{
return;
}
WidgetPoolData data = getPool(wa, poolId);
if (data == null)
{
// this resource wasn't pooled, so nothing to do
return;
}
data.removeResource(resource);
}
/**
* Delete all the non-persistent widget-pools associated with the specified procedure.
*
* @param proc
* The external procedure instance.
*/
public void deleteWidgetPools(Object proc)
{
// an external procedure was deleted, this acts like it's scope has finished.
Set<Integer> ids = wa.poolsPerExtProg.get(proc);
if (ids != null)
{
deleteNonPersistentPools(wa, ids, false);
// remove this from the map, is no longer needed
wa.poolsPerExtProg.remove(proc);
}
wa.unnamedProcPools.remove(proc);
}
}
/**
* Container for the widget-pool data.
*/
private static class WorkArea
{
/** Helper to use the TM without any context local lookups. */
private TransactionManager.TransactionHelper tm = null;
/** Helper to use the ProcedureManager without any context local lookups. */
private ProcedureManager.ProcedureHelper pm = null;
/** Helper to use the ObjectOps without any context local lookups. */
private ObjectHelper oh = null;
/** A registry of pools, per its ID. */
private final Map<Integer, WidgetPoolData> pools = new HashMap<>();
/** Pool names are global, so keep a map of them (key is always lowercased). */
private final Map<String, Integer> namedPools = new HashMap<>();
/** A set containing the ID of all unnamed pools. */
private final Set<Integer> unnamedPools = new HashSet<>();
/** A set containing the ID of all persistent pools. */
private final Set<Integer> persistentPools = new HashSet<>();
/** Identify the pool to which a resource was added (if was pooled). */
private final Map<WrappedResource, Integer> poolForResource = new HashMap<>();
/** Identify the external procedure used to create the pool. */
private final Map<Integer, Object> procForPool = new HashMap<>();
/** Collect all the created pools for an external program. */
private final Map<Object, Set<Integer>> poolsPerExtProg = new IdentityHashMap<>();
/** Keep the scopes of named pools. */
private final Deque<Set<Integer>> namedPoolScopes = new ArrayDeque<>();
/** Keep the scopes of unnamed pools (only one can be active at a certain time). */
private final Deque<Deque<Integer>> unnamedPoolScopes = new ArrayDeque<>();
/** A map of the unnamed pools active at the time the persistent pool has executed. */
private final Map<Object, Deque<Integer>> unnamedProcPools = new IdentityHashMap<>();
/** A cached set instance used by {@link #scopeFinished}. */
private final Set<Integer> ids = new HashSet<>();
/**
* Notify a new scope has started; invoked by {@link ProcedureManager}'s scoping support when
* a new scope is started.
* <p>
* Notification will be performed only on the start of a top-level block (external program,
* procedure, function or trigger).
* <p>
* No default unnamed pool will be created. An unnamed pool must be explicitly created to be
* used.
*
* @param block
* The explicit block definition which required this notification.
*/
public void scopeStart(BlockDefinition block)
{
if (block.type == BlockType.UNKNOWN)
{
// do nothing for the global block (like for appservers, where arguments are processed)
return;
}
block.widgetPool = true;
Deque<Integer> ups = EMPTY_DEQUE;
if (block.type != BlockType.EXTERNAL_PROC)
{
// case 1: even if called from 'scopeFinished', still uses 'this-procedure', as when this is
// called, ProcedureManager.scopeFinished already popped the currently processed 'this-procedure'.
// so we target the previous scope 'naturally'
// case 2: when implicit object deletion happens, this will be performed from within 'popScope',
// but before ProcedureManager.scopeFinished happens. So THIS-PROCEDURE is still on stack - in
// such cases, we need to use 'prev-this-procedure'.
ProcedureData pd = oh.inScopeFinished() ? pm.prevThisProcedureData() : pm.thisProcedureData();
if (pd.isPersistent())
{
Object referent = pd.getReferent();
if (unnamedProcPools.containsKey(referent))
{
ups = new ArrayDeque<>();
ups.addAll(unnamedProcPools.get(referent));
}
}
}
namedPoolScopes.push(Collections.emptySet());
unnamedPoolScopes.push(ups);
}
/**
* Notify a scope has finished; invoked by {@link ProcedureManager}'s scoping support when a
* scope is finished.
* <p>
* This will delete the created non-persistent pools, but only if this is not a notification
* of an external procedure which was ran persistent.
* <p>
* Notification will be performed only on the end of a top-level block (external program,
* procedure, function or trigger).
*
* @param oThis
* Current THIS-PROCEDURE handle.
* @param external
* Flag indicating that this is an external procedure.
* @param persistent
* Flag indicating that this external procedure is ran persistent.
*/
public void scopeFinished(Object oThis, boolean external, boolean persistent)
{
ids.clear();
// compute the set of active pools
Set<Integer> topNamed = namedPoolScopes.peek();
if (!topNamed.isEmpty())
{
ids.addAll(topNamed);
}
Deque<Integer> topUnnamed = unnamedPoolScopes.peek();
if (!topUnnamed.isEmpty())
{
ids.addAll(topUnnamed);
}
if (!external && unnamedProcPools.containsKey(oThis))
{
// the external program's pool get deleted when the extprog gets deleted
Deque<Integer> oThisPools = unnamedProcPools.get(oThis);
if (!oThisPools.isEmpty())
{
ids.removeAll(oThisPools);
topUnnamed.removeAll(oThisPools);
}
}
if (!(external && persistent))
{
deleteNonPersistentPools(this, ids, true);
if (external)
{
poolsPerExtProg.remove(oThis);
unnamedProcPools.remove(oThis);
}
}
else
{
// if finishing an external program ran in persistent mode, let the pools survive;
// they will be deleted when the external program gets deleted
}
// this must be done at this step - deleteNonPersistentPools is dependent on it
namedPoolScopes.pop();
Deque<Integer> unnamedIds = unnamedPoolScopes.pop();
if (!persistent && !unnamedIds.isEmpty())
{
// we need to ensure the previous top-level block has widgetPool support
// this needs to be done after we remove the current widget pool state for the current block
BlockDefinition block = tm.prevTopLevel();
if (block != null && !block.widgetPool)
{
block.widgetPool = true;
Deque<Integer> ups = EMPTY_DEQUE;
ProcedureData prev = pm.prevThisProcedureData();
if (block.type != BlockType.EXTERNAL_PROC && prev.isPersistent())
{
Object referent = prev.getReferent();
if (unnamedProcPools.containsKey(referent))
{
ups = new ArrayDeque<>();
ups.addAll(unnamedProcPools.get(referent));
}
}
namedPoolScopes.push(Collections.emptySet());
unnamedPoolScopes.push(ups);
}
}
// at the time of the pop, all the non-persistent pools have been removed. now, check if
// a persistent unnamed pool is in effect. if so, "leak" it to the previous scope.
// any pool created in a persistent procedure will not be leaked to a previous scope. The
// persistent procedure's pools (named or unnamed, persistent or not) will be deleted when
// the persistent procedure's handle gets deleted.
if (!persistent && !unnamedPoolScopes.isEmpty() && !unnamedIds.isEmpty())
{
// what is left must be a persistent unnamed pool.
int id = unnamedIds.pop();
if (!unnamedIds.isEmpty())
{
if (LOG.isLoggable(Level.SEVERE))
{
final String msg = "Scope [%d] should have no unnamed pools left!";
LOG.log(Level.SEVERE, String.format(msg, unnamedPoolScopes.size() + 1));
}
}
Deque<Integer> prevScope = unnamedPoolScopes.peek();
int prev = prevScope.isEmpty() ? -1 : prevScope.peek();
if (persistentPools.contains(prev))
{
// merge all from this persistent pool to the prev pool
WidgetPoolData prevData = pools.get(prev);
WidgetPoolData currentData = pools.get(id);
for (WrappedResource res : currentData.resources)
{
prevData.addResource(res);
poolForResource.put(res, prev);
}
// remove this pool, as all its resources were leaked to the previous scope
persistentPools.remove(id);
pools.remove(id);
unnamedPools.remove(id);
Set<Integer> pools = poolsPerExtProg.get(oThis);
if (pools != null)
{
poolsPerExtProg.get(oThis).remove(id);
}
procForPool.remove(id);
}
else
{
if (!pools.containsKey(id))
{
throw new RuntimeException("The WidgetPool " + id + " has already been deleted!");
}
// "leak" it to the prev scope
Deque<Integer> ups = unnamedPoolScopes.peek();
if (ups == EMPTY_DEQUE)
{
ups = new ArrayDeque<>();
unnamedPoolScopes.pop();
unnamedPoolScopes.push(ups);
}
ups.push(id);
// re-adjust the pool's creator as the one from the prev scope
Object referent = pm._thisProcedure();
Set<Integer> pools = poolsPerExtProg.get(referent);
if (pools == null)
{
pools = new HashSet<>();
poolsPerExtProg.put(referent, pools);
}
pools.add(id);
procForPool.put(id, referent);
}
}
else if (persistent && external && !unnamedIds.isEmpty())
{
unnamedProcPools.put(oThis, unnamedIds);
}
}
}
/**
* The data needed for a widget pool, named or unnamed.
*/
private static class WidgetPoolData
{
/** Set of resources created in this widget pool. */
private final Set<WrappedResource> resources = new LinkedHashSet<>();
/** The ID of this widget pool. */
private final int id;
/** This pool's key (to identify it by name, if this is a named pool). */
private final String key;
/**
* Create a new widget pool.
*
* @param id
* The ID of this widget pool.
* @param name
* The name of this widget pool. <code>null</code> if unnamed.
*/
public WidgetPoolData(int id, String name)
{
this.id = id;
this.key = (name == null ? null : name.toLowerCase());
}
/**
* Add a new resource to this widget pool.
*
* @param resource
* The resource to be added.
*/
public void addResource(WrappedResource resource)
{
resources.add(resource);
}
/**
* Remove the resource from this widget pool.
*
* @param resource
* The resource to be removed.
*/
public void removeResource(WrappedResource resource)
{
resources.remove(resource);
}
/**
* Delete all the resources from this pool.
*/
public void deleteResources()
{
// use a copy, as the resource-related maps/sets get cleaned when the resource is deleted
Set<WrappedResource> copy = new LinkedHashSet<WrappedResource>(resources);
for (WrappedResource res : copy)
{
if (res.valid())
{
HandleOps.delete(new handle(res), false);
}
}
}
}
}