Permalink
Browse files

Merge pull request #57 from jackalope/jackrabbit_multiget_from_2_4_II

Use POST for multi get and adjust to the new multi getNodes feature of Jackrabbit 2.3.6 / 2.4
  • Loading branch information...
2 parents 0d4e02b + 002446a commit f934c50b78039037b8cc0fb14049fef8fa23cf5b @dbu dbu committed Jan 3, 2012
View
@@ -33,7 +33,7 @@ Please see the file LICENSE in this folder.
## Jackalope Jackrabbit
Besides the Jackalope repository, you need the Jackrabbit server component. For instructions, see [Jackalope Wiki](https://github.com/jackalope/jackalope/wiki/Running-a-jackrabbit-server)
-
+Make sure you have at least the version specified in [the protocol implementation](https://github.com/jackalope/jackalope/blob/master/src/Jackalope/Transport/Jackrabbit/Client.php#L56)
## Jackalope - Doctrine DBAL
@@ -38,8 +38,13 @@
* Connection to one Jackrabbit server.
*
* This class handles the communication between Jackalope and Jackrabbit over
- * Davex. Once the login method has been called, the workspace is set and can not be
- * changed anymore.
+ * Davex. Once the login method has been called, the workspace is set and can
+ * not be changed anymore.
+ *
+ * We make one exception to the rule that nothing may be cached in the
+ * transport: Repository descriptors are considered immutable and cached
+ * (because they are also used in startup to check the backend version is
+ * compatible).
*
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License Version 2.0, January 2004
*
@@ -54,6 +59,11 @@
class Client extends BaseTransport implements QueryTransport, PermissionInterface, WritingInterface, VersioningInterface, NodeTypeCndManagementInterface, TransactionInterface
{
/**
+ * minimal version needed for the backend server
+ */
+ const VERSION = "2.3.6";
+
+ /**
* Description of the namspace to be used for communication with the server.
* @var string
*/
@@ -67,7 +77,7 @@ class Client extends BaseTransport implements QueryTransport, PermissionInterfac
/**
* The factory to instantiate objects
- * @var Factory
+ * @var FactoryInterface
*/
protected $factory;
@@ -83,6 +93,9 @@ class Client extends BaseTransport implements QueryTransport, PermissionInterfac
/**
* Workspace name the transport is bound to
+ *
+ * Set once login() has been executed and may not be changed later on.
+ *
* @var string
*/
protected $workspace;
@@ -109,7 +122,10 @@ class Client extends BaseTransport implements QueryTransport, PermissionInterfac
protected $workspaceUriRoot;
/**
- * Set of credentials necessary to connect to the server or else.
+ * Set of credentials necessary to connect to the server.
+ *
+ * Set once login() has been executed and may not be changed later on.
+ *
* @var CredentialsInterface
*/
protected $credentials;
@@ -152,6 +168,15 @@ class Client extends BaseTransport implements QueryTransport, PermissionInterfac
protected $checkLoginOnServer = true;
/**
+ * Cached result of the repository descriptors.
+ *
+ * This is our exception to the rule that nothing may be cached in transport.
+ *
+ * @var array of strings as returned by getRepositoryDescriptors
+ */
+ protected $descriptors = null;
+
+ /**
* The transaction token received by a LOCKing request
*
* Is FALSE while no transaction running.
@@ -238,7 +263,7 @@ protected function getRequest($method, $uri)
}
- $request = $this->factory->get('Transport\\Jackrabbit\\Request', array($this->curl, $method, $uri));
+ $request = $this->factory->get('Transport\\Jackrabbit\\Request', array($this, $this->curl, $method, $uri));
$request->setCredentials($this->credentials);
foreach ($this->defaultHeaders as $header) {
$request->addHeader($header);
@@ -316,32 +341,47 @@ public function setCheckLoginOnServer($bool)
*/
public function getRepositoryDescriptors()
{
- $request = $this->getRequest(Request::REPORT, $this->server);
- $request->setBody($this->buildReportRequest('dcr:repositorydescriptors'));
- $dom = $request->executeDom();
+ if (null == $this->descriptors) {
+ $request = $this->getRequest(Request::REPORT, $this->server);
+ $request->setBody($this->buildReportRequest('dcr:repositorydescriptors'));
+ $dom = $request->executeDom();
+
+ if ($dom->firstChild->localName != 'repositorydescriptors-report'
+ || $dom->firstChild->namespaceURI != self::NS_DCR
+ ) {
+ throw new RepositoryException('Error talking to the backend. '.$dom->saveXML());
+ }
- if ($dom->firstChild->localName != 'repositorydescriptors-report'
- || $dom->firstChild->namespaceURI != self::NS_DCR
- ) {
- throw new RepositoryException('Error talking to the backend. '.$dom->saveXML());
- }
+ $descs = $dom->getElementsByTagNameNS(self::NS_DCR, 'descriptor');
+ $this->descriptors = array();
+ foreach ($descs as $desc) {
+ $name = $desc->getElementsByTagNameNS(self::NS_DCR, 'descriptorkey')->item(0)->textContent;
- $descs = $dom->getElementsByTagNameNS(self::NS_DCR, 'descriptor');
- $descriptors = array();
- foreach ($descs as $desc) {
- $values = array();
- foreach ($desc->getElementsByTagNameNS(self::NS_DCR, 'descriptorvalue') as $value) {
- $values[] = $value->textContent;
+ $values = array();
+ $valuenodes = $desc->getElementsByTagNameNS(self::NS_DCR, 'descriptorvalue');
+ foreach ($valuenodes as $value) {
+ $values[] = $value->textContent;
+ }
+ if ($valuenodes->length == 1) {
+ //there was one type and one value => this is a single value property
+ //TODO: is this the correct assumption? or should the backend tell us specifically?
+ $this->descriptors[$name] = $values[0];
+ } else {
+ $this->descriptors[$name] = $values;
+ }
}
- if ($desc->childNodes->length == 2) {
- //there was one type and one value => this is a single value property
- //TODO: is this the correct assumption? or should the backend tell us specifically?
- $descriptors[$desc->firstChild->textContent] = $values[0];
- } else {
- $descriptors[$desc->firstChild->textContent] = $values;
+
+ if (! isset($this->descriptors['jcr.repository.version'])) {
+ throw new UnsupportedRepositoryOperationException("The backend at {$this->server} does not provide the jcr.repository.version descriptor");
+ }
+
+ if (! version_compare(self::VERSION, $this->descriptors['jcr.repository.version'], '<=')) {
+ throw new UnsupportedRepositoryOperationException("The backend at {$this->server} is an unsupported version of jackrabbit: \"".
+ $this->descriptors['jcr.repository.version'].
+ '". Need at least "'.self::VERSION.'"');
}
}
- return $descriptors;
+ return $this->descriptors;
}
/**
@@ -388,9 +428,9 @@ public function getNodes($paths)
if (count($paths) == 0) {
return array();
}
- $url = array_shift($paths);
- if (count($paths) == 0) {
+ if (count($paths) == 1) {
+ $url = array_shift($paths);
try {
return array($url => $this->getNode($url));
} catch (ItemNotFoundException $e) {
@@ -399,9 +439,9 @@ public function getNodes($paths)
}
$body = array();
- $url = $this->encodeAndValidatePathForDavex($url).".0.json";
+ $url = $this->encodeAndValidatePathForDavex("/").".0.json";
foreach ($paths as $path) {
- $body[] = http_build_query(array(":get"=>$path));
+ $body[] = http_build_query(array(":include"=>$path));
}
$body = implode("&",$body);
$request = $this->getRequest(Request::POST, $url);
@@ -136,6 +136,11 @@ class Request
const INFINITY = 'infinity';
/**
+ * @var Client
+ */
+ protected $client;
+
+ /**
* @var curl
*/
protected $curl;
@@ -193,16 +198,26 @@ class Request
protected $transactionId = false;
/**
+ * Whether we already did a version check in handling an error.
+ * Doing this once per php process is enough.
+ *
+ * @var bool
+ */
+ static protected $versionChecked = false;
+
+ /**
* Initiaties the NodeTypes request object.
*
* @param FactoryInterface $factory Ignored for now, as this class does not create objects
+ * @param Client $client The jackrabbit client instance
* @param curl $curl The cURL object to use in this request
* @param string $method the HTTP method to use, one of the class constants
* @param string|array $uri the remote url for this request, including protocol,
* host name, workspace and path to the object to manipulate. May be an array of uri
*/
- public function __construct(FactoryInterface $factory, curl $curl, $method, $uri)
+ public function __construct(FactoryInterface $factory, Client $client, curl $curl, $method, $uri)
{
+ $this->client = $client;
$this->curl = $curl;
$this->setMethod($method);
$this->setUri($uri);
@@ -486,6 +501,21 @@ protected function singleRequest($getCurlObject)
*/
protected function handleError(curl $curl, $response, $httpCode)
{
+ // first: check if the backend is too old for us
+ if (! self::$versionChecked) {
+ // avoid endless loops.
+ self::$versionChecked = true;
+ try {
+ // getting the descriptors triggers a version check
+ $this->client->getRepositoryDescriptors();
+ } catch(\Exception $e) {
+ if ($e instanceof \PHPCR\UnsupportedRepositoryOperationException) {
+ throw $e;
+ }
+ //otherwise ignore exception here as to not confuse what happened
+ }
+ }
+
switch ($curl->errno()) {
case CURLE_COULDNT_RESOLVE_HOST:
case CURLE_COULDNT_CONNECT:
@@ -9,7 +9,9 @@ public function testConstructor()
$factory = new \Jackalope\Factory;
$credentials = new \PHPCR\SimpleCredentials('test', 'cred');
$workspaceName = 'sadf3sd';
- $transport = $this->getMock('Jackalope\Transport\Jackrabbit\Client', array('login', 'getRepositoryDescriptors', 'getNamespaces'), array($factory, 'http://example.com'));
+ $transport = $this->getMockBuilder('Jackalope\Transport\Jackrabbit\Client')
+ ->disableOriginalConstructor()
+ ->getMock(array('login', 'getRepositoryDescriptors', 'getNamespaces'), array($factory, 'http://example.com'));
$transport->expects($this->once())
->method('login')
->with($this->equalTo($credentials), $this->equalTo($workspaceName))
@@ -13,7 +13,9 @@ public function testConstructor()
$cred = new \PHPCR\SimpleCredentials($userID, 'xxxx');
$cred->setAttribute('test', 'toast');
$cred->setAttribute('other', 'value');
- $transport = $this->getMock('Jackalope\Transport\Jackrabbit\Client', array('login', 'getRepositoryDescriptors', 'getNamespaces'), array($factory, 'http://example.com'));
+ $transport = $this->getMockBuilder('Jackalope\Transport\Jackrabbit\Client')
+ ->disableOriginalConstructor()
+ ->getMock(array('login', 'getRepositoryDescriptors', 'getNamespaces'), array($factory, 'http://example.com'));
$transport->expects($this->any())
->method('getNamespaces')
->will($this->returnValue(array()));
@@ -45,7 +47,9 @@ public function testSessionRegistry()
{
$factory = new \Jackalope\Factory;
$repository = $this->getMock('Jackalope\Repository', array(), array($factory), '', false);
- $transport = $this->getMock('Jackalope\Transport\Jackrabbit\Client', array('login', 'logout', 'getRepositoryDescriptors', 'getNamespaces'), array($factory, 'http://example.com'));
+ $transport = $this->getMockBuilder('Jackalope\Transport\Jackrabbit\Client')
+ ->disableOriginalConstructor()
+ ->getMock(array('login', 'logout', 'getRepositoryDescriptors', 'getNamespaces'), array($factory, 'http://example.com'));
$transport->expects($this->any())
->method('getNamespaces')
->will($this->returnValue(array()));
@@ -22,7 +22,9 @@ protected function setUp()
protected function getTransportStub()
{
$factory = new \Jackalope\Factory;
- $transport = $this->getMock('\Jackalope\Transport\Jackrabbit\Client', array('getNode', 'getNodeTypes', 'getNodePathForIdentifier'), array($factory, 'http://example.com'));
+ $transport = $this->getMockBuilder('\Jackalope\Transport\Jackrabbit\Client')
+ ->disableOriginalConstructor()
+ ->getMock(array('getNode', 'getNodeTypes', 'getNodePathForIdentifier'), array($factory, 'http://example.com'));
$transport->expects($this->any())
->method('getNode')
@@ -553,6 +553,18 @@ class ClientMock extends Client
public $workspaceUri = 'testWorkspaceUri';
public $workspaceUriRoot = 'testWorkspaceUriRoot';
+ /**
+ * overwrite client constructor which checks backend version
+ */
+ public function __construct($factory, $serverUri)
+ {
+ $this->factory = $factory;
+ // append a slash if not there
+ if ('/' !== substr($serverUri, -1)) {
+ $serverUri .= '/';
+ }
+ $this->server = $serverUri;
+ }
public function buildNodeTypesRequestMock(Array $params)
{
return $this->buildNodeTypesRequest($params);
@@ -38,16 +38,24 @@ protected function getCurlFixture($fixture = null, $httpCode = 200, $errno = nul
return $curl;
}
+ public function getClientMock()
+ {
+ return $this->getMockBuilder('Jackalope\\Transport\\Jackrabbit\\Client')
+ ->disableOriginalConstructor()
+ ->getMock()
+ ;
+ }
+
public function getRequest($fixture = null, $httpCode = 200, $errno = null)
{
$factory = new \Jackalope\Factory;
- return new RequestMock($factory, $this->getCurlFixture($fixture, $httpCode, $errno), 'GET', 'http://foo/');
+ return new RequestMock($factory, $this->getClientMock(), $this->getCurlFixture($fixture, $httpCode, $errno), 'GET', 'http://foo/');
}
public function testExecuteDom()
{
$factory = new \Jackalope\Factory;
- $request = $this->getMock('Jackalope\Transport\Jackrabbit\Request', array('execute'), array($factory, $this->getCurlFixture(),null, null));
+ $request = $this->getMock('Jackalope\\Transport\\Jackrabbit\\Request', array('execute'), array($factory, $this->getClientMock(), $this->getCurlFixture(),null, null));
$request->expects($this->once())
->method('execute')
->will($this->returnValue('<xml/>'));
@@ -8,7 +8,9 @@ public function testConstructor()
{
$factory = new \Jackalope\Factory;
$session = $this->getMock('Jackalope\Session', array(), array($factory), '', false);
- $transport = $this->getMock('Jackalope\Transport\Jackrabbit\Client', array(), array($factory, 'http://example.com'));
+ $transport = $this->getMockBuilder('Jackalope\Transport\Jackrabbit\Client')
+ ->disableOriginalConstructor()
+ ->getMock(array());
$objManager = $this->getMock('Jackalope\ObjectManager', array(), array($factory, $session, $transport, 'a3lkjas'), '', false);
$name = 'a3lkjas';
$w = new Workspace($factory, $session, $objManager, $name);
@@ -20,7 +22,9 @@ public function testGetNodeTypeManager()
{
$factory = new \Jackalope\Factory;
$session = $this->getMock('Jackalope\Session', array(), array($factory), '', false);
- $transport = $this->getMock('Jackalope\Transport\Jackrabbit\Client', array(), array($factory, 'http://example.com'));
+ $transport = $this->getMockBuilder('Jackalope\Transport\Jackrabbit\Client')
+ ->disableOriginalConstructor()
+ ->getMock(array());
$objManager = $this->getMock('Jackalope\ObjectManager', array(), array($factory, $session, $transport, 'a3lkjas'), '', false);
$name = 'a3lkjas';
Oops, something went wrong.

0 comments on commit f934c50

Please sign in to comment.