N°9165 - secure data cleanup

This commit is contained in:
Eric Espié
2026-04-30 10:20:25 +02:00
committed by GitHub
parent 0fc7434794
commit 4582256f01
31 changed files with 1505 additions and 511 deletions

View File

@@ -0,0 +1,301 @@
<?php
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Test\UnitTest\Module\DataFeatureRemoval\Service;
use Cleanup;
use Combodo\iTop\DataFeatureRemoval\Entity\DataCleanupSummaryEntity;
use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalException;
use Combodo\iTop\DataFeatureRemoval\Service\DataCleanupService;
use Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCase;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
use PHPUnit\Framework\MockObject\MockObject;
/**
* Unit tests for the CleanupService cf the Combodo Data Feature Removal module.
*
* These tests cover:
* - GetCleanupSummary method: handling null and empty input, and verifying summary output for various delete/update scenarios.
* - ExecuteCleanup method: confirming that an exception is thrown when issues are detected in the deletion plan.
*
* Key aspects tested:
* - Consistent singleton instance management.
* - Accurate summary generation for deletion and update operations, including mode and issue reporting per class.
* - Edge cases such as null, empty, and multiple classes.
* - Proper exception handling when the deletion plan contains issues.
*
* The tests use PHPUnit, mocks for Cleanup and CleanupService, and data providers to cover multiple scenarios.
*
* @see DataCleanupService
* @see DataCleanupSummaryEntity
* @see ItopDataTestCase
*/
class DataCleanupServiceTest extends ItopCustomDatamodelTestCase
{
private ExecutionLimits&MockObject $oExecutionLimits;
public function GetDatamodelDeltaAbsPath(): string
{
return __DIR__.'/data_cleanup_delta.xml';
}
//--- GetCleanupSummary tests ---
/**
* Tests that GetCleanupSummary returns an empty array when passed null as input.
*/
public function testGetCleanupSummaryReturnsEmptyArrayWhenNull(): void
{
$oService = new DataCleanupService();
$aResult = $oService->GetCleanupSummary(null);
$this->assertIsArray($aResult, 'Expected result to be an array when input is null.');
$this->assertEmpty($aResult, 'Expected result to be empty array when input is null.');
}
//--- ExecuteCleanup tests ---
public function testExecuteCleanup_DeleteOneObjPerClassWithoutLimit()
{
$this->GivenDFRTreeInDB(<<<EOF
DFRToRemoveLeaf_1 <- DFRToUpdate_1
DFRToRemoveLeaf_1 <- DFRRemovedCollateral_1
DFRRemovedCollateral_1 <- DFRRemovedCollateralCascade_1
EOF);
$aClasses = [ 'DFRToRemoveLeaf' ];
$oService = new DataCleanupService();
$aRes = $oService->ExecuteCleanup($aClasses);
$aExpected = [
['DFRToUpdate', 1, 0 ],
['DFRToRemoveLeaf', 0, 1 ],
['DFRRemovedCollateral', 0, 1 ],
['DFRRemovedCollateralCascade', 0, 1 ],
];
$this->AssertSummaryEquals($aExpected, $aRes);
}
public function testExecuteCleanup_DeleteManyObjPerClassWithoutLimit()
{
$this->GivenDFRTreeInDB(<<<EOF
DFRToRemoveLeaf_1 <- DFRToUpdate_1
DFRToRemoveLeaf_1 <- DFRRemovedCollateral_1
DFRRemovedCollateral_1 <- DFRRemovedCollateralCascade_1
DFRToRemoveLeaf_2 <- DFRToUpdate_2
DFRToRemoveLeaf_2 <- DFRRemovedCollateral_2
DFRRemovedCollateral_2 <- DFRRemovedCollateralCascade_2
DFRToRemoveLeaf_3 <- DFRToUpdate_3
DFRToRemoveLeaf_3 <- DFRRemovedCollateral_3
DFRRemovedCollateral_3 <- DFRRemovedCollateralCascade_3
EOF);
$aClasses = [ 'DFRToRemoveLeaf' ];
$oService = new DataCleanupService();
$aRes = $oService->ExecuteCleanup($aClasses);
$aExpected = [
['DFRToUpdate', 3, 0 ],
['DFRToRemoveLeaf', 0, 3 ],
['DFRRemovedCollateral', 0, 3 ],
['DFRRemovedCollateralCascade', 0, 3 ],
];
$this->AssertSummaryEquals($aExpected, $aRes);
}
public function testGetCleanupSummary_DeleteManyObjPerClassWithoutLimit()
{
$this->GivenDFRTreeInDB(<<<EOF
DFRToRemoveLeaf_1 <- DFRToUpdate_1
DFRToRemoveLeaf_1 <- DFRRemovedCollateral_1
DFRRemovedCollateral_1 <- DFRRemovedCollateralCascade_1
DFRToRemoveLeaf_2 <- DFRToUpdate_2
DFRToRemoveLeaf_2 <- DFRRemovedCollateral_2
DFRRemovedCollateral_2 <- DFRRemovedCollateralCascade_2
DFRToRemoveLeaf_3 <- DFRToUpdate_3
DFRToRemoveLeaf_3 <- DFRRemovedCollateral_3
DFRRemovedCollateral_3 <- DFRRemovedCollateralCascade_3
EOF);
$aClasses = [ 'DFRToRemoveLeaf' ];
$oService = new DataCleanupService();
$aRes = $oService->GetCleanupSummary($aClasses);
$aExpected = [
['DFRToUpdate', 3, 0 ],
['DFRToRemoveLeaf', 0, 3 ],
['DFRRemovedCollateral', 0, 3 ],
['DFRRemovedCollateralCascade', 0, 3 ],
];
$this->AssertSummaryEquals($aExpected, $aRes);
}
public function testExecuteCleanup_ManualDeleteShouldFail()
{
$this->GivenDFRTreeInDB(<<<EOF
DFRToRemoveLeaf_1 <- DFRManual_1
EOF);
$aClasses = [ 'DFRToRemoveLeaf' ];
$this->expectException(DataFeatureRemovalException::class);
$this->expectExceptionMessage('Deletion Plan cannot be executed due to issues');
$oService = new DataCleanupService();
$oService->ExecuteCleanup($aClasses);
}
public function testGetCleanupSummary_ManualDeleteShouldFail()
{
$this->GivenDFRTreeInDB(<<<EOF
DFRToRemoveLeaf_1 <- DFRManual_1
DFRToRemoveLeaf_1 <- DFRManual_2
DFRToRemoveLeaf_2 <- DFRManual_3
EOF);
$aClasses = [ 'DFRToRemoveLeaf' ];
$oService = new DataCleanupService();
$aRes = $oService->GetCleanupSummary($aClasses);
$aExpected = [
['DFRManual', 0, 0, 3 ],
['DFRToRemoveLeaf', 0, 2],
];
$this->AssertSummaryEquals($aExpected, $aRes);
}
private function AssertSummaryEquals(array $expected, $actual, $sMessage = '')
{
$aExpected = [];
foreach ($expected as $line) {
$sClass = $line[0];
$iUpdate = $line[1];
$iDelete = $line[2];
$iIssue = $line[3] ?? 0;
$oCleanupSummaryEntity = new DataCleanupSummaryEntity($sClass);
$oCleanupSummaryEntity->iUpdateCount = $iUpdate;
$oCleanupSummaryEntity->iDeleteCount = $iDelete;
$oCleanupSummaryEntity->iIssueCount = $iIssue;
$aExpected[$sClass] = $oCleanupSummaryEntity;
}
$this->assertEquals($aExpected, $actual, $sMessage);
}
public static function ExecuteCleanup_StopInProcessKeepDatabaseOk(): array
{
return [
'Stop after 1' => [
1,
[
['DFRToUpdate', 1, 0],
],
],
'Stop after 2' => [
2,
[
['DFRToUpdate', 1, 0],
['DFRRemovedCollateralCascade', 0, 1],
],
],
'Stop after 3' => [
3,
[
['DFRToUpdate', 1, 0],
['DFRRemovedCollateralCascade', 0, 1],
['DFRRemovedCollateral', 0, 1],
],
],
'Stop after 4' => [
4,
[
['DFRToUpdate', 1, 0],
['DFRRemovedCollateralCascade', 0, 1],
['DFRRemovedCollateral', 0, 1],
['DFRToRemoveLeaf', 0, 1],
],
],
'Stop after 5' => [
5,
[
['DFRToUpdate', 2, 0],
['DFRRemovedCollateralCascade', 0, 1],
['DFRRemovedCollateral', 0, 1],
['DFRToRemoveLeaf', 0, 1],
],
],
];
}
/**
* @dataProvider ExecuteCleanup_StopInProcessKeepDatabaseOk
*/
public function testExecuteCleanup_StopInProcessKeepDatabaseOk(int $iExecutionCount, array $aExpected): void
{
$this->GivenDFRTreeInDB(<<<EOF
DFRToRemoveLeaf_1 <- DFRToUpdate_1
DFRToRemoveLeaf_1 <- DFRRemovedCollateral_1
DFRRemovedCollateral_1 <- DFRRemovedCollateralCascade_1
DFRToRemoveLeaf_2 <- DFRToUpdate_2
DFRToRemoveLeaf_2 <- DFRRemovedCollateral_2
DFRRemovedCollateral_2 <- DFRRemovedCollateralCascade_2
DFRToRemoveLeaf_3 <- DFRToUpdate_3
DFRToRemoveLeaf_3 <- DFRRemovedCollateral_3
DFRRemovedCollateral_3 <- DFRRemovedCollateralCascade_3
EOF);
$aClasses = [ 'DFRToRemoveLeaf' ];
$oDeletionPlaService = new DataCleanupService();
$this->GivenExecutionLimits($iExecutionCount);
$this->SetNonPublicProperty($oDeletionPlaService, 'oExecutionLimits', $this->oExecutionLimits);
$aRes = $oDeletionPlaService->ExecuteCleanup($aClasses);
$this->AssertSummaryEquals($aExpected, $aRes);
}
private function GivenDFRTreeInDB(string $sTree)
{
$aTree = explode("\n", $sTree);
foreach ($aTree as $sLine) {
if (trim($sLine) === "") {
continue;
}
$this->GivenDFRTreeLineInDB($sLine);
}
}
private array $aIdByObjectName = [];
private function GivenDFRTreeLineInDB(string $sLine)
{
list($sLeft, $sRight) = explode('<-', $sLine);
$sLeft = trim($sLeft);
$iLeftId = $this->aIdByObjectName[$sLeft] ?? 0;
if ($iLeftId === 0) {
list($sChildClass, ) = explode('_', $sLeft, 2);
$iLeftId = $this->GivenObjectInDB($sChildClass, ['name' => $sLeft]);
$this->aIdByObjectName[$sLeft] = $iLeftId;
}
$sRight = trim($sRight);
list($sChildClass, ) = explode('_', $sRight, 2);
$iRightId = $this->GivenObjectInDB($sChildClass, ['name' => $sRight, 'extkey_id' => $iLeftId]);
$this->aIdByObjectName[$sRight] = $iRightId;
}
private function GivenExecutionLimits(int $iStopAfterCallNumberReached): void
{
$matcher = $this->any();
$this->oExecutionLimits = $this->createMock(ExecutionLimits::class);
$this->oExecutionLimits->expects($matcher)
->method('ShouldStopExecution')->willReturnCallback(function () use ($matcher, $iStopAfterCallNumberReached) {
$invocationCount = $matcher->getInvocationCount();
return ($invocationCount >= $iStopAfterCallNumberReached);
});
}
}

View File

@@ -1,277 +0,0 @@
<?php
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Test\UnitTest\Module\DataFeatureRemoval\Service;
use Combodo\iTop\DataFeatureRemoval\Entity\DeletionPlanSummaryEntity;
use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalException;
use Combodo\iTop\DataFeatureRemoval\Service\DeletionPlanService;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
use DeletionPlan;
/**
* Unit tests for the DeletionPlanService class in the Combodo Data Feature Removal module.
*
* These tests cover:
* - GetDeletionPlanSummary method: handling null and empty input, and verifying summary output for various delete/update scenarios.
* - ExecuteDeletionPlan method: confirming that an exception is thrown when issues are detected in the deletion plan.
*
* Key aspects tested:
* - Consistent singleton instance management.
* - Accurate summary generation for deletion and update operations, including mode and issue reporting per class.
* - Edge cases such as null, empty, and multiple classes.
* - Proper exception handling when the deletion plan contains issues.
*
* The tests use PHPUnit, mocks for DeletionPlan and DeletionPlanService, and data providers to cover multiple scenarios.
*
* @see DeletionPlanService
* @see DeletionPlanSummaryEntity
* @see ItopDataTestCase
*/
class DeletionPlanServiceTest extends ItopDataTestCase
{
protected function setUp(): void
{
parent::setUp();
$this->RequireOnceItopFile('env-production/combodo-data-feature-removal/vendor/autoload.php');
}
//--- GetDeletionPlanSummary tests ---
/**
* Tests that GetDeletionPlanSummary returns an empty array when passed null as input.
*/
public function testGetDeletionPlanSummaryReturnsEmptyArrayWhenNull(): void
{
$oService = DeletionPlanService::GetInstance();
$aResult = $oService->GetDeletionPlanSummary(null);
$this->assertIsArray($aResult, 'Expected result to be an array when input is null.');
$this->assertEmpty($aResult, 'Expected result to be empty array when input is null.');
}
/**
* Tests that GetDeletionPlanSummary returns an empty array when the input class list is empty.
*/
public function testGetDeletionPlanSummaryReturnsEmptyArrayWhenEmptyClasses(): void
{
$oMockService = $this->getMockBuilder(DeletionPlanService::class)
->disableOriginalConstructor()
->onlyMethods(['GetDeletionPlan'])
->getMock();
$oDeletionPlan = new DeletionPlan();
$oMockService->method('GetDeletionPlan')->willReturn($oDeletionPlan);
DeletionPlanService::SetInstance($oMockService);
$aResult = $oMockService->GetDeletionPlanSummary([]);
$this->assertIsArray($aResult, 'Expected result to be an array when input class list is empty.');
$this->assertEmpty($aResult, 'Expected result to be empty array when input class list is empty.');
}
/**
* Tests GetDeletionPlanSummary for various delete/update scenarios using a data provider.
* Verifies summary output for each class matches expected values.
*
* @dataProvider GetDeletionPlanSummaryWithDeletesProvider
*/
public function testGetDeletionPlanSummaryWithDeletes(array $aToDelete, array $aToUpdate, array $aExpected): void
{
$oDeletionPlan = $this->createMock(DeletionPlan::class);
$oDeletionPlan->method('ListDeletes')->willReturn($aToDelete);
$oDeletionPlan->method('ListUpdates')->willReturn($aToUpdate);
$oMockService = $this->getMockBuilder(DeletionPlanService::class)
->disableOriginalConstructor()
->onlyMethods(['GetDeletionPlan'])
->getMock();
$oMockService->method('GetDeletionPlan')->willReturn($oDeletionPlan);
$aResult = $oMockService->GetDeletionPlanSummary(['SomeClass']);
foreach ($aExpected as $sClass => $aExpectedValues) {
$this->assertArrayHasKey(
$sClass,
$aResult,
"Expected key '$sClass' to exist in summary."
);
$this->assertInstanceOf(
DeletionPlanSummaryEntity::class,
$aResult[$sClass],
"Expected summary for '$sClass' to be instance of DeletionPlanSummaryEntity."
);
$this->assertEquals(
$aExpectedValues['iDeleteCount'],
$aResult[$sClass]->iDeleteCount,
"Expected iDeleteCount for '$sClass' to be {$aExpectedValues['iDeleteCount']}, got {$aResult[$sClass]->iDeleteCount}."
);
$this->assertEquals(
$aExpectedValues['iUpdateCount'],
$aResult[$sClass]->iUpdateCount,
"Expected iUpdateCount for '$sClass' to be {$aExpectedValues['iUpdateCount']}, got {$aResult[$sClass]->iUpdateCount}."
);
$this->assertEquals(
$aExpectedValues['iMode'],
$aResult[$sClass]->iMode,
"Expected iMode for '$sClass' to be {$aExpectedValues['iMode']}, got {$aResult[$sClass]->iMode}."
);
$this->assertEquals(
$aExpectedValues['sIssue'],
$aResult[$sClass]->sIssue,
"Expected sIssue for '$sClass' to be '{$aExpectedValues['sIssue']}', got '{$aResult[$sClass]->sIssue}'."
);
}
}
/**
* Provides multiple scenarios for testGetDeletionPlanSummaryWithDeletes, including deletes, updates, issues, and multiple classes.
*
* @return array
*/
public function GetDeletionPlanSummaryWithDeletesProvider(): array
{
return [
'single class with deletes only' => [
'aToDelete' => [
'Server' => [
1 => ['to_delete' => null, 'mode' => DEL_AUTO, 'requested_explicitely' => true],
2 => ['to_delete' => null, 'mode' => DEL_SILENT, 'requested_explicitely' => false],
],
],
'aToUpdate' => [],
'aExpected' => [
'Server' => [
'iDeleteCount' => 2,
'iUpdateCount' => 0,
'iMode' => DEL_AUTO,
'sIssue' => null,
],
],
],
'single class with deletes and issue' => [
'aToDelete' => [
'Server' => [
1 => ['to_delete' => null, 'mode' => DEL_MANUAL, 'requested_explicitely' => false, 'issue' => 'Cannot delete'],
],
],
'aToUpdate' => [],
'aExpected' => [
'Server' => [
'iDeleteCount' => 1,
'iUpdateCount' => 0,
'iMode' => DEL_MANUAL,
'sIssue' => 'Cannot delete',
],
],
],
'single class with updates only' => [
'aToDelete' => [],
'aToUpdate' => [
'Person' => [
10 => ['to_reset' => null, 'attributes' => ['org_id' => null]],
11 => ['to_reset' => null, 'attributes' => ['org_id' => null]],
12 => ['to_reset' => null, 'attributes' => ['org_id' => null]],
],
],
'aExpected' => [
'Person' => [
'iDeleteCount' => 0,
'iUpdateCount' => 3,
'iMode' => 0,
'sIssue' => null,
],
],
],
'class with both deletes and updates' => [
'aToDelete' => [
'Server' => [
1 => ['to_delete' => null, 'mode' => DEL_AUTO, 'requested_explicitely' => true],
],
],
'aToUpdate' => [
'Server' => [
5 => ['to_reset' => null, 'attributes' => ['org_id' => null]],
],
],
'aExpected' => [
'Server' => [
'iDeleteCount' => 1,
'iUpdateCount' => 1,
'iMode' => DEL_AUTO,
'sIssue' => null,
],
],
],
'multiple classes' => [
'aToDelete' => [
'Server' => [
1 => ['to_delete' => null, 'mode' => DEL_AUTO, 'requested_explicitely' => true],
],
'NetworkDevice' => [
3 => ['to_delete' => null, 'mode' => DEL_SILENT, 'requested_explicitely' => false],
4 => ['to_delete' => null, 'mode' => DEL_SILENT, 'requested_explicitely' => false],
],
],
'aToUpdate' => [
'Person' => [
10 => ['to_reset' => null, 'attributes' => ['org_id' => null]],
],
],
'aExpected' => [
'Server' => [
'iDeleteCount' => 1,
'iUpdateCount' => 0,
'iMode' => DEL_AUTO,
'sIssue' => null,
],
'NetworkDevice' => [
'iDeleteCount' => 2,
'iUpdateCount' => 0,
'iMode' => DEL_SILENT,
'sIssue' => null,
],
'Person' => [
'iDeleteCount' => 0,
'iUpdateCount' => 1,
'iMode' => 0,
'sIssue' => null,
],
],
],
];
}
//--- ExecuteDeletionPlan tests ---
/**
* Tests that ExecuteDeletionPlan throws a DataFeatureRemovalException when the deletion plan contains issues.
*/
public function testExecuteDeletionPlanThrowsExceptionWhenIssuesExist(): void
{
$this->RequireOnceItopFile('env-production/combodo-data-feature-removal/src/Helper/DataFeatureRemovalException.php');
$oDeletionPlan = $this->createMock(DeletionPlan::class);
$oDeletionPlan->method('GetIssues')->willReturn(['Some issue']);
$oDeletionPlan->method('ListDeletes')->willReturn([]);
$oDeletionPlan->method('ListUpdates')->willReturn([]);
$oMockService = $this->getMockBuilder(DeletionPlanService::class)
->disableOriginalConstructor()
->onlyMethods(['GetDeletionPlan'])
->getMock();
$oMockService->method('GetDeletionPlan')->willReturn($oDeletionPlan);
$this->expectException(DataFeatureRemovalException::class);
$this->expectExceptionMessage('Deletion Plan cannot be executed due to issues');
$oMockService->ExecuteDeletionPlan(['SomeClass']);
}
}

View File

@@ -0,0 +1,346 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.2">
<classes>
<class id="DFRToRemove" _created_in="itop-structure" _delta="define">
<properties>
<category>bizmodel,searchable</category>
<abstract>true</abstract>
<db_table>dfrtoremove</db_table>
<naming>
<attributes/>
</naming>
<reconciliation>
<attributes/>
</reconciliation>
</properties>
<fields>
<field id="name" xsi:type="AttributeString">
<sql>name</sql>
<default_value/>
<is_null_allowed>true</is_null_allowed>
<validation_pattern/>
<dependencies/>
<tracking_level>all</tracking_level>
</field>
</fields>
<methods/>
<presentation>
<list>
<items/>
</list>
<search>
<items/>
</search>
<details>
<items>
<item id="name">
<rank>10</rank>
</item>
</items>
</details>
</presentation>
<parent>cmdbAbstractObject</parent>
</class>
<class id="DFRToUpdate" _created_in="itop-structure" _delta="define">
<properties>
<category>bizmodel,searchable</category>
<abstract>false</abstract>
<db_table>dfrtoupdate</db_table>
<naming>
<attributes/>
</naming>
<reconciliation>
<attributes/>
</reconciliation>
</properties>
<fields>
<field id="name" xsi:type="AttributeString">
<sql>name</sql>
<default_value/>
<is_null_allowed>true</is_null_allowed>
<validation_pattern/>
<dependencies/>
<tracking_level>all</tracking_level>
</field>
<field id="extkey_id" xsi:type="AttributeExternalKey">
<sql>extkey_id</sql>
<filter/>
<dependencies/>
<is_null_allowed>true</is_null_allowed>
<target_class>DFRToRemove</target_class>
<on_target_delete>DEL_AUTO</on_target_delete>
<tracking_level>all</tracking_level>
</field>
</fields>
<methods/>
<presentation>
<list>
<items/>
</list>
<search>
<items/>
</search>
<details>
<items>
<item id="name">
<rank>10</rank>
</item>
<item id="extkey_id">
<rank>20</rank>
</item>
</items>
</details>
</presentation>
<parent>cmdbAbstractObject</parent>
</class>
<class id="DFRRemovedCollateral" _created_in="itop-structure" _delta="define">
<properties>
<category>bizmodel,searchable</category>
<abstract>false</abstract>
<db_table>dfrremovedcollateral</db_table>
<naming>
<attributes/>
</naming>
<reconciliation>
<attributes/>
</reconciliation>
</properties>
<fields>
<field id="name" xsi:type="AttributeString">
<sql>name</sql>
<default_value/>
<is_null_allowed>true</is_null_allowed>
<validation_pattern/>
<dependencies/>
<tracking_level>all</tracking_level>
</field>
<field id="extkey_id" xsi:type="AttributeExternalKey">
<sql>extkey_id</sql>
<filter/>
<dependencies/>
<is_null_allowed>false</is_null_allowed>
<target_class>DFRToRemove</target_class>
<on_target_delete>DEL_AUTO</on_target_delete>
<tracking_level>all</tracking_level>
</field>
</fields>
<methods/>
<presentation>
<list>
<items/>
</list>
<search>
<items/>
</search>
<details>
<items>
<item id="name">
<rank>10</rank>
</item>
<item id="extkey_id">
<rank>20</rank>
</item>
</items>
</details>
</presentation>
<parent>cmdbAbstractObject</parent>
</class>
<class id="DFRToRemoveLeaf" _created_in="itop-structure" _delta="define">
<properties>
<category>bizmodel,searchable</category>
<abstract>false</abstract>
<db_table>dfrtoremoveleaf</db_table>
<naming>
<attributes/>
</naming>
<reconciliation>
<attributes/>
</reconciliation>
</properties>
<fields>
<field id="desc" xsi:type="AttributeString">
<sql>desc</sql>
<default_value/>
<is_null_allowed>true</is_null_allowed>
<validation_pattern/>
<dependencies/>
<tracking_level>all</tracking_level>
</field>
</fields>
<methods/>
<presentation>
<list>
<items/>
</list>
<search>
<items/>
</search>
<details>
<items>
<item id="name">
<rank>10</rank>
</item>
<item id="desc">
<rank>20</rank>
</item>
</items>
</details>
</presentation>
<parent>DFRToRemove</parent>
</class>
<class id="DFRRemovedCollateralCascade" _created_in="itop-structure" _delta="define">
<properties>
<category>bizmodel,searchable</category>
<abstract>false</abstract>
<db_table>dfrremovedcollateralcascade</db_table>
<naming>
<attributes/>
</naming>
<reconciliation>
<attributes/>
</reconciliation>
</properties>
<fields>
<field id="name" xsi:type="AttributeString">
<sql>name</sql>
<default_value/>
<is_null_allowed>true</is_null_allowed>
<validation_pattern/>
<dependencies/>
<tracking_level>all</tracking_level>
</field>
<field id="extkey_id" xsi:type="AttributeExternalKey">
<sql>extkey_id</sql>
<filter/>
<dependencies/>
<is_null_allowed>false</is_null_allowed>
<target_class>DFRRemovedCollateral</target_class>
<on_target_delete>DEL_AUTO</on_target_delete>
<tracking_level>all</tracking_level>
</field>
</fields>
<methods/>
<presentation>
<list>
<items/>
</list>
<search>
<items/>
</search>
<details>
<items>
<item id="name">
<rank>10</rank>
</item>
<item id="extkey_id">
<rank>20</rank>
</item>
</items>
</details>
</presentation>
<parent>cmdbAbstractObject</parent>
</class>
<class id="DFRManual" _created_in="itop-structure" _delta="define">
<properties>
<category>bizmodel,searchable</category>
<abstract>false</abstract>
<db_table>dfrmanual</db_table>
<naming>
<attributes/>
</naming>
<reconciliation>
<attributes/>
</reconciliation>
</properties>
<fields>
<field id="name" xsi:type="AttributeString">
<sql>name</sql>
<default_value/>
<is_null_allowed>true</is_null_allowed>
<validation_pattern/>
<dependencies/>
<tracking_level>all</tracking_level>
</field>
<field id="extkey_id" xsi:type="AttributeExternalKey">
<sql>extkey_id</sql>
<filter/>
<dependencies/>
<is_null_allowed>false</is_null_allowed>
<target_class>DFRToRemove</target_class>
<on_target_delete>DEL_MANUAL</on_target_delete>
<tracking_level>all</tracking_level>
</field>
</fields>
<methods/>
<presentation>
<list>
<items/>
</list>
<search>
<items/>
</search>
<details>
<items>
<item id="name">
<rank>10</rank>
</item>
<item id="extkey_id">
<rank>20</rank>
</item>
</items>
</details>
</presentation>
<parent>cmdbAbstractObject</parent>
</class>
</classes>
<dictionaries>
<dictionary id="EN US">
<entries>
<entry id="Class:DFRToRemove/Name" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRToRemove/ComplementaryName" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRToRemove" _delta="define"><![CDATA[DFRToRemove]]></entry>
<entry id="Class:DFRToRemove+" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRToRemove/Attribute:name" _delta="define"><![CDATA[Name]]></entry>
<entry id="Class:DFRToRemove/Attribute:name+" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRToUpdate/Name" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRToUpdate/ComplementaryName" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRToUpdate" _delta="define"><![CDATA[DFRToUpdate]]></entry>
<entry id="Class:DFRToUpdate+" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRToUpdate/Attribute:name" _delta="define"><![CDATA[Name]]></entry>
<entry id="Class:DFRToUpdate/Attribute:name+" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRToUpdate/Attribute:extkey_id" _delta="define"><![CDATA[Dfrtoremove id]]></entry>
<entry id="Class:DFRToUpdate/Attribute:extkey_id+" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRRemovedCollateral/Name" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRRemovedCollateral/ComplementaryName" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRRemovedCollateral" _delta="define"><![CDATA[DFRRemovedCollateral]]></entry>
<entry id="Class:DFRRemovedCollateral+" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRRemovedCollateral/Attribute:name" _delta="define"><![CDATA[Name]]></entry>
<entry id="Class:DFRRemovedCollateral/Attribute:name+" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRRemovedCollateral/Attribute:extkey_id" _delta="define"><![CDATA[Dfrtoremove id]]></entry>
<entry id="Class:DFRRemovedCollateral/Attribute:extkey_id+" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRToRemoveLeaf/Name" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRToRemoveLeaf/ComplementaryName" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRToRemoveLeaf" _delta="define"><![CDATA[DFRToRemoveLeaf]]></entry>
<entry id="Class:DFRToRemoveLeaf+" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRToRemoveLeaf/Attribute:desc" _delta="define"><![CDATA[Desc]]></entry>
<entry id="Class:DFRToRemoveLeaf/Attribute:desc+" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRRemovedCollateralCascade/Name" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRRemovedCollateralCascade/ComplementaryName" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRRemovedCollateralCascade" _delta="define"><![CDATA[DFRRemovedCollateralCascade]]></entry>
<entry id="Class:DFRRemovedCollateralCascade+" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRRemovedCollateralCascade/Attribute:name" _delta="define"><![CDATA[Name]]></entry>
<entry id="Class:DFRRemovedCollateralCascade/Attribute:name+" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRRemovedCollateralCascade/Attribute:extkey_id" _delta="define"><![CDATA[Dfrremovedcollateral id]]></entry>
<entry id="Class:DFRRemovedCollateralCascade/Attribute:extkey_id+" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRManual/Name" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRManual/ComplementaryName" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRManual" _delta="define"><![CDATA[DFRManual]]></entry>
<entry id="Class:DFRManual+" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRManual/Attribute:name" _delta="define"><![CDATA[Name]]></entry>
<entry id="Class:DFRManual/Attribute:name+" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRManual/Attribute:extkey_id" _delta="define"><![CDATA[Dfrtoremove id]]></entry>
<entry id="Class:DFRManual/Attribute:extkey_id+" _delta="define"><![CDATA[]]></entry>
</entries>
</dictionary>
</dictionaries>
</itop_design>