From c52c9705284c1595bf3d250fac4557190435f820 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Wed, 11 Jan 2017 13:36:38 +0100 Subject: [PATCH] LegacyConfigParser: new parser class Takes over and cleans up logic from LegacyStorage --- .../Storage/LegacyConfigParser.php | 331 +++++++++++++++++ .../Businessprocess/Storage/LegacyStorage.php | 332 ++---------------- 2 files changed, 352 insertions(+), 311 deletions(-) create mode 100644 library/Businessprocess/Storage/LegacyConfigParser.php diff --git a/library/Businessprocess/Storage/LegacyConfigParser.php b/library/Businessprocess/Storage/LegacyConfigParser.php new file mode 100644 index 0000000..a8756f7 --- /dev/null +++ b/library/Businessprocess/Storage/LegacyConfigParser.php @@ -0,0 +1,331 @@ +name = $name; + $this->config = new BusinessProcess(); + $this->config->setName($name); + } + + /** + * @return BusinessProcess + */ + public function getParsedConfig() + { + return $this->config; + } + + public static function parseFile($name, $filename) + { + Benchmark::measure('Loading business process ' . $name); + $parser = new static($name); + $parser->reallyParseFile($filename); + Benchmark::measure('Business process ' . $name . ' loaded'); + return $parser->getParsedConfig(); + } + + public static function parseString($name, $string) + { + Benchmark::measure('Loading BP config from file: ' . $name); + $parser = new static($name); + foreach (preg_split('/\n/', $string) as $line) { + $parser->parseLine($line); + } + + Benchmark::measure('Business process ' . $name . ' loaded'); + return $parser->getParsedConfig(); + } + + protected function reallyParseFile($filename) + { + $file = $this->currentFilename = $filename; + $fh = @fopen($file, 'r'); + if (! $fh) { + throw new SystemPermissionException('Could not open "%s"', $filename); + } + + $config = $this->config; + $config->setMetadata( + $this::readMetadataFromFileHeader($config->getName(), $filename) + ); + + $this->currentLineNumber = 0; + while ($line = fgets($fh)) { + $this->parseLine($line); + } + + fclose($fh); + unset($this->currentLineNumber); + unset($this->currentFilename); + } + + public static function readMetadataFromFileHeader($name, $filename) + { + $metadata = new Metadata($name); + $fh = fopen($filename, 'r'); + $cnt = 0; + while ($cnt < 15 && false !== ($line = fgets($fh))) { + $cnt++; + static::parseHeaderLine($line, $metadata); + } + + fclose($fh); + return $metadata; + } + + protected function splitCommaSeparated($string) + { + return preg_split('/\s*,\s*/', $string, -1, PREG_SPLIT_NO_EMPTY); + } + + protected function readHeaderString($string, Metadata $metadata) + { + foreach (preg_split('/\n/', $string) as $line) { + $this->parseHeaderLine($line, $metadata); + } + + return $metadata; + } + + /** + * @return array + */ + protected function emptyHeader() + { + return array( + 'Title' => null, + 'Description' => null, + 'Owner' => null, + 'AllowedUsers' => null, + 'AllowedGroups' => null, + 'AllowedRoles' => null, + 'Backend' => null, + 'Statetype' => 'soft', + 'SLAHosts' => null + ); + } + + /** + * @param $line + * @param Metadata $metadata + */ + protected static function parseHeaderLine($line, Metadata $metadata) + { + if (preg_match('/^\s*#\s+(.+?)\s*:\s*(.+)$/', $line, $m)) { + if ($metadata->hasKey($m[1])) { + $metadata->set($m[1], $m[2]); + } + } + } + + /** + * @param $line + * @param BusinessProcess $bp + */ + protected function parseDisplay(& $line, BusinessProcess $bp) + { + list($display, $name, $desc) = preg_split('~\s*;\s*~', substr($line, 8), 3); + $bp->getBpNode($name)->setAlias($desc)->setDisplay($display); + if ($display > 0) { + $bp->addRootNode($name); + } + } + + /** + * @param $line + * @param BusinessProcess $bp + */ + protected function parseExternalInfo(& $line, BusinessProcess $bp) + { + list($name, $script) = preg_split('~\s*;\s*~', substr($line, 14), 2); + $bp->getBpNode($name)->setInfoCommand($script); + } + + protected function parseExtraInfo(& $line, BusinessProcess $bp) + { + // TODO: Not yet + // list($name, $script) = preg_split('~\s*;\s*~', substr($line, 14), 2); + // $this->getNode($name)->setExtraInfo($script); + } + + protected function parseInfoUrl(& $line, BusinessProcess $bp) + { + list($name, $url) = preg_split('~\s*;\s*~', substr($line, 9), 2); + $bp->getBpNode($name)->setInfoUrl($url); + } + + protected function parseExtraLine(& $line, $typeLength, BusinessProcess $bp) + { + $type = substr($line, 0, $typeLength); + if (substr($type, 0, 7) === 'display') { + $this->parseDisplay($line, $bp); + return true; + } + + switch ($type) { + case 'external_info': + $this->parseExternalInfo($line, $bp); + break; + case 'extra_info': + $this->parseExtraInfo($line, $bp); + break; + case 'info_url': + $this->parseInfoUrl($line, $bp); + break; + default: + return false; + } + + return true; + } + + /** + * Parses a single line + * + * Adds eventual new knowledge to the given Business Process config + * + * @param $line + * + * @throws ConfigurationError + */ + protected function parseLine(& $line) + { + $bp = $this->config; + $line = trim($line); + + $this->currentLineNumber++; + + // Skip empty or comment-only lines + if (empty($line) || $line[0] === '#') { + return; + } + + // Semicolon found in the first 14 cols? Might be a line with extra information + $pos = strpos($line, ';'); + if ($pos !== false && $pos < 14) { + if ($this->parseExtraLine($line, $pos, $bp)) { + return; + } + } + + list($name, $value) = preg_split('~\s*=\s*~', $line, 2); + + if (strpos($name, ';') !== false) { + $this->parseError('No semicolon allowed in varname'); + } + + $op = '&'; + if (preg_match_all('~([\|\+&\!])~', $value, $m)) { + $op = implode('', $m[1]); + for ($i = 1; $i < strlen($op); $i++) { + if ($op[$i] !== $op[$i - 1]) { + $this->parseError('Mixing operators is not allowed'); + } + } + } + $op = $op[0]; + $op_name = $op; + + if ($op === '+') { + if (! preg_match('~^(\d+)(?::(\d+))?\s*of:\s*(.+?)$~', $value, $m)) { + $this->parseError('syntax: = of: + [+ ]*'); + } + $op_name = $m[1]; + // New feature: $minWarn = $m[2]; + $value = $m[3]; + } + $cmps = preg_split('~\s*\\' . $op . '\s*~', $value, -1, PREG_SPLIT_NO_EMPTY); + $childNames = array(); + + foreach ($cmps as $val) { + if (strpos($val, ';') !== false) { + if ($bp->hasNode($val)) { + $childNames[] = $val; + continue; + } + + list($host, $service) = preg_split('~;~', $val, 2); + if ($service === 'Hoststatus') { + $bp->createHost($host); + } else { + $bp->createService($host, $service); + } + } + if ($val[0] === '@') { + if (strpos($val, ':') === false) { + throw new ConfigurationError( + "I'm unable to import full external configs, a node needs to be provided for '%s'", + $val + ); + // TODO: this might work: + // $node = $bp->createImportedNode(substr($val, 1)); + } else { + list($config, $nodeName) = preg_split('~:\s*~', substr($val, 1), 2); + $node = $bp->createImportedNode($config, $nodeName); + } + $val = $node->getName(); + } + + $childNames[] = $val; + } + + $node = new BpNode($bp, (object) array( + 'name' => $name, + 'operator' => $op_name, + 'child_names' => $childNames + )); + + $bp->addNode($name, $node); + } + + /** + * @return string + */ + public function getFilename() + { + return $this->currentFilename ?: '[given string]'; + } + + /** + * @param $msg + * @throws ConfigurationError + */ + protected function parseError($msg) + { + throw new ConfigurationError( + sprintf( + 'Parse error on %s:%s: %s', + $this->getFilename(), + $this->currentLineNumber, + $msg + ) + ); + } +} diff --git a/library/Businessprocess/Storage/LegacyStorage.php b/library/Businessprocess/Storage/LegacyStorage.php index b8bcd48..1f232eb 100644 --- a/library/Businessprocess/Storage/LegacyStorage.php +++ b/library/Businessprocess/Storage/LegacyStorage.php @@ -6,19 +6,12 @@ use DirectoryIterator; use Icinga\Application\Icinga; use Icinga\Module\Businessprocess\BusinessProcess; use Icinga\Exception\SystemPermissionException; -use Icinga\Module\Businessprocess\Metadata; class LegacyStorage extends Storage { /** @var string */ protected $configDir; - /** @var int */ - protected $parsing_line_number; - - /** @var string */ - protected $currentFilename; - public function getConfigDir() { if ($this->configDir === null) { @@ -118,57 +111,17 @@ class LegacyStorage extends Storage return $files; } - protected function splitCommaSeparated($string) + /** + * @inheritdoc + */ + public function loadProcess($name) { - return preg_split('/\s*,\s*/', $string, -1, PREG_SPLIT_NO_EMPTY); - } - - protected function readHeader($file, Metadata $metadata) - { - $fh = fopen($file, 'r'); - $cnt = 0; - while ($cnt < 15 && false !== ($line = fgets($fh))) { - $cnt++; - $this->parseHeaderLine($line, $metadata); - } - - fclose($fh); - return $metadata; - } - - protected function readHeaderString($string, Metadata $metadata) - { - foreach (preg_split('/\n/', $string) as $line) { - $this->parseHeaderLine($line, $metadata); - } - - return $metadata; - } - - protected function emptyHeader() - { - return array( - 'Title' => null, - 'Description' => null, - 'Owner' => null, - 'AllowedUsers' => null, - 'AllowedGroups' => null, - 'AllowedRoles' => null, - 'Backend' => null, - 'Statetype' => 'soft', - 'SLAHosts' => null + return LegacyConfigParser::parseFile( + $name, + $this->getFilename($name) ); } - protected function parseHeaderLine($line, Metadata $metadata) - { - if (preg_match('/^\s*#\s+(.+?)\s*:\s*(.+)$/', $line, $m)) { - if ($metadata->hasKey($m[1])) { - $metadata->set($m[1], $m[2]); - } - } - } - /** * @inheritdoc */ @@ -180,46 +133,23 @@ class LegacyStorage extends Storage ); } - public function render(BusinessProcess $process) + /** + * @inheritdoc + */ + public function deleteProcess($name) { - return $this->renderHeader($process) - . $this->renderNodes($process); + return @unlink($this->getFilename($name)); } - public function renderHeader(BusinessProcess $process) + /** + * @inheritdoc + */ + public function loadMetadata($name) { - $conf = "### Business Process Config File ###\n#\n"; - - $meta = $process->getMetadata(); - foreach ($meta->getProperties() as $key => $value) { - if ($value === null) { - continue; - } - - $conf .= sprintf("# %-11s : %s\n", $key, $value); - } - - $conf .= "#\n###################################\n\n"; - - return $conf; - } - - public function renderNodes(BusinessProcess $bp) - { - $rendered = array(); - $conf = ''; - - foreach ($bp->getRootNodes() as $child) { - $conf .= $child->toLegacyConfigString($rendered); - $rendered[$child->getName()] = true; - } - - foreach ($bp->getUnboundNodes() as $name => $node) { - $conf .= $node->toLegacyConfigString($rendered); - $rendered[$name] = true; - } - - return $conf . "\n"; + return LegacyConfigParser::readMetadataFromFileHeader( + $name, + $this->getFilename($name) + ); } public function getSource($name) @@ -234,19 +164,7 @@ class LegacyStorage extends Storage public function loadFromString($name, $string) { - $bp = new BusinessProcess(); - $bp->setName($name); - $this->parseString($string, $bp); - $this->readHeaderString($string, $bp->getMetadata()); - return $bp; - } - - /** - * @inheritdoc - */ - public function deleteProcess($name) - { - return @unlink($this->getFilename($name)); + return LegacyConfigParser::parseString($name, $string); } /** @@ -262,212 +180,4 @@ class LegacyStorage extends Storage return $this->loadMetadata($name)->canRead(); } - - public function loadMetadata($name) - { - $metadata = new Metadata($name); - return $this->readHeader($this->getFilename($name), $metadata); - } - - /** - * @param string $name - * @param BusinessProcess $bp - */ - protected function loadHeader($name, $bp) - { - // TODO: do not open twice, this is quick and dirty based on existing code - $file = $this->currentFilename = $this->getFilename($name); - $this->readHeader($file, $bp->getMetadata()); - } - - protected function parseFile($name, $bp) - { - $file = $this->currentFilename = $this->getFilename($name); - $fh = @fopen($file, 'r'); - if (! $fh) { - throw new SystemPermissionException('Could not open "%s"', $file); - } - - $this->parsing_line_number = 0; - while ($line = fgets($fh)) { - $this->parseLine($line, $bp); - } - - fclose($fh); - unset($this->parsing_line_number); - unset($this->currentFilename); - } - - protected function parseString($string, BusinessProcess $bp) - { - foreach (preg_split('/\n/', $string) as $line) { - $this->parseLine($line, $bp); - } - } - - /** - * @param $line - * @param BusinessProcess $bp - */ - protected function parseDisplay(& $line, BusinessProcess $bp) - { - list($display, $name, $desc) = preg_split('~\s*;\s*~', substr($line, 8), 3); - $bp->getNode($name)->setAlias($desc)->setDisplay($display); - if ($display > 0) { - $bp->addRootNode($name); - } - } - - protected function parseExternalInfo(& $line, BusinessProcess $bp) - { - list($name, $script) = preg_split('~\s*;\s*~', substr($line, 14), 2); - $bp->getNode($name)->setInfoCommand($script); - } - - protected function parseExtraInfo(& $line, BusinessProcess $bp) - { - // TODO: Not yet - // list($name, $script) = preg_split('~\s*;\s*~', substr($line, 14), 2); - // $this->getNode($name)->setExtraInfo($script); - } - - protected function parseInfoUrl(& $line, BusinessProcess $bp) - { - list($name, $url) = preg_split('~\s*;\s*~', substr($line, 9), 2); - $bp->getNode($name)->setInfoUrl($url); - } - - protected function parseExtraLine(& $line, $typeLength, BusinessProcess $bp) - { - $type = substr($line, 0, $typeLength); - if (substr($type, 0, 7) === 'display') { - $this->parseDisplay($line, $bp); - return true; - } - - switch ($type) { - case 'external_info': - $this->parseExternalInfo($line, $bp); - break; - case 'extra_info': - $this->parseExtraInfo($line, $bp); - break; - case 'info_url': - $this->parseInfoUrl($line, $bp); - break; - default: - return false; - } - - return true; - } - - /** - * Parses a single line - * - * Adds eventual new knowledge to the given Business Process config - * - * @param $line - * @param $bp - - */ - protected function parseLine(& $line, BusinessProcess $bp) - { - $line = trim($line); - - $this->parsing_line_number++; - - // Skip empty or comment-only lines - if (empty($line) || $line[0] === '#') { - return; - } - - // Semicolon found in the first 14 cols? Might be a line with extra information - $pos = strpos($line, ';'); - if ($pos !== false && $pos < 14) { - if ($this->parseExtraLine($line, $pos, $bp)) { - return; - } - } - - list($name, $value) = preg_split('~\s*=\s*~', $line, 2); - - if (strpos($name, ';') !== false) { - $this->parseError('No semicolon allowed in varname'); - } - - $op = '&'; - if (preg_match_all('~([\|\+&\!])~', $value, $m)) { - $op = implode('', $m[1]); - for ($i = 1; $i < strlen($op); $i++) { - if ($op[$i] !== $op[$i - 1]) { - $this->parseError('Mixing operators is not allowed'); - } - } - } - $op = $op[0]; - $op_name = $op; - - if ($op === '+') { - if (! preg_match('~^(\d+)(?::(\d+))?\s*of:\s*(.+?)$~', $value, $m)) { - $this->parseError('syntax: = of: + [+ ]*'); - } - $op_name = $m[1]; - // New feature: $minWarn = $m[2]; - $value = $m[3]; - } - $cmps = preg_split('~\s*\\' . $op . '\s*~', $value, -1, PREG_SPLIT_NO_EMPTY); - $childNames = array(); - - foreach ($cmps as $val) { - if (strpos($val, ';') !== false) { - if ($bp->hasNode($val)) { - continue; - } - - list($host, $service) = preg_split('~;~', $val, 2); - if ($service === 'Hoststatus') { - $bp->createHost($host); - } else { - $bp->createService($host, $service); - } - } - if ($val[0] === '@') { - if (strpos($val, ':') === false) { - throw new ConfigurationError( - "I'm unable to import full external configs, a node needs to be provided for '%s'", - $val - ); - // TODO: this might work: - // $node = $bp->createImportedNode(substr($val, 1)); - } else { - list($config, $nodeName) = preg_split('~:\s*~', substr($val, 1), 2); - $node = $bp->createImportedNode($config, $nodeName); - } - $val = $node->getName(); - } - - $childNames[] = $val; - } - - $node = new BpNode($bp, (object) array( - 'name' => $name, - 'operator' => $op_name, - 'child_names' => $childNames - )); - - $bp->addNode($name, $node); - } - - protected function parseError($msg) - { - throw new ConfigurationError( - sprintf( - 'Parse error on %s:%s: %s', - $this->currentFilename, - $this->parsing_line_number, - $msg - ) - ); - } }