summaryrefslogtreecommitdiffstats
path: root/library/Toplevelview/Tree
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 12:47:21 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 12:47:21 +0000
commit1ac4a2050c8076eb96e07e83721ebc9db864db94 (patch)
treeda9b32212bf99154450a7668f61a75f65617a9fa /library/Toplevelview/Tree
parentInitial commit. (diff)
downloadicingaweb2-module-toplevelview-1ac4a2050c8076eb96e07e83721ebc9db864db94.tar.xz
icingaweb2-module-toplevelview-1ac4a2050c8076eb96e07e83721ebc9db864db94.zip
Adding upstream version 0.3.3.upstream/0.3.3upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'library/Toplevelview/Tree')
-rw-r--r--library/Toplevelview/Tree/TLVHostGroupNode.php134
-rw-r--r--library/Toplevelview/Tree/TLVHostNode.php81
-rw-r--r--library/Toplevelview/Tree/TLVIcingaNode.php25
-rw-r--r--library/Toplevelview/Tree/TLVServiceNode.php143
-rw-r--r--library/Toplevelview/Tree/TLVStatus.php115
-rw-r--r--library/Toplevelview/Tree/TLVTree.php251
-rw-r--r--library/Toplevelview/Tree/TLVTreeNode.php334
7 files changed, 1083 insertions, 0 deletions
diff --git a/library/Toplevelview/Tree/TLVHostGroupNode.php b/library/Toplevelview/Tree/TLVHostGroupNode.php
new file mode 100644
index 0000000..a6dc7e4
--- /dev/null
+++ b/library/Toplevelview/Tree/TLVHostGroupNode.php
@@ -0,0 +1,134 @@
+<?php
+/* Copyright (C) 2017 Icinga Development Team <info@icinga.com> */
+
+namespace Icinga\Module\Toplevelview\Tree;
+
+use Icinga\Application\Benchmark;
+use Icinga\Exception\NotFoundError;
+use Icinga\Module\Toplevelview\Monitoring\Hostgroupsummary;
+
+class TLVHostGroupNode extends TLVIcingaNode
+{
+ protected $type = 'hostgroup';
+
+ protected $key = 'hostgroup';
+
+ protected static $titleKey = 'hostgroup';
+
+ public static function fetch(TLVTree $root)
+ {
+ Benchmark::measure('Begin fetching hostgroups');
+
+ if (! array_key_exists('hostgroup', $root->registeredObjects) or empty($root->registeredObjects['hostgroup'])) {
+ throw new NotFoundError('No hostgroups registered to fetch!');
+ }
+
+ $names = array_keys($root->registeredObjects['hostgroup']);
+
+ $options = [];
+ foreach (['notification_periods', 'host_never_unhandled', 'ignored_notification_periods'] as $opt) {
+ $options[$opt] = $root->get($opt);
+ }
+
+ // Note: this uses a patched version of Hostsgroupsummary / HostgroupsummaryQuery !
+ $hostgroups = new Hostgroupsummary(
+ $root->getBackend(),
+ array(
+ 'hostgroup_name',
+ 'hosts_down_handled',
+ 'hosts_down_unhandled',
+ 'hosts_total',
+ 'hosts_unreachable_handled',
+ 'hosts_unreachable_unhandled',
+ 'hosts_downtime_handled',
+ 'hosts_downtime_active',
+ 'hosts_up',
+ 'services_critical_handled',
+ 'services_critical_unhandled',
+ 'services_ok',
+ 'services_total',
+ 'services_unknown_handled',
+ 'services_unknown_unhandled',
+ 'services_warning_handled',
+ 'services_warning_unhandled',
+ 'services_downtime_handled',
+ 'services_downtime_active',
+ ),
+ $options
+ );
+
+ $hostgroups->where('hostgroup_name', $names);
+
+ foreach ($hostgroups as $hostgroup) {
+ $root->registeredObjects['hostgroup'][$hostgroup->hostgroup_name] = $hostgroup;
+ }
+
+ Benchmark::measure('Finished fetching hostgroups');
+ }
+
+ public function getStatus()
+ {
+ if ($this->status === null) {
+ $this->status = $status = new TLVStatus();
+ $key = $this->getKey();
+
+ if (($data = $this->root->getFetched($this->type, $key)) !== null) {
+ $status->set('total', $data->hosts_total + $data->services_total);
+ $status->set('ok', $data->hosts_up + $data->services_ok);
+
+ $status->set('critical_handled', $data->services_critical_handled);
+ $status->set('critical_unhandled', $data->services_critical_unhandled);
+
+ if ($this->getRoot()->get('host_never_unhandled') === true) {
+ $status->add(
+ 'critical_handled',
+ $data->hosts_down_handled
+ + $data->hosts_unreachable_handled
+ + $data->hosts_down_unhandled
+ + $data->hosts_unreachable_unhandled
+ );
+ } else {
+ $status->add(
+ 'critical_handled',
+ $data->hosts_down_handled
+ + $data->hosts_unreachable_handled
+ );
+ $status->add(
+ 'critical_unhandled',
+ $data->hosts_down_unhandled
+ + $data->hosts_unreachable_unhandled
+ );
+ }
+
+ $status->set('warning_handled', $data->services_warning_handled);
+ $status->set('warning_unhandled', $data->services_warning_unhandled);
+ $status->set('unknown_handled', $data->services_unknown_handled);
+ $status->set('unknown_unhandled', $data->services_unknown_unhandled);
+
+ $status->set(
+ 'downtime_handled',
+ $data->hosts_downtime_handled
+ + $data->services_downtime_handled
+ );
+ $status->set(
+ 'downtime_active',
+ $data->hosts_downtime_active
+ + $data->services_downtime_active
+ );
+
+ // extra metadata for view
+ $status->setMeta('hosts_total', $data->hosts_total);
+ $status->setMeta(
+ 'hosts_unhandled',
+ $data->hosts_down_unhandled
+ + $data->hosts_unreachable_unhandled
+ );
+
+ $status->set('missing', 0);
+ } else {
+ $status->add('missing', 1);
+ }
+ }
+ return $this->status;
+ }
+}
diff --git a/library/Toplevelview/Tree/TLVHostNode.php b/library/Toplevelview/Tree/TLVHostNode.php
new file mode 100644
index 0000000..f03ae47
--- /dev/null
+++ b/library/Toplevelview/Tree/TLVHostNode.php
@@ -0,0 +1,81 @@
+<?php
+/* Copyright (C) 2017 Icinga Development Team <info@icinga.com> */
+
+namespace Icinga\Module\Toplevelview\Tree;
+
+use Icinga\Application\Benchmark;
+use Icinga\Exception\NotFoundError;
+
+class TLVHostNode extends TLVIcingaNode
+{
+ protected $type = 'host';
+
+ protected $key = 'host';
+
+ protected static $titleKey = 'host';
+
+ public static function fetch(TLVTree $root)
+ {
+ Benchmark::measure('Begin fetching hosts');
+
+ if (! array_key_exists('host', $root->registeredObjects) or empty($root->registeredObjects['host'])) {
+ throw new NotFoundError('No hosts registered to fetch!');
+ }
+
+ $names = array_keys($root->registeredObjects['host']);
+
+ $hosts = $root->getBackend()->select()
+ ->from('hoststatus', array(
+ 'host_name',
+ 'host_hard_state',
+ 'host_handled',
+ 'host_in_downtime',
+ 'host_notifications_enabled',
+ ))
+ ->where('host_name', $names);
+
+ foreach ($hosts as $host) {
+ $root->registeredObjects['host'][$host->host_name] = $host;
+ }
+
+ Benchmark::measure('Finished fetching hosts');
+ }
+
+ public function getStatus()
+ {
+ if ($this->status === null) {
+ $this->status = $status = new TLVStatus();
+ $key = $this->getKey();
+
+ if (($data = $this->root->getFetched($this->type, $key)) !== null) {
+ $status->zero();
+ $status->add('total');
+
+ $state = $data->host_hard_state;
+
+ if ($data->host_in_downtime > 0 || $data->host_notifications_enabled === '0') {
+ $status->add('downtime_active');
+ $state = '10';
+ $handled = '';
+ } elseif ($data->host_handled === '1' || $this->getRoot()->get('host_never_unhandled') === true) {
+ $handled = '_handled';
+ } else {
+ $handled = '_unhandled';
+ }
+
+ if ($state === '0') {
+ $status->add('ok');
+ } elseif ($state === '1' || $state === '2') {
+ $status->add('critical' . $handled);
+ } elseif ($state === '10') {
+ $status->add('downtime_handled');
+ } else {
+ $status->add('unknown_handled');
+ }
+ } else {
+ $status->add('missing', 1);
+ }
+ }
+ return $this->status;
+ }
+}
diff --git a/library/Toplevelview/Tree/TLVIcingaNode.php b/library/Toplevelview/Tree/TLVIcingaNode.php
new file mode 100644
index 0000000..82779b0
--- /dev/null
+++ b/library/Toplevelview/Tree/TLVIcingaNode.php
@@ -0,0 +1,25 @@
+<?php
+/* Copyright (C) 2017 Icinga Development Team <info@icinga.com> */
+
+namespace Icinga\Module\Toplevelview\Tree;
+
+use Icinga\Exception\NotImplementedError;
+
+class TLVIcingaNode extends TLVTreeNode
+{
+ protected static $canHaveChildren = false;
+
+ /**
+ * Interface to fetch data for the implementation
+ *
+ * Needs to be extended / replaced by class
+ *
+ * @param $root
+ *
+ * @throws NotImplementedError
+ */
+ public static function fetch(/** @noinspection PhpUnusedParameterInspection */ TLVTree $root)
+ {
+ throw new NotImplementedError('fetch() has not been implemented for %s', get_class(new static));
+ }
+}
diff --git a/library/Toplevelview/Tree/TLVServiceNode.php b/library/Toplevelview/Tree/TLVServiceNode.php
new file mode 100644
index 0000000..2036714
--- /dev/null
+++ b/library/Toplevelview/Tree/TLVServiceNode.php
@@ -0,0 +1,143 @@
+<?php
+/* Copyright (C) 2017 Icinga Development Team <info@icinga.com> */
+
+namespace Icinga\Module\Toplevelview\Tree;
+
+use Icinga\Application\Benchmark;
+use Icinga\Exception\NotFoundError;
+use Icinga\Module\Toplevelview\Monitoring\Servicestatus;
+
+class TLVServiceNode extends TLVIcingaNode
+{
+ protected $type = 'service';
+
+ protected $key = '{host}!{service}';
+
+ public function getTitle()
+ {
+ return sprintf(
+ '%s: %s',
+ $this->get('host'),
+ $this->get('service')
+ );
+ }
+
+ public function register()
+ {
+ // also register host, because that's what we fetch data with
+ $hostDummy = new TLVHostNode();
+ $this->root->registerObject($hostDummy->getType(), $this->get('host'), get_class($hostDummy));
+
+ // register myself
+ return parent::register();
+ }
+
+ public function getKey()
+ {
+ return sprintf('%s!%s', $this->properties['host'], $this->properties['service']);
+ }
+
+ public static function fetch(TLVTree $root)
+ {
+ Benchmark::measure('Begin fetching services');
+
+ if (! array_key_exists('service', $root->registeredObjects) or empty($root->registeredObjects['service'])) {
+ throw new NotFoundError('No services registered to fetch!');
+ }
+
+ $names = array_keys($root->registeredObjects['host']);
+
+ $columns = array(
+ 'host_name',
+ 'service_description',
+ 'service_hard_state',
+ 'service_handled',
+ 'service_handled_wo_host',
+ 'service_notifications_enabled',
+ 'service_notification_period',
+ 'service_is_flapping',
+ 'service_in_downtime',
+ );
+
+ $options = [];
+ foreach (['notification_periods', 'host_never_unhandled', 'ignored_notification_periods'] as $opt) {
+ $options[$opt] = $root->get($opt);
+ }
+
+ if ($root->get('notification_periods') === true) {
+ $columns[] = 'service_in_notification_period';
+ }
+
+ // Note: this uses a patched version of Servicestatus / ServicestatusQuery !
+ $services = new Servicestatus($root->getBackend(), $columns, $options);
+ $services->where('host_name', $names);
+
+ foreach ($services as $service) {
+ $key = sprintf('%s!%s', $service->host_name, $service->service_description);
+ if (array_key_exists($key, $root->registeredObjects['service'])) {
+ $root->registeredObjects['service'][$key] = $service;
+ }
+ }
+
+ Benchmark::measure('Finished fetching services');
+ }
+
+ public function getStatus()
+ {
+ if ($this->status === null) {
+ $this->status = $status = new TLVStatus();
+ $key = $this->getKey();
+
+ if (($data = $this->root->getFetched($this->type, $key)) !== null) {
+ $status->zero();
+ $status->add('total');
+
+ $state = $data->service_hard_state;
+
+ if ($this->getRoot()->get('notification_periods') === true) {
+ $notInPeriod = $data->service_in_notification_period === '0';
+ } else {
+ $notInPeriod = false;
+ }
+
+ if ($this->getRoot()->get('host_never_unhandled') === true) {
+ $isHandled = $data->service_handled_wo_host === '1';
+ } else {
+ $isHandled = $data->service_handled === '1';
+ }
+ $isHandled = $isHandled || $data->service_is_flapping === '1';
+
+ if ($data->service_in_downtime > 0
+ || $data->service_notifications_enabled === '0'
+ || $notInPeriod
+ ) {
+ $status->add('downtime_active');
+ if ($state !== '0') {
+ $state = '10';
+ }
+ }
+
+ if ($isHandled) {
+ $handled = '_handled';
+ } else {
+ $handled = '_unhandled';
+ }
+
+ if ($state === '0' || $state === '99') {
+ $status->add('ok', 1);
+ } elseif ($state === '1') {
+ $status->add('warning' . $handled, 1);
+ } elseif ($state === '2') {
+ $status->add('critical' . $handled, 1);
+ } elseif ($state === '10') {
+ $status->add('downtime_handled');
+ } else {
+ $status->add('unknown' . $handled, 1);
+ }
+ } else {
+ $status->add('missing', 1);
+ }
+ }
+ return $this->status;
+ }
+}
diff --git a/library/Toplevelview/Tree/TLVStatus.php b/library/Toplevelview/Tree/TLVStatus.php
new file mode 100644
index 0000000..afecc1e
--- /dev/null
+++ b/library/Toplevelview/Tree/TLVStatus.php
@@ -0,0 +1,115 @@
+<?php
+/* Copyright (C) 2017 Icinga Development Team <info@icinga.com> */
+
+namespace Icinga\Module\Toplevelview\Tree;
+
+class TLVStatus
+{
+ protected $properties = array(
+ 'critical_unhandled' => null,
+ 'critical_handled' => null,
+ 'warning_unhandled' => null,
+ 'warning_handled' => null,
+ 'unknown_unhandled' => null,
+ 'unknown_handled' => null,
+ 'downtime_handled' => null,
+ 'downtime_active' => null,
+ 'ok' => null,
+ 'missing' => null,
+ 'total' => null,
+ );
+
+ protected static $statusPriority = array(
+ 'critical_unhandled',
+ 'warning_unhandled',
+ 'unknown_unhandled', // Note: old TLV ignored UNKNOWN basically
+ 'critical_handled',
+ 'warning_handled',
+ 'unknown_handled',
+ 'ok',
+ 'downtime_handled',
+ 'missing',
+ );
+
+ protected $meta = array();
+
+ public function merge(TLVStatus $status)
+ {
+ $properties = $status->getProperties();
+ foreach (array_keys($this->properties) as $key) {
+ if ($this->properties[$key] === null) {
+ $this->properties[$key] = $properties[$key];
+ } else {
+ $this->properties[$key] += $properties[$key];
+ }
+ }
+ return $this;
+ }
+
+ public function get($key)
+ {
+ return $this->properties[$key];
+ }
+
+ public function set($key, $value)
+ {
+ $this->properties[$key] = (int) $value;
+ return $this;
+ }
+
+ public function getProperties()
+ {
+ return $this->properties;
+ }
+
+ public function add($key, $value = 1)
+ {
+ if ($this->properties[$key] === null) {
+ $this->properties[$key] = 0;
+ }
+ $this->properties[$key] += (int) $value;
+ return $this;
+ }
+
+ public function zero()
+ {
+ foreach (array_keys($this->properties) as $key) {
+ $this->properties[$key] = 0;
+ }
+ return $this;
+ }
+
+ public function getOverall()
+ {
+ foreach (static::$statusPriority as $key) {
+ if ($this->properties[$key] !== null && $this->properties[$key] > 0) {
+ return $this->cssFriendly($key);
+ }
+ }
+ return 'missing';
+ }
+
+ protected function cssFriendly($key)
+ {
+ return str_replace('_', ' ', $key);
+ }
+
+ public function getMeta($key)
+ {
+ if (array_key_exists($key, $this->meta)) {
+ return $this->meta[$key];
+ } else {
+ return null;
+ }
+ }
+
+ public function getAllMeta()
+ {
+ return $this->meta;
+ }
+
+ public function setMeta($key, $value)
+ {
+ $this->meta[$key] = $value;
+ }
+}
diff --git a/library/Toplevelview/Tree/TLVTree.php b/library/Toplevelview/Tree/TLVTree.php
new file mode 100644
index 0000000..817d52c
--- /dev/null
+++ b/library/Toplevelview/Tree/TLVTree.php
@@ -0,0 +1,251 @@
+<?php
+/* Copyright (C) 2017 Icinga Development Team <info@icinga.com> */
+
+namespace Icinga\Module\Toplevelview\Tree;
+
+use Icinga\Application\Logger;
+use Icinga\Exception\IcingaException;
+use Icinga\Exception\NotFoundError;
+use Icinga\Exception\ProgrammingError;
+use Icinga\Module\Monitoring\Backend\MonitoringBackend;
+use Icinga\Module\Toplevelview\Util\Json;
+use Icinga\Module\Toplevelview\ViewConfig;
+use Icinga\Web\FileCache;
+use stdClass;
+
+class TLVTree extends TLVTreeNode
+{
+ protected static $titleKey = 'name';
+
+ public $registeredTypes = array();
+
+ public $registeredObjects = array();
+
+ protected $fetchedData = array();
+
+ protected $fetched = false;
+
+ protected $fetchTime;
+
+ protected $cacheLifetime = 60;
+
+ /**
+ * @var MonitoringBackend
+ */
+ protected $backend;
+
+ /**
+ * @var ViewConfig
+ */
+ protected $config;
+
+ public function getById($id)
+ {
+ $ids = explode('-', $id);
+ $currentNode = $this;
+
+ foreach ($ids as $i) {
+ $children = $currentNode->getChildren();
+ if (! empty($children) && array_key_exists($i, $children)) {
+ $currentNode = $children[$i];
+ } else {
+ throw new NotFoundError(
+ 'Could not find ID %s after %s for path %s',
+ $i,
+ $currentNode->getFullId(),
+ $id
+ );
+ }
+ }
+
+ return $currentNode;
+ }
+
+ /**
+ * @return ViewConfig
+ */
+ public function getConfig()
+ {
+ return $this->config;
+ }
+
+ /**
+ * @param ViewConfig $config
+ *
+ * @return $this
+ */
+ public function setConfig(ViewConfig $config)
+ {
+ $this->config = $config;
+ return $this;
+ }
+
+ public function registerObject($type, $name, $class)
+ {
+ if (array_key_exists($type, $this->registeredTypes) && $this->registeredTypes[$type] !== $class) {
+ throw new ProgrammingError(
+ 'Tried to register the same type by multiple classes: %s - %s - %s',
+ $type,
+ $this->registeredTypes[$type],
+ $class
+ );
+ }
+
+ $this->registeredTypes[$type] = $class;
+ $this->registeredObjects[$type][$name] = null;
+ }
+
+ protected function getCacheName()
+ {
+ $config = $this->getConfig();
+ return sprintf(
+ '%s-%s.json',
+ $config->getName(),
+ $config->getTextChecksum()
+ );
+ }
+
+ protected function loadCache()
+ {
+ if (($lifetime = $this->getCacheLifetime()) <= 0) {
+ return;
+ }
+
+ $cacheName = $this->getCacheName();
+ try {
+ $cache = FileCache::instance('toplevelview');
+ $currentTime = time();
+ $newerThan = $currentTime - $lifetime;
+
+ if ($cache->has($cacheName)) {
+ $cachedData = Json::decode($cache->get($cacheName));
+
+ if (property_exists($cachedData, 'data')
+ && $cachedData->data !== null
+ && property_exists($cachedData, 'ts')
+ && $cachedData->ts <= $currentTime // too new maybe
+ && $cachedData->ts > $newerThan // too old
+ ) {
+ foreach ($cachedData->data as $type => $objects) {
+ $this->registeredObjects[$type] = (array) $objects;
+ $this->fetchedData[$type] = true;
+ }
+
+ $this->fetchTime = $cachedData->ts;
+ $this->fetched = true;
+ }
+ }
+ } catch (IcingaException $e) {
+ Logger::error('Could not load from toplevelview cache %s: %s', $cacheName, $e->getMessage());
+ }
+ }
+
+ protected function storeCache()
+ {
+ if (($lifetime = $this->getCacheLifetime()) <= 0) {
+ return;
+ }
+
+ $cacheName = $this->getCacheName();
+ try {
+ $cache = FileCache::instance('toplevelview');
+
+ $cachedData = new stdClass;
+ $cachedData->ts = $this->fetchTime;
+ $cachedData->data = $this->registeredObjects;
+
+ $cache->store($cacheName, Json::encode($cachedData));
+ } catch (IcingaException $e) {
+ Logger::error('Could not store to toplevelview cache %s: %s', $cacheName, $e->getMessage());
+ }
+ }
+
+ protected function fetchType($type)
+ {
+ if (! array_key_exists($type, $this->registeredTypes)) {
+ throw new ProgrammingError('Type %s has not been registered', $type);
+ }
+
+ if (! array_key_exists($type, $this->fetchedData)) {
+ /** @var TLVIcingaNode $class */
+ $class = $this->registeredTypes[$type];
+ $class::fetch($this);
+ $this->fetchedData[$type] = true;
+ }
+
+ return $this;
+ }
+
+ protected function ensureFetched()
+ {
+ if ($this->fetched !== true) {
+ $this->loadCache();
+
+ if ($this->fetched !== true) {
+ foreach (array_keys($this->registeredTypes) as $type) {
+ $this->fetchType($type);
+ }
+
+ $this->fetchTime = time();
+ $this->fetched = true;
+
+ $this->storeCache();
+ }
+ }
+
+ return $this;
+ }
+
+ public function getFetched($type, $key)
+ {
+ $this->ensureFetched();
+
+ if (array_key_exists($key, $this->registeredObjects[$type])
+ && $this->registeredObjects[$type][$key] !== null
+ ) {
+ return $this->registeredObjects[$type][$key];
+ } else {
+ return null;
+ }
+ }
+
+ public function getBackend()
+ {
+ return $this->backend;
+ }
+
+ public function setBackend(MonitoringBackend $backend)
+ {
+ $this->backend = $backend;
+ return $this;
+ }
+
+ /**
+ * @return int time
+ */
+ public function getFetchTime()
+ {
+ $this->ensureFetched();
+
+ return $this->fetchTime;
+ }
+
+ /**
+ * @return int seconds
+ */
+ public function getCacheLifetime()
+ {
+ return $this->cacheLifetime;
+ }
+
+ /**
+ * @param int $cacheLifetime In seconds
+ *
+ * @return $this
+ */
+ public function setCacheLifetime($cacheLifetime)
+ {
+ $this->cacheLifetime = $cacheLifetime;
+ return $this;
+ }
+}
diff --git a/library/Toplevelview/Tree/TLVTreeNode.php b/library/Toplevelview/Tree/TLVTreeNode.php
new file mode 100644
index 0000000..fd70e49
--- /dev/null
+++ b/library/Toplevelview/Tree/TLVTreeNode.php
@@ -0,0 +1,334 @@
+<?php
+/* Copyright (C) 2017 Icinga Development Team <info@icinga.com> */
+
+namespace Icinga\Module\Toplevelview\Tree;
+
+use Icinga\Application\Benchmark;
+use Icinga\Data\Tree\TreeNode;
+use Icinga\Exception\ConfigurationError;
+use Icinga\Exception\NotImplementedError;
+use Icinga\Exception\ProgrammingError;
+
+class TLVTreeNode extends TreeNode
+{
+ /**
+ * @var string
+ */
+ protected $type = 'node';
+
+ protected $key = null;
+
+ /**
+ * @var TLVTree
+ */
+ protected $root;
+
+ /**
+ * @var TLVTreeNode
+ */
+ protected $parent;
+
+ /**
+ * @var string
+ */
+ protected $fullId;
+
+ /**
+ * @var TLVStatus
+ */
+ protected $status;
+
+ /**
+ * @var array
+ */
+ protected $properties = array();
+
+ protected static $canHaveChildren = true;
+
+ /**
+ * The key which represents the display title
+ *
+ * @var string
+ */
+ protected static $titleKey = 'name';
+
+ /**
+ * Mapping types to its implementation class
+ *
+ * @var array
+ */
+ protected static $typeMap = array(
+ 'host' => 'Icinga\\Module\\Toplevelview\\Tree\\TLVHostNode',
+ 'service' => 'Icinga\\Module\\Toplevelview\\Tree\\TLVServiceNode',
+ 'hostgroup' => 'Icinga\\Module\\Toplevelview\\Tree\\TLVHostGroupNode',
+ );
+
+ /**
+ * Mapping keys to a type
+ *
+ * Warning: order is important when keys overlap!
+ *
+ * @var array
+ */
+ protected static $typeKeyMap = array(
+ 'service' => array('host', 'service'),
+ 'host' => 'host',
+ 'hostgroup' => 'hostgroup',
+ );
+
+ /**
+ * @param $array
+ * @param TLVTreeNode|null $parent
+ * @param TLVTree $root
+ *
+ * @return static
+ *
+ * @throws NotImplementedError
+ * @throws ProgrammingError
+ */
+ public static function fromArray($array, TLVTreeNode $parent = null, TLVTree $root = null)
+ {
+ if ($root === null) {
+ Benchmark::measure('Begin loading TLVTree from array');
+ }
+
+ // try to detect type
+ if (! array_key_exists('type', $array)) {
+ foreach (self::$typeKeyMap as $type => $keys) {
+ if (! is_array($keys)) {
+ $keys = array($keys);
+ }
+ $matched = false;
+ foreach ($keys as $k) {
+ if (array_key_exists($k, $array)) {
+ $matched = true;
+ } else {
+ continue 2;
+ }
+ }
+ // if all keys are present
+ if ($matched === true) {
+ $array['type'] = $type;
+ break;
+ }
+ }
+ }
+
+ if (array_key_exists('type', $array)) {
+ $type = $array['type'];
+ if (array_key_exists($type, self::$typeMap)) {
+ $node = new self::$typeMap[$type];
+ $node->type = $type;
+ } else {
+ throw new NotImplementedError('Could not find type "%s" for %s', $type, var_export($array, true));
+ }
+ } elseif ($root === null) {
+ $node = new static;
+ } else {
+ $node = new self;
+ }
+
+ if ($root === null) {
+ $node->root = true; // is root
+ $node->parent = null;
+ $root = $parent = $node;
+ } elseif ($parent === null) {
+ throw new ProgrammingError('You must specify the direct parent!');
+ } else {
+ $node->root = $root;
+ $node->parent = $parent;
+ }
+
+ foreach ($array as $key => $value) {
+ if ($key !== 'children') {
+ $node->properties[$key] = $value;
+ } elseif (is_array($value)) { // only array values for children
+ foreach ($value as $i => $child) {
+ $childNode = self::fromArray($child, $node, $root);
+ $childNode->id = $i;
+ $node->appendChild($childNode);
+ }
+ }
+ }
+
+ $node->register();
+
+ if ($root === $node) {
+ Benchmark::measure('Finished loading TLVTree from array');
+ }
+
+ return $node;
+ }
+
+ /**
+ * Retrieve all objects as breadcrumb
+ *
+ * @param array $list for recursion
+ *
+ * @return TLVTreeNode[]
+ */
+ public function getBreadCrumb(&$list = array())
+ {
+ array_unshift($list, $this);
+ if ($this->parent !== $this->root) {
+ $this->parent->getBreadCrumb($list);
+ }
+ return $list;
+ }
+
+ /**
+ *
+ * @return mixed|string
+ */
+ public function getFullId()
+ {
+ if ($this->fullId === null) {
+ $id = (string) $this->id;
+ if ($this->parent !== $this->root) {
+ $this->fullId = $this->parent->getFullId() . '-' . $id;
+ } else {
+ $this->fullId = $id;
+ }
+ }
+ return $this->fullId;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getType()
+ {
+ return $this->type;
+ }
+
+ public function getProperties()
+ {
+ return $this->properties;
+ }
+
+ public function setProperties($array)
+ {
+ $this->properties = $array;
+ return $this;
+ }
+
+ public function get($key)
+ {
+ if (array_key_exists($key, $this->properties)) {
+ return $this->properties[$key];
+ } else {
+ return null;
+ }
+ }
+
+ public function set($key, $value)
+ {
+ $this->properties[$key] = $value;
+ return $this;
+ }
+
+ public function getTitle()
+ {
+ if (array_key_exists(static::$titleKey, $this->properties)) {
+ return $this->properties[static::$titleKey];
+ } else {
+ return null;
+ }
+ }
+
+ public function getKey()
+ {
+ if ($this->key === null) {
+ throw new ProgrammingError('Can not get key for %s', get_class($this));
+ }
+
+ if (array_key_exists($this->key, $this->properties)) {
+ return $this->properties[$this->key];
+ } else {
+ throw new ProgrammingError(
+ 'Can not retrieve key for %s in %s',
+ $this->key,
+ get_class($this)
+ );
+ }
+ }
+
+ /**
+ * @return TLVTree
+ */
+ public function getRoot()
+ {
+ return $this->root;
+ }
+
+ /**
+ * @return TLVTreeNode
+ */
+ public function getParent()
+ {
+ return $this->parent;
+ }
+
+ /**
+ * @return TLVTreeNode[]
+ */
+ public function getChildren()
+ {
+ return parent::getChildren();
+ }
+
+ /**
+ * Append a child node as the last child of this node
+ *
+ * @param TreeNode $child The child to append
+ *
+ * @return $this
+ *
+ * @throws ConfigurationError When node does not allow children
+ */
+ public function appendChild(TreeNode $child)
+ {
+ if (static::$canHaveChildren === true) {
+ $this->children[] = $child;
+ } else {
+ throw new ConfigurationError('Can not add children below type %s', $this->type);
+ }
+ return $this;
+ }
+
+ protected function register()
+ {
+ if ($this->type !== 'node') {
+ $this->root->registerObject($this->type, $this->getKey(), get_class($this));
+ }
+ return $this;
+ }
+
+ /**
+ * @return TLVStatus
+ * @throws ProgrammingError
+ */
+ public function getStatus()
+ {
+ if (static::$canHaveChildren === true) {
+ if ($this->status === null) {
+ $this->status = new TLVStatus;
+
+ $missed = true;
+ foreach ($this->getChildren() as $child) {
+ $this->status->merge($child->getStatus());
+ $missed = false;
+ }
+
+ // Note: old TLV does not count an empty branch as missing...
+ if ($missed) {
+ $this->status->add('missing', 1);
+ }
+ }
+
+ return $this->status;
+ } else {
+ throw new ProgrammingError('getStatus() needs to be implemented for %s', get_class($this));
+ }
+ }
+}