# Patch PAT162007914 created at Tue Sep 24 12:09:36 CEST 2024
#
# Issue: XECMPF-16358
# Module:
# Version: Content Server 16.2.0 64-bit for Windows
# Update: 24.2.0 (2024-03),24.1.0 (2023-12)
#
# Description:
# Simplify License audit (performance)
#
# 1. Stop all Content Server services.
# 2. Make necessary backup before applying the patch.
# 3. Unpack and place this package to <OTHOME>.
# 4. The directory(s) and file(s) listed above are expected to be copied to
<OTHOME> and replace any existing one(s).
# 5. Start all Content Server services.
#
m {'kernel','llpatch','xecmpf'}
o
{'pat00001::AccessBusinessWorkspace',&&xecmpf::Privileges::AccessBusinessWorkspace}
N AccessBusinessWorkspace
f MayExecuteWithAudit
s
function Boolean MayExecuteWithAudit(Object prgCtx, Integer auditType, Integer
auditItem,
Integer subType = .fSubType, Integer factoryType
= .fFactoryType)
// Disable the licensing usage privileges (XECMPF-16358)
Boolean result = TRUE //.MayExecute( prgCtx, subType, factoryType )
if ( result )
.Audit( prgCtx, auditType, auditItem )
end
return result
end
sEND
f __Init
s
function Void __Init()
if ( .fEnabled )
// License Audit changes (XECMPF-16358):
// Disable derived licensing usage privileges for all Extended ECM
products
//$LLIAPI.ObjectFactorySubsystem.RegisterItem( this,
{ .fFactoryType, .fSubType } )
end
end
sEND
o {'pat00001::XecmpfFilterNodesCallback',&&xecmpf::XecmpfFilterNodesCallback}
N XecmpfFilterNodesCallback
f CBFilterNodes
s
/**
* Callback function to apply Extended ECM permissions on the given nodes.
*
* @param dapiCtx
* DAPI context object
* @param nodeRecs
* Array of requested nodes. All nodes for which the given user
has no permission must be removed.
* @param userID
* ID of the user for which the permissions are evaluated
*
* @return Assoc
* .ok : TRUE upon success, FALSE otherwise
* .errMsg : error message if .ok is FALSE, undefined
otherwise
* .apiError : API error object if .ok is FALSE, undefined
otherwise
*/
function Assoc CBFilterNodes(
Object dapiCtx,
RecArray nodeRecs,
Integer userID )
Assoc retVal
Dynamic result
Object prgCtx = dapiCtx.fPrgCtx
String errMsg = Undefined
Dynamic apiError
Assoc cache = undefined
RecArray ancestorRecs
Integer start
Integer polEnabled = 1
Boolean isPolicyEnabled = false
Boolean getPolicyEnabled = false
retVal.ok = true
// Make sure we don't go through this if the input node array is empty or the
current user has system administrative rights
if ( Length( nodeRecs ) > 0 && !dapiCtx.fPrgCtx.USession().HasByPassPerm() )
// *** Policies: Check for Policies ***
result = $OTSAPXECM.Utils.GetPolicyEnabledFlag( prgCtx )
if( result.ok && result.found )
isPolicyEnabled = result.policyEnabled
else
getPolicyEnabled = TRUE
end
if ( getPolicyEnabled )
retVal = $OTSAPXECM.Utils.IsAnyPolicyEnabled( prgCtx )
if ( retval.ok )
isPolicyEnabled = retval.policyEnabled
result = $OTSAPXECM.Utils.PutPolicyEnabledFlag( prgCtx,
isPolicyEnabled )
if( !result.ok )
$OTSAPXECM.LoggingUtils.LogError('policy enabled
register',this,result.errMsg)
end
end
end
if retval.ok && isPolicyEnabled
$OTSAPXECM.LoggingUtils.LogDebug( "FilterNodesCallBack:CBFilterNodes", this,
"...starting to check policies." )
// *** continue with "normal" policy checking
if IsFeature( dapiCtx, "fPolicyCache" )
cache = dapiCtx.fPolicyCache
if IsFeature( cache, 'ValidUntilTick' ) && Date.Tick() <=
cache.ValidUntilTick
$OTSAPXECM.LoggingUtils.LogDebug( "FilterNodesCallBack:CBFilterNodes", this,
"using cache, still valid for ticks: " + Str.String( cache.ValidUntilTick -
Date.Tick() ) )
else
$OTSAPXECM.LoggingUtils.LogDebug( "FilterNodesCallBack:CBFilterNodes", this,
"rebuilding outdated cache" )
cache = undefined
end
else
$OTSAPXECM.LoggingUtils.LogDebug( "FilterNodesCallBack:CBFilterNodes", this,
"creating new cache" )
OS.AddFeature( dapiCtx, "fPolicyCache" )
end
if IsUndefined( cache )
cache = Assoc.CreateAssoc( )
cache.ValidUntilTick = Date.Tick() +
CAPI.IniGet( prgCtx.fDbConnect.fLogin, $XECMPF.fIniSectionConfig,
"FilterNodesCallbackCache", 100000 )
dapiCtx.fPolicyCache = cache
end
start = Date.Tick()
result = .GetFilterInfoFromNodeRecs( prgctx,
RecArray.ColumnToList( nodeRecs, "DataID" ) )
result.records
= .RemoveDuplicateParentsFromNodeRecs( result.records )
$OTSAPXECM.LoggingUtils.LogDebug( "FilterNodesCallBack:CBFilterNodes", this,
str.format( "timing for joined filter query: %1", Date.Tick() - start ) )
if result.ok
ancestorRecs = result.records
//is any policy enabled
if polEnabled in RecArray.ColumnToList( ancestorRecs,
"WKSP_POL_ENABLED" )
$OTSAPXECM.LoggingUtils.LogDebug( "FilterNodesCallBack:CBFilterNodes", this,
str.format( "records prior to filtering: %1", length( nodeRecs ) ) )
result = .FilterItems4Policies( prgCtx, ancestorRecs,
nodeRecs )
$OTSAPXECM.LoggingUtils.LogDebug( "FilterNodesCallBack:CBFilterNodes", this,
str.format( "records after filtering: %1", length( nodeRecs ) ) )
else
$OTSAPXECM.LoggingUtils.LogDebug( "FilterNodesCallBack:CBFilterNodes", this,
"no policy related items found" )
end
else
retVal.ok = false
errMsg = result.errMsg
$OTSAPXECM.LoggingUtils.LogError( "FilterNodesCallBack:CBFilterNodes", this,
"Error applying filter ('GetFilterInfoFromNodeRecs')" )
end
end
// *** END Policies ***/
end
if IsDefined( errMsg )
$OTSAPXECM.LoggingUtils.LogError( "FilterNodesCallBack:CBFilterNodes",
this, errMsg )
retVal.Ok = FALSE
retVal.errMsg = errMsg
retVal.apiError = apiError
$OTSAPXECM.LoggingUtils.LogDebug( "FilterNodesCallBack:CBFilterNodes",
this, "end with error: " + errMsg )
else
retVal.ok = TRUE
end
return retVal
end
sEND
o {'pat00001::XECMPFNodeCallbacks',&&xecmpf::XECMPFNodeCallbacks}
N XECMPFNodeCallbacks
I {'pat00001::BusinessWorkspaceCB','pat00001::XECMPFNodeCallbacks'}
N BusinessWorkspaceCB
f CBListContents
s
/**
* Perform License Audit (old Extended ECM Licensing) if we are opening a business
workspace
* (XECMPF-16358).
*
* This replaces the performance critical License Audit in
XecmpfFilterNodesCallback.
*
* @param {DAPINODE} node
* @param {RecArray} childRecs
* @param {String} viewName
*
* @return {Assoc}
* @returnFeature {Boolean} ok FALSE if an error occurred, TRUE otherwise
* @returnFeature {String} errMsg Error message, if an error occurred
* @returnFeature {Dynamic} apiError Error object, if applicable
*/
function Assoc CBListContents( DAPINODE node, RecArray childRecs, String viewName )
Assoc checkVal
Dynamic apiError
Object dapiCtx
Object prgCtx
String errMsg
RecArray workspaces
Record workspace
Integer nodeID
Boolean ok = TRUE
Boolean skip = TRUE
Boolean enhancedLicensingEnabled = FALSE
// Check for both workspace and workspace volume because
// for workspaces without content this event is only triggered for workspace
volume
if ( IsDefined( node ) && node.pSubType in { $TypeEcmWorkspace,
$TypeEcmWorkspaceVolume } )
skip = FALSE
end
if ( !skip )
checkVal = $LLIAPI.NodeUtil.FindDapiCtx( node )
if ( checkVal.ok != TRUE )
ok = FALSE
errMsg = checkVal.errMsg
apiError = checkVal.apiError
else
dapiCtx = checkVal.DapiCtx
prgCtx = dapiCtx.fPrgCtx
end
if ( ok )
checkVal =
$LLIAPIUTILS.LicenseUtils.GetEnhancedLicenseSetting( prgCtx )
if ( checkVal.ok == TRUE )
enhancedLicensingEnabled = checkVal.enhancedLicenseSetting
else
ok = FALSE
errMsg = checkVal.errMsg
$OTSAPXECM.LoggingUtils.LogError(
"BusinessWorkspaceCB:CBListContents",
this,
"Error retrieving Enhanced License Setting." )
end
end
if ( ok && enhancedLicensingEnabled == FALSE )
nodeID = ( node.pSubType == $TypeEcmWorkspaceVolume ) ?
node.pVolumeID : node.pID
checkVal = ._GetItemsForLicensing( prgCtx, nodeID )
if ( checkVal.ok )
workspaces = checkVal.recs
else
ok = FALSE
errMsg = checkVal.errMsg
$OTSAPXECM.LoggingUtils.LogError(
"BusinessWorkspaceCB:CBListContents",
this,
"Error retrieving items for licensing" )
end
if ( ok )
for workspace in workspaces
// License audit for business application and
workspace type.
// For cross-application workspace all involved
business applications are audited.
// Workspace templates must not be audited.
if ( ._IsNonTemplateNode( prgCtx,
workspace.DataID ) )
if ( workspace.extSysId > 0 )
$XECMPF.AccessBusinessWorkspace.Audit(
prgCtx,
$XECMPF.fLicAuditTypeBusApp,
workspace.extSysId )
end
if ( workspace.wsTypeId > 0 )
$XECMPF.AccessBusinessWorkspace.Audit(
prgCtx,
$XECMPF.fLicAuditTypeWkspType,
workspace.wsTypeId )
end
end
end
end
end
end
if ( IsDefined( errMsg ) && Length( errMsg ) > 0 )
$OTSAPXECM.LoggingUtils.LogError( "BusinessWorkspaceCB:CBListContents",
this, errMsg )
$OTSAPXECM.LoggingUtils.LogDebug(
"BusinessWorkspaceCB:CBListContents",
this,
"ends with error: " + errMsg )
end
return Assoc{ "ok": ok, "errMsg": errMsg, "apiError": apiError }
end
sEND
g _GetItemsForLicensing
s
private function Assoc _GetItemsForLicensing( Object prgCtx, Integer nodeID )
RecArray recs
String q1, q2, q3
String stmt
Dynamic dbResult
String errMsg
Dynamic apiError
Boolean ok = TRUE
Object dbconnect = prgCtx.fDBConnect
// The actual query consists of 3 parts which are accumulated via UNION ALL
statement.
// 1st part selects workspaces with a business object assigned (late
workspaces)
q1 =
"SELECT lk.DATAID AS DataID, " +
" extSys.EXTSYSTEM_NODE_ID AS extSysId, wst.CONFIG_NODE_ID AS wsTypeId
" +
"FROM OTSAPXECM_WKSP_LINKS lk " +
"INNER JOIN OTSAP_BO_TYPES bot ON bot.ID_BO_TYPE = lk.ID_BO_TYPE " +
"INNER JOIN OTSAP_REF_TYPES wst ON wst.ID_REFERENCE_TYPE =
bot.ID_REFERENCE_TYPE " +
"INNER JOIN OTSAP_EXT_SYSTEMS extSys ON extSys.ID_EXTSYSTEM =
bot.ID_EXTSYSTEM " +
"WHERE lk.DATAID = :A1"
// 2nd part selects workspaces without business object type (pure business
workspaces)
q2 =
"SELECT lk.DATAID AS DataID, " +
" 0 AS extSysId, wst.CONFIG_NODE_ID AS wsTypeId " +
"FROM OTSAPXECM_WKSP_NOLINK lk " +
"INNER JOIN OTSAP_REF_TYPES wst ON wst.ID_REFERENCE_TYPE =
lk.ID_REFERENCE_TYPE " +
"WHERE lk.DATAID = :A2"
// 3rd part selects workspaces with business object type but without business
object
// assigned (early workspaces)
q3 =
"SELECT lk.DATAID AS DataID, " +
" extSys.EXTSYSTEM_NODE_ID AS extSysId, wst.CONFIG_NODE_ID AS wsTypeId
" +
"FROM OTSAPXECM_WKSP_NOLINK lk " +
"INNER JOIN OTSAP_REF_TYPES wst ON wst.ID_REFERENCE_TYPE =
lk.ID_REFERENCE_TYPE " +
"INNER JOIN OTSAP_BO_TYPES bot ON bot.ID_REFERENCE_TYPE =
wst.ID_REFERENCE_TYPE " +
"INNER JOIN OTSAP_EXT_SYSTEMS extSys ON extSys.ID_EXTSYSTEM =
bot.ID_EXTSYSTEM " +
"WHERE lk.DATAID = :A3"
stmt = Str.Join( { q1, "UNION ALL", q2, "UNION ALL", q3 }, " " )
dbResult = CAPI.Exec( dbConnect.fConnection, stmt, nodeID, nodeID, nodeID )
if ( IsNotError( dbResult ) )
if ( Length( dbResult ) > 0 )
recs = dbResult
end
else
ok = FALSE
errMsg = 'Error executing SQL query.'
apiError = Error.ErrorToString( dbResult )
end
return Assoc{ "ok": ok, "errMsg": errMsg, "apiError": apiError, "recs":
recs }
end
sEND
g _IsNonTemplateNode
s
private function Boolean _IsNonTemplateNode( Object prgCtx, Integer nodeID )
String stmt
Dynamic result
CAPICONNECT connect = prgCtx.fDBConnect.fConnection
List docTemplCache =
prgCtx.GetSessionProperty( .fDocumentTemplateNodesCacheKey )
if ( IsUndefined ( docTemplCache ) )
docTemplCache = {}
stmt = "SELECT DataID FROM DTreeCore WHERE OwnerID = :A1"
result = CAPI.Exec( connect, stmt, -
$XECMPF.fDocumentTemplatesVolumeID )
if ( !IsError( result ) )
docTemplCache = RecArray.ColumnToList( result, "DataID" )
end
prgCtx.SetSessionProperty( .fDocumentTemplateNodesCacheKey,
docTemplCache )
end
return ( ! ( nodeID in docTemplCache ) )
end
sEND
g fDocumentTemplateNodesCacheKey
v 'XECMPF.DocumentTemplateNodes'
f fEnabled
v true
f fSubTypes
v ?
o {'pat00001::PostPatchActions',&&llpatch::PostPatchActions}
N PostPatchActions
g Action20240918_147100
s
function void Action20240918_147100()
// Unregister all Access Business Workspaces usage privileges
Dynamic items = $LLIAPI.ObjectFactorySubsystem.GetItems()
Dynamic item
Dynamic parent
Integer count = 0
Integer i
Dynamic key
echo("........Going to unregister licensing usage privileges")
for item in items
i = 1
parent = item.OSParent
while IsDefined( parent ) && parent != #0 && i < 10
if( parent.OSName == 'LicensingUsagePrivilege' )
key = { item.fFactoryType, item.fSubType }
$LLIAPI.ObjectFactorySubsystem.UnregisterItem( key )
break
else
parent = parent.OSParent
i += 1
end
end
end
echo("........", count, " privileges successfully unregistered.")
// Register the new BusinessWorkspaceCB callback
Object o
Boolean found = false
echo("........Going to register BusinessWorkspacesCB")
o = XECMPF::XECMPFNodeCallbacks::BusinessWorkspaceCB
if( IsDefined ( o ) )
o.__Init()
found = TRUE
end
/*
for o in
$KERNEL.OSpaceUtils.ChildrenByOSpace(XECMPF::XECMPFNodeCallbacks::BusinessWorkspace
CB)
if( o.OSName == "BusinessWorkspaceCB" )
found = true
echo( "........BusinessWorkspaceCB found." )
o.__Init()
echo( "........BusinessWorkspaceCB initialized." )
break
end
end
*/
if( found )
echo("........Action successfully finished.")
else
EchoError("XECMPF::XECMPFNodeCallbacks::BusinessWorkspaceCB not
found.")
end
end
sEND