summaryrefslogtreecommitdiffstats
path: root/library/Director/Db/Branch/BranchMerger.php
diff options
context:
space:
mode:
Diffstat (limited to 'library/Director/Db/Branch/BranchMerger.php')
-rw-r--r--library/Director/Db/Branch/BranchMerger.php157
1 files changed, 157 insertions, 0 deletions
diff --git a/library/Director/Db/Branch/BranchMerger.php b/library/Director/Db/Branch/BranchMerger.php
new file mode 100644
index 0000000..2e84863
--- /dev/null
+++ b/library/Director/Db/Branch/BranchMerger.php
@@ -0,0 +1,157 @@
+<?php
+
+namespace Icinga\Module\Director\Db\Branch;
+
+use Icinga\Module\Director\Data\Db\DbObject;
+use Icinga\Module\Director\Data\Db\DbObjectTypeRegistry;
+use Icinga\Module\Director\Db;
+use Icinga\Module\Director\Objects\DirectorActivityLog;
+use Ramsey\Uuid\UuidInterface;
+
+class BranchMerger
+{
+ /** @var Branch */
+ protected $branchUuid;
+
+ /** @var Db */
+ protected $connection;
+
+ /** @var \Zend_Db_Adapter_Abstract */
+ protected $db;
+
+ /** @var array */
+ protected $ignoreActivities = [];
+
+ /** @var bool */
+ protected $ignoreDeleteWhenMissing = false;
+
+ /** @var bool */
+ protected $ignoreModificationWhenMissing = false;
+
+ /**
+ * Apply branch modifications
+ *
+ * TODO: allow to skip or ignore modifications, in case modified properties have
+ * been changed in the meantime
+ *
+ * @param UuidInterface $branchUuid
+ * @param Db $connection
+ */
+ public function __construct(UuidInterface $branchUuid, Db $connection)
+ {
+ $this->branchUuid = $branchUuid;
+ $this->db = $connection->getDbAdapter();
+ $this->connection = $connection;
+ }
+
+ /**
+ * Skip a delete operation, when the object to be deleted does not exist
+ *
+ * @param bool $ignore
+ */
+ public function ignoreDeleteWhenMissing($ignore = true)
+ {
+ $this->ignoreDeleteWhenMissing = $ignore;
+ }
+
+ /**
+ * Skip a modification, when the related object does not exist
+ * @param bool $ignore
+ */
+ public function ignoreModificationWhenMissing($ignore = true)
+ {
+ $this->ignoreModificationWhenMissing = $ignore;
+ }
+
+ /**
+ * @param int $key
+ */
+ public function ignoreActivity($key)
+ {
+ $this->ignoreActivities[$key] = true;
+ }
+
+ /**
+ * @param BranchActivity $activity
+ * @return bool
+ */
+ public function ignoresActivity(BranchActivity $activity)
+ {
+ return isset($this->ignoreActivities[$activity->getTimestampNs()]);
+ }
+
+ /**
+ * @throws MergeError
+ */
+ public function merge($comment = null)
+ {
+ $username = DirectorActivityLog::username();
+ $this->connection->runFailSafeTransaction(function () use ($comment, $username) {
+ $formerActivityId = (int) DirectorActivityLog::loadLatest($this->connection)->get('id');
+ $query = $this->db->select()
+ ->from(BranchActivity::DB_TABLE)
+ ->where('branch_uuid = ?', $this->connection->quoteBinary($this->branchUuid->getBytes()))
+ ->order('timestamp_ns ASC');
+ $rows = $this->db->fetchAll($query);
+ foreach ($rows as $row) {
+ $activity = BranchActivity::fromDbRow($row);
+ $author = $activity->getAuthor();
+ if ($username !== $author) {
+ DirectorActivityLog::overrideUsername("$author/$username");
+ }
+ $this->applyModification($activity);
+ }
+ (new BranchStore($this->connection))->deleteByUuid($this->branchUuid);
+ $currentActivityId = (int) DirectorActivityLog::loadLatest($this->connection)->get('id');
+ $firstActivityId = (int) $this->db->fetchOne(
+ $this->db->select()->from('director_activity_log', 'MIN(id)')->where('id > ?', $formerActivityId)
+ );
+ if ($comment && strlen($comment)) {
+ $this->db->insert('director_activity_log_remark', [
+ 'first_related_activity' => $firstActivityId,
+ 'last_related_activity' => $currentActivityId,
+ 'remark' => $comment,
+ ]);
+ }
+ });
+ DirectorActivityLog::restoreUsername();
+ }
+
+ /**
+ * @param BranchActivity $activity
+ * @throws MergeError
+ * @throws \Icinga\Exception\NotFoundError
+ * @throws \Icinga\Module\Director\Exception\DuplicateKeyException
+ */
+ protected function applyModification(BranchActivity $activity)
+ {
+ /** @var string|DbObject $class */
+ $class = DbObjectTypeRegistry::classByType($activity->getObjectTable());
+ $uuid = $activity->getObjectUuid();
+
+ $exists = $class::uniqueIdExists($uuid, $this->connection);
+ if ($activity->isActionCreate()) {
+ if ($exists) {
+ if (! $this->ignoresActivity($activity)) {
+ throw new MergeErrorRecreateOnMerge($activity);
+ }
+ } else {
+ $activity->createDbObject($this->connection)->store($this->connection);
+ }
+ } elseif ($activity->isActionDelete()) {
+ if ($exists) {
+ $activity->deleteDbObject($class::requireWithUniqueId($uuid, $this->connection));
+ } elseif (! $this->ignoreDeleteWhenMissing && ! $this->ignoresActivity($activity)) {
+ throw new MergeErrorDeleteMissingObject($activity);
+ }
+ } else {
+ if ($exists) {
+ $activity->applyToDbObject($class::requireWithUniqueId($uuid, $this->connection))->store();
+ // TODO: you modified an object, and related properties have been changed in the meantime.
+ // We're able to detect this with the given data, and might want to offer a rebase.
+ } elseif (! $this->ignoreModificationWhenMissing && ! $this->ignoresActivity($activity)) {
+ throw new MergeErrorModificationForMissingObject($activity);
+ }
+ }
+ }
+}