synchrodatasource.class.inc.php 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652
  1. <?php
  2. // Copyright (C) 2010 Combodo SARL
  3. //
  4. // This program is free software; you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation; version 3 of the License.
  7. //
  8. // This program is distributed in the hope that it will be useful,
  9. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. // GNU General Public License for more details.
  12. //
  13. // You should have received a copy of the GNU General Public License
  14. // along with this program; if not, write to the Free Software
  15. // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  16. /**
  17. * Data Exchange - synchronization with external applications (incoming data)
  18. *
  19. * @author Erwan Taloc <erwan.taloc@combodo.com>
  20. * @author Romain Quetiez <romain.quetiez@combodo.com>
  21. * @author Denis Flaven <denis.flaven@combodo.com>
  22. * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL
  23. */
  24. class SynchroDataSource extends cmdbAbstractObject
  25. {
  26. public static function Init()
  27. {
  28. $aParams = array
  29. (
  30. "category" => "core/cmdb,view_in_gui",
  31. "key_type" => "autoincrement",
  32. "name_attcode" => array('name'),
  33. "state_attcode" => "",
  34. "reconc_keys" => array(),
  35. "db_table" => "priv_sync_datasource",
  36. "db_key_field" => "id",
  37. "db_finalclass_field" => "realclass",
  38. "display_template" => "",
  39. );
  40. MetaModel::Init_Params($aParams);
  41. //MetaModel::Init_InheritAttributes();
  42. MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
  43. MetaModel::Init_AddAttribute(new AttributeString("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
  44. MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum('implementation,production,obsolete'), "sql"=>"status", "default_value"=>"implementation", "is_null_allowed"=>false, "depends_on"=>array())));
  45. MetaModel::Init_AddAttribute(new AttributeExternalKey("user_id", array("targetclass"=>"User", "jointype"=>null, "allowed_values"=>null, "sql"=>"user_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array())));
  46. MetaModel::Init_AddAttribute(new AttributeClass("scope_class", array("allowed_values"=>null, "sql"=>"scope_class", "default_value"=>null, "class_category" => '', "more_values" => array(), "is_null_allowed"=>false, "depends_on"=>array())));
  47. MetaModel::Init_AddAttribute(new AttributeString("scope_restriction", array("allowed_values"=>null, "sql"=>"scope_restriction", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
  48. //MetaModel::Init_AddAttribute(new AttributeDateTime("last_synchro_date", array("allowed_values"=>null, "sql"=>"last_synchro_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
  49. MetaModel::Init_AddAttribute(new AttributeString("full_load_periodicity", array("allowed_values"=>null, "sql"=>"full_load_periodicity", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
  50. MetaModel::Init_AddAttribute(new AttributeString("reconciliation_list", array("allowed_values"=>null, "sql"=>"reconciliation_list", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
  51. MetaModel::Init_AddAttribute(new AttributeEnum("action_on_zero", array("allowed_values"=>new ValueSetEnum('create,error'), "sql"=>"action_on_zero", "default_value"=>"create", "is_null_allowed"=>false, "depends_on"=>array())));
  52. MetaModel::Init_AddAttribute(new AttributeEnum("action_on_one", array("allowed_values"=>new ValueSetEnum('update,error,delete'), "sql"=>"action_on_one", "default_value"=>"update", "is_null_allowed"=>false, "depends_on"=>array())));
  53. MetaModel::Init_AddAttribute(new AttributeEnum("action_on_multiple", array("allowed_values"=>new ValueSetEnum('take_first,create,error'), "sql"=>"action_on_multiple", "default_value"=>"error", "is_null_allowed"=>false, "depends_on"=>array())));
  54. MetaModel::Init_AddAttribute(new AttributeEnum("delete_policy", array("allowed_values"=>new ValueSetEnum('ignore,delete,update,update_then_delete'), "sql"=>"delete_policy", "default_value"=>"ignore", "is_null_allowed"=>false, "depends_on"=>array())));
  55. MetaModel::Init_AddAttribute(new AttributeString("delete_policy_update", array("allowed_values"=>null, "sql"=>"delete_policy_update", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
  56. MetaModel::Init_AddAttribute(new AttributeString("delete_policy_retention", array("allowed_values"=>null, "sql"=>"delete_policy_retention", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
  57. // Display lists
  58. MetaModel::Init_SetZListItems('details', array('name', 'description', 'status', 'user_id', 'scope_class', 'scope_restriction', 'full_load_periodicity', 'reconciliation_list', 'action_on_zero', 'action_on_one', 'action_on_multiple', 'delete_policy', 'delete_policy_update', 'delete_policy_retention')); // Attributes to be displayed for the complete details
  59. MetaModel::Init_SetZListItems('list', array('name', 'status', 'scope_class', 'user_id')); // Attributes to be displayed for a list
  60. // Search criteria
  61. MetaModel::Init_SetZListItems('standard_search', array('name', 'status', 'scope_class', 'user_id')); // Criteria of the std search form
  62. // MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
  63. }
  64. public function GetTargetClass()
  65. {
  66. return $this->Get('scope_class');
  67. }
  68. public function GetDataTable()
  69. {
  70. $sName = strtolower($this->GetTargetClass());
  71. $sName = str_replace('\'"&@|\\/ ', '_', $sName); // Remove forbidden characters from the table name
  72. $sName .= '_'.$this->GetKey(); // Add a suffix for unicity
  73. $sTable = MetaModel::GetConfig()->GetDBSubName()."synchro_data_$sName"; // Add the prefix if any
  74. return $sTable;
  75. }
  76. protected function AfterInsert()
  77. {
  78. parent::AfterInsert();
  79. $sTable = $this->GetDataTable();
  80. $aColumns = $this->GetSQLColumns();
  81. $aFieldDefs = array();
  82. // Allow '0', otherwise mysql will render an error when the id is not given
  83. // (the trigger is expected to set the value, but it is not executed soon enough)
  84. $aFieldDefs[] = "id INTEGER(11) NOT NULL DEFAULT 0 ";
  85. $aFieldDefs[] = "`primary_key` VARCHAR(255) NULL DEFAULT NULL";
  86. foreach($aColumns as $sColumn => $ColSpec)
  87. {
  88. $aFieldDefs[] = "`$sColumn` $ColSpec NULL DEFAULT NULL";
  89. }
  90. $aFieldDefs[] = "INDEX (id)";
  91. $aFieldDefs[] = "INDEX (primary_key)";
  92. $sFieldDefs = implode(', ', $aFieldDefs);
  93. $sCreateTable = "CREATE TABLE `$sTable` ($sFieldDefs) ENGINE = innodb;";
  94. CMDBSource::Query($sCreateTable);
  95. $sTriggerInsert = "CREATE TRIGGER `{$sTable}_bi` BEFORE INSERT ON $sTable";
  96. $sTriggerInsert .= " FOR EACH ROW";
  97. $sTriggerInsert .= " BEGIN";
  98. $sTriggerInsert .= " INSERT INTO priv_sync_replica (sync_source_id, status_last_seen, `status`) VALUES ({$this->GetKey()}, NOW(), 'new');";
  99. $sTriggerInsert .= " SET NEW.id = LAST_INSERT_ID();";
  100. $sTriggerInsert .= " END;";
  101. CMDBSource::Query($sTriggerInsert);
  102. $aModified = array();
  103. foreach($aColumns as $sColumn => $ColSpec)
  104. {
  105. // <=> is a null-safe 'EQUALS' operator (there is no equivalent for "DIFFERS FROM")
  106. $aModified[] = "NOT(NEW.`$sColumn` <=> OLD.`$sColumn`)";
  107. }
  108. $sIsModified = '('.implode(') OR (', $aModified).')';
  109. // Update the replica
  110. //
  111. // status is forced to "new" if the replica was obsoleted directly from the state "new" (dest_id = null)
  112. // otherwise, if status was either 'obsolete' or 'synchronized' it is turned into 'modified' or 'synchronized' depending on the changes
  113. // otherwise, the status is left as is
  114. $sTriggerUpdate = "CREATE TRIGGER `{$sTable}_bu` BEFORE UPDATE ON $sTable";
  115. $sTriggerUpdate .= " FOR EACH ROW";
  116. $sTriggerUpdate .= " BEGIN";
  117. $sTriggerUpdate .= " IF @itopuser is null THEN";
  118. $sTriggerUpdate .= " UPDATE priv_sync_replica SET status_last_seen = NOW(), `status` = IF(`status` = 'obsolete', IF(`dest_id` IS NULL, 'new', 'modified'), IF(`status` IN ('synchronized') AND ($sIsModified), 'modified', `status`)) WHERE sync_source_id = {$this->GetKey()} AND id = OLD.id;";
  119. $sTriggerUpdate .= " SET NEW.id = OLD.id;"; // make sure this id won't change
  120. $sTriggerUpdate .= " END IF;";
  121. $sTriggerUpdate .= " END;";
  122. CMDBSource::Query($sTriggerUpdate);
  123. }
  124. /**
  125. * Perform a synchronization between the data stored in the replicas (&synchro_data_xxx_xx table)
  126. * and the iTop objects. If the lastFullLoadStartDate is NOT specified then the full_load_periodicity
  127. * is used to determine which records are obsolete.
  128. * @param Hash $aDataToReplica Debugs/Trace information, one entry per replica
  129. * @param DateTime $oLastFullLoadStartDate Date of the last full load (start date/time), if known
  130. * @return void
  131. */
  132. public function Synchronize(&$aDataToReplica, $oLastFullLoadStartDate = null)
  133. {
  134. // Create a change used for logging all the modifications/creations happening during the synchro
  135. $oMyChange = MetaModel::NewObject("CMDBChange");
  136. $oMyChange->Set("date", time());
  137. $sUserString = CMDBChange::GetCurrentUserName();
  138. $oMyChange->Set("userinfo", $sUserString);
  139. $iChangeId = $oMyChange->DBInsert();
  140. // Get all the replicas that were not seen in the last import and mark them as obsolete
  141. if ($oLastFullLoadStartDate == null)
  142. {
  143. // No previous import known, use the full_load_periodicity value... and the current date
  144. $oLastFullLoadStartDate = new DateTime(); // Now
  145. // TO DO: how do we support localization here ??
  146. $oInterval = DateInterval::createFromDateString($this->Get('full_load_periodicity'));
  147. $oLastFullLoadStartDate->sub($oInterval);
  148. }
  149. $sLimitDate = $oLastFullLoadStartDate->Format('Y-m-d H:i:s');
  150. echo "<p>sLimitDate: $sLimitDate</p>\n";
  151. $sOQL = "SELECT SynchroReplica WHERE status IN ('new', 'synchronized', 'modified', 'orphan') AND status_last_seen < :last_import";
  152. $oSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL), array() /* order by*/, array('last_import' => $sLimitDate));
  153. while($oReplica = $oSet->Fetch())
  154. {
  155. // TO DO: take the appropriate action based on the 'delete_policy' field
  156. $sUpdateOnObsolete = $this->Get('delete_policy');
  157. if ( ($sUpdateOnObsolete == 'update') || ($sUpdateOnObsolete == 'update_then_delete') )
  158. {
  159. echo "<p>Destination object: (dest_id:".$oReplica->Get('dest_id').") to be updated.</p>";
  160. // TO DO: update the dest object for real...
  161. }
  162. echo "<p>Replica id:".$oReplica->GetKey()." (dest_id:".$oReplica->Get('dest_id').") marked as obsolete</p>";
  163. $oReplica->Set('status', 'obsolete');
  164. $oReplica->DBUpdateTracked($oMyChange);
  165. }
  166. // Get all the replicas that are 'new' or modified
  167. // Get the list of SQL columns: TO DO: retrieve this list from the SynchroAttributes
  168. $sClass = $this->GetTargetClass();
  169. echo "<p>TargetClass: $sClass</p>";
  170. $aAttCodes = array();
  171. foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
  172. {
  173. if ($sAttCode == 'finalclass') continue;
  174. $aAttCodes[] = $sAttCode;
  175. }
  176. $aColumns = $this->GetSQLColumns($aAttCodes);
  177. $aExtDataSpec = array(
  178. 'table' => $this->GetDataTable(),
  179. 'join_key' => 'id',
  180. 'fields' => array_keys($aColumns));
  181. $sOQL = "SELECT SynchroReplica WHERE (status = 'new' OR status = 'modified') AND sync_source_id = :source_id";
  182. $oSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL), array() /* order by*/, array('source_id' => $this->GetKey()) /* aArgs */, $aExtDataSpec, 0 /* limitCount */, 0 /* limitStart */);
  183. // Get the list of reconciliation keys, make sure they are valid
  184. $aReconciliationKeys = array();
  185. foreach( explode(',', $this->Get('reconciliation_list')) as $sKey)
  186. {
  187. $sFilterCode = trim($sKey);
  188. if (MetaModel::IsValidFilterCode($this->GetTargetClass(), $sFilterCode))
  189. {
  190. $aReconciliationKeys[] = $sFilterCode;
  191. }
  192. else
  193. {
  194. throw(new Exception('Invalid reconciliation criteria: '.$sFilterCode));
  195. }
  196. }
  197. // TO DO: Get the "real" list of enabled attributes ! Not all of them !
  198. // for now get all scalar & writable attributes
  199. $aAttributes = array();
  200. foreach($aAttCodes as $sAttCode)
  201. {
  202. $oAttDef = MetaModel::GetAttributeDef($this->GetTargetClass(), $sAttCode);
  203. if ($oAttDef->IsWritable() && $oAttDef->IsScalar())
  204. {
  205. $aAttributes[] = $sAttCode;
  206. }
  207. }
  208. while($oReplica = $oSet->Fetch())
  209. {
  210. $oReplica->Synchro($this, $aReconciliationKeys, $aAttributes, $oMyChange);
  211. }
  212. // Get all the replicas that are to be deleted
  213. $oInterval = DateInterval::createFromDateString($this->Get('delete_policy_retention'));
  214. $oDeletionDate = $oLastFullLoadStartDate->Sub($oInterval);
  215. $sDeletionDate = $oDeletionDate->Format('Y-m-d H:i:s');
  216. echo "<p>sDeletionDate: $sDeletionDate</p>\n";
  217. $sOQL = "SELECT SynchroReplica WHERE status IN ('new', 'synchronized', 'modified', 'orphan') AND status_last_seen < :last_import";
  218. $oSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL), array() /* order by*/, array('last_import' => $sDeletionDate));
  219. while($oReplica = $oSet->Fetch())
  220. {
  221. $sUpdateOnObsolete = $this->Get('delete_policy');
  222. if ( ($sUpdateOnObsolete == 'delete') || ($sUpdateOnObsolete == 'update_then_delete') )
  223. {
  224. echo "<p>Destination object: (dest_id:".$oReplica->Get('dest_id').") to be DELETED.</p>";
  225. // TO DO: delete the dest object for real...
  226. }
  227. echo "<p>Replica id:".$oReplica->GetKey()." (dest_id:".$oReplica->Get('dest_id').") marked as deleted</p>";
  228. $oReplica->Set('status', 'deleted');
  229. $oReplica->DBUpdateTracked($oMyChange);
  230. }
  231. return;
  232. }
  233. /**
  234. * Get the list of SQL columns corresponding to a particular list of attribute codes
  235. * Defaults to the whole list of columns for the current task
  236. */
  237. public function GetSQLColumns($aAttributeCodes = null)
  238. {
  239. $aColumns = array();
  240. $sClass = $this->GetTargetClass();
  241. if (is_null($aAttributeCodes))
  242. {
  243. $aAttributeCodes = array();
  244. foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
  245. {
  246. if ($sAttCode == 'finalclass') continue;
  247. $aAttributeCodes[] = $sAttCode;
  248. }
  249. }
  250. foreach($aAttributeCodes as $sAttCode)
  251. {
  252. $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
  253. foreach($oAttDef->GetSQLColumns() as $sField => $sDBFieldType)
  254. {
  255. $aColumns[$sField] = $sDBFieldType;
  256. }
  257. }
  258. return $aColumns;
  259. }
  260. public function IsRunning()
  261. {
  262. $sOQL = "SELECT SynchroLog WHERE sync_source_id = :source_id AND status='running'";
  263. $oSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL), array('start_date' => false) /* order by*/, array('source_id' => $this->GetKey()) /* aArgs */, array(), 1 /* limitCount */, 0 /* limitStart */);
  264. if ($oSet->Count() < 1)
  265. {
  266. $bRet = false;
  267. }
  268. else
  269. {
  270. $bRet = true;
  271. }
  272. return $bRet;
  273. }
  274. public function GetLatestLog()
  275. {
  276. $oLog = null;
  277. $sOQL = "SELECT SynchroLog WHERE sync_source_id = :source_id";
  278. $oSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL), array('start_date' => false) /* order by*/, array('source_id' => $this->GetKey()) /* aArgs */, array(), 1 /* limitCount */, 0 /* limitStart */);
  279. if ($oSet->Count() >= 1)
  280. {
  281. $oLog = $oSet->Fetch();
  282. }
  283. return $oLog;
  284. }
  285. /**
  286. * Retrieve from the log, the date of the last completed import
  287. * @return DateTime
  288. */
  289. public function GetLastCompletedImportDate()
  290. {
  291. $date = null;
  292. $sOQL = "SELECT SynchroLog WHERE sync_source_id = :source_id AND status='completed'";
  293. $oSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL), array('end_date' => false) /* order by*/, array('source_id' => $this->GetKey()) /* aArgs */, array(), 0 /* limitCount */, 0 /* limitStart */);
  294. if ($oSet->Count() >= 1)
  295. {
  296. $oLog = $oSet->Fetch();
  297. $date = $oLog->Get('end_date');
  298. }
  299. else
  300. {
  301. echo "<p>No completed log found</p>\n";
  302. }
  303. return $date;
  304. }
  305. }
  306. class SynchroAttribute extends cmdbAbstractObject
  307. {
  308. public static function Init()
  309. {
  310. $aParams = array
  311. (
  312. "category" => "core/cmdb,view_in_gui",
  313. "key_type" => "autoincrement",
  314. "name_attcode" => "",
  315. "state_attcode" => "",
  316. "reconc_keys" => array(),
  317. "db_table" => "priv_sync_att",
  318. "db_key_field" => "id",
  319. "db_finalclass_field" => "",
  320. "display_template" => "",
  321. );
  322. MetaModel::Init_Params($aParams);
  323. MetaModel::Init_InheritAttributes();
  324. MetaModel::Init_AddAttribute(new AttributeExternalKey("sync_source_id", array("targetclass"=>"SynchroDataSource", "jointype"=> "", "allowed_values"=>null, "sql"=>"sync_source_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array())));
  325. MetaModel::Init_AddAttribute(new AttributeString("attcode", array("allowed_values"=>null, "sql"=>"attcode", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
  326. MetaModel::Init_AddAttribute(new AttributeBoolean("enabled", array("allowed_values"=>null, "sql"=>"enabled", "default_value"=>0, "is_null_allowed"=>false, "depends_on"=>array())));
  327. MetaModel::Init_AddAttribute(new AttributeEnum("update_policy", array("allowed_values"=>new ValueSetEnum('master_locked,master_unlocked,write_once'), "sql"=>"update_policy", "default_value"=>"master_locked", "is_null_allowed"=>false, "depends_on"=>array())));
  328. // Display lists
  329. MetaModel::Init_SetZListItems('details', array('sync_source_id', 'attcode', 'enabled', 'update_policy')); // Attributes to be displayed for the complete details
  330. MetaModel::Init_SetZListItems('list', array('sync_source_id', 'attcode', 'enabled', 'update_policy')); // Attributes to be displayed for a list
  331. // Search criteria
  332. // MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form
  333. // MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
  334. }
  335. }
  336. class SynchroAttExtKey extends SynchroAttribute
  337. {
  338. public static function Init()
  339. {
  340. $aParams = array
  341. (
  342. "category" => "core/cmdb,view_in_gui",
  343. "key_type" => "autoincrement",
  344. "name_attcode" => "",
  345. "state_attcode" => "",
  346. "reconc_keys" => array(),
  347. "db_table" => "priv_sync_att_extkey",
  348. "db_key_field" => "id",
  349. "db_finalclass_field" => "",
  350. "display_template" => "",
  351. );
  352. MetaModel::Init_Params($aParams);
  353. MetaModel::Init_InheritAttributes();
  354. MetaModel::Init_AddAttribute(new AttributeString("reconciliation_attcode", array("allowed_values"=>null, "sql"=>"reconciliation_attcode", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
  355. // Display lists
  356. MetaModel::Init_SetZListItems('details', array('sync_source_id', 'attcode', 'enabled', 'update_policy', 'reconciliation_attcode')); // Attributes to be displayed for the complete details
  357. MetaModel::Init_SetZListItems('list', array('sync_source_id', 'attcode', 'enabled', 'update_policy')); // Attributes to be displayed for a list
  358. // Search criteria
  359. // MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form
  360. // MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
  361. }
  362. }
  363. class SynchroAttLinkSet extends SynchroAttribute
  364. {
  365. public static function Init()
  366. {
  367. $aParams = array
  368. (
  369. "category" => "core/cmdb,view_in_gui",
  370. "key_type" => "autoincrement",
  371. "name_attcode" => "",
  372. "state_attcode" => "",
  373. "reconc_keys" => array(),
  374. "db_table" => "priv_sync_att_linkset",
  375. "db_key_field" => "id",
  376. "db_finalclass_field" => "",
  377. "display_template" => "",
  378. );
  379. MetaModel::Init_Params($aParams);
  380. MetaModel::Init_InheritAttributes();
  381. MetaModel::Init_AddAttribute(new AttributeString("row_separator", array("allowed_values"=>null, "sql"=>"row_separator", "default_value"=>'|', "is_null_allowed"=>true, "depends_on"=>array())));
  382. MetaModel::Init_AddAttribute(new AttributeString("attribute_separator", array("allowed_values"=>null, "sql"=>"attribute_separator", "default_value"=>';', "is_null_allowed"=>true, "depends_on"=>array())));
  383. // Display lists
  384. MetaModel::Init_SetZListItems('details', array('sync_source_id', 'attcode', 'enabled', 'update_policy', 'row_separator', 'attribute_separator')); // Attributes to be displayed for the complete details
  385. MetaModel::Init_SetZListItems('list', array('sync_source_id', 'attcode', 'enabled', 'update_policy')); // Attributes to be displayed for a list
  386. // Search criteria
  387. // MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form
  388. // MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
  389. }
  390. }
  391. class SynchroLog extends CmdbAbstractObject
  392. {
  393. public static function Init()
  394. {
  395. $aParams = array
  396. (
  397. "category" => "core/cmdb,view_in_gui",
  398. "key_type" => "autoincrement",
  399. "name_attcode" => "",
  400. "state_attcode" => "",
  401. "reconc_keys" => array(),
  402. "db_table" => "priv_sync_log",
  403. "db_key_field" => "id",
  404. "db_finalclass_field" => "",
  405. "display_template" => "",
  406. );
  407. MetaModel::Init_Params($aParams);
  408. MetaModel::Init_InheritAttributes();
  409. MetaModel::Init_AddAttribute(new AttributeExternalKey("sync_source_id", array("targetclass"=>"SynchroDataSource", "jointype"=> "", "allowed_values"=>null, "sql"=>"sync_source_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array())));
  410. MetaModel::Init_AddAttribute(new AttributeDateTime("start_date", array("allowed_values"=>null, "sql"=>"start_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
  411. MetaModel::Init_AddAttribute(new AttributeDateTime("end_date", array("allowed_values"=>null, "sql"=>"end_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
  412. MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum('running,completed'), "sql"=>"status", "default_value"=>"running", "is_null_allowed"=>false, "depends_on"=>array())));
  413. MetaModel::Init_AddAttribute(new AttributeInteger("stats_nb_seen", array("allowed_values"=>null, "sql"=>"stats_nb_seen", "default_value"=>0, "is_null_allowed"=>false, "depends_on"=>array())));
  414. MetaModel::Init_AddAttribute(new AttributeInteger("stats_nb_modified", array("allowed_values"=>null, "sql"=>"stats_nb_modified", "default_value"=>0, "is_null_allowed"=>false, "depends_on"=>array())));
  415. MetaModel::Init_AddAttribute(new AttributeInteger("stats_nb_errors", array("allowed_values"=>null, "sql"=>"stats_nb_errors", "default_value"=>0, "is_null_allowed"=>false, "depends_on"=>array())));
  416. MetaModel::Init_AddAttribute(new AttributeInteger("stats_nb_created", array("allowed_values"=>null, "sql"=>"stats_nb_created", "default_value"=>0, "is_null_allowed"=>false, "depends_on"=>array())));
  417. MetaModel::Init_AddAttribute(new AttributeInteger("stats_nb_deleted", array("allowed_values"=>null, "sql"=>"stats_nb_deleted", "default_value"=>0, "is_null_allowed"=>false, "depends_on"=>array())));
  418. MetaModel::Init_AddAttribute(new AttributeInteger("stats_nb_reconciled", array("allowed_values"=>null, "sql"=>"stats_nb_reconciled", "default_value"=>0, "is_null_allowed"=>false, "depends_on"=>array())));
  419. // Display lists
  420. MetaModel::Init_SetZListItems('details', array('sync_source_id', 'start_date', 'end_date', 'status', 'stats_nb_seen', 'stats_nb_modified', 'stats_nb_errors', 'stats_nb_created', 'stats_nb_deleted', 'stats_nb_reconciled')); // Attributes to be displayed for the complete details
  421. MetaModel::Init_SetZListItems('list', array('sync_source_id', 'start_date', 'end_date', 'status', 'stats_nb_seen', 'stats_nb_modified', 'stats_nb_errors')); // Attributes to be displayed for a list
  422. // Search criteria
  423. // MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form
  424. // MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
  425. }
  426. }
  427. class SynchroReplica extends cmdbAbstractObject
  428. {
  429. static $aSearches = array(); // Cache of OQL queries used for reconciliation (per data source)
  430. public static function Init()
  431. {
  432. $aParams = array
  433. (
  434. "category" => "core/cmdb,view_in_gui",
  435. "key_type" => "autoincrement",
  436. "name_attcode" => "",
  437. "state_attcode" => "",
  438. "reconc_keys" => array(),
  439. "db_table" => "priv_sync_replica",
  440. "db_key_field" => "id",
  441. "db_finalclass_field" => "",
  442. "display_template" => "",
  443. );
  444. MetaModel::Init_Params($aParams);
  445. MetaModel::Init_InheritAttributes();
  446. MetaModel::Init_AddAttribute(new AttributeExternalKey("sync_source_id", array("targetclass"=>"SynchroDataSource", "jointype"=> "", "allowed_values"=>null, "sql"=>"sync_source_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array())));
  447. MetaModel::Init_AddAttribute(new AttributeInteger("dest_id", array("allowed_values"=>null, "sql"=>"dest_id", "default_value"=>0, "is_null_allowed"=>true, "depends_on"=>array())));
  448. MetaModel::Init_AddAttribute(new AttributeClass("dest_class", array("allowed_values"=>null, "sql"=>"dest_class", "default_value"=>null, "class_category" => '', "more_values" => array(), "is_null_allowed"=>false, "depends_on"=>array())));
  449. MetaModel::Init_AddAttribute(new AttributeDateTime("status_last_seen", array("allowed_values"=>null, "sql"=>"status_last_seen", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
  450. MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum('new,synchronized,modified,orphan,obsolete'), "sql"=>"status", "default_value"=>"new", "is_null_allowed"=>false, "depends_on"=>array())));
  451. MetaModel::Init_AddAttribute(new AttributeBoolean("status_dest_creator", array("allowed_values"=>null, "sql"=>"status_dest_creator", "default_value"=>0, "is_null_allowed"=>true, "depends_on"=>array())));
  452. MetaModel::Init_AddAttribute(new AttributeString("status_last_error", array("allowed_values"=>null, "sql"=>"status_last_error", "default_value"=>'', "is_null_allowed"=>true, "depends_on"=>array())));
  453. MetaModel::Init_AddAttribute(new AttributeDateTime("info_creation_date", array("allowed_values"=>null, "sql"=>"info_creation_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
  454. MetaModel::Init_AddAttribute(new AttributeDateTime("info_last_modified", array("allowed_values"=>null, "sql"=>"info_last_modified", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
  455. MetaModel::Init_AddAttribute(new AttributeDateTime("info_last_synchro", array("allowed_values"=>null, "sql"=>"info_last_synchro", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
  456. // Display lists
  457. MetaModel::Init_SetZListItems('details', array('sync_source_id', 'dest_id', 'dest_class', 'status_last_seen', 'status', 'status_dest_creator', 'status_last_error', 'info_creation_date', 'info_last_modified', 'info_last_synchro')); // Attributes to be displayed for the complete details
  458. MetaModel::Init_SetZListItems('list', array('sync_source_id', 'dest_id', 'dest_class', 'status_last_seen', 'status', 'status_dest_creator', 'status_last_error')); // Attributes to be displayed for a list
  459. // Search criteria
  460. MetaModel::Init_SetZListItems('standard_search', array('sync_source_id', 'status_last_seen', 'status', 'status_dest_creator', 'dest_class', 'dest_id', 'status_last_error')); // Criteria of the std search form
  461. // MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
  462. }
  463. public function Synchro($oDataSource, $aReconciliationKeys, $aAttributes, $oChange)
  464. {
  465. switch($this->Get('status'))
  466. {
  467. case 'new':
  468. // If needed, construct the query used for the reconciliation
  469. if (!isset(self::$aSearches[$oDataSource->GetKey()]))
  470. {
  471. foreach($aReconciliationKeys as $sFilterCode)
  472. {
  473. $aCriterias[] = ($sFilterCode == 'primary_key' ? 'id' : $sFilterCode).' = :'.$sFilterCode;
  474. }
  475. $sOQL = "SELECT ".$oDataSource->GetTargetClass()." WHERE ".implode(' AND ', $aCriterias);
  476. self::$aSearches[$oDataSource->GetKey()] = DBObjectSearch::FromOQL($sOQL);
  477. }
  478. // Get the criterias for the search
  479. $aFilterValues = array();
  480. foreach($aReconciliationKeys as $sFilterCode)
  481. {
  482. $aFilterValues[$sFilterCode] = $this->GetValueFromExtData($sFilterCode);
  483. }
  484. $oDestSet = new DBObjectSet(self::$aSearches[$oDataSource->GetKey()], array(), $aFilterValues);
  485. $iCount = $oDestSet->Count();
  486. // How many objects match the reconciliation criterias
  487. switch($iCount)
  488. {
  489. case 0:
  490. $this->CreateObjectFromReplica($oDataSource->GetTargetClass(), $aAttributes, $oChange);
  491. break;
  492. case 1:
  493. $oDestObj = $oDestSet->Fetch();
  494. $this->UpdateObjectFromReplica($oDestObj, $aAttributes, $oChange);
  495. $this->Set('dest_id', $oDestObj->GetKey());
  496. $this->Set('status_dest_creator', false);
  497. $this->Set('dest_class', get_class($oDestObj));
  498. break;
  499. default:
  500. $aConditions = array();
  501. foreach($aFilterValues as $sCode => $sValue)
  502. {
  503. $aConditions[] = $sCode.'='.$sValue;
  504. }
  505. $sCondition = implode(' AND ', $aConditions);
  506. $this->Set('status_last_error', $iCount.' destination objects match the reconciliation criterias: '.$sCondition);
  507. }
  508. break;
  509. case 'modified':
  510. $oDestObj = MetaModel::GetObject($oDataSource->GetTargetClass(), $this->Get('dest_id'));
  511. if ($oDestObj == null)
  512. {
  513. $this->Set('status', 'orphan'); // The destination object has been deleted !
  514. $this->Set('status_last_error', 'Destination object deleted unexpectedly');
  515. }
  516. else
  517. {
  518. $this->UpdateObjectFromReplica($oDestObj, $aAttributes, $oChange);
  519. }
  520. break;
  521. default: // Do nothing in all other cases
  522. }
  523. $this->DBUpdateTracked($oChange);
  524. }
  525. /**
  526. * Updates the destination object with the Extended data found in the synchro_data_XXXX table
  527. */
  528. protected function UpdateObjectFromReplica($oDestObj, $aAttributes, $oChange)
  529. {
  530. echo "<p>Update object ".$oDestObj->GetName()."</p>";
  531. foreach($aAttributes as $sAttCode)
  532. {
  533. $value = $this->GetValueFromExtData($sAttCode);
  534. $oDestObj->Set($sAttCode, $value);
  535. echo "<p>&nbsp;&nbsp;&nbsp;Setting $sAttCode to $value</p>";
  536. }
  537. try
  538. {
  539. $oDestObj->DBUpdateTracked($oChange);
  540. $this->Set('status_last_error', '');
  541. $this->Set('status', 'synchronized');
  542. }
  543. catch(Exception $e)
  544. {
  545. $this->Set('status_last_error', 'Unable to update destination object');
  546. }
  547. }
  548. /**
  549. * Creates the destination object populating it with the Extended data found in the synchro_data_XXXX table
  550. */
  551. protected function CreateObjectFromReplica($sClass, $aAttributes, $oChange)
  552. {
  553. echo "<p>Creating new $sClass</p>";
  554. $oDestObj = MetaModel::NewObject($sClass);
  555. foreach($aAttributes as $sAttCode)
  556. {
  557. $value = $this->GetValueFromExtData($sAttCode);
  558. $oDestObj->Set($sAttCode, $value);
  559. echo "<p>&nbsp;&nbsp;&nbsp;Setting $sAttCode to $value</p>";
  560. }
  561. try
  562. {
  563. $oDestObj->DBInsertTracked($oChange);
  564. $this->Set('dest_id', $oDestObj->GetKey());
  565. $this->Set('dest_class', get_class($oDestObj));
  566. $this->Set('status_dest_creator', true);
  567. $this->Set('status_last_error', '');
  568. $this->Set('status', 'synchronized');
  569. }
  570. catch(Exception $e)
  571. {
  572. $this->Set('status_last_error', 'Unable to update destination object');
  573. }
  574. }
  575. /**
  576. * Get the value from the 'Extended Data' located in the synchro_data_xxx table for this replica
  577. */
  578. protected function GetValueFromExtData($sColumnName)
  579. {
  580. $aData = $this->GetExtendedData();
  581. return $aData[$sColumnName];
  582. }
  583. }
  584. //if (UserRights::IsAdministrator())
  585. {
  586. $oAdminMenu = new MenuGroup('AdminTools', 80 /* fRank */);
  587. new OQLMenuNode('DataSources', 'SELECT SynchroDataSource', $oAdminMenu->GetIndex(), 12 /* fRank */, true, 'SynchroDataSource', UR_ACTION_MODIFY, UR_ALLOWED_YES);
  588. new WebPageMenuNode('Test:RunSynchro', '../synchro/synchro_exec.php', $oAdminMenu->GetIndex(), 13 /* fRank */);
  589. }
  590. ?>