summaryrefslogtreecommitdiffstats
path: root/library/Director/Objects/IcingaTemplateResolver.php
diff options
context:
space:
mode:
Diffstat (limited to 'library/Director/Objects/IcingaTemplateResolver.php')
-rw-r--r--library/Director/Objects/IcingaTemplateResolver.php479
1 files changed, 479 insertions, 0 deletions
diff --git a/library/Director/Objects/IcingaTemplateResolver.php b/library/Director/Objects/IcingaTemplateResolver.php
new file mode 100644
index 0000000..61122a0
--- /dev/null
+++ b/library/Director/Objects/IcingaTemplateResolver.php
@@ -0,0 +1,479 @@
+<?php
+
+namespace Icinga\Module\Director\Objects;
+
+use Icinga\Application\Benchmark;
+use Icinga\Exception\NotFoundError;
+use Icinga\Module\Director\Db;
+use Icinga\Module\Director\Exception\NestingError;
+
+// TODO: move the 'type' layer to another class
+class IcingaTemplateResolver
+{
+ /** @var IcingaObject */
+ protected $object;
+
+ /** @var Db */
+ protected $connection;
+
+ /** @var \Zend_Db_Adapter_Abstract */
+ protected $db;
+
+ protected $type;
+
+ protected static $templates = array();
+
+ protected static $idIdx = array();
+
+ protected static $reverseIdIdx = array();
+
+ protected static $nameIdx = array();
+
+ protected static $idToName = array();
+
+ protected static $nameToId = array();
+
+ public function __construct(IcingaObject $object)
+ {
+ $this->setObject($object);
+ }
+
+ /**
+ * Set a specific object for this resolver instance
+ */
+ public function setObject(IcingaObject $object)
+ {
+ $this->object = $object;
+ $this->type = $object->getShortTableName();
+ $this->table = $object->getTableName();
+ $this->connection = $object->getConnection();
+ $this->db = $this->connection->getDbAdapter();
+
+ return $this;
+ }
+
+ /**
+ * Forget all template relation of the given object type
+ *
+ * @return self
+ */
+ public function clearCache()
+ {
+ unset(self::$templates[$this->type]);
+ return $this;
+ }
+
+ /**
+ * Fetch direct parents
+ *
+ * return IcingaObject[]
+ */
+ public function fetchParents()
+ {
+ // TODO: involve lookup cache
+ $res = array();
+ $class = $this->object;
+ foreach ($this->listParentIds() as $id) {
+ $object = $class::loadWithAutoIncId($id, $this->connection);
+ $res[$object->object_name] = $object;
+ }
+
+ return $res;
+ }
+
+ public function listParentIds($id = null)
+ {
+ $this->requireTemplates();
+
+ if ($id === null) {
+ $object = $this->object;
+
+ if ($object->hasBeenLoadedFromDb()) {
+ if ($object->gotImports() && $object->imports()->hasBeenModified()) {
+ return $this->listUnstoredParentIds();
+ }
+
+ $id = $object->id;
+ } else {
+ return $this->listUnstoredParentIds();
+ }
+ }
+
+ $type = $this->type;
+
+ if (array_key_exists($id, self::$idIdx[$type])) {
+ return array_keys(self::$idIdx[$type][$id]);
+ }
+
+ return array();
+ }
+
+ protected function listUnstoredParentIds()
+ {
+ return $this->getIdsForNames($this->listUnstoredParentNames());
+ }
+
+ protected function listUnstoredParentNames()
+ {
+ return $this->object->imports()->listImportNames();
+ }
+
+ public function listParentNames($name = null)
+ {
+ $this->requireTemplates();
+
+ if ($name === null) {
+ $object = $this->object;
+
+ if ($object->hasBeenLoadedFromDb()) {
+ if ($object->gotImports() && $object->imports()->hasBeenModified()) {
+ return $this->listUnstoredParentNames();
+ }
+
+ $name = $object->object_name;
+ } else {
+ return $this->listUnstoredParentNames();
+ }
+ }
+
+ $type = $this->type;
+
+ if (array_key_exists($name, self::$nameIdx[$type])) {
+ return array_keys(self::$nameIdx[$type][$name]);
+ }
+
+ return array();
+ }
+
+ public function fetchResolvedParents()
+ {
+ if ($this->object->hasBeenLoadedFromDb()) {
+ return $this->fetchObjectsById($this->listResolvedParentIds());
+ }
+
+ $objects = array();
+ foreach ($this->object->imports()->getObjects() as $parent) {
+ $objects += $parent->templateResolver()->fetchResolvedParents();
+ }
+
+ return $objects;
+ }
+
+ public function listResolvedParentIds()
+ {
+ $this->requireTemplates();
+ return $this->resolveParentIds();
+ }
+
+ /**
+ * TODO: unfinished and not used currently
+ *
+ * @return array
+ */
+ public function listResolvedParentNames()
+ {
+ $this->requireTemplates();
+ if (array_key_exists($name, self::$nameIdx[$type])) {
+ return array_keys(self::$nameIdx[$type][$name]);
+ }
+
+ return $this->resolveParentNames($this->object->object_name);
+ }
+
+ public function listParentsById($id)
+ {
+ return $this->getNamesForIds($this->resolveParentIds($id));
+ }
+
+ public function listParentsByName($name)
+ {
+ return $this->resolveParentNames($name);
+ }
+
+ /**
+ * Gives a list of all object ids met when walking through ancestry
+ *
+ * Tree is walked in import order, duplicates are preserved, the given
+ * objectId is added last
+ *
+ * @param int $objectId
+ *
+ * @return array
+ */
+ public function listFullInheritancePathIds($objectId = null)
+ {
+ $parentIds = $this->listParentIds($objectId);
+ $ids = array();
+
+ foreach ($parentIds as $parentId) {
+ foreach ($this->listFullInheritancePathIds($parentId) as $id) {
+ $ids[] = $id;
+ }
+
+ $ids[] = $parentId;
+ }
+
+ $object = $this->object;
+ if ($objectId === null && $object->hasBeenLoadedFromDb()) {
+ $ids[] = $object->id;
+ }
+
+ return $ids;
+ }
+
+ public function listChildren($objectId = null)
+ {
+ if ($objectId === null) {
+ $objectId = $this->object->id;
+ }
+
+ if (array_key_exists($objectId, self::$reverseIdIdx[$this->type])) {
+ return self::$reverseIdIdx[$this->type][$objectId];
+ } else {
+ return array();
+ }
+ }
+
+ public function listChildIds($objectId = null)
+ {
+ return array_keys($this->listChildren($objectId));
+ }
+
+ public function listDescendantIds($objectId = null)
+ {
+ if ($objectId === null) {
+ $objectId = $this->object->id;
+ }
+ }
+
+ public function listInheritancePathIds($objectId = null)
+ {
+ return $this->uniquePathIds($this->listFullInheritancePathIds($objectId));
+ }
+
+ public function uniquePathIds(array $ids)
+ {
+ $single = array();
+ foreach (array_reverse($ids) as $id) {
+ if (array_key_exists($id, $single)) {
+ continue;
+ }
+ $single[$id] = $id;
+ }
+
+ return array_reverse(array_keys($single));
+ }
+
+ protected function resolveParentNames($name, &$list = array(), $path = array())
+ {
+ $this->assertNotInList($name, $path);
+ $path[$name] = true;
+ foreach ($this->listParentNames($name) as $parent) {
+ $list[$parent] = true;
+ $this->resolveParentNames($parent, $list, $path);
+ unset($list[$parent]);
+ $list[$parent] = true;
+ }
+
+ return array_keys($list);
+ }
+
+ protected function resolveParentIds($id = null, &$list = array(), $path = array())
+ {
+ if ($id === null) {
+ if ($check = $this->object->id) {
+ $this->assertNotInList($check, $path);
+ $path[$check] = true;
+ }
+ } else {
+ $this->assertNotInList($id, $path);
+ $path[$id] = true;
+ }
+
+ foreach ($this->listParentIds($id) as $parent) {
+ $list[$parent] = true;
+ $this->resolveParentIds($parent, $list, $path);
+ unset($list[$parent]);
+ $list[$parent] = true;
+ }
+
+ return array_keys($list);
+ }
+
+ protected function assertNotInList($id, &$list)
+ {
+ if (array_key_exists($id, $list)) {
+ $list = array_keys($list);
+ $list[] = $id;
+ if (is_numeric($id)) {
+ throw new NestingError(
+ 'Loop detected: %s',
+ implode(' -> ', $this->getNamesForIds($list))
+ );
+ } else {
+ throw new NestingError(
+ 'Loop detected: %s',
+ implode(' -> ', $list)
+ );
+ }
+ }
+ }
+
+ protected function getNamesForIds($ids)
+ {
+ $names = array();
+ foreach ($ids as $id) {
+ $names[] = $this->getNameForId($id);
+ }
+
+ return $names;
+ }
+
+ protected function getNameForId($id)
+ {
+ return self::$idToName[$this->type][$id];
+ }
+
+ protected function getIdsForNames($names)
+ {
+ $this->requireTemplates();
+ $ids = array();
+ foreach ($names as $name) {
+ $ids[] = $this->getIdForName($name);
+ }
+
+ return $ids;
+ }
+
+ protected function getIdForName($name)
+ {
+ if (! array_key_exists($name, self::$nameToId[$this->type])) {
+ throw new NotFoundError('There is no such import: "%s"', $name);
+ }
+
+ return self::$nameToId[$this->type][$name];
+ }
+
+ protected function fetchObjectsById($ids)
+ {
+ $class = $this->object;
+ $connection = $this->connection;
+ $res = array();
+
+ foreach ($ids as $id) {
+ $res[] = $class::loadWithAutoIncId($id, $connection);
+ }
+
+ return $res;
+ }
+
+ protected function requireTemplates()
+ {
+ if (! array_key_exists($this->type, self::$templates)) {
+ $this->prepareLookupTables();
+ }
+
+ return $this;
+ }
+
+ protected function prepareLookupTables()
+ {
+ $type = $this->type;
+
+ Benchmark::measure("Preparing '$type' TemplateResolver lookup tables");
+ $templates = $this->fetchTemplates();
+
+ $ids = array();
+ $reverseIds = array();
+ $names = array();
+ $idToName = array();
+ $nameToId = array();
+
+ foreach ($templates as $row) {
+ $id = $row->id;
+ $idToName[$id] = $row->name;
+ $nameToId[$row->name] = $id;
+
+ if ($row->parent_id === null) {
+ continue;
+ }
+ $parentId = $row->parent_id;
+ $parentName = $row->parent_name;
+
+ if (array_key_exists($id, $ids)) {
+ $ids[$id][$parentId] = $parentName;
+ $names[$row->name][$parentName] = $row->parent_id;
+ } else {
+ $ids[$id] = array(
+ $parentId => $parentName
+ );
+
+ $names[$row->name] = array(
+ $parentName => $parentId
+ );
+ }
+
+ if (! array_key_exists($parentId, $reverseIds)) {
+ $reverseIds[$parentId] = array();
+ }
+ $reverseIds[$parentId][$id] = $row->name;
+ }
+
+ self::$idIdx[$type] = $ids;
+ self::$reverseIdIdx[$type] = $reverseIds;
+ self::$nameIdx[$type] = $names;
+ self::$templates[$type] = $templates; // TODO: this is unused, isn't it?
+ self::$idToName[$type] = $idToName;
+ self::$nameToId[$type] = $nameToId;
+ Benchmark::measure('Preparing TemplateResolver lookup tables');
+ }
+
+ protected function fetchTemplates()
+ {
+ $db = $this->db;
+ $type = $this->type;
+ $table = $this->object->getTableName();
+
+ $query = $db->select()->from(
+ array('o' => $table),
+ array(
+ 'id' => 'o.id',
+ 'name' => 'o.object_name',
+ 'parent_id' => 'p.id',
+ 'parent_name' => 'p.object_name',
+ )
+ )->joinLeft(
+ array('i' => $table . '_inheritance'),
+ 'o.id = i.' . $type . '_id',
+ array()
+ )->joinLeft(
+ array('p' => $table),
+ 'p.id = i.parent_' . $type . '_id',
+ array()
+ )->order('o.id')->order('i.weight');
+
+ return $db->fetchAll($query);
+ }
+
+ public function __destruct()
+ {
+ unset($this->connection);
+ unset($this->db);
+ unset($this->object);
+ }
+
+ public function refreshObject(IcingaObject $object)
+ {
+ $type = $object->getShortTableName();
+ $name = $object->getObjectName();
+ $parentNames = $object->imports;
+ self::$nameIdx[$type][$name] = $parentNames;
+ if ($object->hasBeenLoadedFromDb()) {
+ $id = $object->getProperty('id');
+ self::$idIdx[$type][$id] = $this->getIdsForNames($parentNames);
+ self::$idToName[$type][$id] = $name;
+ self::$nameToId[$type][$name] = $id;
+ }
+ return $this;
+ }
+}