From 86c4a16e2e1eb106c01048167815656816f17aa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20J=C3=A4ger?= Date: Mon, 27 Jun 2011 08:56:22 +0200 Subject: [PATCH] Implemented BinaryStreamWrapper for lazy loading All streams for binary properties are wrapped for lazy loading since fetching them from the backend requires an extra call. --- src/Jackalope/BinaryStreamWrapper.php | 122 ++++++++++++++++++++++++++ src/Jackalope/Property.php | 30 +++++-- src/Jackalope/Session.php | 51 +++++++++++ tests/Jackalope/SessionTest.php | 15 ++++ 4 files changed, 210 insertions(+), 8 deletions(-) create mode 100644 src/Jackalope/BinaryStreamWrapper.php diff --git a/src/Jackalope/BinaryStreamWrapper.php b/src/Jackalope/BinaryStreamWrapper.php new file mode 100644 index 00000000..8f778ec1 --- /dev/null +++ b/src/Jackalope/BinaryStreamWrapper.php @@ -0,0 +1,122 @@ +path = $path; + return true; + } + + public function stream_read($count) + { + $this->init_stream(); + return fread($this->stream, $count); + } + + public function stream_write($data) + { + $this->init_stream(); + return fwrite($this->stream, $data); + } + + public function stream_tell() + { + $this->init_stream(); + return ftell($this->stream); + } + + public function stream_eof() + { + $this->init_stream(); + return feof($this->stream); + } + + public function stream_stat() + { + $this->init_stream(); + return fstat($this->stream); + } + + public function stream_seek($offset, $whence) + { + $this->init_stream(); + return fseek($this->stream, $offset, $whence); + } + + public function stream_close() + { + if ($this->stream) { + fclose($this->stream); + } + } + + public function stream_flush() + { + if ($this->stream) { + return fflush($this->stream); + } else { + return false; + } + } + + /** + * Check whether stream was already loaded, otherwise fetch from backend. + * + * Multivalued properties have a special handling since the backend returns all + * streams in a single call. + */ + private function init_stream() + { + if (null === $this->stream) { + $url = parse_url($this->path); + $session = Session::getSessionFromRegistry($url['host']); + $property_path = $url['path']; + $token = null; + if (isset($url['user'])) { + $token = $url['user']; + } + if (isset($url['port'])) { + $index = $url['port'] - 1; + } + if ($session->isLive()) { + if (null === $token) { + $this->stream = $session->getObjectManager()->getBinaryStream($property_path); + } else { + // check if streams have been loaded for multivalued properties + if (!isset(self::$multiValueMap[$token])) { + self::$multiValueMap[$token] = $session->getObjectManager()->getBinaryStream($property_path); + } + $this->stream = self::$multiValueMap[$token][$index]; + } + } else { + // TODO proper exception? + } + } + } +} diff --git a/src/Jackalope/Property.php b/src/Jackalope/Property.php index 7e59dc98..d1e9b68c 100644 --- a/src/Jackalope/Property.php +++ b/src/Jackalope/Property.php @@ -15,6 +15,9 @@ */ class Property extends Item implements \IteratorAggregate, \PHPCR\PropertyInterface { + /** flag to call stream_wrapper_register only once */ + protected static $binaryStreamWrapperRegistered = false; + protected $value; /** length (only used for binary property */ protected $length; @@ -205,12 +208,6 @@ public function getBinary() if ($this->type != PropertyType::BINARY) { return PropertyType::convertType($this->value, PropertyType::BINARY); } - /* - OPTIMIZE: store and clone the stream? or is re-fetch from backend faster? - if (null == $this->value) { - $this->value = $this->objectManager->getBinaryStream($this->path); - } - */ if ($this->value != null) { // new or updated property $val = is_array($this->value) ? $this->value : array($this->value); @@ -224,8 +221,25 @@ public function getBinary() } return is_array($this->value) ? $ret : $ret[0]; } - return $this->objectManager->getBinaryStream($this->path); - } + // register a stream wrapper to lazily load binary property values + if (!self::$binaryStreamWrapperRegistered) { + stream_wrapper_register('jackalope', 'Jackalope\\BinaryStreamWrapper'); + self::$binaryStreamWrapperRegistered = true; + } + // return wrapped stream + if (!$this->isMultiple()) { + return fopen('jackalope://' . $this->session->getRegistryKey() . $this->path , 'rwb+'); + } else { + $results = array(); + // identifies all streams loaded by one backend call + $token = md5(uniqid(mt_rand(), true)); + // start with part = 1 since 0 will not be parsed properly by parse_url + for ($i = 1; $i <= count($this->length); $i++) { + $results[] = fopen('jackalope://' . $token. '@' . $this->session->getRegistryKey() . ':' . $i . $this->path , 'rwb+'); + } + return $results; + } + } /** * Returns an integer representation of the value of this property. A shortcut diff --git a/src/Jackalope/Session.php b/src/Jackalope/Session.php index ce515674..65803313 100644 --- a/src/Jackalope/Session.php +++ b/src/Jackalope/Session.php @@ -21,6 +21,14 @@ class Session implements \PHPCR\SessionInterface { + /** + * A registry for all created sessions to be able to reference them by id in + * the stream wrapper for lazy laoding binary properties. + * + * Keys are spl_object_hash'es for the sessions which are the values + */ + protected static $sessionRegistry = array(); + /** * The factory to instantiate objects * @var Factory @@ -58,6 +66,7 @@ public function __construct($factory, Repository $repository, $workspaceName, \P $this->workspace = $this->factory->get('Workspace', array($this, $this->objectManager, $workspaceName)); $this->credentials = $credentials; $this->namespaceRegistry = $this->workspace->getNamespaceRegistry(); + self::registerSession($this); } /** @@ -895,6 +904,7 @@ public function logout() //OPTIMIZATION: flush object manager $this->logout = true; $this->getTransport()->logout(); + self::unregisterSession($this); } /** @@ -953,4 +963,45 @@ public function getTransport() { return $this->objectManager->getTransport(); } + + /** + * Implementation specific: register session in session registry + */ + protected static function registerSession(Session $session) + { + $key = $session->getRegistryKey(); + self::$sessionRegistry[$key] = $session; + } + + /** + * Implementation specific: unregister session in session registry + */ + protected static function unregisterSession(Session $session) + { + $key = $session->getRegistryKey(); + unset(self::$sessionRegistry[$key]); + } + + /** + * Implementation specific: create an id for the session registry + */ + public function getRegistryKey() + { + return spl_object_hash($this); + } + + /** + * Implementation specific: get a session from the session registry + * + * @param $key key for the session + * @return the session or null if none is registered with the given key + */ + public static function getSessionFromRegistry($key) + { + if (isset(self::$sessionRegistry[$key])) { + return self::$sessionRegistry[$key]; + } else { + return null; + } + } } diff --git a/tests/Jackalope/SessionTest.php b/tests/Jackalope/SessionTest.php index 7b4a82f0..fd6c01a0 100644 --- a/tests/Jackalope/SessionTest.php +++ b/tests/Jackalope/SessionTest.php @@ -30,4 +30,19 @@ public function testLogout() $this->markTestSkipped(); //TODO: test flush object manager with the help of mock objects } + + public function testSessionRegistry() + { + $factory = new \Jackalope\Factory; + $repository = $this->getMock('Jackalope\Repository', array(), array($factory), '', false); + $transport = $this->getMock('Jackalope\Transport\Davex\Client', array('login', 'getRepositoryDescriptors', 'getNamespaces'), array($factory, 'http://example.com')); + $transport->expects($this->any()) + ->method('getNamespaces') + ->will($this->returnValue(array())); + $s = new Session($factory, $repository, 'workspaceName', new \PHPCR\SimpleCredentials('foo', 'bar'), $transport); + + $this->assertSame(Session::getSessionFromRegistry($s->getRegistryKey()), $s); + $s->logout(); + $this->assertNull(Session::getSessionFromRegistry($s->getRegistryKey())); + } }