DataSetManager.java
/*
** Module : DataSetManager.java
** Abstract : Manages all the DataSets, taking care for matching shared ones.
**
** Copyright (c) 2019-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- ----------------------------------------Description---------------------------------------
** 001 OM 20190403 Created initial version.
** 002 OM 20190624 Added runtime implementation.
** 003 CA 20190722 Allow lookup for handles registered as OUTPUT arguments.
** OM 20190731 Added REFERENCE-ONLY support.
** 004 CA 20200110 More fixes for DATASET parameters and resource delete.
** 005 CA 20201003 Allow dictionaries with an identity map.
** 006 CA 20220524 The scope ends when the procedure returns, not on 'scopeFinished', to allow parameter
** processing.
** CA 20220613 The static DATASET must be registered as 'pending' to be properly registered with the next
** external program call.
** 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.
** 007 AL2 20230613 Use procedure helper where possible. Allow faster gateways to the transaction manager.
** 008 CA 20231031 Added 'BlockDefinition' parameter to 'scopeStart'.
** 009 CA 20240404 Static temp-table or dataset resolution by name must be done using the currently executing
** type and the static temp-table/dataset's defining type.
*/
/*
** 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.persist;
import com.goldencode.p2j.security.*;
import com.goldencode.p2j.util.*;
import java.util.*;
/**
* A manager for {@code DataSet}-s:
* <ul>
* <li>registers new {@code DataSet}-s and matches shared ones;
* <li>keeps the head and tail of the list of {@code DataSet}-s.
* </ul>
*/
public class DataSetManager
implements Scopeable
{
/**
* The context-local manager.
*/
private final static ContextLocal<DataSetManager> instance = new ContextLocal<DataSetManager>()
{
/**
* This method will be invoked to retrieve the initial value of this context local variable,
* the first time {@link #get} is invoked (but only if {@link #set} was not previously
* invoked. The returned value will be stored as the variable's initial value.
* <p>
*
* @return A new instance of {@code DataSetManager}.
*/
@Override
protected DataSetManager initialValue()
{
return new DataSetManager();
}
};
/** The pending datasets defined in the next external program being invoked. */
private Set<StaticDataSet> pendingRegistry = new HashSet<>();
/**
* Scoped dictionary which contains the maps of output copiers for a procedure/function to
* their 0-based indexes in the set of all DATASET-HANDLE parameters for the procedure/function
* (excluding other types of parameters).
*/
private final ScopedDictionary<OutputDataSetHandleCopier, Integer> dshOutputParams =
new ScopedDictionary<>();
/** A dictionary of BY-REFERENCE dataset parameters. */
private final ScopedDictionary<DataSet, Integer> byReferenceParameters = new ScopedDictionary<>();
/** The transaction helper. */
private final TransactionManager.TransactionHelper tm;
/** The procedure helper. */
private final ProcedureManager.ProcedureHelper pm;
/** The buffer helper. */
private final BufferManager bm;
/**
* Default c'tor. Marks dictionaries which can use identity maps.
*/
private DataSetManager()
{
tm = TransactionManager.getTransactionHelper();
pm = ProcedureManager.getProcedureHelper();
bm = BufferManager.get();
dshOutputParams.setIdentityKeys(true);
byReferenceParameters.setIdentityKeys(true);
}
/**
* Factory method. Corresponds to {@code DEFINE DATASET} statement. Creates a new builder for a
* new {@link DataSet} and returns it for further setting up of options.
*
* @param legacyName
* The legacy name of the {@code DataSet}. Used for lookup.
* @param isShared
* This is a shared {@code DataSet}.
* @param isNew
* This is a new {@code DataSet}. Only in combination with {@code isShared}.
*/
public static DataSet.Builder define(String legacyName, boolean isShared, boolean isNew)
{
return new DataSet.Builder(legacyName, isShared, isNew);
}
/**
* Obtain the instance of {@code DataSetManager} (associated with this work area ?).
*
* @return the instance of {@code DataSetManager}.
*/
public static DataSetManager instance()
{
return instance.get();
}
/**
* Register the {@link DataSetManager} for scope notifications at the current block.
*/
public static void registerScopeable()
{
instance()._registerScopeable();
}
/**
* Registers a ((new) shared) dataset.
*
* @param dataSet
* The new {@code DataSet} to be registered.
* @param legacyName
* The legacy name of the {@code DataSet}.
* @param isShared
* {@code true} if this is a SHARED DATASET.
* @param isNew
* {@code true} if this is a NEW SHARED DATASET.
*/
public void register(DataSet dataSet, String legacyName, boolean isShared, boolean isNew)
{
tm.registerPendingScopeable(this);
if (!dataSet._dynamic())
{
pendingRegistry.add((StaticDataSet) dataSet);
}
}
/**
* Register the {@link DataSetManager} for scope notifications at the current block.
* This is a convenient method when the manager is already retrieved, so that there is
* no need for a further context look-up.
*/
public void _registerScopeable()
{
tm.registerBlockScopeable(this);
// if there is dataset usage, then buffers are needed, too
BufferManager.registerScopeable(bm);
}
/**
* Convenient gateway to the transaction manager. This is used to avoid an additional
* context look-up if the caller already has access to this manager. This is not a
* good pattern to follow (add a method in data set manager to gateway each method
* in transaction manager). However, this is the only case we want to have such gateway.
*
* @param dsHandle
* the target to be registered with the transaction manager
*/
public void registerCurrent(handle dsHandle)
{
tm.registerCurrent(dsHandle);
}
/**
* Looks up for a {@code DataSet} by its name. Only STATIC dataset are managed.
*
* @param dsName
* The name to search.
*
* @return The {@code DataSet} statically named {@code sdName} or {@code null} is such
* {@code DataSet} is not accessible.
*/
public DataSet byName(String dsName)
{
Object ref = pm._thisProcedure();
Class<?> type = pm.getExecuting();
return bm.getStaticDataSet(type, dsName, ref);
}
/**
* Provides a notification that a new scope is about to be entered.
*
* @param block
* The explicit block definition which required this notification.
*/
@Override
public void scopeStart(BlockDefinition block)
{
if (block.topLevel)
{
dshOutputParams.addScope(null);
byReferenceParameters.addScope(null);
if (!pendingRegistry.isEmpty())
{
for (StaticDataSet dataset : pendingRegistry)
{
String name = dataset.getDefinitionName();
Object ref = pm._thisProcedure();
Class<?> type = dataset.getDefiningType();
bm.registerDataSet(type, name, ref, dataset);
}
pendingRegistry.clear();
}
pm.executeOnReturn(() ->
{
dshOutputParams.deleteScope();
byReferenceParameters.deleteScope();
}, WeightFactor.LAST);
}
}
/**
* Provides a notification that a scope is about to be exited.
*/
@Override
public void scopeFinished()
{
}
/**
* Provides a notification that an external scope is about to be deleted.
*/
@Override
public void scopeDeleted()
{
// TODO: implement~me
}
/**
* Get the {@link ScopeId} for the instance.
*
* @return {@link ScopeId#DATA_SET_MANAGER}.
*/
@Override
public ScopeId getScopeId()
{
return ScopeId.DATA_SET_MANAGER;
}
/**
* Check if the given dataset is passed as a BY-REFERENCE parameter.
*
* @param ds
* The dataset to check.
*
* @return <code>true</code> if the dataset exists in the {@link #byReferenceParameters}
* dictionary.
*/
public boolean isByReferenceParameter(DataSet ds)
{
for (int i = 0; i < byReferenceParameters.size(); i++)
{
Map<DataSet, Integer> map = byReferenceParameters.getDictionaryAtScope(i, false);
if (map == null)
{
continue;
}
if (map.containsKey(ds))
{
return true;
}
}
return false;
}
/**
* Register the given dataset as BY-REFERENCE, in the {@link #byReferenceParameters} dictionary.
*
* @param ds
* The dataset.
*/
public void addByReferenceParameter(DataSet ds)
{
Map<DataSet, Integer> map = byReferenceParameters.getDictionaryAtScope(0, true);
byReferenceParameters.addEntry(false, ds, map.size());
}
/**
* Deregister the given dataset as BY-REFERENCE, from the {@link #byReferenceParameters}
* dictionary.
*
* @param ds
* The dataset.
*/
public void removeByReferenceParameter(DataSet ds)
{
byReferenceParameters.removeEntryAtScope(ds, 0);
}
/**
* Check if the specified dataset instance is registered as an OUTPUT argument.
*
* @param ds
* The dataset instance.
*
* @return <code>true</code> if the handle originates from the caller as an OUTPUT argument.
*/
public boolean isOutputDataSetHandle(DataSet ds)
{
for (int i = 0; i < dshOutputParams.size(); i++)
{
Map<OutputDataSetHandleCopier, Integer> map = dshOutputParams.getDictionaryAtScope(i, false);
if (map == null)
{
continue;
}
for (OutputDataSetHandleCopier out : map.keySet())
{
if (out.getCalled().get() == ds)
{
return true;
}
}
}
return false;
}
/**
* Create and register the output table copier.
*
* @param dsParam
* The source dataset parameter.
* @param dsHandle
* The target dataset handle..
*/
public void registerOutputDataSetHandleCopier(DataSetParameter dsParam,
handle dsHandle)
{
OutputDataSetHandleCopier copier = new OutputDataSetHandleCopier(dsParam, dsHandle);
pm.executeOnReturn(copier::finished, copier.weight());
Map<OutputDataSetHandleCopier, Integer> map = dshOutputParams.getDictionaryAtScope(0, true);
dshOutputParams.addEntry(false, copier, map.size());
copier.setSiblingsMap(map);
}
/**
* Unregister the output dataset copier <b>in the current scope</b>.
*
* @param copier
* Copier to unregister.
*/
public void unregisterOutputDataSetHandleCopier(OutputDataSetHandleCopier copier)
{
dshOutputParams.removeEntryAtScope(copier, 0);
}
}