diff options
Diffstat (limited to 'library/Director/Db/Branch/BranchedObject.php')
-rw-r--r-- | library/Director/Db/Branch/BranchedObject.php | 404 |
1 files changed, 404 insertions, 0 deletions
diff --git a/library/Director/Db/Branch/BranchedObject.php b/library/Director/Db/Branch/BranchedObject.php new file mode 100644 index 0000000..0f276c2 --- /dev/null +++ b/library/Director/Db/Branch/BranchedObject.php @@ -0,0 +1,404 @@ +<?php + +namespace Icinga\Module\Director\Db\Branch; + +use Icinga\Exception\NotFoundError; +use Icinga\Module\Director\Data\Db\DbObject; +use Icinga\Module\Director\Data\Db\DbObjectTypeRegistry; +use Icinga\Module\Director\Data\Json; +use Icinga\Module\Director\Db; +use Ramsey\Uuid\UuidInterface; +use stdClass; + +class BranchedObject +{ + /** @var UuidInterface */ + protected $branchUuid; + + /** @var ?DbObject */ + protected $object; + + /** @var ?stdClass */ + protected $changes; + + /** @var bool */ + protected $branchDeleted; + + /** @var bool */ + protected $branchCreated; + + /** @var UuidInterface */ + private $objectUuid; + + /** @var string */ + private $objectTable; + + /** @var bool */ + private $loadedAsBranchedObject = false; + + /** + * @param BranchActivity $activity + * @param Db $connection + * @return static + */ + public static function withActivity(BranchActivity $activity, Db $connection) + { + return self::loadOptional( + $connection, + $activity->getObjectTable(), + $activity->getObjectUuid(), + $activity->getBranchUuid() + )->applyActivity($activity, $connection); + } + + public function store(Db $connection) + { + if ($this->object && ! $this->object->hasBeenModified() && empty($this->changes)) { + return false; + } + $db = $connection->getDbAdapter(); + + $properties = [ + 'branch_deleted' => $this->branchDeleted ? 'y' : 'n', + 'branch_created' => $this->branchCreated ? 'y' : 'n', + ] + $this->prepareChangedProperties(); + + $table = 'branched_' . $this->objectTable; + if ($this->loadedAsBranchedObject) { + return $db->update( + $table, + $properties, + $this->prepareWhereString($connection) + ) === 1; + } else { + try { + return $db->insert($table, $this->prepareKeyProperties($connection) + $properties) === 1; + } catch (\Exception $e) { + var_dump($e->getMessage()); + var_dump($this->prepareKeyProperties($connection) + $properties); + exit; + } + } + } + + public function delete(Db $connection) + { + $db = $connection->getDbAdapter(); + $table = 'branched_' . $this->objectTable; + $branchCreated = $db->fetchOne($this->filterQuery($db->select()->from($table, 'branch_created'), $connection)); + // We do not want to nullify all properties, therefore: delete & insert + $db->delete($table, $this->prepareWhereString($connection)); + + if (! $branchCreated) { + // No need to insert a deleted object in case this object lived in this branch only + return $db->insert($table, $this->prepareKeyProperties($connection) + [ + 'branch_deleted' => 'y', + 'branch_created' => 'n', + ]) === 1; + } + + return true; + } + + protected function prepareKeyProperties(Db $connection) + { + return [ + 'uuid' => $connection->quoteBinary($this->objectUuid->getBytes()), + 'branch_uuid' => $connection->quoteBinary($this->branchUuid->getBytes()), + ]; + } + + protected function prepareWhereString(Db $connection) + { + $db = $connection->getDbAdapter(); + $objectUuid = $connection->quoteBinary($this->objectUuid->getBytes()); + $branchUuid = $connection->quoteBinary($this->branchUuid->getBytes()); + + return $db->quoteInto('uuid = ?', $objectUuid) . $db->quoteInto(' AND branch_uuid = ?', $branchUuid); + } + + /** + * @param \Zend_Db_Select $query + * @param Db $connection + * @return \Zend_Db_Select + */ + protected function filterQuery(\Zend_Db_Select $query, Db $connection) + { + return $query->where('uuid = ?', $connection->quoteBinary($this->objectUuid->getBytes())) + ->where('branch_uuid = ?', $connection->quoteBinary($this->branchUuid->getBytes())); + } + + protected function prepareChangedProperties() + { + $properties = (array) $this->changes; + + foreach (BranchSettings::ENCODED_DICTIONARIES as $property) { + $this->combineFlatDictionaries($properties, $property); + } + foreach (BranchSettings::ENCODED_DICTIONARIES as $property) { + if (isset($properties[$property])) { + $properties[$property] = Json::encode($properties[$property]); + } + } + foreach (BranchSettings::ENCODED_ARRAYS as $property) { + if (isset($properties[$property])) { + $properties[$property] = Json::encode($properties[$property]); + } + } + foreach (BranchSettings::RELATED_SETS as $property) { + if (isset($properties[$property])) { + $properties[$property] = Json::encode($properties[$property]); + } + } + $setNull = []; + if (array_key_exists('disabled', $properties) && $properties['disabled'] === null) { + unset($properties['disabled']); + } + foreach ($properties as $key => $value) { + if ($value === null) { + $setNull[] = $key; + } + } + if (empty($setNull)) { + $properties['set_null'] = null; + } else { + $properties['set_null'] = Json::encode($setNull); + } + + return $properties; + } + + protected function combineFlatDictionaries(&$properties, $prefix) + { + $vars = []; + $length = strlen($prefix) + 1; + foreach ($properties as $key => $value) { + if (substr($key, 0, $length) === "$prefix.") { + $vars[substr($key, $length)] = $value; + } + } + if (! empty($vars)) { + foreach (array_keys($vars) as $key) { + unset($properties["$prefix.$key"]); + } + $properties[$prefix] = (object) $vars; + } + } + + public function applyActivity(BranchActivity $activity, Db $connection) + { + if ($activity->isActionDelete()) { + throw new \RuntimeException('Cannot apply a delete action'); + } + if ($activity->isActionCreate()) { + if ($this->hasBeenTouchedByBranch()) { + throw new \RuntimeException('Cannot apply a CREATE activity to an already branched object'); + } else { + $this->branchCreated = true; + } + } + + foreach ($activity->getModifiedProperties()->jsonSerialize() as $key => $value) { + $this->changes[$key] = $value; + } + + return $this; + } + + /** + * @param Db $connection + * @param string $objectTable + * @param UuidInterface $uuid + * @param Branch $branch + * @return static + * @throws NotFoundError + */ + public static function load(Db $connection, $objectTable, UuidInterface $uuid, Branch $branch) + { + $object = static::loadOptional($connection, $objectTable, $uuid, $branch->getUuid()); + if ($object->getOriginalDbObject() === null && ! $object->hasBeenTouchedByBranch()) { + throw new NotFoundError('Not found'); + } + + return $object; + } + + /** + * @return bool + */ + public function hasBeenTouchedByBranch() + { + return $this->loadedAsBranchedObject; + } + + /** + * @return bool + */ + public function hasBeenDeletedByBranch() + { + return $this->branchDeleted; + } + + /** + * @return bool + */ + public function hasBeenCreatedByBranch() + { + return $this->branchCreated; + } + + /** + * @return ?DbObject + */ + public function getOriginalDbObject() + { + return $this->object; + } + + /** + * @return ?DbObject + */ + public function getBranchedDbObject(Db $connection) + { + if ($this->object) { + $branched = DbObjectTypeRegistry::newObject($this->objectTable, [], $connection); + // object_type first, to avoid: + // I can only assign for applied objects or objects with native support for assignments + if ($this->object->hasProperty('object_type')) { + $branched->set('object_type', $this->object->get('object_type')); + } + $branched->set('id', $this->object->get('id')); + $branched->set('uuid', $this->object->get('uuid')); + foreach ((array) $this->object->toPlainObject(false, true) as $key => $value) { + if ($key === 'object_type') { + continue; + } + $branched->set($key, $value); + } + } else { + $branched = DbObjectTypeRegistry::newObject($this->objectTable, [], $connection); + $branched->setUniqueId($this->objectUuid); + } + if ($this->changes === null) { + return $branched; + } + foreach ($this->changes as $key => $value) { + if ($key === 'set_null') { + if ($value !== null) { + foreach ($value as $k) { + $branched->set($k, null); + } + } + } else { + $branched->set($key, $value); + } + } + + return $branched; + } + + /** + * @return UuidInterface + */ + public function getBranchUuid() + { + return $this->branchUuid; + } + + /** + * @param Db $connection + * @param string $table + * @param UuidInterface $uuid + * @param ?UuidInterface $branchUuid + * @return static + */ + protected static function loadOptional( + Db $connection, + $table, + UuidInterface $uuid, + UuidInterface $branchUuid = null + ) { + $class = DbObjectTypeRegistry::classByType($table); + if ($row = static::optionalTableRowByUuid($connection, $table, $uuid)) { + $object = $class::fromDbRow((array) $row, $connection); + } else { + $object = null; + } + + $self = new static(); + $self->object = $object; + $self->objectUuid = $uuid; + $self->branchUuid = $branchUuid; + $self->objectTable = $table; + + if ($branchUuid && $row = static::optionalBranchedTableRowByUuid($connection, $table, $uuid, $branchUuid)) { + if ($row->branch_deleted === 'y') { + $self->branchDeleted = true; + } elseif ($row->branch_created === 'y') { + $self->branchCreated = true; + } + $self->changes = BranchSettings::normalizeBranchedObjectFromDb($row); + $self->loadedAsBranchedObject = true; + } + + return $self; + } + + public static function exists( + Db $connection, + $table, + UuidInterface $uuid, + UuidInterface $branchUuid = null + ) { + if (static::optionalTableRowByUuid($connection, $table, $uuid)) { + return true; + } + + if ($branchUuid && static::optionalBranchedTableRowByUuid($connection, $table, $uuid, $branchUuid)) { + return true; + } + + return false; + } + + /** + * @param Db $connection + * @param string $table + * @param UuidInterface $uuid + * @return stdClass|boolean + */ + protected static function optionalTableRowByUuid(Db $connection, $table, UuidInterface $uuid) + { + $db = $connection->getDbAdapter(); + + return $db->fetchRow( + $db->select()->from($table)->where('uuid = ?', $connection->quoteBinary($uuid->getBytes())) + ); + } + + /** + * @param Db $connection + * @param string $table + * @param UuidInterface $uuid + * @return stdClass|boolean + */ + protected static function optionalBranchedTableRowByUuid( + Db $connection, + $table, + UuidInterface $uuid, + UuidInterface $branchUuid + ) { + $db = $connection->getDbAdapter(); + + $query = $db->select() + ->from("branched_$table") + ->where('uuid = ?', $connection->quoteBinary($uuid->getBytes())) + ->where('branch_uuid = ?', $connection->quoteBinary($branchUuid->getBytes())); + + return $db->fetchRow($query); + } + + protected function __construct() + { + } +} |