      1: <?php
      2: /**
      3:  * Licensed to the Apache Software Foundation (ASF) under one or more
      4:  * contributor license agreements. See the NOTICE file distributed with
      5:  * this work for additional information regarding copyright ownership.
      6:  * The ASF licenses this file to You under the Apache License, Version 2.0
      7:  * (the "License"); you may not use this file except in compliance with
      8:  * the License. You may obtain a copy of the License at
      9:  *
     10:  *     http://www.apache.org/licenses/LICENSE-2.0
     11:  *
     12:  * Unless required by applicable law or agreed to in writing, software
     13:  * distributed under the License is distributed on an "AS IS" BASIS,
     14:  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     15:  * See the License for the specific language governing permissions and
     16:  * limitations under the License.
     17:  *
     18:  * @package log4php
     19:  */
     20:
     21: /**
     22:  * Converts XML configuration files to a PHP array.
     23:  *
     24:  * @package log4php
     25:  * @subpackage configurators
     26:  * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
     27:  * @version $Revision: 1394956 $
     28:  * @since 2.2
     29:  */
     30: class LoggerConfigurationAdapterXML implements LoggerConfigurationAdapter
     31: {
     32:     /** Path to the XML schema used for validation. */
     33:     const SCHEMA_PATH = '/../xml/log4php.xsd';
     34:
     35:     private $config = array(
     36:         'appenders' => array(),
     37:         'loggers' => array(),
     38:         'renderers' => array(),
     39:     );
     40:
     41:     public function convert($url) {
     42:         $xml = $this->loadXML($url);
     43:
     44:         $this->parseConfiguration($xml);
     45:
     46:         // Parse the <root> node
     47:         if (isset($xml->root)) {
     48:             $this->parseRootLogger($xml->root);
     49:         }
     50:
     51:         // Process <logger> nodes
     52:         foreach($xml->logger as $logger) {
     53:             $this->parseLogger($logger);
     54:         }
     55:
     56:         // Process <appender> nodes
     57:         foreach($xml->appender as $appender) {
     58:             $this->parseAppender($appender);
     59:         }
     60:
     61:         // Process <renderer> nodes
     62:         foreach($xml->renderer as $rendererNode) {
     63:             $this->parseRenderer($rendererNode);
     64:         }
     65:
     66:         // Process <defaultRenderer> node
     67:         foreach($xml->defaultRenderer as $rendererNode) {
     68:             $this->parseDefaultRenderer($rendererNode);
     69:         }
     70:
     71:         return $this->config;
     72:     }
     73:
     74:     /**
     75:      * Loads and validates the XML.
     76:      * @param string $url Input XML.
     77:      */
     78:     private function loadXML($url) {
     79:         if (!file_exists($url)) {
     80:             throw new LoggerException("File [$url] does not exist.");
     81:         }
     82:
     83:         libxml_clear_errors();
     84:         $oldValue = libxml_use_internal_errors(true);
     85:
     86:         // Load XML
     87:         $xml = @simplexml_load_file($url);
     88:         if ($xml === false) {
     89:
     90:             $errorStr = "";
     91:             foreach(libxml_get_errors() as $error) {
     92:                 $errorStr .= $error->message;
     93:             }
     94:
     95:             throw new LoggerException("Error loading configuration file: " . trim($errorStr));
     96:         }
     97:
     98:         libxml_clear_errors();
     99:         libxml_use_internal_errors($oldValue);
    100:
    101:         return $xml;
    102:     }
    103:
    104:     /**
    105:      * Parses the <configuration> node.
    106:      */
    107:     private function parseConfiguration(SimpleXMLElement $xml) {
    108:         $attributes = $xml->attributes();
    109:         if (isset($attributes['threshold'])) {
    110:             $this->config['threshold'] = (string) $attributes['threshold'];
    111:         }
    112:     }
    113:
    114:     /** Parses an <appender> node. */
    115:     private function parseAppender(SimpleXMLElement $node) {
    116:         $name = $this->getAttributeValue($node, 'name');
    117:         if (empty($name)) {
    118:             $this->warn("An <appender> node is missing the required 'name' attribute. Skipping appender definition.");
    119:             return;
    120:         }
    121:
    122:         $appender = array();
    123:         $appender['class'] = $this->getAttributeValue($node, 'class');
    124:
    125:         if (isset($node['threshold'])) {
    126:             $appender['threshold'] = $this->getAttributeValue($node, 'threshold');
    127:         }
    128:
    129:         if (isset($node->layout)) {
    130:             $appender['layout']= $this->parseLayout($node->layout, $name);
    131:         }
    132:
    133:         if (count($node->param) > 0) {
    134:             $appender['params'] = $this->parseParameters($node);
    135:         }
    136:
    137:         foreach($node->filter as $filterNode) {
    138:             $appender['filters'][] = $this->parseFilter($filterNode);
    139:         }
    140:
    141:         $this->config['appenders'][$name] = $appender;
    142:     }
    143:
    144:     /** Parses a <layout> node. */
    145:     private function parseLayout(SimpleXMLElement $node, $appenderName) {
    146:         $layout = array();
    147:         $layout['class'] = $this->getAttributeValue($node, 'class');
    148:
    149:         if (count($node->param) > 0) {
    150:             $layout['params'] = $this->parseParameters($node);
    151:         }
    152:
    153:         return $layout;
    154:     }
    155:
    156:     /** Parses any <param> child nodes returning them in an array. */
    157:     private function parseParameters($paramsNode) {
    158:         $params = array();
    159:
    160:         foreach($paramsNode->param as $paramNode) {
    161:             if (empty($paramNode['name'])) {
    162:                 $this->warn("A <param> node is missing the required 'name' attribute. Skipping parameter.");
    163:                 continue;
    164:             }
    165:
    166:             $name = $this->getAttributeValue($paramNode, 'name');
    167:             $value = $this->getAttributeValue($paramNode, 'value');
    168:
    169:             $params[$name] = $value;
    170:         }
    171:
    172:         return $params;
    173:     }
    174:
    175:     /** Parses a <root> node. */
    176:     private function parseRootLogger(SimpleXMLElement $node) {
    177:         $logger = array();
    178:
    179:         if (isset($node->level)) {
    180:             $logger['level'] = $this->getAttributeValue($node->level, 'value');
    181:         }
    182:
    183:         $logger['appenders'] = $this->parseAppenderReferences($node);
    184:
    185:         $this->config['rootLogger'] = $logger;
    186:     }
    187:
    188:     /** Parses a <logger> node. */
    189:     private function parseLogger(SimpleXMLElement $node) {
    190:         $logger = array();
    191:
    192:         $name = $this->getAttributeValue($node, 'name');
    193:         if (empty($name)) {
    194:             $this->warn("A <logger> node is missing the required 'name' attribute. Skipping logger definition.");
    195:             return;
    196:         }
    197:
    198:         if (isset($node->level)) {
    199:             $logger['level'] = $this->getAttributeValue($node->level, 'value');
    200:         }
    201:
    202:         if (isset($node['additivity'])) {
    203:             $logger['additivity'] = $this->getAttributeValue($node, 'additivity');
    204:         }
    205:
    206:         $logger['appenders'] = $this->parseAppenderReferences($node);
    207:
    208:         // Check for duplicate loggers
    209:         if (isset($this->config['loggers'][$name])) {
    210:             $this->warn("Duplicate logger definition [$name]. Overwriting.");
    211:         }
    212:
    213:         $this->config['loggers'][$name] = $logger;
    214:     }
    215:
    216:     /**
    217:      * Parses a <logger> node for appender references and returns them in an array.
    218:      *
    219:      * Previous versions supported appender-ref, as well as appender_ref so both
    220:      * are parsed for backward compatibility.
    221:      */
    222:     private function parseAppenderReferences(SimpleXMLElement $node) {
    223:         $refs = array();
    224:         foreach($node->appender_ref as $ref) {
    225:             $refs[] = $this->getAttributeValue($ref, 'ref');
    226:         }
    227:
    228:         foreach($node->{'appender-ref'} as $ref) {
    229:             $refs[] = $this->getAttributeValue($ref, 'ref');
    230:         }
    231:
    232:         return $refs;
    233:     }
    234:
    235:     /** Parses a <filter> node. */
    236:     private function parseFilter($filterNode) {
    237:         $filter = array();
    238:         $filter['class'] = $this->getAttributeValue($filterNode, 'class');
    239:
    240:         if (count($filterNode->param) > 0) {
    241:             $filter['params'] = $this->parseParameters($filterNode);
    242:         }
    243:
    244:         return $filter;
    245:     }
    246:
    247:     /** Parses a <renderer> node. */
    248:     private function parseRenderer(SimpleXMLElement $node) {
    249:         $renderedClass = $this->getAttributeValue($node, 'renderedClass');
    250:         $renderingClass = $this->getAttributeValue($node, 'renderingClass');
    251:
    252:         $this->config['renderers'][] = compact('renderedClass', 'renderingClass');
    253:     }
    254:
    255:     /** Parses a <defaultRenderer> node. */
    256:     private function parseDefaultRenderer(SimpleXMLElement $node) {
    257:         $renderingClass = $this->getAttributeValue($node, 'renderingClass');
    258:
    259:         // Warn on duplicates
    260:         if(isset($this->config['defaultRenderer'])) {
    261:             $this->warn("Duplicate <defaultRenderer> node. Overwriting.");
    262:         }
    263:
    264:         $this->config['defaultRenderer'] = $renderingClass;
    265:     }
    266:
    267:     // ******************************************
    268:     // ** Helper methods                       **
    269:     // ******************************************
    270:
    271:     private function getAttributeValue(SimpleXMLElement $node, $name) {
    272:         return isset($node[$name]) ? (string) $node[$name] : null;
    273:     }
    274:
    275:     private function warn($message) {
    276:         trigger_error("log4php: " . $message, E_USER_WARNING);
    277:     }
    278: }
    279:
    280: