mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-13 07:24:13 +01:00
N°8760 - rename GetCreatedIn <- GetModuleName + compute module name live instead having complex stuff in MetaModel/compilation
This commit is contained in:
@@ -22,6 +22,8 @@ use Combodo\iTop\Application\EventRegister\ApplicationEvents;
|
||||
use Combodo\iTop\Core\MetaModel\FriendlyNameType;
|
||||
use Combodo\iTop\Service\Events\EventData;
|
||||
use Combodo\iTop\Service\Events\EventService;
|
||||
use Combodo\iTop\Setup\ModuleDependency\Module;
|
||||
use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReader;
|
||||
|
||||
require_once APPROOT.'core/modulehandler.class.inc.php';
|
||||
require_once APPROOT.'core/querymodifier.class.inc.php';
|
||||
@@ -30,7 +32,6 @@ require_once APPROOT.'core/computing.inc.php';
|
||||
require_once APPROOT.'core/relationgraph.class.inc.php';
|
||||
require_once APPROOT.'core/apc-compat.php';
|
||||
require_once APPROOT.'core/expressioncache.class.inc.php';
|
||||
require_once APPROOT.'core/metamodel/IncludeFileReader.php';
|
||||
|
||||
/**
|
||||
* We need to have all iLoginFSMExtension/iLoginUIExtension impl loaded ! Cannot use autoloader...
|
||||
@@ -289,10 +290,6 @@ abstract class MetaModel
|
||||
* @var array
|
||||
*/
|
||||
private static $m_aRootClasses = [];
|
||||
|
||||
//used to populate DM create_in field for classes declared in 'datamodel'
|
||||
public static array $m_aCreatedIn = [];
|
||||
|
||||
/**
|
||||
* array of ("classname" => array of "parentclass")
|
||||
*
|
||||
@@ -473,11 +470,36 @@ abstract class MetaModel
|
||||
* @return string
|
||||
* @throws \CoreException
|
||||
*/
|
||||
final public static function GetCreatedIn($sClass)
|
||||
final public static function GetModuleName($sClass)
|
||||
{
|
||||
self::_check_subclass($sClass);
|
||||
try {
|
||||
$oReflectionClass = new ReflectionClass($sClass);
|
||||
$sDir = realpath(dirname($oReflectionClass->getFileName()));
|
||||
$sApproot = realpath(APPROOT);
|
||||
while (($sDir !== $sApproot) && (str_contains($sDir, $sApproot))) {
|
||||
$aFiles = glob("$sDir/module.*.php");
|
||||
if (count($aFiles) > 1) {
|
||||
return 'core';
|
||||
}
|
||||
|
||||
return self::$m_aClassParams[$sClass]['created_in'] ?? "";
|
||||
if (count($aFiles) == 0) {
|
||||
$sDir = realpath(dirname($sDir));
|
||||
continue;
|
||||
}
|
||||
|
||||
$sModuleFilePath = $aFiles[0];
|
||||
$aModuleInfo = ModuleFileReader::GetInstance()->ReadModuleFileInformation($sModuleFilePath);
|
||||
$sModuleId = $aModuleInfo[ModuleFileReader::MODULE_INFO_ID];
|
||||
list($sModuleName, ) = ModuleDiscovery::GetModuleName($sModuleId);
|
||||
|
||||
return $sModuleName;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
IssueLog::Error("Cannot compute class properly", null, [$e->getTraceAsString(), $e->getMessage()]);
|
||||
return "";
|
||||
}
|
||||
|
||||
return 'core';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3163,7 +3185,6 @@ abstract class MetaModel
|
||||
$aMandatParams = [
|
||||
"category" => "group classes by modules defining their visibility in the UI",
|
||||
"key_type" => "autoincrement | string",
|
||||
//"created_in" => "module_name where class is defined",
|
||||
"name_attcode" => "define which attribute is the class name, may be an array of attributes (format specified in the dictionary as 'Class:myclass/Name' => '%1\$s %2\$s...'",
|
||||
"state_attcode" => "define which attribute is representing the state (object lifecycle)",
|
||||
"reconc_keys" => "define the attributes that will 'almost uniquely' identify an object in batch processes",
|
||||
@@ -3190,10 +3211,6 @@ abstract class MetaModel
|
||||
self::$m_aParentClasses[$sClass] = [];
|
||||
self::$m_aChildClasses[$sClass] = [];
|
||||
|
||||
if (! array_key_exists('created_in', $aParams)) {
|
||||
$aParams['created_in'] = self::$m_aCreatedIn[$sClass] ?? '';
|
||||
}
|
||||
|
||||
self::$m_aClassParams[$sClass] = $aParams;
|
||||
|
||||
self::$m_aAttribDefs[$sClass] = [];
|
||||
@@ -5873,7 +5890,6 @@ abstract class MetaModel
|
||||
// todo - verifier que toutes les classes mentionnees ici sont chargees dans InitClasses()
|
||||
self::$m_aExtensionClassNames = $result['m_aExtensionClassNames'];
|
||||
self::$m_Category2Class = $result['m_Category2Class'];
|
||||
self::$m_aCreatedIn = $result['m_aCreatedIn'];
|
||||
self::$m_aRootClasses = $result['m_aRootClasses'];
|
||||
self::$m_aParentClasses = $result['m_aParentClasses'];
|
||||
self::$m_aChildClasses = $result['m_aChildClasses'];
|
||||
@@ -5909,7 +5925,6 @@ abstract class MetaModel
|
||||
$aCache = [];
|
||||
$aCache['m_aExtensionClassNames'] = self::$m_aExtensionClassNames;
|
||||
$aCache['m_Category2Class'] = self::$m_Category2Class;
|
||||
$aCache['m_aCreatedIn'] = self::$m_aCreatedIn; // array of "classname" => "created_in"
|
||||
$aCache['m_aRootClasses'] = self::$m_aRootClasses; // array of "classname" => "rootclass"
|
||||
$aCache['m_aParentClasses'] = self::$m_aParentClasses; // array of ("classname" => array of "parentclass")
|
||||
$aCache['m_aChildClasses'] = self::$m_aChildClasses; // array of ("classname" => array of "childclass")
|
||||
@@ -6010,12 +6025,11 @@ abstract class MetaModel
|
||||
|
||||
/**
|
||||
* @param string $sToInclude
|
||||
* @param string|null $sModuleType
|
||||
* @param string|null $sModuleName
|
||||
* @param string $sModuleType
|
||||
*
|
||||
* @throws \CoreException
|
||||
*/
|
||||
public static function IncludeModule($sToInclude, $sModuleType = null, $sModuleName = null)
|
||||
public static function IncludeModule($sToInclude, $sModuleType = null)
|
||||
{
|
||||
$sFirstChar = substr($sToInclude, 0, 1);
|
||||
$sSecondChar = substr($sToInclude, 1, 1);
|
||||
@@ -6046,17 +6060,6 @@ abstract class MetaModel
|
||||
}
|
||||
}
|
||||
|
||||
if (! is_null($sModuleName)) {
|
||||
try {
|
||||
$aClasses = IncludeFileReader::GetInstance()->GetClasses($sFile);
|
||||
foreach ($aClasses as $sFoundClass) {
|
||||
MetaModel::$m_aCreatedIn[$sFoundClass] = $sModuleName;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
SetupLog::Error(__METHOD__, null, [$e]);
|
||||
}
|
||||
}
|
||||
|
||||
// Note: We do not expect the modules to output characters while loading them.
|
||||
// Therefore, and because unexpected characters can corrupt the output,
|
||||
// they must be trashed here.
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
<?php
|
||||
|
||||
use PhpParser\Error;
|
||||
use PhpParser\ParserFactory;
|
||||
|
||||
class IncludeFileReader
|
||||
{
|
||||
private static IncludeFileReader $oInstance;
|
||||
|
||||
protected function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
final public static function GetInstance(): IncludeFileReader
|
||||
{
|
||||
if (!isset(IncludeFileReader::$oInstance)) {
|
||||
IncludeFileReader::$oInstance = new IncludeFileReader();
|
||||
}
|
||||
|
||||
return self::$oInstance;
|
||||
}
|
||||
|
||||
final public static function SetInstance(?IncludeFileReader $oInstance): void
|
||||
{
|
||||
IncludeFileReader::$oInstance = $oInstance;
|
||||
}
|
||||
|
||||
public function GetClasses($sPath): array
|
||||
{
|
||||
try {
|
||||
$oParser = (new ParserFactory())->createForNewestSupportedVersion();
|
||||
$aNodes = $oParser->parse(file_get_contents($sPath));
|
||||
} catch (Error $e) {
|
||||
throw new Exception("PHP Class Parsing of $sPath caused an exception: ".$e->getMessage(), 0, $e);
|
||||
}
|
||||
|
||||
$aRes = [];
|
||||
try {
|
||||
foreach ($aNodes as $sKey => $oNode) {
|
||||
if ($oNode instanceof PhpParser\Node\Stmt\Class_) {
|
||||
/** @var PhpParser\Node\Stmt\Class_ $oNode */
|
||||
//var_dump($oNode->name);
|
||||
$aRes[] = $oNode->name->name;
|
||||
}
|
||||
|
||||
}
|
||||
return $aRes;
|
||||
} catch (Exception $e) {
|
||||
throw new Exception("PHP Class Discovery of $sPath caused an exception: ".$e->getMessage(), 0, $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -477,7 +477,7 @@ class MFCompiler
|
||||
$sClass = $oClass->getAttribute("id");
|
||||
$aAllClasses[] = $sClass;
|
||||
try {
|
||||
$sCompiledCode .= $this->CompileClass($oClass, $sModuleName, $sTempTargetDir, $sFinalTargetDir, $sRelativeDir);
|
||||
$sCompiledCode .= $this->CompileClass($oClass, $sTempTargetDir, $sFinalTargetDir, $sRelativeDir);
|
||||
} catch (DOMFormatException $e) {
|
||||
$sMessage = "Failed to process class '$sClass', ";
|
||||
if (!empty($sModuleRootDir)) {
|
||||
@@ -626,7 +626,7 @@ EOF;
|
||||
// files to include (PHP datamodels)
|
||||
foreach ($oModule->GetFilesToInclude('business') as $sRelFileName) {
|
||||
if (file_exists("{$sTempTargetDir}/{$sRelativeDir}/{$sRelFileName}")) {
|
||||
$aDataModelFiles[] = sprintf("MetaModel::IncludeModule(MODULESROOT.'/$sRelativeDir/$sRelFileName', null, '%s');", $oModule->GetName());
|
||||
$aDataModelFiles[] = "MetaModel::IncludeModule(MODULESROOT.'/$sRelativeDir/$sRelFileName');";
|
||||
} else {
|
||||
/** @noinspection NestedPositiveIfStatementsInspection */
|
||||
if (utils::IsDevelopmentEnvironment()) {
|
||||
@@ -643,7 +643,7 @@ EOF;
|
||||
}
|
||||
// files to include (PHP webservices providers)
|
||||
foreach ($oModule->GetFilesToInclude('webservices') as $sRelFileName) {
|
||||
$aWebservicesFiles[] = sprintf("MetaModel::IncludeModule(MODULESROOT.'/$sRelativeDir/$sRelFileName', null, '%s');", $oModule->GetName());
|
||||
$aWebservicesFiles[] = "MetaModel::IncludeModule(MODULESROOT.'/$sRelativeDir/$sRelFileName');";
|
||||
}
|
||||
} // foreach module
|
||||
|
||||
@@ -1189,7 +1189,6 @@ EOF
|
||||
|
||||
/**
|
||||
* @param \MFElement $oClass
|
||||
* @param string $sModuleName
|
||||
* @param string $sTempTargetDir
|
||||
* @param string $sFinalTargetDir
|
||||
* @param string $sModuleRelativeDir
|
||||
@@ -1197,7 +1196,7 @@ EOF
|
||||
* @return string
|
||||
* @throws \DOMFormatException
|
||||
*/
|
||||
protected function CompileClass($oClass, $sModuleName, $sTempTargetDir, $sFinalTargetDir, $sModuleRelativeDir)
|
||||
protected function CompileClass($oClass, $sTempTargetDir, $sFinalTargetDir, $sModuleRelativeDir)
|
||||
{
|
||||
$sClass = $oClass->getAttribute('id');
|
||||
$oProperties = $oClass->GetUniqueElement('properties');
|
||||
@@ -1210,7 +1209,6 @@ EOF
|
||||
$aClassParams = [];
|
||||
$aClassParams['category'] = $this->GetPropString($oProperties, 'category', '');
|
||||
$aClassParams['key_type'] = "'autoincrement'";
|
||||
$aClassParams['created_in'] = "'$sModuleName'";
|
||||
if ((bool)$this->GetPropNumber($oProperties, 'is_link', 0)) {
|
||||
$aClassParams['is_link'] = 'true';
|
||||
}
|
||||
|
||||
@@ -499,24 +499,29 @@ class MetaModelTest extends ItopDataTestCase
|
||||
];
|
||||
}
|
||||
|
||||
public function testGetCreatedIn_UnknownClass()
|
||||
{
|
||||
$this->assertEquals('', MetaModel::GetModuleName('GABUZOMEU'));
|
||||
}
|
||||
|
||||
public function testGetCreatedIn_ClassComingFromCorePhpFile()
|
||||
{
|
||||
$this->assertEquals('', MetaModel::GetCreatedIn('BackgroundTask'));
|
||||
$this->assertEquals('core', MetaModel::GetModuleName('BackgroundTask'));
|
||||
}
|
||||
|
||||
public function testGetCreatedIn_ClassComingFromCorePhpFile2()
|
||||
{
|
||||
$this->assertEquals('core', MetaModel::GetCreatedIn('lnkActionNotificationToContact'));
|
||||
$this->assertEquals('core', MetaModel::GetModuleName('lnkActionNotificationToContact'));
|
||||
}
|
||||
|
||||
public function testGetCreatedIn_ClassComingFromModulePhpFile()
|
||||
{
|
||||
$this->assertEquals('itop-attachments', MetaModel::GetCreatedIn('CMDBChangeOpAttachmentAdded'));
|
||||
$this->assertEquals('itop-attachments', MetaModel::GetModuleName('CMDBChangeOpAttachmentAdded'));
|
||||
}
|
||||
|
||||
public function testGetCreatedIn_ClassComingFromXmlDataModelFile()
|
||||
{
|
||||
$this->assertEquals('authent-ldap', MetaModel::GetCreatedIn('UserLDAP'));
|
||||
$this->assertEquals('authent-ldap', MetaModel::GetModuleName('UserLDAP'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Combodo\iTop\Test\UnitTest\ItopTestCase;
|
||||
|
||||
class IncludeFileReaderTest extends ItopTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->RequireOnceItopFile('core/metamodel/IncludeFileReader.php');
|
||||
}
|
||||
|
||||
public function testGetClasses()
|
||||
{
|
||||
$expected = [
|
||||
'iTopOwnershipToken',
|
||||
'iTopOwnershipLock',
|
||||
];
|
||||
$this->assertEquals($expected, IncludeFileReader::GetInstance()->GetClasses(__DIR__.'/resources/ownershiplock.class.inc.php'));
|
||||
}
|
||||
}
|
||||
@@ -1,328 +0,0 @@
|
||||
<?php
|
||||
|
||||
// Copyright (C) 2024 Combodo SAS
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
// iTop 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.
|
||||
//
|
||||
// iTop 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 should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
/**
|
||||
* Mechanism to obtain an exclusive lock while editing an object
|
||||
*
|
||||
* @package iTopORM
|
||||
*/
|
||||
|
||||
/**
|
||||
* Persistent storage (in the database) for remembering that an object is locked
|
||||
*/
|
||||
class iTopOwnershipToken extends DBObject
|
||||
{
|
||||
public static function Init()
|
||||
{
|
||||
$aParams =
|
||||
[
|
||||
'category' => '',
|
||||
'key_type' => 'autoincrement',
|
||||
'name_attcode' => ['obj_class', 'obj_key'],
|
||||
'state_attcode' => '',
|
||||
'reconc_keys' => [''],
|
||||
'db_table' => 'priv_ownership_token',
|
||||
'db_key_field' => 'id',
|
||||
'db_finalclass_field' => '',
|
||||
];
|
||||
MetaModel::Init_Params($aParams);
|
||||
MetaModel::Init_InheritAttributes();
|
||||
MetaModel::Init_AddAttribute(new AttributeDateTime("acquired", ["allowed_values" => null, "sql" => 'acquired', "default_value" => 'NOW()', "is_null_allowed" => false, "depends_on" => []]));
|
||||
MetaModel::Init_AddAttribute(new AttributeDateTime("last_seen", ["allowed_values" => null, "sql" => 'last_seen', "default_value" => 'NOW()', "is_null_allowed" => false, "depends_on" => []]));
|
||||
MetaModel::Init_AddAttribute(new AttributeString("obj_class", ["allowed_values" => null, "sql" => 'obj_class', "default_value" => '', "is_null_allowed" => false, "depends_on" => []]));
|
||||
MetaModel::Init_AddAttribute(new AttributeInteger("obj_key", ["allowed_values" => null, "sql" => 'obj_key', "default_value" => '', "is_null_allowed" => true, "depends_on" => []]));
|
||||
MetaModel::Init_AddAttribute(new AttributeString("token", ["allowed_values" => null, "sql" => 'token', "default_value" => '', "is_null_allowed" => true, "depends_on" => []]));
|
||||
MetaModel::Init_AddAttribute(new AttributeExternalKey("user_id", ["targetclass" => "User", "jointype" => '', "allowed_values" => null, "sql" => "user_id", "is_null_allowed" => true, "on_target_delete" => DEL_SILENT, "depends_on" => []]));
|
||||
|
||||
MetaModel::Init_SetZListItems('details', ['obj_class', 'obj_key', 'last_seen', 'token']);
|
||||
MetaModel::Init_SetZListItems('standard_search', ['obj_class', 'obj_key', 'last_seen', 'token']);
|
||||
MetaModel::Init_SetZListItems('list', ['obj_class', 'obj_key', 'last_seen', 'token']);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility class to acquire/extend/release/kill an exclusive lock on a given persistent object,
|
||||
* for example to prevent concurrent edition of the same object.
|
||||
* Each lock has an expiration delay of 120 seconds (tunable via the configuration parameter 'concurrent_lock_expiration_delay')
|
||||
* A watchdog (called twice during this delay) is in charge of keeping the lock "alive" while an object is being edited.
|
||||
*/
|
||||
class iTopOwnershipLock
|
||||
{
|
||||
protected $sObjClass;
|
||||
protected $iObjKey;
|
||||
protected $oToken;
|
||||
|
||||
/**
|
||||
* Acquires an exclusive lock on the specified DBObject. Once acquired, the lock is identified
|
||||
* by a unique "token" string.
|
||||
* @param string $sObjClass The class of the object for which to acquire the lock
|
||||
* @param integer $iObjKey The identifier of the object for which to acquire the lock
|
||||
* @return multitype:boolean iTopOwnershipLock Ambigous <boolean, string, DBObjectSet>
|
||||
*/
|
||||
public static function AcquireLock($sObjClass, $iObjKey)
|
||||
{
|
||||
$oMutex = new iTopMutex('lock_'.$sObjClass.'::'.$iObjKey);
|
||||
|
||||
$oMutex->Lock();
|
||||
$oOwnershipLock = new iTopOwnershipLock($sObjClass, $iObjKey);
|
||||
$token = $oOwnershipLock->Acquire();
|
||||
$oMutex->Unlock();
|
||||
|
||||
return ['success' => $token !== false, 'token' => $token, 'lock' => $oOwnershipLock, 'acquired' => $oOwnershipLock->oToken->Get('acquired')];
|
||||
}
|
||||
|
||||
/**
|
||||
* Extends the ownership lock or acquires it if none exists
|
||||
* Returns a hash array with 3 elements:
|
||||
* 'status': either true or false, tells if the lock is still owned
|
||||
* 'owner': is status is false, the User object currently owning the lock
|
||||
* 'operation': whether the lock was 'renewed' (i.e. the lock was valid, its duration has been extended) or 'acquired' (there was no valid lock for this object and a new one was created)
|
||||
* @param string $sToken
|
||||
* @return multitype:boolean string User
|
||||
*/
|
||||
public static function ExtendLock($sObjClass, $iObjKey, $sToken)
|
||||
{
|
||||
$oMutex = new iTopMutex('lock_'.$sObjClass.'::'.$iObjKey);
|
||||
|
||||
$oMutex->Lock();
|
||||
$oOwnershipLock = new iTopOwnershipLock($sObjClass, $iObjKey);
|
||||
$aResult = $oOwnershipLock->Extend($sToken);
|
||||
$oMutex->Unlock();
|
||||
|
||||
return $aResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases the given lock for the specified object
|
||||
*
|
||||
* @param string $sObjClass The class of the object
|
||||
* @param int $iObjKey The identifier of the object
|
||||
* @param string $sToken The string identifying the lock
|
||||
* @return boolean
|
||||
*/
|
||||
public static function ReleaseLock($sObjClass, $iObjKey, $sToken)
|
||||
{
|
||||
$oMutex = new iTopMutex('lock_'.$sObjClass.'::'.$iObjKey);
|
||||
|
||||
$oMutex->Lock();
|
||||
$oOwnershipLock = new iTopOwnershipLock($sObjClass, $iObjKey);
|
||||
$bResult = $oOwnershipLock->Release($sToken);
|
||||
self::DeleteExpiredLocks(); // Cleanup orphan locks
|
||||
$oMutex->Unlock();
|
||||
|
||||
return $bResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Kills the lock for the specified object
|
||||
*
|
||||
* @param string $sObjClass The class of the object
|
||||
* @param int $iObjKey The identifier of the object
|
||||
* @return boolean
|
||||
*/
|
||||
public static function KillLock($sObjClass, $iObjKey)
|
||||
{
|
||||
$oMutex = new iTopMutex('lock_'.$sObjClass.'::'.$iObjKey);
|
||||
|
||||
$oMutex->Lock();
|
||||
$sOQL = "SELECT iTopOwnershipToken WHERE obj_class = :obj_class AND obj_key = :obj_key";
|
||||
$oSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL, ['obj_class' => $sObjClass, 'obj_key' => $iObjKey]));
|
||||
while ($oLock = $oSet->Fetch()) {
|
||||
$oLock->DBDelete();
|
||||
}
|
||||
$oMutex->Unlock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an exclusive lock exists on the specified DBObject.
|
||||
* @param string $sObjClass The class of the object for which to acquire the lock
|
||||
* @param integer $iObjKey The identifier of the object for which to acquire the lock
|
||||
* @return multitype:boolean iTopOwnershipLock Ambigous <boolean, string, DBObjectSet>
|
||||
*/
|
||||
public static function IsLocked($sObjClass, $iObjKey)
|
||||
{
|
||||
$bLocked = false;
|
||||
$oMutex = new iTopMutex('lock_'.$sObjClass.'::'.$iObjKey);
|
||||
|
||||
$oMutex->Lock();
|
||||
$oOwnershipLock = new iTopOwnershipLock($sObjClass, $iObjKey);
|
||||
if ($oOwnershipLock->IsOwned()) {
|
||||
$bLocked = true;
|
||||
}
|
||||
$oMutex->Unlock();
|
||||
|
||||
return ['locked' => $bLocked, 'owner' => $oOwnershipLock->GetOwner()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current owner of the lock
|
||||
* @return User
|
||||
*/
|
||||
public function GetOwner()
|
||||
{
|
||||
if ($this->IsTokenValid()) {
|
||||
return MetaModel::GetObject('User', $this->oToken->Get('user_id'), false, true);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The constructor is protected. Use the static methods AcquireLock / ExtendLock / ReleaseLock / KillLock
|
||||
* which are protected against concurrent access by a Mutex.
|
||||
* @param string $sObjClass The class of the object for which to create a lock
|
||||
* @param integer $iObjKey The identifier of the object for which to create a lock
|
||||
*/
|
||||
protected function __construct($sObjClass, $iObjKey)
|
||||
{
|
||||
$sOQL = "SELECT iTopOwnershipToken WHERE obj_class = :obj_class AND obj_key = :obj_key";
|
||||
$oSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL, ['obj_class' => $sObjClass, 'obj_key' => $iObjKey]));
|
||||
$this->oToken = $oSet->Fetch();
|
||||
$this->sObjClass = $sObjClass;
|
||||
$this->iObjKey = $iObjKey;
|
||||
// IssueLog::Info("iTopOwnershipLock::__construct($sObjClass, $iObjKey) oToken::".($this->oToken ? $this->oToken->GetKey() : 'null'));
|
||||
}
|
||||
|
||||
protected function IsOwned()
|
||||
{
|
||||
return $this->IsTokenValid();
|
||||
}
|
||||
|
||||
protected function Acquire($sToken = null)
|
||||
{
|
||||
if ($this->IsTokenValid()) {
|
||||
// IssueLog::Info("Acquire($sToken) returns false");
|
||||
return false;
|
||||
} else {
|
||||
$sToken = $this->TakeOwnership($sToken);
|
||||
// IssueLog::Info("Acquire($sToken) returns $sToken");
|
||||
return $sToken;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extends the ownership lock or acquires it if none exists
|
||||
* Returns a hash array with 3 elements:
|
||||
* 'status': either true or false, tells if the lock is still owned
|
||||
* 'owner': is status is false, the User object currently owning the lock
|
||||
* 'operation': whether the lock was 'renewed' (i.e. the lock was valid, its duration was extended) or 'expired' (there was no valid lock for this object) or 'lost' (someone else grabbed it)
|
||||
* 'acquired': date at which the lock was initially acquired
|
||||
* @param string $sToken
|
||||
* @return multitype:boolean string User
|
||||
*/
|
||||
protected function Extend($sToken)
|
||||
{
|
||||
$aResult = ['status' => true, 'owner' => '', 'operation' => 'renewed'];
|
||||
|
||||
if ($this->IsTokenValid()) {
|
||||
if ($sToken === $this->oToken->Get('token')) {
|
||||
$this->oToken->Set('last_seen', date(AttributeDateTime::GetSQLFormat()));
|
||||
$this->oToken->DBUpdate();
|
||||
$aResult['acquired'] = $this->oToken->Get('acquired');
|
||||
} else {
|
||||
// IssueLog::Info("Extend($sToken) returns false");
|
||||
$aResult['status'] = false;
|
||||
$aResult['operation'] = 'lost';
|
||||
$aResult['owner'] = $this->GetOwner();
|
||||
$aResult['acquired'] = $this->oToken->Get('acquired');
|
||||
}
|
||||
} else {
|
||||
$aResult['status'] = false;
|
||||
$aResult['operation'] = 'expired';
|
||||
}
|
||||
// IssueLog::Info("Extend($sToken) returns true");
|
||||
return $aResult;
|
||||
}
|
||||
|
||||
protected function HasOwnership($sToken)
|
||||
{
|
||||
$bRet = false;
|
||||
if ($this->IsTokenValid()) {
|
||||
if ($sToken === $this->oToken->Get('token')) {
|
||||
$bRet = true;
|
||||
}
|
||||
}
|
||||
// IssueLog::Info("HasOwnership($sToken) return $bRet");
|
||||
return $bRet;
|
||||
}
|
||||
|
||||
protected function Release($sToken)
|
||||
{
|
||||
$bRet = false;
|
||||
// IssueLog::Info("Release... begin [$sToken]");
|
||||
if (($this->oToken) && ($sToken === $this->oToken->Get('token'))) {
|
||||
// IssueLog::Info("oToken::".$this->oToken->GetKey().' ('.$sToken.') to be deleted');
|
||||
$this->oToken->DBDelete();
|
||||
// IssueLog::Info("oToken deleted");
|
||||
$this->oToken = null;
|
||||
$bRet = true;
|
||||
} elseif ($this->oToken == null) {
|
||||
// IssueLog::Info("Release FAILED oToken == null !!!");
|
||||
} else {
|
||||
// IssueLog::Info("Release FAILED inconsistent tokens: sToken=\"".$sToken.'", oToken->Get(\'token\')="'.$this->oToken->Get('token').'"');
|
||||
}
|
||||
// IssueLog::Info("Release... end");
|
||||
return $bRet;
|
||||
}
|
||||
|
||||
protected function IsTokenValid()
|
||||
{
|
||||
$bRet = false;
|
||||
if ($this->oToken != null) {
|
||||
$sToken = $this->oToken->Get('token');
|
||||
$sDate = $this->oToken->Get('last_seen');
|
||||
if (($sDate != '') && ($sToken != '')) {
|
||||
$oLastSeenTime = new DateTime($sDate);
|
||||
$iNow = date('U');
|
||||
if (($iNow - $oLastSeenTime->format('U')) < MetaModel::GetConfig()->Get('concurrent_lock_expiration_delay')) {
|
||||
$bRet = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $bRet;
|
||||
}
|
||||
|
||||
protected function TakeOwnership($sToken = null)
|
||||
{
|
||||
if ($this->oToken == null) {
|
||||
$this->oToken = new iTopOwnershipToken();
|
||||
$this->oToken->Set('obj_class', $this->sObjClass);
|
||||
$this->oToken->Set('obj_key', $this->iObjKey);
|
||||
}
|
||||
$this->oToken->Set('acquired', date(AttributeDateTime::GetSQLFormat()));
|
||||
$this->oToken->Set('user_id', UserRights::GetUserId());
|
||||
$this->oToken->Set('last_seen', date(AttributeDateTime::GetSQLFormat()));
|
||||
if ($sToken === null) {
|
||||
$sToken = sprintf('%X', microtime(true));
|
||||
}
|
||||
$this->oToken->Set('token', $sToken);
|
||||
$this->oToken->DBWrite();
|
||||
return $this->oToken->Get('token');
|
||||
}
|
||||
|
||||
protected static function DeleteExpiredLocks()
|
||||
{
|
||||
$sOQL = "SELECT iTopOwnershipToken WHERE last_seen < :last_seen_limit";
|
||||
$oSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL, ['last_seen_limit' => date(AttributeDateTime::GetSQLFormat(), time() - MetaModel::GetConfig()->Get('concurrent_lock_expiration_delay'))]));
|
||||
while ($oToken = $oSet->Fetch()) {
|
||||
$oToken->DBDelete();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user