icingaweb2-module-director/library/Director/Objects/ObjectApplyMatches.php

220 lines
6.3 KiB
PHP

<?php
namespace Icinga\Module\Director\Objects;
use Icinga\Application\Benchmark;
use Icinga\Data\Filter\Filter;
use Icinga\Data\Filter\FilterExpression;
use Icinga\Exception\ProgrammingError;
use Icinga\Module\Director\Application\MemoryLimit;
use Icinga\Module\Director\Db;
use Icinga\Module\Director\Db\Cache\PrefetchCache;
use stdClass;
abstract class ObjectApplyMatches
{
protected static $flatObjects;
protected static $columnMap = array(
'name' => 'object_name'
);
protected $object;
protected $flatObject;
protected static $type;
protected static $preparedFilters = array();
public static function prepare(IcingaObject $object)
{
return new static($object);
}
/**
* Prepare a Filter with fixed columns, and store the result
*
* @param Filter $filter
*
* @return Filter
*/
protected static function getPreparedFilter(Filter $filter)
{
$hash = spl_object_hash($filter);
if (! array_key_exists($hash, self::$preparedFilters)) {
$filter = clone($filter);
static::fixFilterColumns($filter);
self::$preparedFilters[$hash] = $filter;
}
return self::$preparedFilters[$hash];
}
public function matchesFilter(Filter $filter)
{
$filterObj = static::getPreparedFilter($filter);
if ($filterObj->isExpression() || ! $filterObj->isEmpty()) {
return $filterObj->matches($this->flatObject);
} else {
return false;
}
}
/**
* @param Filter $filter
* @param Db $db
*
* @return array
*/
public static function forFilter(Filter $filter, Db $db)
{
$result = array();
Benchmark::measure(sprintf('Starting Filter %s', $filter));
$filter = clone($filter);
static::fixFilterColumns($filter);
foreach (static::flatObjects($db) as $object) {
if ($filter->matches($object)) {
$name = $object->object_name;
$result[] = $name;
}
}
Benchmark::measure(sprintf('Got %d results for %s', count($result), $filter));
return array_values($result);
}
protected static function getType()
{
if (static::$type === null) {
throw new ProgrammingError(
'Implementations of %s need ::$type to be defined, %s has not',
__CLASS__,
get_called_class()
);
}
return static::$type;
}
protected static function flatObjects(Db $db)
{
if (self::$flatObjects === null) {
self::$flatObjects = static::fetchFlatObjects($db);
}
return self::$flatObjects;
}
protected static function raiseLimits()
{
// Note: IcingaConfig also raises the limit for generation, **but** we
// need the higher limit for preview.
MemoryLimit::raiseTo('1024M');
}
protected static function fetchFlatObjects(Db $db)
{
return static::fetchFlatObjectsByType($db, static::getType());
}
protected static function fetchFlatObjectsByType(Db $db, $type)
{
self::raiseLimits();
Benchmark::measure("ObjectApplyMatches: prefetching $type");
PrefetchCache::initialize($db);
/** @var IcingaObject $class */
$class = IcingaObject::classByType($type);
$all = $class::prefetchAll($db);
Benchmark::measure("ObjectApplyMatches: related objects for $type");
$class::prefetchAllRelationsByType($type, $db);
Benchmark::measure("ObjectApplyMatches: preparing flat $type objects");
$objects = array();
foreach ($all as $object) {
if ($object->isTemplate()) {
continue;
}
$flat = $object->toPlainObject(true, false);
static::flattenVars($flat);
$objects[$object->getObjectName()] = $flat;
}
Benchmark::measure("ObjectApplyMatches: $type cache ready");
return $objects;
}
public static function fixFilterColumns(Filter $filter)
{
if ($filter->isExpression()) {
static::fixFilterExpressionColumn($filter);
} else {
foreach ($filter->filters() as $sub) {
static::fixFilterColumns($sub);
}
}
}
protected static function fixFilterExpressionColumn(FilterExpression $filter)
{
if (static::columnIsJson($filter)) {
$column = $filter->getExpression();
$filter->setExpression($filter->getColumn());
$filter->setColumn($column);
}
/** @var FilterExpression $filter */
$col = $filter->getColumn();
$type = static::$type;
if (substr($col, 0, strlen($type) + 1) === "${type}.") {
$filter->setColumn($col = substr($col, strlen($type) + 1));
}
if (array_key_exists($col, self::$columnMap)) {
$filter->setColumn(self::$columnMap[$col]);
}
$filter->setExpression(json_decode($filter->getExpression()));
}
protected static function columnIsJson(FilterExpression $filter)
{
$col = $filter->getColumn();
return strlen($col) && $col[0] === '"';
}
/**
* Helper, flattens all vars of a given object
*
* The object itself will be modified, and the 'vars' property will be
* replaced with corresponding 'vars.whatever' properties
*
* @param $object
* @param string $key
*/
protected static function flattenVars(stdClass $object, $key = 'vars')
{
if (property_exists($object, 'vars')) {
foreach ($object->vars as $k => $v) {
if (is_object($v)) {
static::flattenVars($v, $k);
}
$object->{$key . '.' . $k} = $v;
}
unset($object->vars);
}
}
protected function __construct(IcingaObject $object)
{
$this->object = $object;
$this->flatObject = $object->toPlainObject(true, false);
// Sure, we are flat - but we might still want to match templates.
unset($this->flatObject->imports);
$this->flatObject->templates = $object->listFlatResolvedImportNames();
static::flattenVars($this->flatObject);
}
}