Skip to content

Commit

Permalink
Implemented BinaryStreamWrapper for lazy loading
Browse files Browse the repository at this point in the history
All streams for binary properties are wrapped for lazy loading since
fetching them from the backend requires an extra call.
  • Loading branch information
uwej711 authored and Daniel Barsotti committed Jun 30, 2011
1 parent 60ad584 commit 86c4a16
Show file tree
Hide file tree
Showing 4 changed files with 210 additions and 8 deletions.
122 changes: 122 additions & 0 deletions src/Jackalope/BinaryStreamWrapper.php
@@ -0,0 +1,122 @@
<?php

namespace Jackalope;

/**
* This class implements a stream wrapper that allows for lazy loaded binary properties
*
* The stream is registered for the protocol "jackalope://". The URL must contain the sessions
* registryKey as the host part and the oath of the binary property as the path part, e.g.
* "jackalope://abc0123/content/node/binary"
*
* For multivalued properties the url also contains the position of the stream in the property array
* in the port field and a token to identify all streams loaded by the single backend call in an static
* array as username.
*
* The loading from the backend is deferred until the stream is accessed. Then it is loaded and all
* stream functions are passed on to the underlying stream.
*/
class BinaryStreamWrapper
{
/** array to store the streams loaded by a backend call for all values of a multivalue property */
private static $multiValueMap = array();

private $path = null;
private $stream = null;

/**
* Parse the url and store the information.
*/
public function stream_open($path, $mode, $options, &$opened_path)
{
$this->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?
}
}
}
}
30 changes: 22 additions & 8 deletions src/Jackalope/Property.php
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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
Expand Down
51 changes: 51 additions & 0 deletions src/Jackalope/Session.php
Expand Up @@ -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
Expand Down Expand Up @@ -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);
}

/**
Expand Down Expand Up @@ -895,6 +904,7 @@ public function logout()
//OPTIMIZATION: flush object manager
$this->logout = true;
$this->getTransport()->logout();
self::unregisterSession($this);
}

/**
Expand Down Expand Up @@ -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;
}
}
}
15 changes: 15 additions & 0 deletions tests/Jackalope/SessionTest.php
Expand Up @@ -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()));
}
}

0 comments on commit 86c4a16

Please sign in to comment.