Subversion-Projekte lars-tiefland.prado

Revision

Blame | Letzte Änderung | Log anzeigen | RSS feed

<?php
/**
 * TSqlMapXmlConfigBuilder, TSqlMapXmlConfiguration, TSqlMapXmlMappingConfiguration classes file.
 *
 * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
 * @link http://www.pradosoft.com/
 * @copyright Copyright &copy; 2005-2008 PradoSoft
 * @license http://www.pradosoft.com/license/
 * @version $Id: TSqlMapXmlConfiguration.php 2541 2008-10-21 15:05:13Z qiang.xue $
 * @package System.Data.SqlMap.Configuration
 */

Prado::using('System.Data.SqlMap.Configuration.TSqlMapStatement');

/**
 * TSqlMapXmlConfig class file.
 *
 * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
 * @version $Id: TSqlMapXmlConfiguration.php 2541 2008-10-21 15:05:13Z qiang.xue $
 * @package System.Data.SqlMap.Configuration
 */
abstract class TSqlMapXmlConfigBuilder
{
        /**
         * Create an instance of an object give by the attribute named 'class' in the
         * node and set the properties on the object given by attribute names and values.
         * @param SimpleXmlNode property node
         * @return Object new instance of class with class name given by 'class' attribute value.
         */
        protected function createObjectFromNode($node)
        {
                if(isset($node['class']))
                {
                        $obj = Prado::createComponent((string)$node['class']);
                        $this->setObjectPropFromNode($obj,$node,array('class'));
                        return $obj;
                }
                throw new TSqlMapConfigurationException(
                        'sqlmap_node_class_undef', $node, $this->getConfigFile());
        }

        /**
         * For each attributes (excluding attribute named in $except) set the
         * property of the $obj given by the name of the attribute with the value
         * of the attribute.
         * @param Object object instance
         * @param SimpleXmlNode property node
         * @param array exception property name
         */
        protected function setObjectPropFromNode($obj,$node,$except=array())
        {
                foreach($node->attributes() as $name=>$value)
                {
                        if(!in_array($name,$except))
                        {
                                if($obj->canSetProperty($name))
                                        $obj->{$name} = (string)$value;
                                else
                                        throw new TSqlMapConfigurationException(
                                                'sqlmap_invalid_property', $name, get_class($obj),
                                                $node, $this->getConfigFile());
                        }
                }
        }

        /**
         * Gets the filename relative to the basefile.
         * @param string base filename
         * @param string relative filename
         * @return string absolute filename.
         */
        protected function getAbsoluteFilePath($basefile,$resource)
        {
                $basedir = dirname($basefile);
                $file = realpath($basedir.DIRECTORY_SEPARATOR.$resource);
                if(!is_string($file) || !is_file($file))
                        $file = realpath($resource);
                if(is_string($file) && is_file($file))
                        return $file;
                else
                        throw new TSqlMapConfigurationException(
                                'sqlmap_unable_to_find_resource', $resource);
        }

        /**
         * Load document using simple xml.
         * @param string filename.
         * @return SimpleXmlElement xml document.
         */
        protected function loadXmlDocument($filename,TSqlMapXmlConfiguration $config)
        {
                if(!is_file($filename))
                        throw new TSqlMapConfigurationException(
                                'sqlmap_unable_to_find_config', $filename);
                return simplexml_load_string($config->replaceProperties(file_get_contents($filename)));
        }

        /**
         * Get element node by ID value (try for attribute name ID as case insensitive).
         * @param SimpleXmlDocument $document
         * @param string tag name.
         * @param string id value.
         * @return SimpleXmlElement node if found, null otherwise.
         */
        protected function getElementByIdValue($document, $tag, $value)
        {
                //hack to allow upper case and lower case attribute names.
                foreach(array('id','ID','Id', 'iD') as $id)
                {
                        $xpath = "//{$tag}[@{$id}='{$value}']";
                        foreach($document->xpath($xpath) as $node)
                                return $node;
                }
        }

        /**
         * @return string configuration file.
         */
        protected abstract function getConfigFile();
}

/**
 * TSqlMapXmlConfig class.
 *
 * Configures the TSqlMapManager using xml configuration file.
 *
 * @author Wei Zhuo <weizho[at]gmail[dot]com>
 * @version $Id: TSqlMapXmlConfiguration.php 2541 2008-10-21 15:05:13Z qiang.xue $
 * @package System.Data.SqlMap.Configuration
 * @since 3.1
 */
class TSqlMapXmlConfiguration extends TSqlMapXmlConfigBuilder
{
        /**
         * @var TSqlMapManager manager
         */
        private $_manager;
        /**
         * @var string configuration file.
         */
        private $_configFile;
        /**
         * @var array global properties.
         */
        private $_properties=array();

        /**
         * @param TSqlMapManager manager instance.
         */
        public function __construct($manager)
        {
                $this->_manager=$manager;
        }

        public function getManager()
        {
                return $this->_manager;
        }

        protected function getConfigFile()
        {
                return $this->_configFile;
        }

        /**
         * Configure the TSqlMapManager using the given xml file.
         * @param string SqlMap configuration xml file.
         */
        public function configure($filename=null)
        {
                $this->_configFile=$filename;
                $document = $this->loadXmlDocument($filename,$this);

                foreach($document->xpath('//property') as $property)
                        $this->loadGlobalProperty($property);

                foreach($document->xpath('//typeHandler') as $handler)
                        $this->loadTypeHandler($handler);

                foreach($document->xpath('//connection[last()]') as $conn)
                        $this->loadDatabaseConnection($conn);

                //try to load configuration in the current config file.
                $mapping = new TSqlMapXmlMappingConfiguration($this);
                $mapping->configure($filename);

                foreach($document->xpath('//sqlMap') as $sqlmap)
                        $this->loadSqlMappingFiles($sqlmap);

                $this->resolveResultMapping();
                $this->attachCacheModels();
        }

        /**
         * Load global replacement property.
         * @param SimpleXmlElement property node.
         */
        protected function loadGlobalProperty($node)
        {
                $this->_properties[(string)$node['name']] = (string)$node['value'];
        }

        /**
         * Load the type handler configurations.
         * @param SimpleXmlElement type handler node
         */
        protected function loadTypeHandler($node)
        {
                $handler = $this->createObjectFromNode($node);
                $this->_manager->getTypeHandlers()->registerTypeHandler($handler);
        }

        /**
         * Load the database connection tag.
         * @param SimpleXmlElement connection node.
         */
        protected function loadDatabaseConnection($node)
        {
                $conn = $this->createObjectFromNode($node);
                $this->_manager->setDbConnection($conn);
        }

        /**
         * Load SqlMap mapping configuration.
         * @param unknown_type $node
         */
        protected function loadSqlMappingFiles($node)
        {
                if(strlen($resource = (string)$node['resource']) > 0)
                {
                        $mapping = new TSqlMapXmlMappingConfiguration($this);
                        $filename = $this->getAbsoluteFilePath($this->_configFile, $resource);
                        $mapping->configure($filename);
                }
        }

        /**
         * Resolve nest result mappings.
         */
        protected function resolveResultMapping()
        {
                $maps = $this->_manager->getResultMaps();
                foreach($maps as $entry)
                {
                        foreach($entry->getColumns() as $item)
                        {
                                $resultMap = $item->getResultMapping();
                                if(strlen($resultMap) > 0)
                                {
                                        if($maps->contains($resultMap))
                                                $item->setNestedResultMap($maps[$resultMap]);
                                        else
                                                throw new TSqlMapConfigurationException(
                                                        'sqlmap_unable_to_find_result_mapping',
                                                                $resultMap, $this->_configFile, $entry->getID());
                                }
                        }
                        if(!is_null($entry->getDiscriminator()))
                                $entry->getDiscriminator()->initialize($this->_manager);
                }
        }

        /**
         * Set the cache for each statement having a cache model property.
         */
        protected function attachCacheModels()
        {
                foreach($this->_manager->getMappedStatements() as $mappedStatement)
                {
                        if(strlen($model = $mappedStatement->getStatement()->getCacheModel()) > 0)
                        {
                                $cache = $this->_manager->getCacheModel($model);
                                $mappedStatement->getStatement()->setCache($cache);
                        }
                }
        }

        /**
         * Replace the place holders ${name} in text with properties the
         * corresponding global property value.
         * @param string original string.
         * @return string string with global property replacement.
         */
        public function replaceProperties($string)
        {
                foreach($this->_properties as $find => $replace)
                        $string = str_replace('${'.$find.'}', $replace, $string);
                return $string;
        }
}

/**
 * Loads the statements, result maps, parameters maps from xml configuration.
 *
 * description
 *
 * @author Wei Zhuo <weizho[at]gmail[dot]com>
 * @version $Id: TSqlMapXmlConfiguration.php 2541 2008-10-21 15:05:13Z qiang.xue $
 * @package System.Data.SqlMap.Configuration
 * @since 3.1
 */
class TSqlMapXmlMappingConfiguration extends TSqlMapXmlConfigBuilder
{
        private $_xmlConfig;
        private $_configFile;
        private $_manager;

        private $_document;

        private $_FlushOnExecuteStatements=array();

        /**
         * Regular expressions for escaping simple/inline parameter symbols
         */
        const SIMPLE_MARK='$';
        const INLINE_SYMBOL='#';
        const ESCAPED_SIMPLE_MARK_REGEXP='/\$\$/';
        const ESCAPED_INLINE_SYMBOL_REGEXP='/\#\#/';
        const SIMPLE_PLACEHOLDER='`!!`';
        const INLINE_PLACEHOLDER='`!!!`';

        /**
         * @param TSqlMapXmlConfiguration parent xml configuration.
         */
        public function __construct(TSqlMapXmlConfiguration $xmlConfig)
        {
                $this->_xmlConfig=$xmlConfig;
                $this->_manager=$xmlConfig->getManager();
        }

        protected function getConfigFile()
        {
                return $this->_configFile;
        }

        /**
         * Configure an XML mapping.
         * @param string xml mapping filename.
         */
        public function configure($filename)
        {
                $this->_configFile=$filename;
                $document = $this->loadXmlDocument($filename,$this->_xmlConfig);
                $this->_document=$document;

                foreach($document->xpath('//resultMap') as $node)
                        $this->loadResultMap($node);

                foreach($document->xpath('//parameterMap') as $node)
                        $this->loadParameterMap($node);

                foreach($document->xpath('//statement') as $node)
                        $this->loadStatementTag($node);

                foreach($document->xpath('//select') as $node)
                        $this->loadSelectTag($node);

                foreach($document->xpath('//insert') as $node)
                        $this->loadInsertTag($node);

                foreach($document->xpath('//update') as $node)
                        $this->loadUpdateTag($node);

                foreach($document->xpath('//delete') as $node)
                        $this->loadDeleteTag($node);

                foreach($document->xpath('//procedure') as $node)
                        $this->loadProcedureTag($node);

                foreach($document->xpath('//cacheModel') as $node)
                                $this->loadCacheModel($node);

                $this->registerCacheTriggers();
        }

        /**
         * Load the result maps.
         * @param SimpleXmlElement result map node.
         */
        protected function loadResultMap($node)
        {
                $resultMap = $this->createResultMap($node);

                //find extended result map.
                if(strlen($extendMap = $resultMap->getExtends()) > 0)
                {
                        if(!$this->_manager->getResultMaps()->contains($extendMap))
                        {
                                $extendNode=$this->getElementByIdValue($this->_document,'resultMap',$extendMap);
                                if($extendNode!==null)
                                        $this->loadResultMap($extendNode);
                        }

                        if(!$this->_manager->getResultMaps()->contains($extendMap))
                                throw new TSqlMapConfigurationException(
                                        'sqlmap_unable_to_find_parent_result_map', $node, $this->_configFile, $extendMap);

                        $superMap = $this->_manager->getResultMap($extendMap);
                        $resultMap->getColumns()->mergeWith($superMap->getColumns());
                }

                //add the result map
                if(!$this->_manager->getResultMaps()->contains($resultMap->getID()))
                        $this->_manager->addResultMap($resultMap);
        }

        /**
         * Create a new result map and its associated result properties,
         * disciminiator and sub maps.
         * @param SimpleXmlElement result map node
         * @return TResultMap SqlMap result mapping.
         */
        protected function createResultMap($node)
        {
                $resultMap = new TResultMap();
                $this->setObjectPropFromNode($resultMap,$node);

                //result nodes
                foreach($node->result as $result)
                {
                        $property = new TResultProperty($resultMap);
                        $this->setObjectPropFromNode($property,$result);
                        $resultMap->addResultProperty($property);
                }

                //create the discriminator
                $discriminator = null;
                if(isset($node->discriminator))
                {
                        $discriminator = new TDiscriminator();
                        $this->setObjectPropFromNode($discriminator, $node->discriminator);
                        $discriminator->initMapping($resultMap);
                }

                foreach($node->xpath('subMap') as $subMapNode)
                {
                        if(is_null($discriminator))
                                throw new TSqlMapConfigurationException(
                                        'sqlmap_undefined_discriminator', $node, $this->_configFile,$subMapNode);
                        $subMap = new TSubMap;
                        $this->setObjectPropFromNode($subMap,$subMapNode);
                        $discriminator->addSubMap($subMap);
                }

                if(!is_null($discriminator))
                        $resultMap->setDiscriminator($discriminator);

                return $resultMap;
        }

        /**
         * Load parameter map from xml.
         *
         * @param SimpleXmlElement parameter map node.
         */
        protected function loadParameterMap($node)
        {
                $parameterMap = $this->createParameterMap($node);

                if(strlen($extendMap = $parameterMap->getExtends()) > 0)
                {
                        if(!$this->_manager->getParameterMaps()->contains($extendMap))
                        {
                                $extendNode=$this->getElementByIdValue($this->_document,'parameterMap',$extendMap);
                                if($extendNode!==null)
                                        $this->loadParameterMap($extendNode);
                        }

                        if(!$this->_manager->getParameterMaps()->contains($extendMap))
                                throw new TSqlMapConfigurationException(
                                        'sqlmap_unable_to_find_parent_parameter_map', $node, $this->_configFile,$extendMap);
                        $superMap = $this->_manager->getParameterMap($extendMap);
                        $index = 0;
                        foreach($superMap->getPropertyNames() as $propertyName)
                                $parameterMap->insertProperty($index++,$superMap->getProperty($propertyName));
                }
                $this->_manager->addParameterMap($parameterMap);
        }

        /**
         * Create a new parameter map from xml node.
         * @param SimpleXmlElement parameter map node.
         * @return TParameterMap new parameter mapping.
         */
        protected function createParameterMap($node)
        {
                $parameterMap = new TParameterMap();
                $this->setObjectPropFromNode($parameterMap,$node);
                foreach($node->parameter as $parameter)
                {
                        $property = new TParameterProperty();
                        $this->setObjectPropFromNode($property,$parameter);
                        $parameterMap->addProperty($property);
                }
                return $parameterMap;
        }

        /**
         * Load statement mapping from xml configuration file.
         * @param SimpleXmlElement statement node.
         */
        protected function loadStatementTag($node)
        {
                $statement = new TSqlMapStatement();
                $this->setObjectPropFromNode($statement,$node);
                $this->processSqlStatement($statement, $node);
                $mappedStatement = new TMappedStatement($this->_manager, $statement);
                $this->_manager->addMappedStatement($mappedStatement);
        }

        /**
         * Load extended SQL statements if application. Replaces global properties
         * in the sql text. Extracts inline parameter maps.
         * @param TSqlMapStatement mapped statement.
         * @param SimpleXmlElement statement node.
         */
        protected function processSqlStatement($statement, $node)
        {
                $commandText = (string)$node;
                if(strlen($extend = $statement->getExtends()) > 0)
                {
                        $superNode = $this->getElementByIdValue($this->_document,'*',$extend);
                        if($superNode!==null)
                                $commandText = (string)$superNode . $commandText;
                        else
                                throw new TSqlMapConfigurationException(
                                                'sqlmap_unable_to_find_parent_sql', $extend, $this->_configFile,$node);
                }
                //$commandText = $this->_xmlConfig->replaceProperties($commandText);
                $statement->initialize($this->_manager);
                $this->applyInlineParameterMap($statement, $commandText, $node);
        }

        /**
         * Extract inline parameter maps.
         * @param TSqlMapStatement statement object.
         * @param string sql text
         * @param SimpleXmlElement statement node.
         */
        protected function applyInlineParameterMap($statement, $sqlStatement, $node)
        {
                $scope['file'] = $this->_configFile;
                $scope['node'] = $node;

                $sqlStatement=preg_replace(self::ESCAPED_INLINE_SYMBOL_REGEXP,self::INLINE_PLACEHOLDER,$sqlStatement);
                if($statement->parameterMap() === null)
                {
                        // Build a Parametermap with the inline parameters.
                        // if they exist. Then delete inline infos from sqltext.
                        $parameterParser = new TInlineParameterMapParser;
                        $sqlText = $parameterParser->parse($sqlStatement, $scope);
                        if(count($sqlText['parameters']) > 0)
                        {
                                $map = new TParameterMap();
                                $map->setID($statement->getID().'-InLineParameterMap');
                                $statement->setInlineParameterMap($map);
                                foreach($sqlText['parameters'] as $property)
                                        $map->addProperty($property);
                        }
                        $sqlStatement = $sqlText['sql'];
                }
                $sqlStatement=preg_replace('/'.self::INLINE_PLACEHOLDER.'/',self::INLINE_SYMBOL,$sqlStatement);

                $this->prepareSql($statement, $sqlStatement, $node);
        }

        /**
         * Prepare the sql text (may extend to dynamic sql).
         * @param TSqlMapStatement mapped statement.
         * @param string sql text.
         * @param SimpleXmlElement statement node.
         * @todo Extend to dynamic sql.
         */
        protected function prepareSql($statement,$sqlStatement, $node)
        {
                $simpleDynamic = new TSimpleDynamicParser;
                $sqlStatement=preg_replace(self::ESCAPED_SIMPLE_MARK_REGEXP,self::SIMPLE_PLACEHOLDER,$sqlStatement);
                $dynamics = $simpleDynamic->parse($sqlStatement);
                if(count($dynamics['parameters']) > 0)
                {
                        $sql = new TSimpleDynamicSql($dynamics['parameters']);
                        $sqlStatement = $dynamics['sql'];
                }
                else
                        $sql = new TStaticSql();
                $sqlStatement=preg_replace('/'.self::SIMPLE_PLACEHOLDER.'/',self::SIMPLE_MARK,$sqlStatement);
                $sql->buildPreparedStatement($statement, $sqlStatement);
                $statement->setSqlText($sql);
        }

        /**
         * Load select statement from xml mapping.
         * @param SimpleXmlElement select node.
         */
        protected function loadSelectTag($node)
        {
                $select = new TSqlMapSelect;
                $this->setObjectPropFromNode($select,$node);
                $this->processSqlStatement($select,$node);
                $mappedStatement = new TMappedStatement($this->_manager, $select);
                if(strlen($select->getCacheModel()) > 0)
                        $mappedStatement = new TCachingStatement($mappedStatement);

                $this->_manager->addMappedStatement($mappedStatement);
        }

        /**
         * Load insert statement from xml mapping.
         * @param SimpleXmlElement insert node.
         */
        protected function loadInsertTag($node)
        {
                $insert = $this->createInsertStatement($node);
                $this->processSqlStatement($insert, $node);
                $mappedStatement = new TInsertMappedStatement($this->_manager, $insert);
                $this->_manager->addMappedStatement($mappedStatement);
        }

        /**
         * Create new insert statement from xml node.
         * @param SimpleXmlElement insert node.
         * @return TSqlMapInsert insert statement.
         */
        protected function createInsertStatement($node)
        {
                $insert = new TSqlMapInsert;
                $this->setObjectPropFromNode($insert,$node);
                if(isset($node->selectKey))
                        $this->loadSelectKeyTag($insert,$node->selectKey);
                return $insert;
        }

        /**
         * Load the selectKey statement from xml mapping.
         * @param SimpleXmlElement selectkey node
         */
        protected function loadSelectKeyTag($insert, $node)
        {
                $selectKey = new TSqlMapSelectKey;
                $this->setObjectPropFromNode($selectKey,$node);
                $selectKey->setID($insert->getID());
                $selectKey->setID($insert->getID().'.SelectKey');
                $this->processSqlStatement($selectKey,$node);
                $insert->setSelectKey($selectKey);
                $mappedStatement = new TMappedStatement($this->_manager, $selectKey);
                $this->_manager->addMappedStatement($mappedStatement);
        }

        /**
         * Load update statement from xml mapping.
         * @param SimpleXmlElement update node.
         */
        protected function loadUpdateTag($node)
        {
                $update = new TSqlMapUpdate;
                $this->setObjectPropFromNode($update,$node);
                $this->processSqlStatement($update, $node);
                $mappedStatement = new TUpdateMappedStatement($this->_manager, $update);
                $this->_manager->addMappedStatement($mappedStatement);
        }

        /**
         * Load delete statement from xml mapping.
         * @param SimpleXmlElement delete node.
         */
        protected function loadDeleteTag($node)
        {
                $delete = new TSqlMapDelete;
                $this->setObjectPropFromNode($delete,$node);
                $this->processSqlStatement($delete, $node);
                $mappedStatement = new TDeleteMappedStatement($this->_manager, $delete);
                $this->_manager->addMappedStatement($mappedStatement);
        }

        /**
         * Load procedure statement from xml mapping.
         * @todo Implement loading procedure
         * @param SimpleXmlElement procedure node
         */
        protected function loadProcedureTag($node)
        {
                //var_dump('todo: add load procedure');
        }

        /**
         * Load cache models from xml mapping.
         * @param SimpleXmlElement cache node.
         */
        protected function loadCacheModel($node)
        {
                $cacheModel = new TSqlMapCacheModel;
                $properties = array('id','implementation');
                foreach($node->attributes() as $name=>$value)
                {
                        if(in_array(strtolower($name), $properties))
                                $cacheModel->{'set'.$name}((string)$value);
                }
                $cache = Prado::createComponent($cacheModel->getImplementationClass());
                $this->setObjectPropFromNode($cache,$node,$properties);
                $cacheModel->initialize($cache);
                $this->_manager->addCacheModel($cacheModel);
                foreach($node->xpath('flushOnExecute') as $flush)
                        $this->loadFlushOnCache($cacheModel,$node,$flush);
        }

        /**
         * Load the flush on cache properties.
         * @param TSqlMapCacheModel cache model
         * @param SimpleXmlElement parent node.
         * @param SimpleXmlElement flush node.
         */
        protected function loadFlushOnCache($cacheModel,$parent,$node)
        {
                $id = $cacheModel->getID();
                if(!isset($this->_FlushOnExecuteStatements[$id]))
                        $this->_FlushOnExecuteStatements[$id] = array();
                foreach($node->attributes() as $name=>$value)
                {
                        if(strtolower($name)==='statement')
                                $this->_FlushOnExecuteStatements[$id][] = (string)$value;
                }
        }

        /**
         * Attach CacheModel to statement and register trigger statements for cache models
         */
        protected function registerCacheTriggers()
        {
                foreach($this->_FlushOnExecuteStatements as $cacheID => $statementIDs)
                {
                        $cacheModel = $this->_manager->getCacheModel($cacheID);
                        foreach($statementIDs as $statementID)
                        {
                                $statement = $this->_manager->getMappedStatement($statementID);
                                $cacheModel->registerTriggerStatement($statement);
                        }
                }
        }
}