Skip to content

Commit

Permalink
Merge pull request #14 from uwej711/binary_property_stream_wrapper
Browse files Browse the repository at this point in the history
Binary property stream wrapper
  • Loading branch information
uwej711 committed Jun 28, 2011
2 parents 197eb89 + c25e7f1 commit 698fac9
Show file tree
Hide file tree
Showing 4 changed files with 222 additions and 8 deletions.
127 changes: 127 additions & 0 deletions src/Jackalope/BinaryStreamWrapper.php
@@ -0,0 +1,127 @@
<?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.
*
* @package jackalope
* @private
*/
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;
private $session = 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.
*
* Always checks that the current session is still alive.
*/
private function init_stream()
{
if ($this->session && !$this->session->isLive()) {
throw new \LogicException("Trying to read a stream from a closed transport.");
}
if (null === $this->stream) {
$url = parse_url($this->path);
$this->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 (null === $token) {
$this->stream = $this->session->getObjectManager()->getBinaryStream($property_path);
} else {
// check if streams have been loaded for multivalued properties
if (!isset(self::$multiValueMap[$token])) {
self::$multiValueMap[$token] = $this->session->getObjectManager()->getBinaryStream($property_path);
}
$this->stream = self::$multiValueMap[$token][$index];
}
}
}
}
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 @@ -240,12 +243,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 @@ -259,8 +256,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()) {
$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;
}
// single property case
return fopen('jackalope://' . $this->session->getRegistryKey() . $this->path , 'rwb+');
}

/**
* Returns an integer representation of the value of this property. A shortcut
Expand Down
58 changes: 58 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 @@ -894,6 +903,7 @@ public function logout()
//TODO anything to do on logout?
//OPTIMIZATION: flush object manager
$this->logout = true;
self::unregisterSession($this);
$this->getTransport()->logout();
}

Expand Down Expand Up @@ -940,6 +950,7 @@ public function getRetentionManager()
/**
* Implementation specific: The object manager is also used by other components, i.e. the QueryManager.
* DO NOT USE if you are a consumer of the api
* @private
*/
public function getObjectManager()
{
Expand All @@ -948,9 +959,56 @@ public function getObjectManager()

/**
* Implementation specific: The transport implementation is also used by other components, i.e. the NamespaceRegistry
* @private
*/
public function getTransport()
{
return $this->objectManager->getTransport();
}

/**
* Implementation specific: register session in session registry
* @private
*/
protected static function registerSession(Session $session)
{
$key = $session->getRegistryKey();
self::$sessionRegistry[$key] = $session;
}

/**
* Implementation specific: unregister session in session registry
* @private
*/
protected static function unregisterSession(Session $session)
{
$key = $session->getRegistryKey();
unset(self::$sessionRegistry[$key]);
}

/**
* Implementation specific: create an id for the session registry
* @private
* @return an id for this session
*/
public function getRegistryKey()
{
return spl_object_hash($this);
}

/**
* Implementation specific: get a session from the session registry
*
* @private
* @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', 'logout', '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 698fac9

Please sign in to comment.